From 6215d2a8dc145274cbc8258f0eaf11217281f559 Mon Sep 17 00:00:00 2001 From: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> Date: Tue, 27 Oct 2015 19:11:37 -0400 Subject: [PATCH] Revert "packaging: remove sources" This reverts commit 4cb851a3dd68e1996802c5d4d97e589f1809f96c. Not usable with current GoGoBuild Change-Id: Iece300cb4ac247b136b300dee313d45645c5b6bc --- AUTHORS | 86 + CODING | 27 + COPYING | 675 ++++ ChangeLog | 2 + INSTALL | 370 ++ Makefile.am | 51 + NEWS | 141 + README | 174 + astylerc | 18 + autogen.sh | 41 + bin/Makefile.am | 29 + bin/dbus/Makefile.am | 72 + bin/dbus/callmanager-introspec.xml | 790 ++++ bin/dbus/configurationmanager-introspec.xml | 725 ++++ bin/dbus/cx.ring.Ring.service.in | 3 + bin/dbus/dbus_cpp.h | 41 + bin/dbus/dbuscallmanager.cpp | 288 ++ bin/dbus/dbuscallmanager.h | 111 + bin/dbus/dbusclient.cpp | 259 ++ bin/dbus/dbusclient.h | 79 + bin/dbus/dbusconfigurationmanager.cpp | 455 +++ bin/dbus/dbusconfigurationmanager.h | 137 + bin/dbus/dbusinstance.cpp | 56 + bin/dbus/dbusinstance.h | 69 + bin/dbus/dbuspresencemanager.cpp | 66 + bin/dbus/dbuspresencemanager.h | 72 + bin/dbus/dbusvideomanager.cpp | 96 + bin/dbus/dbusvideomanager.h | 76 + bin/dbus/instance-introspec.xml | 36 + bin/dbus/presencemanager-introspec.xml | 183 + bin/dbus/videomanager-introspec.xml | 118 + bin/main.cpp | 210 + bin/osxmain.cpp | 227 ++ bin/winmain.cpp | 10 + configure.ac | 610 +++ contrib/.gitignore | 3 + contrib/bootstrap | 268 ++ contrib/src/README | 125 + contrib/src/boost-headers/SHA512SUMS | 1 + contrib/src/boost-headers/rules.mak | 17 + contrib/src/change_prefix.sh | 62 + contrib/src/expat/SHA512SUMS | 1 + contrib/src/expat/rules.mak | 24 + contrib/src/flac/SHA512SUMS | 1 + contrib/src/flac/libFLAC-pc.patch | 10 + contrib/src/flac/rules.mak | 51 + .../0001-Fix-assembly-division-check.patch | 30 + contrib/src/gcrypt/SHA512SUMS | 1 + .../fix-amd64-assembly-on-solaris.patch | 130 + contrib/src/gcrypt/rules.mak | 47 + contrib/src/get-arch.sh | 29 + contrib/src/gmp/SHA512SUMS | 1 + contrib/src/gmp/clang.patch | 27 + contrib/src/gmp/rules.mak | 20 + contrib/src/gmp/thumb.patch | 22 + contrib/src/gnutls/SHA512SUMS | 1 + .../downgrade-automake-requirement.patch | 11 + contrib/src/gnutls/gnutls-no-egd.patch | 81 + contrib/src/gnutls/gnutls-pkgconfig-osx.patch | 51 + contrib/src/gnutls/gnutls-win32.patch | 28 + contrib/src/gnutls/mac-keychain-lookup.patch | 74 + contrib/src/gnutls/no-create-time-h.patch | 11 + contrib/src/gnutls/read-file-limits.h.patch | 12 + contrib/src/gnutls/rules.mak | 60 + contrib/src/gpg-error/SHA512SUMS | 1 + contrib/src/gpg-error/gpgerror-android.patch | 14 + .../gpg-error/missing-unistd-include.patch | 11 + contrib/src/gpg-error/rules.mak | 26 + contrib/src/gpg-error/windres-make.patch | 22 + contrib/src/gsm/SHA512SUMS | 1 + contrib/src/gsm/gsm-cross.patch | 38 + contrib/src/gsm/rules.mak | 24 + contrib/src/iax/rules.mak | 24 + contrib/src/iconv/SHA512SUMS | 1 + contrib/src/iconv/bins.patch | 82 + contrib/src/iconv/libiconv-android-ios.patch | 34 + contrib/src/iconv/libiconv-win64.patch | 1293 ++++++ contrib/src/iconv/libiconv-winrt.patch | 11 + contrib/src/iconv/rules.mak | 40 + contrib/src/iconv/win32.patch | 17 + contrib/src/jack/SHA512SUMS | 1 + contrib/src/jack/config-osx.patch | 25 + contrib/src/jack/rules.mak | 29 + contrib/src/libav/osx.patch | 765 ++++ contrib/src/libav/rules.mak | 174 + contrib/src/main.mak | 467 +++ contrib/src/nettle/SHA512SUMS | 1 + contrib/src/nettle/rules.mak | 25 + contrib/src/ogg/SHA512SUMS | 1 + contrib/src/ogg/libogg-1.1.patch | 56 + contrib/src/ogg/libogg-disable-check.patch | 12 + contrib/src/ogg/rules.mak | 29 + contrib/src/opendht/rules.mak | 31 + contrib/src/opus/SHA512SUMS | 1 + contrib/src/opus/rules.mak | 30 + contrib/src/pcre/SHA512SUMS | 1 + contrib/src/pcre/rules.mak | 27 + contrib/src/pjproject/SHA512SUMS | 1 + contrib/src/pjproject/aconfigureupdate.patch | 484 +++ contrib/src/pjproject/endianness.patch | 19 + contrib/src/pjproject/gnutls.patch | 3257 +++++++++++++++ contrib/src/pjproject/ice_config.patch | 23 + contrib/src/pjproject/intptr_t.patch | 11 + contrib/src/pjproject/ipv6.patch | 11 + .../src/pjproject/multiple_listeners.patch | 66 + contrib/src/pjproject/notestsapps.patch | 97 + contrib/src/pjproject/pj_ice_sess.patch | 22 + contrib/src/pjproject/pj_win.patch | 11 + contrib/src/pjproject/rules.mak | 69 + contrib/src/pjproject/tls_cert.patch | 10 + contrib/src/pjproject/unknowncipher.patch | 12 + contrib/src/pkg-static.sh | 38 + contrib/src/samplerate/SHA512SUMS | 1 + contrib/src/samplerate/carbon.patch | 10 + contrib/src/samplerate/rules.mak | 25 + contrib/src/samplerate/soundcard.patch | 14 + contrib/src/sndfile/SHA512SUMS | 1 + contrib/src/sndfile/autotools.patch | 35 + contrib/src/sndfile/carbon.patch | 10 + contrib/src/sndfile/rules.mak | 28 + contrib/src/sndfile/soundcard.patch | 16 + contrib/src/speex/rules.mak | 40 + contrib/src/speexdsp/rules.mak | 44 + contrib/src/upnp/SHA512SUMS | 1 + contrib/src/upnp/libupnp-configure.patch | 82 + contrib/src/upnp/libupnp-ipv6.patch | 49 + contrib/src/upnp/libupnp-win32.patch | 45 + contrib/src/upnp/libupnp-win64.patch | 41 + contrib/src/upnp/miniserver.patch | 17 + contrib/src/upnp/rules.mak | 38 + contrib/src/uuid/SHA512SUMS | 1 + contrib/src/uuid/android.patch | 16 + contrib/src/uuid/rules.mak | 23 + contrib/src/vorbis/SHA512SUMS | 1 + contrib/src/vorbis/osx.patch | 13 + contrib/src/vorbis/rules.mak | 46 + contrib/src/vpx/SHA512SUMS | 1 + contrib/src/vpx/rules.mak | 110 + contrib/src/x264/remove-align.patch | 11 + contrib/src/x264/rules.mak | 47 + contrib/src/yaml-cpp/SHA512SUMS | 1 + contrib/src/yaml-cpp/cmake.patch | 13 + contrib/src/yaml-cpp/rules.mak | 32 + contrib/src/zlib/SHA512SUMS | 1 + contrib/src/zlib/rules.mak | 28 + contrib/tarballs/.gitignore | 5 + doc/Makefile.am | 5 + doc/README | 2 + doc/dbus-api/Makefile.am | 28 + doc/dbus-api/README | 7 + doc/dbus-api/doc/templates/devhelp.devhelp2 | 18 + doc/dbus-api/doc/templates/errors.html | 60 + doc/dbus-api/doc/templates/fullindex.html | 60 + doc/dbus-api/doc/templates/generic-types.html | 59 + doc/dbus-api/doc/templates/index.html | 66 + doc/dbus-api/doc/templates/interface.html | 424 ++ doc/dbus-api/doc/templates/interfaces.html | 50 + doc/dbus-api/doc/templates/style.css | 237 ++ doc/dbus-api/spec/all.xml | 55 + doc/dbus-api/spec/callmanager-introspec.xml | 1 + .../spec/configurationmanager-introspec.xml | 1 + doc/dbus-api/spec/errors.xml | 417 ++ doc/dbus-api/spec/generic-types.xml | 168 + doc/dbus-api/spec/instance-introspec.xml | 1 + .../spec/presencemanager-introspec.xml | 1 + doc/dbus-api/tools/devhelp.xsl | 91 + doc/dbus-api/tools/doc-generator.py | 104 + doc/dbus-api/tools/doc-generator.xsl | 1266 ++++++ doc/dbus-api/tools/specparser.py | 866 ++++ doc/dbus-api/tools/xincludator.py | 39 + doc/doxygen/Makefile.am | 56 + doc/doxygen/blank.html | 1 + doc/doxygen/core-doc.cfg.in | 1716 ++++++++ doc/doxygen/gtk-gui-doc.cfg.in | 231 ++ doc/general_component_diagram.png | Bin 0 -> 67769 bytes extras/tools/.gitignore | 6 + extras/tools/bootstrap | 118 + extras/tools/packages.mak | 44 + extras/tools/tools.mak | 279 ++ globals.mak | 41 + m4/.gitignore | 5 + m4/as-ac-expand.m4 | 43 + m4/ax_cxx_compile_stdcxx_11.m4 | 135 + m4/ax_pthread.m4 | 309 ++ m4/dolt.m4 | 181 + man/Makefile.am | 19 + man/README.manpages | 13 + man/dring.pod | 56 + ringtones/Makefile.am | 5 + ringtones/konga.ul | 11 + ringtones/phone.au | Bin 0 -> 225521 bytes ringtones/phone2.au | Bin 0 -> 18456 bytes src/Makefile.am | 156 + src/account.cpp | 539 +++ src/account.h | 425 ++ src/account_factory.cpp | 219 + src/account_factory.h | 176 + src/account_schema.h | 120 + src/array_size.h | 43 + src/call.cpp | 307 ++ src/call.h | 382 ++ src/call_factory.cpp | 162 + src/call_factory.h | 246 ++ src/client/Makefile.am | 26 + src/client/callmanager.cpp | 346 ++ src/client/configurationmanager.cpp | 686 ++++ src/client/presencemanager.cpp | 168 + src/client/signal.cpp | 90 + src/client/signal.h | 81 + src/client/videomanager.cpp | 199 + src/client/videomanager.h | 72 + src/conference.cpp | 192 + src/conference.h | 132 + src/config/Makefile.am | 5 + src/config/serializable.h | 51 + src/config/yamlparser.cpp | 51 + src/config/yamlparser.h | 51 + src/dring/account_const.h | 214 + src/dring/call_const.h | 57 + src/dring/callmanager_interface.h | 209 + src/dring/configurationmanager_interface.h | 171 + src/dring/dring.h | 164 + src/dring/presencemanager_interface.h | 74 + src/dring/security_const.h | 115 + src/dring/videomanager_interface.h | 83 + src/enumclass_utils.h | 296 ++ src/fileutils.cpp | 368 ++ src/fileutils.h | 81 + src/gnutls_support.h | 67 + src/hooks/Makefile.am | 4 + src/hooks/urlhook.cpp | 44 + src/hooks/urlhook.h | 42 + src/iax/Makefile.am | 21 + src/iax/iaxaccount.cpp | 244 ++ src/iax/iaxaccount.h | 168 + src/iax/iaxcall.cpp | 255 ++ src/iax/iaxcall.h | 154 + src/iax/iaxvoiplink.cpp | 463 +++ src/iax/iaxvoiplink.h | 173 + src/ice_socket.h | 57 + src/ice_transport.cpp | 887 ++++ src/ice_transport.h | 285 ++ src/im/Makefile.am | 5 + src/im/instant_messaging.cpp | 215 + src/im/instant_messaging.h | 148 + src/intrin.h | 38 + src/ip_utils.cpp | 314 ++ src/ip_utils.h | 270 ++ src/logger.c | 114 + src/logger.h | 148 + src/manager.cpp | 49 + src/manager.h | 46 + src/managerimpl.cpp | 2892 +++++++++++++ src/managerimpl.h | 1011 +++++ src/map_utils.h | 64 + src/media/Makefile.am | 48 + src/media/audio/Makefile.am | 91 + src/media/audio/alsa/Makefile.am | 11 + src/media/audio/alsa/alsalayer.cpp | 854 ++++ src/media/audio/alsa/alsalayer.h | 269 ++ src/media/audio/audio_rtp_session.cpp | 465 +++ src/media/audio/audio_rtp_session.h | 70 + src/media/audio/audiobuffer.cpp | 292 ++ src/media/audio/audiobuffer.h | 351 ++ src/media/audio/audiolayer.cpp | 125 + src/media/audio/audiolayer.h | 285 ++ src/media/audio/audioloop.cpp | 102 + src/media/audio/audioloop.h | 93 + src/media/audio/audiorecord.cpp | 241 ++ src/media/audio/audiorecord.h | 159 + src/media/audio/audiorecorder.cpp | 104 + src/media/audio/audiorecorder.h | 79 + src/media/audio/coreaudio/Makefile.am | 8 + src/media/audio/coreaudio/audiodevice.cpp | 180 + src/media/audio/coreaudio/audiodevice.h | 65 + src/media/audio/coreaudio/corelayer.cpp | 579 +++ src/media/audio/coreaudio/corelayer.h | 189 + src/media/audio/dcblocker.cpp | 78 + src/media/audio/dcblocker.h | 64 + src/media/audio/dsp.cpp | 104 + src/media/audio/dsp.h | 65 + src/media/audio/jack/Makefile.am | 6 + src/media/audio/jack/jacklayer.cpp | 537 +++ src/media/audio/jack/jacklayer.h | 116 + src/media/audio/opensl/Makefile.am | 11 + src/media/audio/opensl/opensllayer.cpp | 861 ++++ src/media/audio/opensl/opensllayer.h | 274 ++ src/media/audio/pulseaudio/Makefile.am | 16 + src/media/audio/pulseaudio/audiostream.cpp | 156 + src/media/audio/pulseaudio/audiostream.h | 114 + src/media/audio/pulseaudio/pulselayer.cpp | 854 ++++ src/media/audio/pulseaudio/pulselayer.h | 238 ++ src/media/audio/recordable.cpp | 63 + src/media/audio/recordable.h | 92 + src/media/audio/resampler.cpp | 136 + src/media/audio/resampler.h | 90 + src/media/audio/ringbuffer.cpp | 316 ++ src/media/audio/ringbuffer.h | 185 + src/media/audio/ringbufferpool.cpp | 428 ++ src/media/audio/ringbufferpool.h | 169 + src/media/audio/sound/Makefile.am | 17 + src/media/audio/sound/audiofile.cpp | 142 + src/media/audio/sound/audiofile.h | 71 + src/media/audio/sound/dtmf.cpp | 85 + src/media/audio/sound/dtmf.h | 76 + src/media/audio/sound/dtmfgenerator.cpp | 149 + src/media/audio/sound/dtmfgenerator.h | 131 + src/media/audio/sound/tone.cpp | 139 + src/media/audio/sound/tone.h | 84 + src/media/audio/sound/tonelist.cpp | 138 + src/media/audio/sound/tonelist.h | 73 + src/media/libav_deps.h | 113 + src/media/libav_utils.cpp | 132 + src/media/libav_utils.h | 54 + src/media/media_buffer.cpp | 174 + src/media/media_buffer.h | 106 + src/media/media_codec.cpp | 228 ++ src/media/media_codec.h | 268 ++ src/media/media_decoder.cpp | 476 +++ src/media/media_decoder.h | 132 + src/media/media_device.h | 56 + src/media/media_encoder.cpp | 633 +++ src/media/media_encoder.h | 128 + src/media/media_io_handle.cpp | 53 + src/media/media_io_handle.h | 70 + src/media/rtp_session.h | 75 + src/media/socket_pair.cpp | 472 +++ src/media/socket_pair.h | 110 + src/media/srtp.c | 325 ++ src/media/srtp.h | 63 + src/media/system_codec_container.cpp | 213 + src/media/system_codec_container.h | 79 + src/media/video/.gitignore | 2 + src/media/video/Makefile.am | 32 + src/media/video/TODO | 8 + src/media/video/osxvideo/Makefile.am | 9 + src/media/video/osxvideo/video_device_impl.mm | 201 + .../osxvideo/video_device_monitor_impl.mm | 143 + src/media/video/shm_header.h | 53 + src/media/video/sinkclient.cpp | 331 ++ src/media/video/sinkclient.h | 94 + src/media/video/test/.gitignore | 5 + src/media/video/test/Makefile.am | 18 + src/media/video/test/README | 1 + src/media/video/test/make_rtp_stream.sh | 2 + src/media/video/test/shm_src.cpp | 153 + src/media/video/test/shm_src.h | 71 + src/media/video/test/shmclient.cpp | 146 + src/media/video/test/test_shm.cpp | 136 + src/media/video/test/test_video_input.cpp | 56 + src/media/video/test/test_video_input.h | 43 + src/media/video/test/test_video_rtp.cpp | 53 + src/media/video/v4l2/Makefile.am | 10 + src/media/video/v4l2/video_device_impl.cpp | 572 +++ .../video/v4l2/video_device_monitor_impl.cpp | 262 ++ src/media/video/video_base.cpp | 157 + src/media/video/video_base.h | 193 + src/media/video/video_device.h | 116 + src/media/video/video_device_monitor.cpp | 321 ++ src/media/video/video_device_monitor.h | 114 + src/media/video/video_input.cpp | 368 ++ src/media/video/video_input.h | 107 + src/media/video/video_mixer.cpp | 228 ++ src/media/video/video_mixer.h | 107 + src/media/video/video_provider.h | 44 + src/media/video/video_receive_thread.cpp | 246 ++ src/media/video/video_receive_thread.h | 105 + src/media/video/video_rtp_session.cpp | 284 ++ src/media/video/video_rtp_session.h | 90 + src/media/video/video_scaler.cpp | 171 + src/media/video/video_scaler.h | 64 + src/media/video/video_sender.cpp | 81 + src/media/video/video_sender.h | 82 + src/noncopyable.h | 46 + src/numbercleaner.cpp | 66 + src/numbercleaner.h | 43 + src/plugin_loader.h | 57 + src/plugin_loader_dl.cpp | 81 + src/plugin_manager.cpp | 264 ++ src/plugin_manager.h | 166 + src/preferences.cpp | 531 +++ src/preferences.h | 456 +++ src/registration_states.h | 53 + src/ring_api.cpp | 97 + src/ring_plugin.h | 119 + src/ring_types.h | 88 + src/ringdht/Makefile.am | 18 + src/ringdht/ringaccount.cpp | 1125 ++++++ src/ringdht/ringaccount.h | 378 ++ src/ringdht/sip_transport_ice.cpp | 255 ++ src/ringdht/sip_transport_ice.h | 98 + src/ringdht/sips_transport_ice.cpp | 1178 ++++++ src/ringdht/sips_transport_ice.h | 199 + src/rw_mutex.h | 126 + src/sip/Makefile.am | 47 + src/sip/base64.c | 121 + src/sip/base64.h | 106 + src/sip/pattern.cpp | 163 + src/sip/pattern.h | 184 + src/sip/pres_sub_client.cpp | 621 +++ src/sip/pres_sub_client.h | 189 + src/sip/pres_sub_server.cpp | 351 ++ src/sip/pres_sub_server.h | 95 + src/sip/sdes_negotiator.cpp | 188 + src/sip/sdes_negotiator.h | 134 + src/sip/sdp.cpp | 774 ++++ src/sip/sdp.h | 359 ++ src/sip/sip_utils.cpp | 214 + src/sip/sip_utils.h | 86 + src/sip/sipaccount.cpp | 2155 ++++++++++ src/sip/sipaccount.h | 814 ++++ src/sip/sipaccountbase.cpp | 285 ++ src/sip/sipaccountbase.h | 305 ++ src/sip/sipcall.cpp | 978 +++++ src/sip/sipcall.h | 262 ++ src/sip/sippresence.cpp | 537 +++ src/sip/sippresence.h | 258 ++ src/sip/siptransport.cpp | 472 +++ src/sip/siptransport.h | 243 ++ src/sip/sipvoiplink.cpp | 1358 +++++++ src/sip/sipvoiplink.h | 201 + src/sip/tlsvalidator.cpp | 1157 ++++++ src/sip/tlsvalidator.h | 276 ++ src/string_utils.cpp | 72 + src/string_utils.h | 78 + src/threadloop.cpp | 101 + src/threadloop.h | 79 + src/upnp/Makefile.am | 14 + src/upnp/upnp_context.cpp | 1070 +++++ src/upnp/upnp_context.h | 233 ++ src/upnp/upnp_control.cpp | 151 + src/upnp/upnp_control.h | 131 + src/upnp/upnp_igd.cpp | 75 + src/upnp/upnp_igd.h | 193 + src/utf8_utils.cpp | 308 ++ src/utf8_utils.h | 73 + test/.gitignore | 4 + test/Makefile.am | 65 + test/accounttest.cpp | 61 + test/accounttest.h | 53 + test/audiobuffertest.cpp | 104 + test/audiobuffertest.h | 72 + test/audiocodectest.cpp | 132 + test/audiocodectest.h | 77 + test/audiolayertest.cpp | 108 + test/audiolayertest.h | 83 + test/configurationtest.cpp | 56 + test/configurationtest.h | 70 + test/constants.h | 43 + test/dring-sample.yml | 199 + test/history-sample.tpl | 30 + test/historytest.cpp | 106 + test/historytest.h | 103 + test/hooktest.cpp | 46 + test/hooktest.h | 56 + test/instantmessagingtest.cpp | 294 ++ test/instantmessagingtest.h | 76 + test/iptest.cpp | 93 + test/iptest.h | 69 + test/main.cpp | 155 + test/numbercleanertest.cpp | 132 + test/numbercleanertest.h | 90 + test/resamplertest.cpp | 239 ++ test/resamplertest.h | 152 + test/ringbufferpooltest.cpp | 223 + test/ringbufferpooltest.h | 90 + test/run_tests.sh | 4 + test/scripts/presence_test.py | 219 + test/scripts/stress_test.py | 421 ++ test/sdesnegotiatortest.cpp | 236 ++ test/sdesnegotiatortest.h | 111 + test/sdptest.cpp | 307 ++ test/sdptest.h | 138 + test/sippxml/test_1.xml | 157 + test/sippxml/test_2.xml | 161 + test/sippxml/test_3.xml | 187 + test/sippxml/test_4.xml | 122 + test/siptest.cpp | 441 ++ test/siptest.h | 104 + test/test_utils.h | 38 + test/tlsSample/ca.crt | 22 + test/tlsSample/cert.crt | 23 + test/tlsSample/certwithkey.pem | 35 + test/tlsSample/corruptedkey.pem | 29 + test/tlsSample/expired.crt | 19 + test/tlsSample/fake.crt | Bin 0 -> 4546 bytes test/tlsSample/keyonly.pem | 32 + test/tlstest.cpp | 92 + test/tlstest.h | 70 + test/waveSample/M1F1-int16WE-AFsp.wav | Bin 0 -> 47134 bytes tools/.gitignore | 27 + tools/asterisk/extensions.conf | 838 ++++ tools/asterisk/keys/500.crt | 21 + tools/asterisk/keys/500.csr | 10 + tools/asterisk/keys/500.key | 15 + tools/asterisk/keys/500.pem | 36 + tools/asterisk/keys/600.crt | 21 + tools/asterisk/keys/600.csr | 10 + tools/asterisk/keys/600.key | 15 + tools/asterisk/keys/600.pem | 36 + tools/asterisk/keys/asterisk.crt | 21 + tools/asterisk/keys/asterisk.csr | 10 + tools/asterisk/keys/asterisk.key | 15 + tools/asterisk/keys/asterisk.pem | 36 + tools/asterisk/keys/ca.cfg | 10 + tools/asterisk/keys/ca.crt | 29 + tools/asterisk/keys/ca.key | 54 + tools/asterisk/keys/testphone1.crt | 21 + tools/asterisk/keys/testphone1.csr | 10 + tools/asterisk/keys/testphone1.key | 15 + tools/asterisk/keys/testphone1.pem | 36 + tools/asterisk/keys/tmp.cfg | 7 + tools/asterisk/sip.conf | 1378 +++++++ tools/build-system/README.launchpad | 10 + tools/build-system/build_tarball.sh | 76 + tools/build-system/get-kde.sh | 38 + tools/build-system/hudson-sflphone-script.sh | 260 ++ .../launch-build-machine-jenkins.sh | 316 ++ tools/build-system/launchpad/dput.conf | 27 + .../mozilla-telify-sflphone/debian/changelog | 13 + .../mozilla-telify-sflphone/debian/compat | 1 + .../mozilla-telify-sflphone/debian/control | 19 + .../debian/control.debian | 19 + .../debian/control.ubuntu | 19 + .../mozilla-telify-sflphone/debian/copyright | 1 + .../mozilla-telify-sflphone/debian/files | 1 + .../mozilla-telify-sflphone.debhelper.log | 11 + .../debian/mozilla-telify-sflphone.install | 2 + .../debian/mozilla-telify-sflphone.links | 1 + .../mozilla-telify-sflphone.links.debian | 1 + .../mozilla-telify-sflphone.links.ubuntu | 1 + .../debian/mozilla-telify-sflphone.substvars | 1 + .../mozilla-telify-sflphone/DEBIAN/control | 14 + .../mozilla-telify-sflphone/DEBIAN/md5sums | 282 ++ .../mozilla-telify-sflphone/DEBIAN/postinst | 16 + .../usr/bin/sflphone-handler | 52 + .../doc/mozilla-telify-sflphone/changelog.gz | Bin 0 -> 175 bytes .../usr/share/telify/chrome.manifest | 5 + .../usr/share/telify/chrome/content/ask32.png | Bin 0 -> 2671 bytes .../share/telify/chrome/content/browser.xul | 87 + .../usr/share/telify/chrome/content/config.js | 196 + .../share/telify/chrome/content/config.xul | 180 + .../telify/chrome/content/country_data.js | 258 ++ .../share/telify/chrome/content/dialog.css | 33 + .../share/telify/chrome/content/edit22x15.png | Bin 0 -> 2946 bytes .../share/telify/chrome/content/editNumber.js | 180 + .../telify/chrome/content/editNumber.xul | 45 + .../share/telify/chrome/content/error32.png | Bin 0 -> 2478 bytes .../telify/chrome/content/flag/1-canada.png | Bin 0 -> 2979 bytes .../share/telify/chrome/content/flag/1.png | Bin 0 -> 2878 bytes .../share/telify/chrome/content/flag/1242.png | Bin 0 -> 2928 bytes .../share/telify/chrome/content/flag/1246.png | Bin 0 -> 347 bytes .../share/telify/chrome/content/flag/1264.png | Bin 0 -> 3228 bytes .../share/telify/chrome/content/flag/1268.png | Bin 0 -> 587 bytes .../share/telify/chrome/content/flag/1284.png | Bin 0 -> 3287 bytes .../share/telify/chrome/content/flag/1340.png | Bin 0 -> 785 bytes .../share/telify/chrome/content/flag/1345.png | Bin 0 -> 3245 bytes .../share/telify/chrome/content/flag/1441.png | Bin 0 -> 3238 bytes .../share/telify/chrome/content/flag/1473.png | Bin 0 -> 3288 bytes .../share/telify/chrome/content/flag/1649.png | Bin 0 -> 3206 bytes .../share/telify/chrome/content/flag/1664.png | Bin 0 -> 3220 bytes .../share/telify/chrome/content/flag/1670.png | Bin 0 -> 3233 bytes .../share/telify/chrome/content/flag/1671.png | Bin 0 -> 3003 bytes .../share/telify/chrome/content/flag/1684.png | Bin 0 -> 3124 bytes .../share/telify/chrome/content/flag/1758.png | Bin 0 -> 3002 bytes .../share/telify/chrome/content/flag/1767.png | Bin 0 -> 3064 bytes .../share/telify/chrome/content/flag/1784.png | Bin 0 -> 302 bytes .../share/telify/chrome/content/flag/1787.png | Bin 0 -> 440 bytes .../share/telify/chrome/content/flag/1809.png | Bin 0 -> 2961 bytes .../share/telify/chrome/content/flag/1829.png | Bin 0 -> 2961 bytes .../share/telify/chrome/content/flag/1868.png | Bin 0 -> 3278 bytes .../share/telify/chrome/content/flag/1869.png | Bin 0 -> 689 bytes .../share/telify/chrome/content/flag/1876.png | Bin 0 -> 2954 bytes .../share/telify/chrome/content/flag/1939.png | Bin 0 -> 440 bytes .../share/telify/chrome/content/flag/20.png | Bin 0 -> 223 bytes .../share/telify/chrome/content/flag/212.png | Bin 0 -> 312 bytes .../share/telify/chrome/content/flag/213.png | Bin 0 -> 411 bytes .../share/telify/chrome/content/flag/216.png | Bin 0 -> 401 bytes .../share/telify/chrome/content/flag/218.png | Bin 0 -> 2820 bytes .../share/telify/chrome/content/flag/220.png | Bin 0 -> 146 bytes .../share/telify/chrome/content/flag/221.png | Bin 0 -> 245 bytes .../share/telify/chrome/content/flag/222.png | Bin 0 -> 410 bytes .../share/telify/chrome/content/flag/223.png | Bin 0 -> 151 bytes .../share/telify/chrome/content/flag/224.png | Bin 0 -> 151 bytes .../share/telify/chrome/content/flag/225.png | Bin 0 -> 147 bytes .../share/telify/chrome/content/flag/226.png | Bin 0 -> 256 bytes .../share/telify/chrome/content/flag/227.png | Bin 0 -> 2931 bytes .../share/telify/chrome/content/flag/228.png | Bin 0 -> 3019 bytes .../share/telify/chrome/content/flag/229.png | Bin 0 -> 164 bytes .../share/telify/chrome/content/flag/230.png | Bin 0 -> 172 bytes .../share/telify/chrome/content/flag/231.png | Bin 0 -> 3043 bytes .../share/telify/chrome/content/flag/232.png | Bin 0 -> 137 bytes .../share/telify/chrome/content/flag/233.png | Bin 0 -> 253 bytes .../share/telify/chrome/content/flag/234.png | Bin 0 -> 2837 bytes .../share/telify/chrome/content/flag/235.png | Bin 0 -> 151 bytes .../share/telify/chrome/content/flag/236.png | Bin 0 -> 380 bytes .../share/telify/chrome/content/flag/237.png | Bin 0 -> 240 bytes .../share/telify/chrome/content/flag/238.png | Bin 0 -> 3037 bytes .../share/telify/chrome/content/flag/239.png | Bin 0 -> 2990 bytes .../share/telify/chrome/content/flag/240.png | Bin 0 -> 434 bytes .../share/telify/chrome/content/flag/241.png | Bin 0 -> 2888 bytes .../share/telify/chrome/content/flag/242.png | Bin 0 -> 449 bytes .../share/telify/chrome/content/flag/243.png | Bin 0 -> 3451 bytes .../share/telify/chrome/content/flag/244.png | Bin 0 -> 374 bytes .../share/telify/chrome/content/flag/245.png | Bin 0 -> 2932 bytes .../share/telify/chrome/content/flag/246.png | Bin 0 -> 3646 bytes .../share/telify/chrome/content/flag/247.png | Bin 0 -> 3739 bytes .../share/telify/chrome/content/flag/248.png | Bin 0 -> 3105 bytes .../share/telify/chrome/content/flag/249.png | Bin 0 -> 2972 bytes .../share/telify/chrome/content/flag/250.png | Bin 0 -> 323 bytes .../share/telify/chrome/content/flag/251.png | Bin 0 -> 3025 bytes .../share/telify/chrome/content/flag/252.png | Bin 0 -> 248 bytes .../share/telify/chrome/content/flag/253.png | Bin 0 -> 390 bytes .../share/telify/chrome/content/flag/254.png | Bin 0 -> 380 bytes .../share/telify/chrome/content/flag/255.png | Bin 0 -> 635 bytes .../share/telify/chrome/content/flag/256.png | Bin 0 -> 306 bytes .../share/telify/chrome/content/flag/257.png | Bin 0 -> 3326 bytes .../share/telify/chrome/content/flag/258.png | Bin 0 -> 471 bytes .../share/telify/chrome/content/flag/260.png | Bin 0 -> 280 bytes .../share/telify/chrome/content/flag/261.png | Bin 0 -> 161 bytes .../share/telify/chrome/content/flag/262.png | Bin 0 -> 147 bytes .../share/telify/chrome/content/flag/263.png | Bin 0 -> 3044 bytes .../share/telify/chrome/content/flag/264.png | Bin 0 -> 720 bytes .../share/telify/chrome/content/flag/265.png | Bin 0 -> 286 bytes .../share/telify/chrome/content/flag/266.png | Bin 0 -> 284 bytes .../share/telify/chrome/content/flag/267.png | Bin 0 -> 165 bytes .../share/telify/chrome/content/flag/268.png | Bin 0 -> 539 bytes .../share/telify/chrome/content/flag/269.png | Bin 0 -> 3169 bytes .../share/telify/chrome/content/flag/27.png | Bin 0 -> 558 bytes .../share/telify/chrome/content/flag/290.png | Bin 0 -> 3202 bytes .../share/telify/chrome/content/flag/291.png | Bin 0 -> 3091 bytes .../share/telify/chrome/content/flag/297.png | Bin 0 -> 260 bytes .../share/telify/chrome/content/flag/298.png | Bin 0 -> 2877 bytes .../share/telify/chrome/content/flag/299.png | Bin 0 -> 408 bytes .../share/telify/chrome/content/flag/30.png | Bin 0 -> 325 bytes .../share/telify/chrome/content/flag/31.png | Bin 0 -> 137 bytes .../share/telify/chrome/content/flag/32.png | Bin 0 -> 145 bytes .../share/telify/chrome/content/flag/33.png | Bin 0 -> 147 bytes .../share/telify/chrome/content/flag/34.png | Bin 0 -> 325 bytes .../share/telify/chrome/content/flag/350.png | Bin 0 -> 3054 bytes .../share/telify/chrome/content/flag/351.png | Bin 0 -> 413 bytes .../share/telify/chrome/content/flag/352.png | Bin 0 -> 2844 bytes .../share/telify/chrome/content/flag/353.png | Bin 0 -> 2839 bytes .../share/telify/chrome/content/flag/354.png | Bin 0 -> 2895 bytes .../share/telify/chrome/content/flag/355.png | Bin 0 -> 3109 bytes .../share/telify/chrome/content/flag/356.png | Bin 0 -> 235 bytes .../share/telify/chrome/content/flag/357.png | Bin 0 -> 3049 bytes .../share/telify/chrome/content/flag/358.png | Bin 0 -> 2910 bytes .../share/telify/chrome/content/flag/359.png | Bin 0 -> 2842 bytes .../share/telify/chrome/content/flag/36.png | Bin 0 -> 2839 bytes .../share/telify/chrome/content/flag/370.png | Bin 0 -> 2842 bytes .../share/telify/chrome/content/flag/371.png | Bin 0 -> 2841 bytes .../share/telify/chrome/content/flag/372.png | Bin 0 -> 2838 bytes .../share/telify/chrome/content/flag/373.png | Bin 0 -> 2976 bytes .../share/telify/chrome/content/flag/374.png | Bin 0 -> 2842 bytes .../telify/chrome/content/flag/37447.png | Bin 0 -> 3069 bytes .../telify/chrome/content/flag/37497.png | Bin 0 -> 3069 bytes .../share/telify/chrome/content/flag/375.png | Bin 0 -> 2946 bytes .../share/telify/chrome/content/flag/376.png | Bin 0 -> 404 bytes .../share/telify/chrome/content/flag/377.png | Bin 0 -> 2836 bytes .../telify/chrome/content/flag/37744.png | Bin 0 -> 473 bytes .../share/telify/chrome/content/flag/378.png | Bin 0 -> 3126 bytes .../share/telify/chrome/content/flag/379.png | Bin 0 -> 3050 bytes .../share/telify/chrome/content/flag/380.png | Bin 0 -> 137 bytes .../telify/chrome/content/flag/381-kosovo.png | Bin 0 -> 473 bytes .../share/telify/chrome/content/flag/381.png | Bin 0 -> 408 bytes .../share/telify/chrome/content/flag/382.png | Bin 0 -> 3001 bytes .../share/telify/chrome/content/flag/385.png | Bin 0 -> 3045 bytes .../share/telify/chrome/content/flag/386.png | Bin 0 -> 2933 bytes .../telify/chrome/content/flag/38649.png | Bin 0 -> 473 bytes .../share/telify/chrome/content/flag/387.png | Bin 0 -> 3078 bytes .../share/telify/chrome/content/flag/3883.png | Bin 0 -> 421 bytes .../share/telify/chrome/content/flag/389.png | Bin 0 -> 3170 bytes .../telify/chrome/content/flag/39-vatican.png | Bin 0 -> 3050 bytes .../share/telify/chrome/content/flag/39.png | Bin 0 -> 151 bytes .../share/telify/chrome/content/flag/40.png | Bin 0 -> 151 bytes .../share/telify/chrome/content/flag/41.png | Bin 0 -> 2849 bytes .../share/telify/chrome/content/flag/420.png | Bin 0 -> 400 bytes .../share/telify/chrome/content/flag/421.png | Bin 0 -> 409 bytes .../share/telify/chrome/content/flag/423.png | Bin 0 -> 2921 bytes .../share/telify/chrome/content/flag/43.png | Bin 0 -> 134 bytes .../share/telify/chrome/content/flag/44.png | Bin 0 -> 3739 bytes .../share/telify/chrome/content/flag/45.png | Bin 0 -> 3015 bytes .../share/telify/chrome/content/flag/46.png | Bin 0 -> 3059 bytes .../share/telify/chrome/content/flag/47.png | Bin 0 -> 3115 bytes .../share/telify/chrome/content/flag/48.png | Bin 0 -> 2820 bytes .../share/telify/chrome/content/flag/49.png | Bin 0 -> 2870 bytes .../share/telify/chrome/content/flag/500.png | Bin 0 -> 3238 bytes .../share/telify/chrome/content/flag/501.png | Bin 0 -> 654 bytes .../share/telify/chrome/content/flag/502.png | Bin 0 -> 2978 bytes .../share/telify/chrome/content/flag/503.png | Bin 0 -> 2902 bytes .../share/telify/chrome/content/flag/504.png | Bin 0 -> 2912 bytes .../share/telify/chrome/content/flag/505.png | Bin 0 -> 2934 bytes .../share/telify/chrome/content/flag/506.png | Bin 0 -> 2852 bytes .../share/telify/chrome/content/flag/507.png | Bin 0 -> 320 bytes .../share/telify/chrome/content/flag/508.png | Bin 0 -> 147 bytes .../share/telify/chrome/content/flag/509.png | Bin 0 -> 2964 bytes .../share/telify/chrome/content/flag/51.png | Bin 0 -> 147 bytes .../share/telify/chrome/content/flag/52.png | Bin 0 -> 2954 bytes .../share/telify/chrome/content/flag/53.png | Bin 0 -> 3234 bytes .../share/telify/chrome/content/flag/54.png | Bin 0 -> 3003 bytes .../share/telify/chrome/content/flag/55.png | Bin 0 -> 662 bytes .../share/telify/chrome/content/flag/56.png | Bin 0 -> 239 bytes .../share/telify/chrome/content/flag/57.png | Bin 0 -> 157 bytes .../share/telify/chrome/content/flag/58.png | Bin 0 -> 273 bytes .../share/telify/chrome/content/flag/590.png | Bin 0 -> 147 bytes .../share/telify/chrome/content/flag/591.png | Bin 0 -> 140 bytes .../share/telify/chrome/content/flag/592.png | Bin 0 -> 3185 bytes .../share/telify/chrome/content/flag/593.png | Bin 0 -> 3025 bytes .../share/telify/chrome/content/flag/594.png | Bin 0 -> 147 bytes .../share/telify/chrome/content/flag/595.png | Bin 0 -> 2922 bytes .../share/telify/chrome/content/flag/596.png | Bin 0 -> 147 bytes .../share/telify/chrome/content/flag/597.png | Bin 0 -> 295 bytes .../share/telify/chrome/content/flag/598.png | Bin 0 -> 380 bytes .../share/telify/chrome/content/flag/599.png | Bin 0 -> 255 bytes .../share/telify/chrome/content/flag/60.png | Bin 0 -> 3266 bytes .../share/telify/chrome/content/flag/61.png | Bin 0 -> 3547 bytes .../share/telify/chrome/content/flag/62.png | Bin 0 -> 138 bytes .../share/telify/chrome/content/flag/63.png | Bin 0 -> 3087 bytes .../share/telify/chrome/content/flag/64.png | Bin 0 -> 3206 bytes .../share/telify/chrome/content/flag/65.png | Bin 0 -> 319 bytes .../share/telify/chrome/content/flag/66.png | Bin 0 -> 157 bytes .../share/telify/chrome/content/flag/670.png | Bin 0 -> 3040 bytes .../content/flag/672-norfolk_island.png | Bin 0 -> 3053 bytes .../share/telify/chrome/content/flag/672.png | Bin 0 -> 3237 bytes .../share/telify/chrome/content/flag/673.png | Bin 0 -> 3236 bytes .../share/telify/chrome/content/flag/674.png | Bin 0 -> 2918 bytes .../share/telify/chrome/content/flag/675.png | Bin 0 -> 3408 bytes .../share/telify/chrome/content/flag/676.png | Bin 0 -> 2945 bytes .../share/telify/chrome/content/flag/677.png | Bin 0 -> 3090 bytes .../share/telify/chrome/content/flag/678.png | Bin 0 -> 3137 bytes .../share/telify/chrome/content/flag/679.png | Bin 0 -> 3236 bytes .../share/telify/chrome/content/flag/680.png | Bin 0 -> 3019 bytes .../share/telify/chrome/content/flag/681.png | Bin 0 -> 147 bytes .../share/telify/chrome/content/flag/682.png | Bin 0 -> 3333 bytes .../share/telify/chrome/content/flag/683.png | Bin 0 -> 3104 bytes .../share/telify/chrome/content/flag/685.png | Bin 0 -> 2936 bytes .../share/telify/chrome/content/flag/686.png | Bin 0 -> 3301 bytes .../share/telify/chrome/content/flag/687.png | Bin 0 -> 147 bytes .../share/telify/chrome/content/flag/688.png | Bin 0 -> 3330 bytes .../share/telify/chrome/content/flag/689.png | Bin 0 -> 347 bytes .../share/telify/chrome/content/flag/690.png | Bin 0 -> 3179 bytes .../share/telify/chrome/content/flag/691.png | Bin 0 -> 2956 bytes .../share/telify/chrome/content/flag/692.png | Bin 0 -> 3333 bytes .../chrome/content/flag/7-kazakhstan.png | Bin 0 -> 3324 bytes .../share/telify/chrome/content/flag/7.png | Bin 0 -> 136 bytes .../share/telify/chrome/content/flag/81.png | Bin 0 -> 327 bytes .../share/telify/chrome/content/flag/82.png | Bin 0 -> 666 bytes .../share/telify/chrome/content/flag/84.png | Bin 0 -> 354 bytes .../share/telify/chrome/content/flag/850.png | Bin 0 -> 2968 bytes .../share/telify/chrome/content/flag/852.png | Bin 0 -> 403 bytes .../share/telify/chrome/content/flag/853.png | Bin 0 -> 417 bytes .../share/telify/chrome/content/flag/855.png | Bin 0 -> 421 bytes .../share/telify/chrome/content/flag/856.png | Bin 0 -> 264 bytes .../share/telify/chrome/content/flag/86.png | Bin 0 -> 319 bytes .../share/telify/chrome/content/flag/870.png | Bin 0 -> 3427 bytes .../share/telify/chrome/content/flag/871.png | Bin 0 -> 3427 bytes .../share/telify/chrome/content/flag/872.png | Bin 0 -> 3427 bytes .../share/telify/chrome/content/flag/873.png | Bin 0 -> 3427 bytes .../share/telify/chrome/content/flag/874.png | Bin 0 -> 3427 bytes .../share/telify/chrome/content/flag/880.png | Bin 0 -> 2992 bytes .../share/telify/chrome/content/flag/886.png | Bin 0 -> 322 bytes .../share/telify/chrome/content/flag/90.png | Bin 0 -> 389 bytes .../telify/chrome/content/flag/90392.png | Bin 0 -> 390 bytes .../share/telify/chrome/content/flag/91.png | Bin 0 -> 249 bytes .../share/telify/chrome/content/flag/92.png | Bin 0 -> 419 bytes .../share/telify/chrome/content/flag/93.png | Bin 0 -> 377 bytes .../share/telify/chrome/content/flag/94.png | Bin 0 -> 3164 bytes .../share/telify/chrome/content/flag/95.png | Bin 0 -> 2998 bytes .../share/telify/chrome/content/flag/960.png | Bin 0 -> 295 bytes .../share/telify/chrome/content/flag/961.png | Bin 0 -> 383 bytes .../share/telify/chrome/content/flag/962.png | Bin 0 -> 2965 bytes .../share/telify/chrome/content/flag/963.png | Bin 0 -> 241 bytes .../share/telify/chrome/content/flag/964.png | Bin 0 -> 261 bytes .../share/telify/chrome/content/flag/965.png | Bin 0 -> 2923 bytes .../share/telify/chrome/content/flag/966.png | Bin 0 -> 437 bytes .../share/telify/chrome/content/flag/967.png | Bin 0 -> 137 bytes .../share/telify/chrome/content/flag/968.png | Bin 0 -> 2927 bytes .../share/telify/chrome/content/flag/971.png | Bin 0 -> 2873 bytes .../share/telify/chrome/content/flag/972.png | Bin 0 -> 3052 bytes .../share/telify/chrome/content/flag/973.png | Bin 0 -> 2964 bytes .../share/telify/chrome/content/flag/974.png | Bin 0 -> 2834 bytes .../share/telify/chrome/content/flag/975.png | Bin 0 -> 601 bytes .../share/telify/chrome/content/flag/976.png | Bin 0 -> 2972 bytes .../share/telify/chrome/content/flag/977.png | Bin 0 -> 3373 bytes .../share/telify/chrome/content/flag/98.png | Bin 0 -> 2957 bytes .../share/telify/chrome/content/flag/992.png | Bin 0 -> 2933 bytes .../share/telify/chrome/content/flag/993.png | Bin 0 -> 519 bytes .../share/telify/chrome/content/flag/994.png | Bin 0 -> 2927 bytes .../share/telify/chrome/content/flag/995.png | Bin 0 -> 286 bytes .../share/telify/chrome/content/flag/996.png | Bin 0 -> 3044 bytes .../share/telify/chrome/content/flag/998.png | Bin 0 -> 2949 bytes .../telify/chrome/content/icon18_active.png | Bin 0 -> 3820 bytes .../telify/chrome/content/icon18_inactive.png | Bin 0 -> 1424 bytes .../share/telify/chrome/content/icon32.png | Bin 0 -> 5391 bytes .../share/telify/chrome/content/icon96.png | Bin 0 -> 12314 bytes .../share/telify/chrome/content/icon_menu.png | Bin 0 -> 583 bytes .../share/telify/chrome/content/info32.png | Bin 0 -> 2328 bytes .../telify/chrome/content/jshashtable.js | 380 ++ .../share/telify/chrome/content/messagebox.js | 43 + .../telify/chrome/content/messagebox.xul | 34 + .../usr/share/telify/chrome/content/pref.js | 164 + .../usr/share/telify/chrome/content/telify.js | 715 ++++ .../usr/share/telify/chrome/content/util.js | 516 +++ .../share/telify/chrome/content/warn32.png | Bin 0 -> 2125 bytes .../chrome/locale/de-DE/country_locale.js | 158 + .../chrome/locale/de-DE/custom_preset.js | 8 + .../share/telify/chrome/locale/de-DE/lang.dtd | 39 + .../chrome/locale/de-DE/lang.properties | 13 + .../telify/chrome/locale/de-DE/locale.js | 25 + .../chrome/locale/en-US/country_locale.js | 3 + .../chrome/locale/en-US/custom_preset.js | 8 + .../share/telify/chrome/locale/en-US/lang.dtd | 38 + .../chrome/locale/en-US/lang.properties | 13 + .../telify/chrome/locale/en-US/locale.js | 25 + .../defaults/preferences/preferences.js | 18 + .../usr/share/telify/install.rdf | 37 + .../mozilla-telify-sflphone/debian/postinst | 16 + .../mozilla-telify-sflphone/debian/rules | 56 + .../mozilla-telify-sflphone/debian/watch | 2 + .../sflphone-daemon-video/debian/changelog | 3585 +++++++++++++++++ .../sflphone-daemon-video/debian/compat | 1 + .../sflphone-daemon-video/debian/control | 20 + .../sflphone-daemon-video/debian/copyright | 28 + .../sflphone-daemon-video/debian/cron.d | 4 + .../sflphone-daemon-video/debian/dirs | 9 + .../sflphone-daemon-video/debian/docs | 5 + .../sflphone-daemon-video/debian/manpages | 1 + .../sflphone-daemon-video/debian/postinst | 56 + .../sflphone-daemon-video/debian/postrm | 34 + .../sflphone-daemon-video/debian/preinst | 16 + .../sflphone-daemon-video/debian/rules | 91 + .../sflphone-daemon/debian/changelog | 3585 +++++++++++++++++ .../launchpad/sflphone-daemon/debian/compat | 1 + .../launchpad/sflphone-daemon/debian/control | 29 + .../sflphone-daemon/debian/copyright | 28 + .../launchpad/sflphone-daemon/debian/cron.d | 4 + .../launchpad/sflphone-daemon/debian/dirs | 9 + .../launchpad/sflphone-daemon/debian/docs | 6 + .../launchpad/sflphone-daemon/debian/manpages | 1 + .../launchpad/sflphone-daemon/debian/postinst | 56 + .../launchpad/sflphone-daemon/debian/postrm | 36 + .../launchpad/sflphone-daemon/debian/preinst | 16 + .../launchpad/sflphone-daemon/debian/rules | 93 + .../sflphone-gnome-video/debian/changelog | 3138 +++++++++++++++ .../sflphone-gnome-video/debian/compat | 1 + .../sflphone-gnome-video/debian/control | 21 + .../sflphone-gnome-video/debian/copyright | 28 + .../sflphone-gnome-video/debian/cron.d | 4 + .../sflphone-gnome-video/debian/dirs | 7 + .../sflphone-gnome-video/debian/docs | 4 + .../sflphone-gnome-video/debian/manpages | 2 + .../sflphone-gnome-video/debian/postinst | 7 + .../sflphone-gnome-video/debian/postrm | 34 + .../sflphone-gnome-video/debian/preinst | 19 + .../sflphone-gnome-video/debian/prerm | 7 + .../sflphone-gnome-video/debian/rules | 117 + .../sflphone-gnome-video/debian/substvars | 1 + .../launchpad/sflphone-gnome/debian/changelog | 3138 +++++++++++++++ .../launchpad/sflphone-gnome/debian/compat | 1 + .../launchpad/sflphone-gnome/debian/control | 21 + .../launchpad/sflphone-gnome/debian/copyright | 28 + .../launchpad/sflphone-gnome/debian/cron.d | 4 + .../launchpad/sflphone-gnome/debian/dirs | 7 + .../launchpad/sflphone-gnome/debian/docs | 5 + .../launchpad/sflphone-gnome/debian/manpages | 2 + .../launchpad/sflphone-gnome/debian/postinst | 7 + .../launchpad/sflphone-gnome/debian/postrm | 36 + .../launchpad/sflphone-gnome/debian/preinst | 19 + .../launchpad/sflphone-gnome/debian/prerm | 7 + .../launchpad/sflphone-gnome/debian/rules | 117 + .../launchpad/sflphone-gnome/debian/substvars | 1 + .../launchpad/sflphone-kde/debian/changelog | 180 + .../launchpad/sflphone-kde/debian/compat | 1 + .../launchpad/sflphone-kde/debian/control | 13 + .../launchpad/sflphone-kde/debian/copyright | 8 + .../launchpad/sflphone-kde/debian/menu | 6 + .../launchpad/sflphone-kde/debian/rules | 5 + .../sflphone-kde/debian/source.backup/format | 1 + .../sflphone-plugins/debian/changelog | 3117 ++++++++++++++ .../launchpad/sflphone-plugins/debian/compat | 1 + .../launchpad/sflphone-plugins/debian/control | 20 + .../sflphone-plugins/debian/copyright | 28 + .../launchpad/sflphone-plugins/debian/cron.d | 4 + .../launchpad/sflphone-plugins/debian/dirs | 3 + .../launchpad/sflphone-plugins/debian/docs | 5 + .../sflphone-plugins/debian/manpages | 0 .../launchpad/sflphone-plugins/debian/rules | 116 + .../sflphone-plugins/debian/substvars | 1 + .../launchpad/sflphone-video/debian/changelog | 3585 +++++++++++++++++ .../launchpad/sflphone-video/debian/control | 12 + .../launchpad/sflphone/debian/changelog | 3138 +++++++++++++++ .../launchpad/sflphone/debian/control | 12 + tools/build-system/make-telify-package.sh | 45 + tools/build-system/rpm/sflphone.spec | 372 ++ .../build-system/scripts/run_package_test.sh | 291 ++ .../scripts/sflphone_integration.sh | 90 + tools/build-system/setenv.sh | 19 + tools/build-system/sfl-git-dch-2.sh | 94 + tools/dringctrl/__init__.py | 29 + tools/dringctrl/controler.py | 616 +++ tools/dringctrl/dring.functest.yml | 208 + tools/dringctrl/dringctrl.py | 239 ++ tools/dringctrl/dringctrl_testdbus.py | 390 ++ tools/dringctrl/errors.py | 50 + tools/dringctrl/sippwrap.py | 160 + tools/dringctrl/test_dring_dbus_interface.py | 412 ++ tools/dringctrl/tester.py | 187 + tools/dringctrl/toggle_video_preview.py | 50 + tools/git-gerrit | 82 + tools/git-redmine | 58 + tools/sflphone-callto | 57 + tools/sippxml/account_uac_send_hangup.xml | 147 + .../sippxml/account_uac_send_peer_hungup.xml | 155 + .../sippxml/account_uas_receive_transfer.xml | 101 + tools/sippxml/account_uas_recv_hangup.xml | 119 + .../sippxml/account_uas_recv_peer_hungup.xml | 127 + tools/sippxml/account_uas_recv_transfered.xml | 132 + tools/sippxml/account_uas_register.xml | 63 + tools/sippxml/accountcalluac.xml | 186 + tools/sippxml/accountcalluas.xml | 159 + tools/sippxml/g711a.pcap | Bin 0 -> 73184 bytes tools/sippxml/ip2ip_uac_send_hangup.xml | 98 + tools/sippxml/ip2ip_uac_send_peer_hungup.xml | 92 + tools/sippxml/ip2ip_uas_recv_hangup.xml | 87 + tools/sippxml/ip2ip_uas_recv_hold_offhold.xml | 187 + tools/sippxml/ip2ip_uas_recv_peer_hungup.xml | 78 + tools/sippxml/ip2ipcalluac.xml | 85 + tools/sippxml/ip2ipcalluas.xml | 80 + tools/sippxml/simpleServiceRoute.xml | 85 + tools/sippxml/sippusage.txt | 460 +++ tools/sippxml/tempscript.sh | 5 + tools/sippxml/testEarlyMedia.xml | 107 + tools/sippxml/testsuiteuac.sh | 383 ++ tools/sippxml/uac_register_diff_contact.xml | 55 + tools/sippxml/uac_register_no_cvs_300.xml | 110 + tools/sippxml/uac_register_no_cvs_400.xml | 110 + tools/sippxml/uas_register_diff_contact.xml | 55 + tools/sippxml/voice | Bin 0 -> 252 bytes .../sflphone-translation-push-template | 38 + .../translations/sflphone-translation-update | 55 + tools/update-copyright | 42 + 942 files changed, 123854 insertions(+) create mode 100644 AUTHORS create mode 100644 CODING create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 INSTALL create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100644 astylerc create mode 100755 autogen.sh create mode 100644 bin/Makefile.am create mode 100644 bin/dbus/Makefile.am create mode 100644 bin/dbus/callmanager-introspec.xml create mode 100644 bin/dbus/configurationmanager-introspec.xml create mode 100644 bin/dbus/cx.ring.Ring.service.in create mode 100644 bin/dbus/dbus_cpp.h create mode 100644 bin/dbus/dbuscallmanager.cpp create mode 100644 bin/dbus/dbuscallmanager.h create mode 100644 bin/dbus/dbusclient.cpp create mode 100644 bin/dbus/dbusclient.h create mode 100644 bin/dbus/dbusconfigurationmanager.cpp create mode 100644 bin/dbus/dbusconfigurationmanager.h create mode 100644 bin/dbus/dbusinstance.cpp create mode 100644 bin/dbus/dbusinstance.h create mode 100644 bin/dbus/dbuspresencemanager.cpp create mode 100644 bin/dbus/dbuspresencemanager.h create mode 100644 bin/dbus/dbusvideomanager.cpp create mode 100644 bin/dbus/dbusvideomanager.h create mode 100644 bin/dbus/instance-introspec.xml create mode 100644 bin/dbus/presencemanager-introspec.xml create mode 100644 bin/dbus/videomanager-introspec.xml create mode 100644 bin/main.cpp create mode 100644 bin/osxmain.cpp create mode 100644 bin/winmain.cpp create mode 100644 configure.ac create mode 100644 contrib/.gitignore create mode 100755 contrib/bootstrap create mode 100644 contrib/src/README create mode 100644 contrib/src/boost-headers/SHA512SUMS create mode 100644 contrib/src/boost-headers/rules.mak create mode 100755 contrib/src/change_prefix.sh create mode 100644 contrib/src/expat/SHA512SUMS create mode 100644 contrib/src/expat/rules.mak create mode 100644 contrib/src/flac/SHA512SUMS create mode 100644 contrib/src/flac/libFLAC-pc.patch create mode 100644 contrib/src/flac/rules.mak create mode 100644 contrib/src/gcrypt/0001-Fix-assembly-division-check.patch create mode 100644 contrib/src/gcrypt/SHA512SUMS create mode 100644 contrib/src/gcrypt/fix-amd64-assembly-on-solaris.patch create mode 100644 contrib/src/gcrypt/rules.mak create mode 100755 contrib/src/get-arch.sh create mode 100644 contrib/src/gmp/SHA512SUMS create mode 100644 contrib/src/gmp/clang.patch create mode 100644 contrib/src/gmp/rules.mak create mode 100644 contrib/src/gmp/thumb.patch create mode 100644 contrib/src/gnutls/SHA512SUMS create mode 100644 contrib/src/gnutls/downgrade-automake-requirement.patch create mode 100644 contrib/src/gnutls/gnutls-no-egd.patch create mode 100644 contrib/src/gnutls/gnutls-pkgconfig-osx.patch create mode 100644 contrib/src/gnutls/gnutls-win32.patch create mode 100644 contrib/src/gnutls/mac-keychain-lookup.patch create mode 100644 contrib/src/gnutls/no-create-time-h.patch create mode 100644 contrib/src/gnutls/read-file-limits.h.patch create mode 100644 contrib/src/gnutls/rules.mak create mode 100644 contrib/src/gpg-error/SHA512SUMS create mode 100644 contrib/src/gpg-error/gpgerror-android.patch create mode 100644 contrib/src/gpg-error/missing-unistd-include.patch create mode 100644 contrib/src/gpg-error/rules.mak create mode 100644 contrib/src/gpg-error/windres-make.patch create mode 100644 contrib/src/gsm/SHA512SUMS create mode 100644 contrib/src/gsm/gsm-cross.patch create mode 100644 contrib/src/gsm/rules.mak create mode 100644 contrib/src/iax/rules.mak create mode 100644 contrib/src/iconv/SHA512SUMS create mode 100644 contrib/src/iconv/bins.patch create mode 100644 contrib/src/iconv/libiconv-android-ios.patch create mode 100644 contrib/src/iconv/libiconv-win64.patch create mode 100644 contrib/src/iconv/libiconv-winrt.patch create mode 100644 contrib/src/iconv/rules.mak create mode 100644 contrib/src/iconv/win32.patch create mode 100644 contrib/src/jack/SHA512SUMS create mode 100644 contrib/src/jack/config-osx.patch create mode 100644 contrib/src/jack/rules.mak create mode 100644 contrib/src/libav/osx.patch create mode 100644 contrib/src/libav/rules.mak create mode 100644 contrib/src/main.mak create mode 100644 contrib/src/nettle/SHA512SUMS create mode 100644 contrib/src/nettle/rules.mak create mode 100644 contrib/src/ogg/SHA512SUMS create mode 100644 contrib/src/ogg/libogg-1.1.patch create mode 100644 contrib/src/ogg/libogg-disable-check.patch create mode 100644 contrib/src/ogg/rules.mak create mode 100644 contrib/src/opendht/rules.mak create mode 100644 contrib/src/opus/SHA512SUMS create mode 100644 contrib/src/opus/rules.mak create mode 100644 contrib/src/pcre/SHA512SUMS create mode 100644 contrib/src/pcre/rules.mak create mode 100644 contrib/src/pjproject/SHA512SUMS create mode 100644 contrib/src/pjproject/aconfigureupdate.patch create mode 100644 contrib/src/pjproject/endianness.patch create mode 100644 contrib/src/pjproject/gnutls.patch create mode 100644 contrib/src/pjproject/ice_config.patch create mode 100644 contrib/src/pjproject/intptr_t.patch create mode 100644 contrib/src/pjproject/ipv6.patch create mode 100644 contrib/src/pjproject/multiple_listeners.patch create mode 100644 contrib/src/pjproject/notestsapps.patch create mode 100644 contrib/src/pjproject/pj_ice_sess.patch create mode 100644 contrib/src/pjproject/pj_win.patch create mode 100644 contrib/src/pjproject/rules.mak create mode 100644 contrib/src/pjproject/tls_cert.patch create mode 100644 contrib/src/pjproject/unknowncipher.patch create mode 100755 contrib/src/pkg-static.sh create mode 100644 contrib/src/samplerate/SHA512SUMS create mode 100644 contrib/src/samplerate/carbon.patch create mode 100644 contrib/src/samplerate/rules.mak create mode 100644 contrib/src/samplerate/soundcard.patch create mode 100644 contrib/src/sndfile/SHA512SUMS create mode 100644 contrib/src/sndfile/autotools.patch create mode 100644 contrib/src/sndfile/carbon.patch create mode 100644 contrib/src/sndfile/rules.mak create mode 100644 contrib/src/sndfile/soundcard.patch create mode 100644 contrib/src/speex/rules.mak create mode 100644 contrib/src/speexdsp/rules.mak create mode 100644 contrib/src/upnp/SHA512SUMS create mode 100644 contrib/src/upnp/libupnp-configure.patch create mode 100644 contrib/src/upnp/libupnp-ipv6.patch create mode 100644 contrib/src/upnp/libupnp-win32.patch create mode 100644 contrib/src/upnp/libupnp-win64.patch create mode 100644 contrib/src/upnp/miniserver.patch create mode 100644 contrib/src/upnp/rules.mak create mode 100644 contrib/src/uuid/SHA512SUMS create mode 100644 contrib/src/uuid/android.patch create mode 100644 contrib/src/uuid/rules.mak create mode 100644 contrib/src/vorbis/SHA512SUMS create mode 100644 contrib/src/vorbis/osx.patch create mode 100644 contrib/src/vorbis/rules.mak create mode 100644 contrib/src/vpx/SHA512SUMS create mode 100644 contrib/src/vpx/rules.mak create mode 100644 contrib/src/x264/remove-align.patch create mode 100644 contrib/src/x264/rules.mak create mode 100644 contrib/src/yaml-cpp/SHA512SUMS create mode 100644 contrib/src/yaml-cpp/cmake.patch create mode 100644 contrib/src/yaml-cpp/rules.mak create mode 100644 contrib/src/zlib/SHA512SUMS create mode 100644 contrib/src/zlib/rules.mak create mode 100644 contrib/tarballs/.gitignore create mode 100644 doc/Makefile.am create mode 100644 doc/README create mode 100644 doc/dbus-api/Makefile.am create mode 100644 doc/dbus-api/README create mode 100644 doc/dbus-api/doc/templates/devhelp.devhelp2 create mode 100644 doc/dbus-api/doc/templates/errors.html create mode 100644 doc/dbus-api/doc/templates/fullindex.html create mode 100644 doc/dbus-api/doc/templates/generic-types.html create mode 100644 doc/dbus-api/doc/templates/index.html create mode 100644 doc/dbus-api/doc/templates/interface.html create mode 100644 doc/dbus-api/doc/templates/interfaces.html create mode 100644 doc/dbus-api/doc/templates/style.css create mode 100644 doc/dbus-api/spec/all.xml create mode 120000 doc/dbus-api/spec/callmanager-introspec.xml create mode 120000 doc/dbus-api/spec/configurationmanager-introspec.xml create mode 100644 doc/dbus-api/spec/errors.xml create mode 100644 doc/dbus-api/spec/generic-types.xml create mode 120000 doc/dbus-api/spec/instance-introspec.xml create mode 120000 doc/dbus-api/spec/presencemanager-introspec.xml create mode 100644 doc/dbus-api/tools/devhelp.xsl create mode 100755 doc/dbus-api/tools/doc-generator.py create mode 100644 doc/dbus-api/tools/doc-generator.xsl create mode 100644 doc/dbus-api/tools/specparser.py create mode 100644 doc/dbus-api/tools/xincludator.py create mode 100644 doc/doxygen/Makefile.am create mode 100644 doc/doxygen/blank.html create mode 100644 doc/doxygen/core-doc.cfg.in create mode 100644 doc/doxygen/gtk-gui-doc.cfg.in create mode 100644 doc/general_component_diagram.png create mode 100644 extras/tools/.gitignore create mode 100755 extras/tools/bootstrap create mode 100644 extras/tools/packages.mak create mode 100644 extras/tools/tools.mak create mode 100644 globals.mak create mode 100644 m4/.gitignore create mode 100644 m4/as-ac-expand.m4 create mode 100644 m4/ax_cxx_compile_stdcxx_11.m4 create mode 100644 m4/ax_pthread.m4 create mode 100644 m4/dolt.m4 create mode 100644 man/Makefile.am create mode 100644 man/README.manpages create mode 100644 man/dring.pod create mode 100644 ringtones/Makefile.am create mode 100644 ringtones/konga.ul create mode 100644 ringtones/phone.au create mode 100644 ringtones/phone2.au create mode 100644 src/Makefile.am create mode 100644 src/account.cpp create mode 100644 src/account.h create mode 100644 src/account_factory.cpp create mode 100644 src/account_factory.h create mode 100644 src/account_schema.h create mode 100644 src/array_size.h create mode 100644 src/call.cpp create mode 100644 src/call.h create mode 100644 src/call_factory.cpp create mode 100644 src/call_factory.h create mode 100644 src/client/Makefile.am create mode 100644 src/client/callmanager.cpp create mode 100644 src/client/configurationmanager.cpp create mode 100644 src/client/presencemanager.cpp create mode 100644 src/client/signal.cpp create mode 100644 src/client/signal.h create mode 100644 src/client/videomanager.cpp create mode 100644 src/client/videomanager.h create mode 100644 src/conference.cpp create mode 100644 src/conference.h create mode 100644 src/config/Makefile.am create mode 100644 src/config/serializable.h create mode 100644 src/config/yamlparser.cpp create mode 100644 src/config/yamlparser.h create mode 100644 src/dring/account_const.h create mode 100644 src/dring/call_const.h create mode 100644 src/dring/callmanager_interface.h create mode 100644 src/dring/configurationmanager_interface.h create mode 100644 src/dring/dring.h create mode 100644 src/dring/presencemanager_interface.h create mode 100644 src/dring/security_const.h create mode 100644 src/dring/videomanager_interface.h create mode 100644 src/enumclass_utils.h create mode 100644 src/fileutils.cpp create mode 100644 src/fileutils.h create mode 100644 src/gnutls_support.h create mode 100644 src/hooks/Makefile.am create mode 100644 src/hooks/urlhook.cpp create mode 100644 src/hooks/urlhook.h create mode 100644 src/iax/Makefile.am create mode 100644 src/iax/iaxaccount.cpp create mode 100644 src/iax/iaxaccount.h create mode 100644 src/iax/iaxcall.cpp create mode 100644 src/iax/iaxcall.h create mode 100644 src/iax/iaxvoiplink.cpp create mode 100644 src/iax/iaxvoiplink.h create mode 100644 src/ice_socket.h create mode 100644 src/ice_transport.cpp create mode 100644 src/ice_transport.h create mode 100644 src/im/Makefile.am create mode 100644 src/im/instant_messaging.cpp create mode 100644 src/im/instant_messaging.h create mode 100644 src/intrin.h create mode 100644 src/ip_utils.cpp create mode 100644 src/ip_utils.h create mode 100644 src/logger.c create mode 100644 src/logger.h create mode 100644 src/manager.cpp create mode 100644 src/manager.h create mode 100644 src/managerimpl.cpp create mode 100644 src/managerimpl.h create mode 100644 src/map_utils.h create mode 100644 src/media/Makefile.am create mode 100644 src/media/audio/Makefile.am create mode 100644 src/media/audio/alsa/Makefile.am create mode 100644 src/media/audio/alsa/alsalayer.cpp create mode 100644 src/media/audio/alsa/alsalayer.h create mode 100644 src/media/audio/audio_rtp_session.cpp create mode 100644 src/media/audio/audio_rtp_session.h create mode 100644 src/media/audio/audiobuffer.cpp create mode 100644 src/media/audio/audiobuffer.h create mode 100644 src/media/audio/audiolayer.cpp create mode 100644 src/media/audio/audiolayer.h create mode 100644 src/media/audio/audioloop.cpp create mode 100644 src/media/audio/audioloop.h create mode 100644 src/media/audio/audiorecord.cpp create mode 100644 src/media/audio/audiorecord.h create mode 100644 src/media/audio/audiorecorder.cpp create mode 100644 src/media/audio/audiorecorder.h create mode 100644 src/media/audio/coreaudio/Makefile.am create mode 100644 src/media/audio/coreaudio/audiodevice.cpp create mode 100644 src/media/audio/coreaudio/audiodevice.h create mode 100644 src/media/audio/coreaudio/corelayer.cpp create mode 100644 src/media/audio/coreaudio/corelayer.h create mode 100644 src/media/audio/dcblocker.cpp create mode 100644 src/media/audio/dcblocker.h create mode 100644 src/media/audio/dsp.cpp create mode 100644 src/media/audio/dsp.h create mode 100644 src/media/audio/jack/Makefile.am create mode 100644 src/media/audio/jack/jacklayer.cpp create mode 100644 src/media/audio/jack/jacklayer.h create mode 100644 src/media/audio/opensl/Makefile.am create mode 100644 src/media/audio/opensl/opensllayer.cpp create mode 100644 src/media/audio/opensl/opensllayer.h create mode 100644 src/media/audio/pulseaudio/Makefile.am create mode 100644 src/media/audio/pulseaudio/audiostream.cpp create mode 100644 src/media/audio/pulseaudio/audiostream.h create mode 100644 src/media/audio/pulseaudio/pulselayer.cpp create mode 100644 src/media/audio/pulseaudio/pulselayer.h create mode 100644 src/media/audio/recordable.cpp create mode 100644 src/media/audio/recordable.h create mode 100644 src/media/audio/resampler.cpp create mode 100644 src/media/audio/resampler.h create mode 100644 src/media/audio/ringbuffer.cpp create mode 100644 src/media/audio/ringbuffer.h create mode 100644 src/media/audio/ringbufferpool.cpp create mode 100644 src/media/audio/ringbufferpool.h create mode 100644 src/media/audio/sound/Makefile.am create mode 100644 src/media/audio/sound/audiofile.cpp create mode 100644 src/media/audio/sound/audiofile.h create mode 100644 src/media/audio/sound/dtmf.cpp create mode 100644 src/media/audio/sound/dtmf.h create mode 100644 src/media/audio/sound/dtmfgenerator.cpp create mode 100644 src/media/audio/sound/dtmfgenerator.h create mode 100644 src/media/audio/sound/tone.cpp create mode 100644 src/media/audio/sound/tone.h create mode 100644 src/media/audio/sound/tonelist.cpp create mode 100644 src/media/audio/sound/tonelist.h create mode 100644 src/media/libav_deps.h create mode 100644 src/media/libav_utils.cpp create mode 100644 src/media/libav_utils.h create mode 100644 src/media/media_buffer.cpp create mode 100644 src/media/media_buffer.h create mode 100644 src/media/media_codec.cpp create mode 100644 src/media/media_codec.h create mode 100644 src/media/media_decoder.cpp create mode 100644 src/media/media_decoder.h create mode 100644 src/media/media_device.h create mode 100644 src/media/media_encoder.cpp create mode 100644 src/media/media_encoder.h create mode 100644 src/media/media_io_handle.cpp create mode 100644 src/media/media_io_handle.h create mode 100644 src/media/rtp_session.h create mode 100644 src/media/socket_pair.cpp create mode 100644 src/media/socket_pair.h create mode 100644 src/media/srtp.c create mode 100644 src/media/srtp.h create mode 100644 src/media/system_codec_container.cpp create mode 100644 src/media/system_codec_container.h create mode 100644 src/media/video/.gitignore create mode 100644 src/media/video/Makefile.am create mode 100644 src/media/video/TODO create mode 100644 src/media/video/osxvideo/Makefile.am create mode 100644 src/media/video/osxvideo/video_device_impl.mm create mode 100644 src/media/video/osxvideo/video_device_monitor_impl.mm create mode 100644 src/media/video/shm_header.h create mode 100644 src/media/video/sinkclient.cpp create mode 100644 src/media/video/sinkclient.h create mode 100644 src/media/video/test/.gitignore create mode 100644 src/media/video/test/Makefile.am create mode 100644 src/media/video/test/README create mode 100755 src/media/video/test/make_rtp_stream.sh create mode 100644 src/media/video/test/shm_src.cpp create mode 100644 src/media/video/test/shm_src.h create mode 100644 src/media/video/test/shmclient.cpp create mode 100644 src/media/video/test/test_shm.cpp create mode 100644 src/media/video/test/test_video_input.cpp create mode 100644 src/media/video/test/test_video_input.h create mode 100644 src/media/video/test/test_video_rtp.cpp create mode 100644 src/media/video/v4l2/Makefile.am create mode 100644 src/media/video/v4l2/video_device_impl.cpp create mode 100644 src/media/video/v4l2/video_device_monitor_impl.cpp create mode 100644 src/media/video/video_base.cpp create mode 100644 src/media/video/video_base.h create mode 100644 src/media/video/video_device.h create mode 100644 src/media/video/video_device_monitor.cpp create mode 100644 src/media/video/video_device_monitor.h create mode 100644 src/media/video/video_input.cpp create mode 100644 src/media/video/video_input.h create mode 100644 src/media/video/video_mixer.cpp create mode 100644 src/media/video/video_mixer.h create mode 100644 src/media/video/video_provider.h create mode 100644 src/media/video/video_receive_thread.cpp create mode 100644 src/media/video/video_receive_thread.h create mode 100644 src/media/video/video_rtp_session.cpp create mode 100644 src/media/video/video_rtp_session.h create mode 100644 src/media/video/video_scaler.cpp create mode 100644 src/media/video/video_scaler.h create mode 100644 src/media/video/video_sender.cpp create mode 100644 src/media/video/video_sender.h create mode 100644 src/noncopyable.h create mode 100644 src/numbercleaner.cpp create mode 100644 src/numbercleaner.h create mode 100644 src/plugin_loader.h create mode 100644 src/plugin_loader_dl.cpp create mode 100644 src/plugin_manager.cpp create mode 100644 src/plugin_manager.h create mode 100644 src/preferences.cpp create mode 100644 src/preferences.h create mode 100644 src/registration_states.h create mode 100644 src/ring_api.cpp create mode 100644 src/ring_plugin.h create mode 100644 src/ring_types.h create mode 100644 src/ringdht/Makefile.am create mode 100644 src/ringdht/ringaccount.cpp create mode 100644 src/ringdht/ringaccount.h create mode 100644 src/ringdht/sip_transport_ice.cpp create mode 100644 src/ringdht/sip_transport_ice.h create mode 100644 src/ringdht/sips_transport_ice.cpp create mode 100644 src/ringdht/sips_transport_ice.h create mode 100644 src/rw_mutex.h create mode 100644 src/sip/Makefile.am create mode 100644 src/sip/base64.c create mode 100644 src/sip/base64.h create mode 100644 src/sip/pattern.cpp create mode 100644 src/sip/pattern.h create mode 100644 src/sip/pres_sub_client.cpp create mode 100644 src/sip/pres_sub_client.h create mode 100644 src/sip/pres_sub_server.cpp create mode 100644 src/sip/pres_sub_server.h create mode 100644 src/sip/sdes_negotiator.cpp create mode 100644 src/sip/sdes_negotiator.h create mode 100644 src/sip/sdp.cpp create mode 100644 src/sip/sdp.h create mode 100644 src/sip/sip_utils.cpp create mode 100644 src/sip/sip_utils.h create mode 100644 src/sip/sipaccount.cpp create mode 100644 src/sip/sipaccount.h create mode 100644 src/sip/sipaccountbase.cpp create mode 100644 src/sip/sipaccountbase.h create mode 100644 src/sip/sipcall.cpp create mode 100644 src/sip/sipcall.h create mode 100644 src/sip/sippresence.cpp create mode 100644 src/sip/sippresence.h create mode 100644 src/sip/siptransport.cpp create mode 100644 src/sip/siptransport.h create mode 100644 src/sip/sipvoiplink.cpp create mode 100644 src/sip/sipvoiplink.h create mode 100644 src/sip/tlsvalidator.cpp create mode 100644 src/sip/tlsvalidator.h create mode 100644 src/string_utils.cpp create mode 100644 src/string_utils.h create mode 100644 src/threadloop.cpp create mode 100644 src/threadloop.h create mode 100644 src/upnp/Makefile.am create mode 100644 src/upnp/upnp_context.cpp create mode 100644 src/upnp/upnp_context.h create mode 100644 src/upnp/upnp_control.cpp create mode 100644 src/upnp/upnp_control.h create mode 100644 src/upnp/upnp_igd.cpp create mode 100644 src/upnp/upnp_igd.h create mode 100644 src/utf8_utils.cpp create mode 100644 src/utf8_utils.h create mode 100644 test/.gitignore create mode 100644 test/Makefile.am create mode 100644 test/accounttest.cpp create mode 100644 test/accounttest.h create mode 100644 test/audiobuffertest.cpp create mode 100644 test/audiobuffertest.h create mode 100644 test/audiocodectest.cpp create mode 100644 test/audiocodectest.h create mode 100644 test/audiolayertest.cpp create mode 100644 test/audiolayertest.h create mode 100644 test/configurationtest.cpp create mode 100644 test/configurationtest.h create mode 100644 test/constants.h create mode 100644 test/dring-sample.yml create mode 100644 test/history-sample.tpl create mode 100644 test/historytest.cpp create mode 100644 test/historytest.h create mode 100644 test/hooktest.cpp create mode 100644 test/hooktest.h create mode 100644 test/instantmessagingtest.cpp create mode 100644 test/instantmessagingtest.h create mode 100644 test/iptest.cpp create mode 100644 test/iptest.h create mode 100644 test/main.cpp create mode 100644 test/numbercleanertest.cpp create mode 100644 test/numbercleanertest.h create mode 100644 test/resamplertest.cpp create mode 100644 test/resamplertest.h create mode 100644 test/ringbufferpooltest.cpp create mode 100644 test/ringbufferpooltest.h create mode 100755 test/run_tests.sh create mode 100755 test/scripts/presence_test.py create mode 100755 test/scripts/stress_test.py create mode 100644 test/sdesnegotiatortest.cpp create mode 100644 test/sdesnegotiatortest.h create mode 100644 test/sdptest.cpp create mode 100644 test/sdptest.h create mode 100644 test/sippxml/test_1.xml create mode 100644 test/sippxml/test_2.xml create mode 100644 test/sippxml/test_3.xml create mode 100644 test/sippxml/test_4.xml create mode 100644 test/siptest.cpp create mode 100644 test/siptest.h create mode 100644 test/test_utils.h create mode 100644 test/tlsSample/ca.crt create mode 100644 test/tlsSample/cert.crt create mode 100644 test/tlsSample/certwithkey.pem create mode 100644 test/tlsSample/corruptedkey.pem create mode 100644 test/tlsSample/expired.crt create mode 100644 test/tlsSample/fake.crt create mode 100644 test/tlsSample/keyonly.pem create mode 100644 test/tlstest.cpp create mode 100644 test/tlstest.h create mode 100644 test/waveSample/M1F1-int16WE-AFsp.wav create mode 100644 tools/.gitignore create mode 100644 tools/asterisk/extensions.conf create mode 100644 tools/asterisk/keys/500.crt create mode 100644 tools/asterisk/keys/500.csr create mode 100644 tools/asterisk/keys/500.key create mode 100644 tools/asterisk/keys/500.pem create mode 100644 tools/asterisk/keys/600.crt create mode 100644 tools/asterisk/keys/600.csr create mode 100644 tools/asterisk/keys/600.key create mode 100644 tools/asterisk/keys/600.pem create mode 100644 tools/asterisk/keys/asterisk.crt create mode 100644 tools/asterisk/keys/asterisk.csr create mode 100644 tools/asterisk/keys/asterisk.key create mode 100644 tools/asterisk/keys/asterisk.pem create mode 100644 tools/asterisk/keys/ca.cfg create mode 100644 tools/asterisk/keys/ca.crt create mode 100644 tools/asterisk/keys/ca.key create mode 100644 tools/asterisk/keys/testphone1.crt create mode 100644 tools/asterisk/keys/testphone1.csr create mode 100644 tools/asterisk/keys/testphone1.key create mode 100644 tools/asterisk/keys/testphone1.pem create mode 100644 tools/asterisk/keys/tmp.cfg create mode 100644 tools/asterisk/sip.conf create mode 100644 tools/build-system/README.launchpad create mode 100755 tools/build-system/build_tarball.sh create mode 100755 tools/build-system/get-kde.sh create mode 100755 tools/build-system/hudson-sflphone-script.sh create mode 100755 tools/build-system/launch-build-machine-jenkins.sh create mode 100644 tools/build-system/launchpad/dput.conf create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/changelog create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/compat create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/control create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/control.debian create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/control.ubuntu create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/copyright create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/files create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.debhelper.log create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.install create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.links create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.links.debian create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.links.ubuntu create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.substvars create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/DEBIAN/control create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/DEBIAN/md5sums create mode 100755 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/DEBIAN/postinst create mode 100755 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/bin/sflphone-handler create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/doc/mozilla-telify-sflphone/changelog.gz create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome.manifest create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/ask32.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/browser.xul create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/config.js create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/config.xul create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/country_data.js create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/dialog.css create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/edit22x15.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/editNumber.js create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/editNumber.xul create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/error32.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1-canada.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1242.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1246.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1264.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1268.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1284.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1340.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1345.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1441.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1473.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1649.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1664.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1670.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1671.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1684.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1758.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1767.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1784.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1787.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1809.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1829.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1868.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1869.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1876.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1939.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/20.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/212.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/213.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/216.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/218.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/220.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/221.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/222.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/223.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/224.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/225.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/226.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/227.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/228.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/229.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/230.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/231.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/232.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/233.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/234.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/235.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/236.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/237.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/238.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/239.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/240.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/241.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/242.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/243.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/244.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/245.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/246.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/247.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/248.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/249.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/250.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/251.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/252.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/253.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/254.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/255.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/256.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/257.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/258.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/260.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/261.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/262.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/263.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/264.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/265.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/266.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/267.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/268.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/269.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/27.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/290.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/291.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/297.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/298.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/299.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/30.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/31.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/32.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/33.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/34.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/350.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/351.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/352.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/353.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/354.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/355.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/356.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/357.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/358.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/359.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/36.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/370.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/371.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/372.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/373.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/374.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/37447.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/37497.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/375.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/376.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/377.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/37744.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/378.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/379.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/380.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/381-kosovo.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/381.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/382.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/385.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/386.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/38649.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/387.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/3883.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/389.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/39-vatican.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/39.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/40.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/41.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/420.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/421.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/423.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/43.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/44.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/45.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/46.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/47.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/48.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/49.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/500.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/501.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/502.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/503.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/504.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/505.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/506.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/507.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/508.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/509.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/51.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/52.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/53.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/54.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/55.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/56.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/57.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/58.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/590.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/591.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/592.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/593.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/594.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/595.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/596.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/597.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/598.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/599.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/60.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/61.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/62.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/63.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/64.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/65.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/66.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/670.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/672-norfolk_island.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/672.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/673.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/674.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/675.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/676.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/677.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/678.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/679.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/680.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/681.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/682.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/683.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/685.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/686.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/687.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/688.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/689.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/690.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/691.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/692.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/7-kazakhstan.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/7.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/81.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/82.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/84.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/850.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/852.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/853.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/855.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/856.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/86.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/870.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/871.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/872.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/873.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/874.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/880.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/886.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/90.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/90392.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/91.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/92.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/93.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/94.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/95.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/960.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/961.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/962.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/963.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/964.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/965.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/966.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/967.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/968.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/971.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/972.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/973.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/974.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/975.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/976.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/977.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/98.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/992.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/993.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/994.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/995.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/996.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/998.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/icon18_active.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/icon18_inactive.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/icon32.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/icon96.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/icon_menu.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/info32.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/jshashtable.js create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/messagebox.js create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/messagebox.xul create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/pref.js create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/telify.js create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/util.js create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/warn32.png create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/de-DE/country_locale.js create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/de-DE/custom_preset.js create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/de-DE/lang.dtd create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/de-DE/lang.properties create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/de-DE/locale.js create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/en-US/country_locale.js create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/en-US/custom_preset.js create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/en-US/lang.dtd create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/en-US/lang.properties create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/en-US/locale.js create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/defaults/preferences/preferences.js create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/install.rdf create mode 100755 tools/build-system/launchpad/mozilla-telify-sflphone/debian/postinst create mode 100755 tools/build-system/launchpad/mozilla-telify-sflphone/debian/rules create mode 100644 tools/build-system/launchpad/mozilla-telify-sflphone/debian/watch create mode 100644 tools/build-system/launchpad/sflphone-daemon-video/debian/changelog create mode 100644 tools/build-system/launchpad/sflphone-daemon-video/debian/compat create mode 100644 tools/build-system/launchpad/sflphone-daemon-video/debian/control create mode 100644 tools/build-system/launchpad/sflphone-daemon-video/debian/copyright create mode 100644 tools/build-system/launchpad/sflphone-daemon-video/debian/cron.d create mode 100644 tools/build-system/launchpad/sflphone-daemon-video/debian/dirs create mode 100644 tools/build-system/launchpad/sflphone-daemon-video/debian/docs create mode 100644 tools/build-system/launchpad/sflphone-daemon-video/debian/manpages create mode 100644 tools/build-system/launchpad/sflphone-daemon-video/debian/postinst create mode 100644 tools/build-system/launchpad/sflphone-daemon-video/debian/postrm create mode 100644 tools/build-system/launchpad/sflphone-daemon-video/debian/preinst create mode 100755 tools/build-system/launchpad/sflphone-daemon-video/debian/rules create mode 100644 tools/build-system/launchpad/sflphone-daemon/debian/changelog create mode 100644 tools/build-system/launchpad/sflphone-daemon/debian/compat create mode 100644 tools/build-system/launchpad/sflphone-daemon/debian/control create mode 100644 tools/build-system/launchpad/sflphone-daemon/debian/copyright create mode 100644 tools/build-system/launchpad/sflphone-daemon/debian/cron.d create mode 100644 tools/build-system/launchpad/sflphone-daemon/debian/dirs create mode 100644 tools/build-system/launchpad/sflphone-daemon/debian/docs create mode 100644 tools/build-system/launchpad/sflphone-daemon/debian/manpages create mode 100644 tools/build-system/launchpad/sflphone-daemon/debian/postinst create mode 100644 tools/build-system/launchpad/sflphone-daemon/debian/postrm create mode 100644 tools/build-system/launchpad/sflphone-daemon/debian/preinst create mode 100755 tools/build-system/launchpad/sflphone-daemon/debian/rules create mode 100644 tools/build-system/launchpad/sflphone-gnome-video/debian/changelog create mode 100644 tools/build-system/launchpad/sflphone-gnome-video/debian/compat create mode 100644 tools/build-system/launchpad/sflphone-gnome-video/debian/control create mode 100644 tools/build-system/launchpad/sflphone-gnome-video/debian/copyright create mode 100644 tools/build-system/launchpad/sflphone-gnome-video/debian/cron.d create mode 100644 tools/build-system/launchpad/sflphone-gnome-video/debian/dirs create mode 100644 tools/build-system/launchpad/sflphone-gnome-video/debian/docs create mode 100644 tools/build-system/launchpad/sflphone-gnome-video/debian/manpages create mode 100644 tools/build-system/launchpad/sflphone-gnome-video/debian/postinst create mode 100644 tools/build-system/launchpad/sflphone-gnome-video/debian/postrm create mode 100644 tools/build-system/launchpad/sflphone-gnome-video/debian/preinst create mode 100644 tools/build-system/launchpad/sflphone-gnome-video/debian/prerm create mode 100755 tools/build-system/launchpad/sflphone-gnome-video/debian/rules create mode 100644 tools/build-system/launchpad/sflphone-gnome-video/debian/substvars create mode 100644 tools/build-system/launchpad/sflphone-gnome/debian/changelog create mode 100644 tools/build-system/launchpad/sflphone-gnome/debian/compat create mode 100644 tools/build-system/launchpad/sflphone-gnome/debian/control create mode 100644 tools/build-system/launchpad/sflphone-gnome/debian/copyright create mode 100644 tools/build-system/launchpad/sflphone-gnome/debian/cron.d create mode 100644 tools/build-system/launchpad/sflphone-gnome/debian/dirs create mode 100644 tools/build-system/launchpad/sflphone-gnome/debian/docs create mode 100644 tools/build-system/launchpad/sflphone-gnome/debian/manpages create mode 100644 tools/build-system/launchpad/sflphone-gnome/debian/postinst create mode 100644 tools/build-system/launchpad/sflphone-gnome/debian/postrm create mode 100644 tools/build-system/launchpad/sflphone-gnome/debian/preinst create mode 100644 tools/build-system/launchpad/sflphone-gnome/debian/prerm create mode 100755 tools/build-system/launchpad/sflphone-gnome/debian/rules create mode 100644 tools/build-system/launchpad/sflphone-gnome/debian/substvars create mode 100644 tools/build-system/launchpad/sflphone-kde/debian/changelog create mode 100644 tools/build-system/launchpad/sflphone-kde/debian/compat create mode 100644 tools/build-system/launchpad/sflphone-kde/debian/control create mode 100644 tools/build-system/launchpad/sflphone-kde/debian/copyright create mode 100644 tools/build-system/launchpad/sflphone-kde/debian/menu create mode 100755 tools/build-system/launchpad/sflphone-kde/debian/rules create mode 100644 tools/build-system/launchpad/sflphone-kde/debian/source.backup/format create mode 100644 tools/build-system/launchpad/sflphone-plugins/debian/changelog create mode 100644 tools/build-system/launchpad/sflphone-plugins/debian/compat create mode 100644 tools/build-system/launchpad/sflphone-plugins/debian/control create mode 100644 tools/build-system/launchpad/sflphone-plugins/debian/copyright create mode 100644 tools/build-system/launchpad/sflphone-plugins/debian/cron.d create mode 100644 tools/build-system/launchpad/sflphone-plugins/debian/dirs create mode 100644 tools/build-system/launchpad/sflphone-plugins/debian/docs create mode 100644 tools/build-system/launchpad/sflphone-plugins/debian/manpages create mode 100755 tools/build-system/launchpad/sflphone-plugins/debian/rules create mode 100644 tools/build-system/launchpad/sflphone-plugins/debian/substvars create mode 100644 tools/build-system/launchpad/sflphone-video/debian/changelog create mode 100644 tools/build-system/launchpad/sflphone-video/debian/control create mode 100644 tools/build-system/launchpad/sflphone/debian/changelog create mode 100644 tools/build-system/launchpad/sflphone/debian/control create mode 100644 tools/build-system/make-telify-package.sh create mode 100644 tools/build-system/rpm/sflphone.spec create mode 100755 tools/build-system/scripts/run_package_test.sh create mode 100755 tools/build-system/scripts/sflphone_integration.sh create mode 100755 tools/build-system/setenv.sh create mode 100755 tools/build-system/sfl-git-dch-2.sh create mode 100644 tools/dringctrl/__init__.py create mode 100644 tools/dringctrl/controler.py create mode 100644 tools/dringctrl/dring.functest.yml create mode 100755 tools/dringctrl/dringctrl.py create mode 100644 tools/dringctrl/dringctrl_testdbus.py create mode 100644 tools/dringctrl/errors.py create mode 100644 tools/dringctrl/sippwrap.py create mode 100644 tools/dringctrl/test_dring_dbus_interface.py create mode 100644 tools/dringctrl/tester.py create mode 100755 tools/dringctrl/toggle_video_preview.py create mode 100755 tools/git-gerrit create mode 100755 tools/git-redmine create mode 100755 tools/sflphone-callto create mode 100644 tools/sippxml/account_uac_send_hangup.xml create mode 100644 tools/sippxml/account_uac_send_peer_hungup.xml create mode 100644 tools/sippxml/account_uas_receive_transfer.xml create mode 100644 tools/sippxml/account_uas_recv_hangup.xml create mode 100644 tools/sippxml/account_uas_recv_peer_hungup.xml create mode 100644 tools/sippxml/account_uas_recv_transfered.xml create mode 100644 tools/sippxml/account_uas_register.xml create mode 100644 tools/sippxml/accountcalluac.xml create mode 100644 tools/sippxml/accountcalluas.xml create mode 100644 tools/sippxml/g711a.pcap create mode 100644 tools/sippxml/ip2ip_uac_send_hangup.xml create mode 100644 tools/sippxml/ip2ip_uac_send_peer_hungup.xml create mode 100644 tools/sippxml/ip2ip_uas_recv_hangup.xml create mode 100644 tools/sippxml/ip2ip_uas_recv_hold_offhold.xml create mode 100644 tools/sippxml/ip2ip_uas_recv_peer_hungup.xml create mode 100644 tools/sippxml/ip2ipcalluac.xml create mode 100644 tools/sippxml/ip2ipcalluas.xml create mode 100644 tools/sippxml/simpleServiceRoute.xml create mode 100644 tools/sippxml/sippusage.txt create mode 100644 tools/sippxml/tempscript.sh create mode 100644 tools/sippxml/testEarlyMedia.xml create mode 100644 tools/sippxml/testsuiteuac.sh create mode 100644 tools/sippxml/uac_register_diff_contact.xml create mode 100644 tools/sippxml/uac_register_no_cvs_300.xml create mode 100644 tools/sippxml/uac_register_no_cvs_400.xml create mode 100644 tools/sippxml/uas_register_diff_contact.xml create mode 100644 tools/sippxml/voice create mode 100755 tools/translations/sflphone-translation-push-template create mode 100755 tools/translations/sflphone-translation-update create mode 100755 tools/update-copyright diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000000..e57ef475a1 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,86 @@ +Current authors +--------------- + +Adrien Béraud <adrien dot beraud at savoirfairelinux dot com> +Alexandre Lision <alexandre dot lision at savoirfairelinux dot com> +Guillaume Roguez <guillaume dot roguez at savoirfairelinux dot com> +Edric Milaret <edric dot ladent-milaret at savoirfairelinux dot com> +Eloi BAIL <eloi dot bail at savoirfairelinux dot com> +Emmanuel Lepage <emmanuel dot lepage at savoirfairelinux dot com> +Stepan Salenikovich <stepan dot salenikovich at savoirfairelinux dot com> + +Former authors +-------------- + +Philippe Groarke <philippe.groarke@savoirfairelinux.com> + - audio fixes + - windows port startup + +Tristan Matthews <tristan dot matthews at savoirfairelinux dot com> + - Many portions of code + +Emmanuel Milou <emmanuel dot milou at savoirfairelinux dot com> + - ALSA implementation ( replaces portaudio ) + - Dynamic loading of audio codecs ( shared libraries ) + - Debian packages + - GTK client error handling + - Plus many portions of code in dring and sflphone-gtk + - Test and debugging + +Alexandre Savard <alexandre dot savard at savoirfairelinux dot com> + +Rafaël Carré <rafael dot carre at savoirfairelinux dot com> + +Vivien Didelot <vivien dot didelot at savoirfairelinux dot com> + +Alexandre Bourget <alexandre dot bourget at savoirfairelinux dot com> + - IAX implementation + +Guillaume Carmel-Archambault <guillaume.carmel-archambault at savoirfairelinux dot com> + - Presence + - Contacts +Yun Liu <yun.liu at savoirfairelinux dot com> + - Change sip library to pjsip + - Support multiple accounts registration + - Add chinese translation + - Many portions of test and debugging + +Polytechnic School of Montreal: + - Jean-Francois Blanchard-Dionne <jean-francois.blanchard-dionne at polymtl dot ca> + - Ala Eddine Limame <ala-eddine.limame at polymtl dot ca> + - Alexis S. Bourrelle <bourrelle at polymtl dot ca> + - Marilyne Mercier <marilyne.mercier at polymtl dot ca> + - Jean Tessier <jean.tessier at polymtl dot ca> + - Video layer implementation + - Video conference + +Pierre-Luc Beaudoin <pierre-luc.beaudoin at savoirfairelinux dot com> + - Many portions of code + - GTK client implementation + +Yan Morin <yan dot morin at savoirfairelinux dot com> + - zeroconf integration + - dring deamon + - add and improve sip core feature + - tests and debugging + +Jerome Oufella <jerome dot oufella at savoirfairelinux dot com> + - Many portions of code and bug fixes + +Julien Plissonneau Duquene <... at savoirfairelinux dot com> + - autotools cleanups + +Jean-Philippe Barrette-LaPierre + - Autotools support and portions of code + +Laurielle Lea + - Implementation of SFLphone + +Sherry Yang <syangs04 at yahoo dot com> + +Imran Akbar <imr at stanford dot edu> + - Working on Win32 port + +Contributors: + +Mikael Magnusson diff --git a/CODING b/CODING new file mode 100644 index 0000000000..696968e5de --- /dev/null +++ b/CODING @@ -0,0 +1,27 @@ += Coding standards = + +Please follow our coding standards when developing +http://projects.savoirfairelinux.com/projects/ring-daemon/wiki/Daemon_Coding_Rules + += Gerrit workflow = + +We are now using Gerrit as a code review tool. +Wiki documentation: http://projects.savoirfairelinux.com/projects/ring/wiki/WorkingWithGerrit + +== commit messages == + +Use Issue: followed by the ticket number + +== commit-msg hook == + +You may want to install the commit-msg hook that inserts a Change-Id on commit messages: +$ gitdir=$(git rev-parse --git-dir); scp -p -P 29420 username@gerrit-ring.savoirfairelinux.com:hooks/commit-msg ${gitdir}/hooks/ + +Other documentation source: + +* http://strongspace.com/rtyler/public/gerrit-jenkins-notes.pdf +* https://wiki.jenkins-ci.org/display/JENKINS/Gerrit+Trigger + +Thank you team! + +Test diff --git a/COPYING b/COPYING new file mode 100644 index 0000000000..32d670017b --- /dev/null +++ b/COPYING @@ -0,0 +1,675 @@ +GNU GENERAL PUBLIC LICENSE +Version 3, 29 June 2007 + +Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +Preamble + +The GNU General Public License is a free, copyleft license for +software and other kinds of works. + +The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, + the GNU General Public License is intended to guarantee your freedom to + share and change all versions of a program--to make sure it remains free + software for all its users. We, the Free Software Foundation, use the + GNU General Public License for most of our software; it applies also to + any other work released this way by its authors. You can apply it to + your programs, too. + + When we speak of free software, we are referring to freedom, not + price. Our General Public Licenses are designed to make sure that you + have the freedom to distribute copies of free software (and charge for + them if you wish), that you receive source code or can get it if you + want it, that you can change the software or use pieces of it in new + free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you + these rights or asking you to surrender the rights. Therefore, you have + certain responsibilities if you distribute copies of the software, or if + you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether + gratis or for a fee, you must pass on to the recipients the same + freedoms that you received. You must make sure that they, too, receive + or can get the source code. And you must show them these terms so they + know their rights. + + Developers that use the GNU GPL protect your rights with two steps: + (1) assert copyright on the software, and (2) offer you this License + giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains + that there is no warranty for this free software. For both users' and + authors' sake, the GPL requires that modified versions be marked as + changed, so that their problems will not be attributed erroneously to + authors of previous versions. + + Some devices are designed to deny users access to install or run + modified versions of the software inside them, although the manufacturer + can do so. This is fundamentally incompatible with the aim of + protecting users' freedom to change the software. The systematic + pattern of such abuse occurs in the area of products for individuals to + use, which is precisely where it is most unacceptable. Therefore, we + have designed this version of the GPL to prohibit the practice for those + products. If such problems arise substantially in other domains, we + stand ready to extend this provision to those domains in future versions + of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. + States should not allow patents to restrict development and use of + software on general-purpose computers, but in those that do, we wish to + avoid the special danger that patents applied to a free program could + make it effectively proprietary. To prevent this, the GPL assures that + patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and + modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of + works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this + License. Each licensee is addressed as "you". "Licensees" and + "recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work + in a fashion requiring copyright permission, other than the making of an + exact copy. The resulting work is called a "modified version" of the + earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based + on the Program. + + To "propagate" a work means to do anything with it that, without + permission, would make you directly or secondarily liable for + infringement under applicable copyright law, except executing it on a + computer or modifying a private copy. Propagation includes copying, + distribution (with or without modification), making available to the + public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other + parties to make or receive copies. Mere interaction with a user through + a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" + to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) + tells the user that there is no warranty for the work (except to the + extent that warranties are provided), that licensees may convey the + work under this License, and how to view a copy of this License. If + the interface presents a list of user commands or options, such as a + menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work + for making modifications to it. "Object code" means any non-source + form of a work. + + A "Standard Interface" means an interface that either is an official + standard defined by a recognized standards body, or, in the case of + interfaces specified for a particular programming language, one that + is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other + than the work as a whole, that (a) is included in the normal form of + packaging a Major Component, but which is not part of that Major + Component, and (b) serves only to enable use of the work with that + Major Component, or to implement a Standard Interface for which an + implementation is available to the public in source code form. A + "Major Component", in this context, means a major essential component + (kernel, window system, and so on) of the specific operating system + (if any) on which the executable work runs, or a compiler used to + produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all + the source code needed to generate, install, and (for an executable + work) run the object code and to modify the work, including scripts to + control those activities. However, it does not include the work's + System Libraries, or general-purpose tools or generally available free + programs which are used unmodified in performing those activities but + which are not part of the work. For example, Corresponding Source + includes interface definition files associated with source files for + the work, and the source code for shared libraries and dynamically + linked subprograms that the work is specifically designed to require, + such as by intimate data communication or control flow between those + subprograms and other parts of the work. + + The Corresponding Source need not include anything that users + can regenerate automatically from other parts of the Corresponding + Source. + + The Corresponding Source for a work in source code form is that + same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of + copyright on the Program, and are irrevocable provided the stated + conditions are met. This License explicitly affirms your unlimited + permission to run the unmodified Program. The output from running a + covered work is covered by this License only if the output, given its + content, constitutes a covered work. This License acknowledges your + rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not + convey, without conditions so long as your license otherwise remains + in force. You may convey covered works to others for the sole purpose + of having them make modifications exclusively for you, or provide you + with facilities for running those works, provided that you comply with + the terms of this License in conveying all material for which you do + not control copyright. Those thus making or running the covered works + for you must do so exclusively on your behalf, under your direction + and control, on terms that prohibit them from making any copies of + your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under + the conditions stated below. Sublicensing is not allowed; section 10 + makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological + measure under any applicable law fulfilling obligations under article + 11 of the WIPO copyright treaty adopted on 20 December 1996, or + similar laws prohibiting or restricting circumvention of such + measures. + + When you convey a covered work, you waive any legal power to forbid + circumvention of technological measures to the extent such circumvention + is effected by exercising rights under this License with respect to + the covered work, and you disclaim any intention to limit operation or + modification of the work as a means of enforcing, against the work's + users, your or third parties' legal rights to forbid circumvention of + technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you + receive it, in any medium, provided that you conspicuously and + appropriately publish on each copy an appropriate copyright notice; + keep intact all notices stating that this License and any + non-permissive terms added in accord with section 7 apply to the code; + keep intact all notices of the absence of any warranty; and give all + recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, + and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to + produce it from the Program, in the form of source code under the + terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent + works, which are not by their nature extensions of the covered work, + and which are not combined with it such as to form a larger program, + in or on a volume of a storage or distribution medium, is called an + "aggregate" if the compilation and its resulting copyright are not + used to limit the access or legal rights of the compilation's users + beyond what the individual works permit. Inclusion of a covered work + in an aggregate does not cause this License to apply to the other + parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms + of sections 4 and 5, provided that you also convey the + machine-readable Corresponding Source under the terms of this License, + in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source +may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded + from the Corresponding Source as a System Library, need not be + included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any + tangible personal property which is normally used for personal, family, + or household purposes, or (2) anything designed or sold for incorporation + into a dwelling. In determining whether a product is a consumer product, + doubtful cases shall be resolved in favor of coverage. For a particular + product received by a particular user, "normally used" refers to a + typical or common use of that class of product, regardless of the status + of the particular user or of the way in which the particular user + actually uses, or expects or is expected to use, the product. A product + is a consumer product regardless of whether the product has substantial + commercial, industrial or non-consumer uses, unless such uses represent + the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, + procedures, authorization keys, or other information required to install + and execute modified versions of a covered work in that User Product from + a modified version of its Corresponding Source. The information must + suffice to ensure that the continued functioning of the modified object + code is in no case prevented or interfered with solely because + modification has been made. + + If you convey an object code work under this section in, or with, or + specifically for use in, a User Product, and the conveying occurs as + part of a transaction in which the right of possession and use of the + User Product is transferred to the recipient in perpetuity or for a + fixed term (regardless of how the transaction is characterized), the + Corresponding Source conveyed under this section must be accompanied + by the Installation Information. But this requirement does not apply + if neither you nor any third party retains the ability to install + modified object code on the User Product (for example, the work has + been installed in ROM). + + The requirement to provide Installation Information does not include a + requirement to continue to provide support service, warranty, or updates + for a work that has been modified or installed by the recipient, or for + the User Product in which it has been modified or installed. Access to a + network may be denied when the modification itself materially and + adversely affects the operation of the network or violates the rules and + protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, + in accord with this section must be in a format that is publicly + documented (and with an implementation available to the public in + source code form), and must require no special password or key for + unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this + License by making exceptions from one or more of its conditions. + Additional permissions that are applicable to the entire Program shall + be treated as though they were included in this License, to the extent + that they are valid under applicable law. If additional permissions + apply only to part of the Program, that part may be used separately + under those permissions, but the entire Program remains governed by + this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option + remove any additional permissions from that copy, or from any part of + it. (Additional permissions may be written to require their own + removal in certain cases when you modify the work.) You may place + additional permissions on material, added by you to a covered work, + for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you + add to a covered work, you may (if authorized by the copyright holders of + that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further + restrictions" within the meaning of section 10. If the Program as you + received it, or any part of it, contains a notice stating that it is + governed by this License along with a term that is a further + restriction, you may remove that term. If a license document contains + a further restriction but permits relicensing or conveying under this + License, you may add to a covered work material governed by the terms + of that license document, provided that the further restriction does + not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you + must place, in the relevant source files, a statement of the + additional terms that apply to those files, or a notice indicating + where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the + form of a separately written license, or stated as exceptions; + the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly + provided under this License. Any attempt otherwise to propagate or + modify it is void, and will automatically terminate your rights under + this License (including any patent licenses granted under the third + paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) + provisionally, unless and until the copyright holder explicitly and + finally terminates your license, and (b) permanently, if the copyright + holder fails to notify you of the violation by some reasonable means + prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is + reinstated permanently if the copyright holder notifies you of the + violation by some reasonable means, this is the first time you have + received notice of violation of this License (for any work) from that + copyright holder, and you cure the violation prior to 30 days after + your receipt of the notice. + + Termination of your rights under this section does not terminate the + licenses of parties who have received copies or rights from you under + this License. If your rights have been terminated and not permanently + reinstated, you do not qualify to receive new licenses for the same + material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or + run a copy of the Program. Ancillary propagation of a covered work + occurring solely as a consequence of using peer-to-peer transmission + to receive a copy likewise does not require acceptance. However, + nothing other than this License grants you permission to propagate or + modify any covered work. These actions infringe copyright if you do + not accept this License. Therefore, by modifying or propagating a + covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically + receives a license from the original licensors, to run, modify and + propagate that work, subject to this License. You are not responsible + for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an + organization, or substantially all assets of one, or subdividing an + organization, or merging organizations. If propagation of a covered + work results from an entity transaction, each party to that + transaction who receives a copy of the work also receives whatever + licenses to the work the party's predecessor in interest had or could + give under the previous paragraph, plus a right to possession of the + Corresponding Source of the work from the predecessor in interest, if + the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the + rights granted or affirmed under this License. For example, you may + not impose a license fee, royalty, or other charge for exercise of + rights granted under this License, and you may not initiate litigation + (including a cross-claim or counterclaim in a lawsuit) alleging that + any patent claim is infringed by making, using, selling, offering for + sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this + License of the Program or a work on which the Program is based. The + work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims + owned or controlled by the contributor, whether already acquired or + hereafter acquired, that would be infringed by some manner, permitted + by this License, of making, using, or selling its contributor version, + but do not include claims that would be infringed only as a + consequence of further modification of the contributor version. For + purposes of this definition, "control" includes the right to grant + patent sublicenses in a manner consistent with the requirements of + this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free + patent license under the contributor's essential patent claims, to + make, use, sell, offer for sale, import and otherwise run, modify and + propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express + agreement or commitment, however denominated, not to enforce a patent + (such as an express permission to practice a patent or covenant not to + sue for patent infringement). To "grant" such a patent license to a + party means to make such an agreement or commitment not to enforce a + patent against the party. + + If you convey a covered work, knowingly relying on a patent license, + and the Corresponding Source of the work is not available for anyone + to copy, free of charge and under the terms of this License, through a + publicly available network server or other readily accessible means, + then you must either (1) cause the Corresponding Source to be so + available, or (2) arrange to deprive yourself of the benefit of the + patent license for this particular work, or (3) arrange, in a manner + consistent with the requirements of this License, to extend the patent + license to downstream recipients. "Knowingly relying" means you have + actual knowledge that, but for the patent license, your conveying the + covered work in a country, or your recipient's use of the covered work + in a country, would infringe one or more identifiable patents in that + country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or + arrangement, you convey, or propagate by procuring conveyance of, a + covered work, and grant a patent license to some of the parties + receiving the covered work authorizing them to use, propagate, modify + or convey a specific copy of the covered work, then the patent license + you grant is automatically extended to all recipients of the covered + work and works based on it. + + A patent license is "discriminatory" if it does not include within + the scope of its coverage, prohibits the exercise of, or is + conditioned on the non-exercise of one or more of the rights that are + specifically granted under this License. You may not convey a covered + work if you are a party to an arrangement with a third party that is + in the business of distributing software, under which you make payment + to the third party based on the extent of your activity of conveying + the work, and under which the third party grants, to any of the + parties who would receive the covered work from you, a discriminatory + patent license (a) in connection with copies of the covered work + conveyed by you (or copies made from those copies), or (b) primarily + for and in connection with specific products or compilations that + contain the covered work, unless you entered into that arrangement, + or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting + any implied license or other defenses to infringement that may + otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or + otherwise) that contradict the conditions of this License, they do not + excuse you from the conditions of this License. If you cannot convey a + covered work so as to satisfy simultaneously your obligations under this + License and any other pertinent obligations, then as a consequence you may + not convey it at all. For example, if you agree to terms that obligate you + to collect a royalty for further conveying from those to whom you convey + the Program, the only way you could satisfy both those terms and this + License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have + permission to link or combine any covered work with a work licensed + under version 3 of the GNU Affero General Public License into a single + combined work, and to convey the resulting work. The terms of this + License will continue to apply to the part which is the covered work, + but the special requirements of the GNU Affero General Public License, + section 13, concerning interaction through a network will apply to the + combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of + the GNU General Public License from time to time. Such new versions will + be similar in spirit to the present version, but may differ in detail to + address new problems or concerns. + + Each version is given a distinguishing version number. If the + Program specifies that a certain numbered version of the GNU General + Public License "or any later version" applies to it, you have the + option of following the terms and conditions either of that numbered + version or of any later version published by the Free Software + Foundation. If the Program does not specify a version number of the + GNU General Public License, you may choose any version ever published + by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future + versions of the GNU General Public License can be used, that proxy's + public statement of acceptance of a version permanently authorizes you + to choose that version for the Program. + + Later license versions may give you additional or different + permissions. However, no additional obligations are imposed on any + author or copyright holder as a result of your choosing to follow a + later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY + APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT + HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY + OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, + THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM + IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF + ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING + WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS + THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY + GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE + USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF + DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD + PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), + EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF + SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided + above cannot be given local legal effect according to their terms, + reviewing courts shall apply local law that most closely approximates + an absolute waiver of all civil liability in connection with the + Program, unless a warranty or assumption of liability accompanies a + copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest + possible use to the public, the best way to achieve this is to make it + free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest + to attach them to the start of each source file to most effectively + state the exclusion of warranty; and each file should have at least + the "copyright" line and a pointer to where the full notice is found. + + <one line to give the program's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + + Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short + notice like this when it starts in an interactive mode: + + <program> Copyright (C) <year> <name of author> + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + + The hypothetical commands `show w' and `show c' should show the appropriate + parts of the General Public License. Of course, your program's commands + might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, + if any, to sign a "copyright disclaimer" for the program, if necessary. + For more information on this, and how to apply and follow the GNU GPL, see + <http://www.gnu.org/licenses/>. + + The GNU General Public License does not permit incorporating your program + into proprietary programs. If your program is a subroutine library, you + may consider it more useful to permit linking proprietary applications with + the library. If this is what you want to do, use the GNU Lesser General + Public License instead of this License. But first, please read + <http://www.gnu.org/philosophy/why-not-lgpl.html>. + diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000000..157ded7cb9 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,2 @@ +This file is not used for logging changes but to satisfies autotool requirements. +Please, read NEWS file for changes. diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000000..007e9396d0 --- /dev/null +++ b/INSTALL @@ -0,0 +1,370 @@ +Installation Instructions +************************* + +Copyright (C) 1994-1996, 1999-2002, 2004-2013 Free Software Foundation, +Inc. + + Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. This file is offered as-is, +without warranty of any kind. + +Basic Installation +================== + + Briefly, the shell commands `./configure; make; make install' should +configure, build, and install this package. The following +more-detailed instructions are generic; see the `README' file for +instructions specific to this package. Some packages provide this +`INSTALL' file but do not implement all of the features documented +below. The lack of an optional feature in a given package is not +necessarily a bug. More recommendations for GNU packages can be found +in *note Makefile Conventions: (standards)Makefile Conventions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. Caching is +disabled by default to prevent problems with accidental use of stale +cache files. + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You need `configure.ac' if +you want to change it or regenerate `configure' using a newer version +of `autoconf'. + + The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. + + Running `configure' might take a while. While running, it prints + some messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package, generally using the just-built uninstalled binaries. + + 4. Type `make install' to install the programs and any data files and + documentation. When installing into a prefix owned by root, it is + recommended that the package be configured and built as a regular + user, and only the `make install' phase executed with root + privileges. + + 5. Optionally, type `make installcheck' to repeat any self-tests, but + this time using the binaries in their final installed location. + This target does not install anything. Running this target as a + regular user, particularly if the prior `make install' required + root privileges, verifies that the installation completed + correctly. + + 6. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + + 7. Often, you can also type `make uninstall' to remove the installed + files again. In practice, not all packages have tested that + uninstallation works correctly, even though it is required by the + GNU Coding Standards. + + 8. Some packages, particularly those that use Automake, provide `make + distcheck', which can by used by developers to test that all other + targets like `make install' and `make uninstall' work correctly. + This target is generally not run by end users. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. Run `./configure --help' +for details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c99 CFLAGS=-g LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you can use GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. This +is known as a "VPATH" build. + + With a non-GNU `make', it is safer to compile the package for one +architecture at a time in the source code directory. After you have +installed the package for one architecture, use `make distclean' before +reconfiguring for another architecture. + + On MacOS X 10.5 and later systems, you can create libraries and +executables that work on multiple system types--known as "fat" or +"universal" binaries--by specifying multiple `-arch' options to the +compiler but only a single `-arch' option to the preprocessor. Like +this: + + ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CPP="gcc -E" CXXCPP="g++ -E" + + This is not guaranteed to produce working output in all cases, you +may have to build one architecture at a time and combine the results +using the `lipo' tool if you have problems. + +Installation Names +================== + + By default, `make install' installs the package's commands under +`/usr/local/bin', include files under `/usr/local/include', etc. You +can specify an installation prefix other than `/usr/local' by giving +`configure' the option `--prefix=PREFIX', where PREFIX must be an +absolute file name. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +pass the option `--exec-prefix=PREFIX' to `configure', the package uses +PREFIX as the prefix for installing programs and libraries. +Documentation and other data files still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=DIR' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. In general, the +default for these options is expressed in terms of `${prefix}', so that +specifying just `--prefix' will affect all of the other directory +specifications that were not explicitly provided. + + The most portable way to affect installation locations is to pass the +correct locations to `configure'; however, many packages provide one or +both of the following shortcuts of passing variable assignments to the +`make install' command line to change installation locations without +having to reconfigure or recompile. + + The first method involves providing an override variable for each +affected directory. For example, `make install +prefix=/alternate/directory' will choose an alternate location for all +directory configuration variables that were expressed in terms of +`${prefix}'. Any directories that were specified during `configure', +but not in terms of `${prefix}', must each be overridden at install +time for the entire installation to be relocated. The approach of +makefile variable overrides for each directory variable is required by +the GNU Coding Standards, and ideally causes no recompilation. +However, some platforms have known limitations with the semantics of +shared libraries that end up requiring recompilation when using this +method, particularly noticeable in packages that use GNU Libtool. + + The second method involves providing the `DESTDIR' variable. For +example, `make install DESTDIR=/alternate/directory' will prepend +`/alternate/directory' before all installation names. The approach of +`DESTDIR' overrides is not required by the GNU Coding Standards, and +does not work on platforms that have drive letters. On the other hand, +it does better at avoiding recompilation issues, and works well even +when some directory options were not specified in terms of `${prefix}' +at `configure' time. + +Optional Features +================= + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + + Some packages offer the ability to configure how verbose the +execution of `make' will be. For these packages, running `./configure +--enable-silent-rules' sets the default to minimal output, which can be +overridden with `make V=1'; while running `./configure +--disable-silent-rules' sets the default to verbose, which can be +overridden with `make V=0'. + +Particular systems +================== + + On HP-UX, the default C compiler is not ANSI C compatible. If GNU +CC is not installed, it is recommended to use the following options in +order to use an ANSI C compiler: + + ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" + +and if that doesn't work, install pre-built binaries of GCC for HP-UX. + + HP-UX `make' updates targets which have the same time stamps as +their prerequisites, which makes it generally unusable when shipped +generated files such as `configure' are involved. Use GNU `make' +instead. + + On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot +parse its `<wchar.h>' header file. The option `-nodtk' can be used as +a workaround. If GNU CC is not installed, it is therefore recommended +to try + + ./configure CC="cc" + +and if that doesn't work, try + + ./configure CC="cc -nodtk" + + On Solaris, don't put `/usr/ucb' early in your `PATH'. This +directory contains several dysfunctional programs; working variants of +these programs are available in `/usr/bin'. So, if you need `/usr/ucb' +in your `PATH', put it _after_ `/usr/bin'. + + On Haiku, software installed for all users goes in `/boot/common', +not `/usr/local'. It is recommended to use the following options: + + ./configure --prefix=/boot/common + +Specifying the System Type +========================== + + There may be some features `configure' cannot figure out +automatically, but needs to determine by the type of machine the package +will run on. Usually, assuming the package is built to be run on the +_same_ architectures, `configure' can figure that out, but if it prints +a message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS + KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the option `--target=TYPE' to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + + Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified `gcc' to be used as the C compiler (unless it is +overridden in the site shell script). + +Unfortunately, this technique does not work for `CONFIG_SHELL' due to +an Autoconf limitation. Until the limitation is lifted, you can use +this workaround: + + CONFIG_SHELL=/bin/bash ./configure CONFIG_SHELL=/bin/bash + +`configure' Invocation +====================== + + `configure' recognizes the following options to control how it +operates. + +`--help' +`-h' + Print a summary of all of the options to `configure', and exit. + +`--help=short' +`--help=recursive' + Print a summary of the options unique to this package's + `configure', and exit. The `short' variant lists options used + only in the top level, while the `recursive' variant lists options + also present in any nested packages. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--prefix=DIR' + Use DIR as the installation prefix. *note Installation Names:: + for more details, including other options available for fine-tuning + the installation locations. + +`--no-create' +`-n' + Run the configure checks, but stop before creating any output + files. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000000..939db31e1d --- /dev/null +++ b/Makefile.am @@ -0,0 +1,51 @@ +include globals.mak + +# Makefile.am + +SOURCES= + +if BUILD_TEST +TESTS_DIR=test +unittest: + @(cd test; make) + @echo "" + @echo "NOTICE: Unitary tests successfully build" + @echo "Go in the test directory to run them" + @echo "" +else +unittest: + @echo "Ring WARNING:" + @echo " -- You need the cppunit devel package to compile the unitary tests." +endif + +ACLOCAL_AMFLAGS = -I m4 + + +#FIXME: this will change when libring is fixed +if HAVE_WIN32 +SUBDIRS = bin +else +SUBDIRS = src ringtones man $(TESTS_DIR) doc bin +endif + +EXTRA_DIST = m4/libtool.m4 \ + m4/lt~obsolete.m4 \ + m4/ltoptions.m4 \ + m4/ltsugar.m4 \ + m4/ltversion.m4 \ + contrib/bootstrap \ + contrib/src + +coverage: + $(AM_V_at)lcov --directory $(top_builddir) --capture --output-file dring-coverage.info + $(MKDIR_P) html-output + genhtml -o html-output dring-coverage.info + +coverage-clean: + $(AM_V_at)lcov --directory $(top_builddir) --zerocounters + $(AM_V_at)rm -rf dring-coverage.info + $(AM_V_at)rm -rf html-output + $(AM_V_at)find -name '*.gcda' -exec rm -f {} + + $(AM_V_at)find -name '*.gcno' -exec rm -f {} + + +.PHONY: coverage coverage-clean diff --git a/NEWS b/NEWS new file mode 100644 index 0000000000..091ae3d335 --- /dev/null +++ b/NEWS @@ -0,0 +1,141 @@ +DRing (2.0.1) / 2015-03-27 + * Add FPS calculation for streamed files + * Set defaults video bitrate to 800 Kb/s + * Use RGBA for direct rendering (MacOSX) + * MacOSX: force video device format on supported values + * SecureSIP: fix race condition and incoming packet handling + * minor documentation fixes + +DRing (2.0.0) / 2015-03-25 + * Project renaming + * First release under this name + * Ring account (DHT) introduced + * Complete SIP/SDP protocol handling re-write + * Encrypted communication through TLS/SRTP + * NAT-traversal: ICE, UPnP + * UI are separate repository + * And a lot more... + +SFLphoned (1.0.2) / 2012-07-02 + * Stable version + * Update Contact header from 200 OK + * Keep alive for account regstration + * Call history now managed by client + +SFLphoned (0.9.12) / 2011-11-12 + * Refactoring of the RTP session allowing dynamic update of audio codecs + * Updated synchronization between transport layer and audio layer + * Better implementation of SIP Early media playback + * Fixed memory leaks in configuration serialization engine + * Evolution addressbook: default addressbook support, addressbook authentication support + * Improved Gnome client initialization error handling + * Updated dbus-c++ binding + +SFLphoned (0.9.9) / 2010-28-09 + * Instant messaging + * Full Evolution addressbook integration + * A noise reduction engine to improve audio quality + * Linear int 16bit wave format support for ringtones + * Ability to select a different ringtone playback device than voice playback + * SIP early media + * A new configuration system based on YAML serialization format. + +SFLphoned (0.7.0) / 2006-... + * adding reload (sound driver) button + * fix: peer hangup remove flashing buttons + * removing old packaging stuff like FIXME or sflphone.spec.in + * fix: click on flashing buttons + * fix: send ringing, hangup, busy message + * fix for using call command two times in a row + * add --disable-sflphoneqt 2006-05-01 + * remove libexosip2 internally, it's in debian unstable 2005-01-01 + * add iax support - in development + * add account support + * remove callid string/int + * add account SIP0 (default) in sflphone-cli + * add account SIP0 (default) in sflphone-qt + * fix nat handling (use the same port that it test) + * add register/unregister in qt + * add test audio driver button + * add hold/unhold/hangup to sflphone-cli + +SFLphoned (0.6.2) / 2005-11-29 + * integral mono support + * libsamplerate added for macosx + +SFLphoned (0.6.1) / 2005-11-26 + * add speex codec (experimental) + * fix for codec handling in sdp and audiortp + * new codec class + * add portaudio missing file + (thanks to Pierre POMES) + * add samplerate and inChannel/outChannel in AudioLayer + we could use microphone in mono and speaker in stereo + +SFLphoned/SFLphone (0.6) / 2005-11-04 + * improve sip protocol + * can now receive text message + * accept reinvite call + * cleaning headers + * improving bash launcher script + * select audio input and output device + * installation review + +SFLphoned/SFLphone (0.5a) / 2005-10-17 + * dring + * bug fixes + * sflphone-qt + * client/server protocol + +SFLphone (0.4.1-pre2) / 2005-09-30 + * rearranged utilspp use + * Bug fix when trying to use more than 6 lines. + * Now, we send a final response to reINVITEs + (thanks to Mikael Magnusson) + * We can build in different build directories. + (thanks to Mikael Magnusson) + * We actualy check if portaudio and eXosip2 libs + are installed + * zeroconf integration start + +SFLphone (0.4.1-pre1) / 2005-08-11 + * Use libeXosip2 + * Add blink notification for voice-message + * Add scrolling text + +SFLphone (0.4) / 2005-07-06 + * Cleanup code + * Add autotools support + * Handle CANCEL method + * PortAudio replace OSS and ALSA devices choice + * Add PortAudioCpp to make easy use of RingBuffer object for + audio callback + * Add PortAudio library to make easy sound portability + * Fix a few memory leak + * Reorganisation of SFLphone architecture + * Handle error messages on screen + * Handle "refused call" + * Notification of remote RINGING event added + * Call management updated + +SFLphone (0.3) / 2005-04-05 + * ALSA driver support added + * GSM audio codec support added + * Ringtones support (just ulaw format) added + * Notification incoming call added + * Apply skin feature added + * Volume control added + * Registration manually added + * Config-file in home directory changed to ".sflphone" + +SFLphone (0.2) / 2005-01-18 + * Lines management updated + * Blocking bug fixed + * Configuration tree, made from a config file, added for setup + * Dial tone updated + * Timer call updated + * Apply feature updated + * Non-network or request failure cases updated + +SFLphone (0.1a) / 2004-12-22 + * First release diff --git a/README b/README new file mode 100644 index 0000000000..067c976d4e --- /dev/null +++ b/README @@ -0,0 +1,174 @@ +COPYRIGHT NOTICE + +Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. + +Additional permission under GNU GPL version 3 section 7: + +If you modify this program, or any covered work, by linking or +combining it with the OpenSSL project's OpenSSL library (or a +modified version of that library), containing parts covered by the +terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. +grants you additional permission to convey the resulting work. +Corresponding Source for a non-source form of such a combination +shall include the source code for the parts of OpenSSL used as well +as that of the covered work. + + +Introduction +------------ + +Ring is a Voice-over-IP software phone. We want it to be: +- user friendly (fast, sleek, easy to learn interface) +- professional grade (transfers, holds, optimal audio quality) +- fully compatible with Asterisk (SIP and IAX protocols) +- de-centralized call (P2P-DHT) +- customizable + +As the SIP/audio daemon and the user interface are separate processes, +it is easy to provide different user interfaces. Ring comes with +various graphical user interfaces and even scripts to control the daemon from +the shell. + +Ring is currently used by the support team of Savoir-Faire Linux Inc. + +More information is available on the project homepage: + http://www.ring.cx/ + +This source tree contains the daemon application only, DRing, that handles +the business logic of Ring. UI are located in differents repositories. +Check our client subprojects here: +https://projects.savoirfairelinux.com/projects/ring + + +Short description of content of source tree +------------------------------------------- + +- src/ is the core of DRing. +- bin/ contains applications main code. +- bin/dbus, the D-Bus xml interfaces, and c++ bindings + + +About Savoir-Faire Linux +------------------------ + +Savoir-Faire Linux is a consulting company based in Montreal, Quebec. +For more information, please check out our website: +http://www.savoirfairelinux.com/ + + +How to compile on Linux +----------------------- + +1) Compile the dependencies first + +cd ../contrib/ +mkdir native +cd native +../bootstrap +make + +2) Then the dring application + +cd ../../ +./autogen.sh +./configure +make +make install + +Done ! + +More details available here: +https://projects.savoirfairelinux.com/projects/ring/wiki/How_to_build + + +How to compile on OSX +--------------------- + +# These first steps are only necessary if you don't use a package manager. +cd extras/tools +./bootstrap +make +export PATH=$PATH:/location/of/ring/daemon/extras/tools/build/bin + +# Or, use your favorite package manager to install the necessary tools +(macports or brew). +automake libtool check gettext libtoolize yasm ... + +# Compile the dependencies +cd contrib +mkdir native +cd native +../bootstrap +make -j + +# Then the daemon +cd ../../ +./autogen.sh +./configure --without-alsa --without-pulse --without-dbus --disable-video +make + +If you want to link against libringclient and native client easiest way is to +add to ./configure: --prefix=<prefix_path> + +Do a little dance! + + +Common Issues +------------- + +autopoint not found: When using Homebrew, autopoint is not found even when +gettext is installed, because symlinks are not created. +Run: 'brew link --force gettext' to fix it. + + +Clang compatibility (developers only) +------------------------------------- + +It is possible to compile dring with Clang by setting CC and CXX variables +to 'clang' and 'clang++' respectively when calling ./configure. + +Currently it is not possible to use the DBus interface mechanism, and the +interaction between daemon and client will not work; for each platform where +dbus is not available the client should implement all the methods in the +*_stub.cpp files. + + +SIP accounts +--------------------- + +You may register an existing SIP account through the account wizard in both +clients (KDE and GNOME). +By doing this, you will be able to call other accounts known to this server. + + +Contributing to Ring +------------------------ + +Of course we love patches. And contributions. And spring rolls. + +Development website: http://projects.savoirfairelinux.com/projects/ring/wiki + +Do not hesitate to join us and post comments, suggestions, questions +and general feedback on the Ring mailing-list: +http://lists.savoirfairelinux.net/mailman/listinfo/ring + +Bug reports: +http://projects.savoirfairelinux.com/projects/ring/wiki/BugReports + +IRC (on #freenode): +#sflphone + + -- The Ring Team diff --git a/astylerc b/astylerc new file mode 100644 index 0000000000..86203e5287 --- /dev/null +++ b/astylerc @@ -0,0 +1,18 @@ +# Filename: astylerc +# Purpose: config file for astyle +# http://astyle.sourceforge.net/astyle.html +# Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> +# Savoir-faire Linux Inc +# http://www.ring.cx + +style=stroustrup # stroustrup style http://astyle.sourceforge.net/astyle.html#_style=stroustrup +indent=spaces=4 # Use spaces instead of tabs for indentation +indent-classes # Indent 'class' and 'struct' blocks so that the blocks 'public:', 'protected:' and 'private:' are indented +indent-switches # Indent 'switch' blocks so that the 'case X:' statements are indented in the switch block +break-blocks # Pad empty lines around header blocks (e.g. 'if', 'while'...). +brackets=linux +unpad-paren # Remove unwanted space around parentheses +pad-header # Insert space padding after paren headers only (e.g. 'if', 'for', 'while'...) +pad-oper # Insert space padding around operator +formatted # only display files that have changed +suffix=none # don't create backup files (that's what version control is for) diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000000..b0d9083087 --- /dev/null +++ b/autogen.sh @@ -0,0 +1,41 @@ +#!/bin/bash + +AUTORECONF=`which autoreconf` +if test -z $AUTORECONF; then + echo "*** No autoreconf found, please install it ***" + exit 1 +fi + +if !"${PKG_CONFIG:-pkg-config}" --version >/dev/null 2>&1; then + echo "*** No pkg-config found, please install it ***" + # warn without exiting, since pkg-config is only needed + # by configure, not autogen.sh +fi + +LIBTOOLIZE=`which libtoolize || which glibtoolize` +if test -z $LIBTOOLIZE; then + LIBTOOLIZE=`which glibtoolize` + if test -z $LIBTOOLIZE; then + echo "*** No libtool found, please install it ***" + exit 1 + fi +fi + +# Workaround for http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=565663 +mkdir -p m4 + +HOOKS_DIR=".git/hooks" +# install pre-commit hook for doing clean commits +if [ -d "$HOOKS_DIR" ]; +then + pushd ${HOOKS_DIR} + if test ! \( -x pre-commit -a -L pre-commit \); + then + echo "Creating symbolic link for pre-commit hook..." + rm -f pre-commit + ln -s pre-commit.sample pre-commit + fi + popd +fi + +autoreconf --force --install --verbose -Wall -I m4 diff --git a/bin/Makefile.am b/bin/Makefile.am new file mode 100644 index 0000000000..96b12d76ef --- /dev/null +++ b/bin/Makefile.am @@ -0,0 +1,29 @@ +if HAVE_WIN32 +libexec_PROGRAMS = ringcli +ringcli_SOURCES = winmain.cpp +#FIXME This is temporary +ringcli_LDADD = $(SPEEX_LIBS) +endif + +if HAVE_OSX +libexec_PROGRAMS = ringcli +ringcli_SOURCES = osxmain.cpp +ringcli_CXXFLAGS = -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/dring \ + -DTOP_BUILDDIR=\"$$(cd "$(top_builddir)"; pwd)\" +ringcli_LDADD = $(top_builddir)/src/libring.la +endif + +if RING_DBUS +SUBDIRS=dbus + +libexec_PROGRAMS = dring + +dring_SOURCES = main.cpp + +dring_CXXFLAGS= -I$(top_srcdir)/src ${DBUSCPP_CFLAGS} \ + -I$(top_srcdir)/src/dring \ + -DTOP_BUILDDIR=\"$$(cd "$(top_builddir)"; pwd)\" + +dring_LDADD = dbus/libclient_dbus.la ${DBUSCPP_LIBS} $(top_builddir)/src/libring.la +endif diff --git a/bin/dbus/Makefile.am b/bin/dbus/Makefile.am new file mode 100644 index 0000000000..965e4588bc --- /dev/null +++ b/bin/dbus/Makefile.am @@ -0,0 +1,72 @@ +include $(top_srcdir)/globals.mak + +noinst_LTLIBRARIES = libclient_dbus.la + +BUILT_SOURCES= \ + dbuscallmanager.adaptor.h \ + dbusconfigurationmanager.adaptor.h \ + dbusinstance.adaptor.h + +BUILT_SOURCES+=dbuspresencemanager.adaptor.h +dbuspresencemanager.adaptor.h: presencemanager-introspec.xml Makefile.am + dbusxx-xml2cpp $< --adaptor=$@ + +if RING_VIDEO +BUILT_SOURCES+=dbusvideomanager.adaptor.h +dbusvideomanager.adaptor.h: videomanager-introspec.xml Makefile.am + dbusxx-xml2cpp $< --adaptor=$@ +endif + +# Rule to generate the binding headers +dbuscallmanager.adaptor.h: callmanager-introspec.xml Makefile.am + dbusxx-xml2cpp $< --adaptor=$@ + +# Rule to generate the binding headers +dbusconfigurationmanager.adaptor.h: configurationmanager-introspec.xml Makefile.am + dbusxx-xml2cpp $< --adaptor=$@ + +# Rule to generate the binding headers +dbusinstance.adaptor.h: instance-introspec.xml Makefile.am + dbusxx-xml2cpp $< --adaptor=$@ + +libclient_dbus_la_SOURCES = \ + dbuscallmanager.cpp \ + dbuscallmanager.h \ + dbusconfigurationmanager.cpp \ + dbusconfigurationmanager.h \ + dbusinstance.cpp \ + dbusclient.cpp \ + dbusclient.h \ + dbusinstance.h \ + dbus_cpp.h \ + dbuspresencemanager.cpp \ + dbuspresencemanager.h \ + $(BUILT_SOURCES) + +if RING_VIDEO +libclient_dbus_la_SOURCES += dbusvideomanager.cpp dbusvideomanager.h +endif + +libclient_dbus_la_CXXFLAGS = -I../ \ + -I$(top_srcdir)/src \ + -I$(top_srcdir)/src/dring \ + -DPREFIX=\"$(prefix)\" \ + -DPROGSHAREDIR=\"${datadir}/ring\" \ + $(DBUSCPP_CFLAGS) + +# Dbus service file +servicedir = $(datadir)/dbus-1/services +service_DATA = cx.ring.Ring.service + +EXTRA_DIST = callmanager-introspec.xml \ + configurationmanager-introspec.xml \ + instance-introspec.xml \ + presencemanager-introspec.xml + +if RING_VIDEO +EXTRA_DIST += videomanager-introspec.xml +endif + +CLEANFILES= \ + $(BUILT_SOURCES) \ + cx.ring.Ring.service diff --git a/bin/dbus/callmanager-introspec.xml b/bin/dbus/callmanager-introspec.xml new file mode 100644 index 0000000000..11c2a9beb7 --- /dev/null +++ b/bin/dbus/callmanager-introspec.xml @@ -0,0 +1,790 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<node name="/callmanager-introspec" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + <interface name="cx.ring.Ring.CallManager"> + + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The CallManager interface is used to manage call and conference related actions.</p> + <p>Since Ring-daemon supports multiple incoming/outgoing calls, any actions involving a specific call must address the method by the means of a unique callID. + Ring-daemon will generate a unique callID for outgoing and incoming calls.</p> + </tp:docstring> + <method name="placeCall" tp:name-for-bindings="placeCall"> + <tp:docstring> + <p>This is the main method in order to place a new call. The call is registered with the daemon using this method.</p> + </tp:docstring> + <arg type="s" name="accountID" direction="in"> + <tp:docstring> + The ID of the account with which you want to make a call. If the call is to be placed without any account by means of a SIP URI (i.e. sip:num@server), the "IP2IP_PROFILE" is passed as the accountID. For more details on accounts see the configuration manager interface. + </tp:docstring> + </arg> + <arg type="s" name="to" direction="in"> + <tp:docstring> + If bound to a VoIP account, then the argument is the phone number. In case of calls involving "IP2IP_PROFILE", a complete SIP URI must be specified. + </tp:docstring> + </arg> + <arg type="s" name="callID" direction="out"/> + </method> + + <method name="refuse" tp:name-for-bindings="refuse"> + <tp:docstring> + Refuse an incoming call. + </tp:docstring> + <arg type="s" name="callID" direction="in"> + <tp:docstring> + The callID. + </tp:docstring> + </arg> + <arg type="b" name="refuseSucceeded" direction="out"/> + + </method> + + <method name="accept" tp:name-for-bindings="accept"> + <tp:docstring> + Answer an incoming call. Automatically puts the current call on HOLD. + </tp:docstring> + <arg type="s" name="callID" direction="in"> + <tp:docstring> + The callID. + </tp:docstring> + </arg> + <arg type="b" name="acceptSucceeded" direction="out"/> + </method> + + <method name="hangUp" tp:name-for-bindings="hangUp"> + <tp:docstring> + Hangup a call in state "CURRENT" or "HOLD". + </tp:docstring> + <arg type="s" name="callID" direction="in"> + <tp:docstring> + The callID. + </tp:docstring> + </arg> + <arg type="b" name="hangupSucceeded" direction="out"/> + </method> + + <method name="hangUpConference" tp:name-for-bindings="hangUpConference"> + <tp:added version="0.9.7"/> + <tp:docstring> + Hangup a conference, and every call participating to the conference. + </tp:docstring> + <arg type="s" name="confID" direction="in"> + <tp:docstring> + The unique conference ID. + </tp:docstring> + </arg> + <arg type="b" name="hangupSucceeded" direction="out"/> + </method> + + <method name="hold" tp:name-for-bindings="hold"> + <tp:docstring> + Place a call on hold. + </tp:docstring> + <arg type="s" name="callID" direction="in"> + <tp:docstring> + The callID. + </tp:docstring> + </arg> + <arg type="b" name="holdSucceeded" direction="out"/> + </method> + + <method name="unhold" tp:name-for-bindings="unhold"> + <tp:docstring> + Take a call off hold, and place this call in state CURRENT. + </tp:docstring> + <arg type="s" name="callID" direction="in"> + <tp:docstring> + The callID. + </tp:docstring> + </arg> + <arg type="b" name="unHoldSucceeded" direction="out"/> + </method> + + <method name="transfer" tp:name-for-bindings="transfer"> + <tp:docstring> + Transfer a call to the given phone number. + </tp:docstring> + <arg type="s" name="callID" direction="in"> + <tp:docstring> + The callID. + </tp:docstring> + </arg> + <arg type="s" name="to" direction="in"> + <tp:docstring> + The phone number to which the call will be transferred. + </tp:docstring> + </arg> + <arg type="b" name="transferSucceeded" direction="out"/> + </method> + + <method name="attendedTransfer" tp:name-for-bindings="attendedTransfer"> + <tp:docstring> + Perform an attended transfer on two calls. + </tp:docstring> + <arg type="s" name="transferID" direction="in"> + <tp:docstring> + The callID of the call to be transfered. + </tp:docstring> + </arg> + <arg type="s" name="targetID" direction="in"> + <tp:docstring> + The callID of the target call. + </tp:docstring> + </arg> + <arg type="b" name="transferSucceeded" direction="out"/> + </method> + + <method name="playDTMF" tp:name-for-bindings="playDTMF"> + <tp:docstring> + Dual-Tone multi-frequency. Tell the core to play dialtones. A SIP INFO message is sent to notify the server. + </tp:docstring> + <arg type="s" name="key" direction="in"> + <tp:docstring> + Unicode character for pressed key. + </tp:docstring> + </arg> + </method> + + <method name="startTone" tp:name-for-bindings="startTone"> + <tp:docstring> + Start audio stream and play tone. + </tp:docstring> + <arg type="i" name="start" direction="in"/> + <arg type="i" name="type" direction="in"/> + </method> + + <method name="joinParticipant" tp:name-for-bindings="joinParticipant"> + <tp:added version="0.9.7"/> + <tp:docstring> + <p>Join two participants together to create a 3-way conference including the current client.</p> + <tp:rationale>The signal <tp:member-ref>conferenceCreated</tp:member-ref> is emitted on success.</tp:rationale> + </tp:docstring> + <arg type="s" name="sel_callID" direction="in"/> + <arg type="s" name="drag_callID" direction="in"/> + <arg type="b" name="joinSucceeded" direction="out"/> + </method> + + <method name="createConfFromParticipantList" tp:name-for-bindings="createConfFromParticipantList"> + <tp:added version="0.9.14"/> + <tp:docstring> + <p>Create a conference from a list of participants</p> + <tp:rationale>The signal <tp:member-ref>conferenceCreated</tp:member-ref> is emitted on success.</tp:rationale> + </tp:docstring> + <arg type="as" name="participants" direction="in"/> + </method> + + <method name="isConferenceParticipant" tp:name-for-bindings="isConferenceParticipant"> + <arg type="s" name="callID" direction="in"/> + <arg type="b" name="isParticipant" direction="out"/> + </method> + + <method name="addParticipant" tp:name-for-bindings="addParticipant"> + <tp:added version="0.9.7"/> + <tp:docstring> + <p>Join a new particiant to an existing conference.</p> + <tp:rationale>The signal <tp:member-ref>conferenceChanged</tp:member-ref> is emitted on success.</tp:rationale> + </tp:docstring> + <arg type="s" name="callID" direction="in"> + <tp:docstring> + The ID of the call to add to the conference + </tp:docstring> + </arg> + <arg type="s" name="confID" direction="in"> + <tp:docstring> + An existing conference ID + </tp:docstring> + </arg> + <arg type="b" name="addSucceeded" direction="out"/> + </method> + + <method name="addMainParticipant" tp:name-for-bindings="addMainParticipant"> + <tp:added version="0.9.7"/> + <tp:docstring> + <p>As the core can handle multiple calls and conferences, it may happen that the client's user leaves a conference to answer an incoming call or to start new calls. This method is used to reintroduce Ring-client's user into the conference.</p> + <p>Its put the current call on HOLD or detaches Ring-client's user from the another conference.</p> + </tp:docstring> + <arg type="s" name="confID" direction="in"> + <tp:docstring> + An existing conference ID + </tp:docstring> + </arg> + <arg type="b" name="addSucceeded" direction="out"/> + </method> + + <method name="detachParticipant" tp:name-for-bindings="detachParticipant"> + <tp:added version="0.9.7"/> + <tp:docstring> + Detach the given call from the conference. If only one participant is left, the conference is deleted and the signal <tp:member-ref>conferenceRemoved</tp:member-ref> is emited. + </tp:docstring> + <arg type="s" name="callID" direction="in"> + <tp:docstring> + The call ID + </tp:docstring> + </arg> + <arg type="b" name="detachSucceeded" direction="out"/> + </method> + + <method name="joinConference" tp:name-for-bindings="joinConference"> + <tp:added version="0.9.7"/> + <tp:docstring> + Join two conferences together. + </tp:docstring> + <arg type="s" name="sel_confID" direction="in"/> + <arg type="s" name="drag_confID" direction="in"/> + <arg type="b" name="joinSucceeded" direction="out"/> + </method> + + <method name="getConferenceDetails" tp:name-for-bindings="getConferenceDetails"> + <tp:added version="0.9.7"/> + <tp:docstring> + Returns a hashtable containing conference details. + </tp:docstring> + <arg type="s" name="callID" direction="in"> + <tp:docstring> + The conference ID + </tp:docstring> + </arg> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MapStringString"/> + <arg type="a{ss}" name="infos" direction="out"> + <tp:docstring> + A map containing the ID of the conferences + and their states: + <ul> + <li>ACTIVE_ATTACHED</li> + <li>ACTIVE_DETACHED</li> + <li>HOLD</li> + </ul> + </tp:docstring> + </arg> + </method> + + <method name="getConferenceList" tp:name-for-bindings="getConferenceList"> + <tp:added version="0.9.7"/> + <tp:docstring> + Returns a list containing all active + conferences. + <tp:rationale>To update client status, one should + use <tp:member-ref>getParticipantList</tp:member-ref> + with provided conference IDs.</tp:rationale> + </tp:docstring> + <arg type="as" name="list" direction="out"> + <tp:docstring> + The list of conferences. + </tp:docstring> + </arg> + </method> + + <method name="getConferenceId" tp:name-for-bindings="getConferenceId"> + <tp:added version="1.1.0"/> + <tp:docstring> + If thsi call participate to a conference, return the conference id. + Return an empty string elsewhere. + </tp:docstring> + <arg type="s" name="callID" direction="in"> + <tp:docstring> + The call id. + </tp:docstring> + </arg> + <arg type="s" name="confID" direction="out"> + <tp:docstring> + A string containing the conference ID, or an empty string. + </tp:docstring> + </arg> + </method> + + <method name="toggleRecording" tp:name-for-bindings="toggleRecording"> + <tp:docstring> + Toggle recording for a call. + </tp:docstring> + <arg type="s" name="callID" direction="in"> + <tp:docstring> + The ID of the call to start/stop recording. + </tp:docstring> + </arg> + <arg type="b" name="isRecording" direction="out"> + <tp:docstring> + Returns true is the call is being recorded. False otherwise. + </tp:docstring> + </arg> + </method> + + <method name="setRecording" tp:name-for-bindings="setRecording"> + <annotation name="org.freedesktop.DBus.Deprecated" value="true"/> + <tp:docstring> + Start recording a call. + </tp:docstring> + <arg type="s" name="callID" direction="in"> + <tp:docstring> + The ID of the call to record. + </tp:docstring> + </arg> + </method> + + <method name="getIsRecording" tp:name-for-bindings="getIsRecording"> + <tp:docstring> + Tells whether or not a call is being recorded. + </tp:docstring> + <arg type="s" name="callID" direction="in"> + <tp:docstring> + The call ID. + </tp:docstring> + </arg> + <arg type="b" name="isRecording" direction="out"> + <tp:docstring> + Returns true is the call is being recorded. False otherwise. + </tp:docstring> + </arg> + </method> + + <method name="recordPlaybackSeek" tp:name-for-bindings="recordPlaybackSeek"> + <tp:docstring> + <p>Sets the playback position using a linear scale [0,100].</p> + </tp:docstring> + <arg type="d" name="value" direction="in"/> + </method> + + <signal name="recordPlaybackFilepath" tp:name-for-bindings="recordPlaybackFilepath"> + <tp:docstring> + Once after starting recording for the first time, this signal is emited to + provide the recorded file path to client application. + </tp:docstring> + <arg type="s" name="callID" /> + <arg type="s" name="filepath"/> + </signal> + + <signal name="recordPlaybackStopped" tp:name-for-bindings="recordPlaybackStopped"> + <tp:docstring/> + <arg type="s" name="filepath" /> + </signal> + + <signal name="updatePlaybackScale" tp:name-for-bindings="updatePlaybackScale"> + <tp:docstring/> + <arg type="s" name="filepath" /> + <arg type="i" name="position" /> + <arg type="i" name="size" /> + </signal> + + <method name="getCallDetails" tp:name-for-bindings="getCallDetails"> + <tp:docstring> + Get all the details about a specific call. + </tp:docstring> + <arg type="s" name="callID" direction="in"> + <tp:docstring> + The call ID. + </tp:docstring> + </arg> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MapStringString"/> + <arg type="a{ss}" name="infos" direction="out" tp:type="String_String_Map"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>A map containing the call details: </p> + <ul> + <li>ACCOUNTID</li> + <li>PEER_NUMBER</li> + <li>PEER_NAME</li> + <li>DISPLAY_NAME</li> + <li>CALL_STATE</li> + <li>CALL_TYPE</li> + <li>CONF_ID</li> + </ul> + </tp:docstring> + </arg> + </method> + + <method name="getCallList" tp:name-for-bindings="getCallList"> + <tp:docstring> + Get the list of active calls. + <tp:rationale>To get the call details, iterate on the return value and call <tp:member-ref>getCallDetails</tp:member-ref> method.</tp:rationale> + </tp:docstring> + <arg type="as" name="list" direction="out"> + <tp:docstring> + A list of call IDs. + </tp:docstring> + </arg> + </method> + + <method name="getCurrentAudioCodecName" tp:name-for-bindings="getCurrentAudioCodecName"> + <annotation name="org.freedesktop.DBus.Deprecated" value="true"/> + <arg type="s" name="callID" direction="in"/> + <arg type="s" name="codecName" direction="out"/> + </method> + + <method name="switchInput" tp:name-for-bindings="switchInput"> + <tp:docstring> + Switch input for the specified call + </tp:docstring> + <arg type="s" name="callID" direction="in"/> + <arg type="s" name="input" direction="in"/> + </method> + + <method name="sendTextMessage" tp:name-for-bindings="sendTextMessage"> + <tp:docstring> + Send a text message to the specified call + </tp:docstring> + <arg type="s" name="callID" direction="in"/> + <arg type="s" name="message" direction="in"/> + </method> + + <signal name="newCallCreated" tp:name-for-bindings="newCallCreated"> + <tp:docstring> + <p>Notify that a call has been created.</p> + <p>The callID generated by the daemon must be stored by the clients in order to address other actions for + this call. This signal is emitted when call haves been created by the daemon itself.</p> + <tp:rationale>The client must subscribe to this signal to handle calls created by other clients</tp:rationale> + </tp:docstring> + <arg type="s" name="accountID"> + <tp:docstring> + The account ID of the call. Clients must notify the right account when receiving this signal. + </tp:docstring> + </arg> + <arg type="s" name="callID"> + <tp:docstring> + A new call ID. + </tp:docstring> + </arg> + <arg type="s" name="to"> + <tp:docstring> + The SIP URI this call is trying to reach. + </tp:docstring> + </arg> + </signal> + + <signal name="incomingCall" tp:name-for-bindings="incomingCall"> + <tp:docstring> + <p>Notify an incoming call.</p> + <p>The callID generated by the daemon must be stored by the clients in order to address other action for + this call. This signal is emitted when we receive a call from a remote peer</p> + <tp:rationale>The client must subscribe to this signal to handle incoming calls.</tp:rationale> + </tp:docstring> + <arg type="s" name="accountID"> + <tp:docstring> + The account ID of the callee. Clients must notify the right account when receiving this signal. + </tp:docstring> + </arg> + <arg type="s" name="callID"> + <tp:docstring> + A new call ID. + </tp:docstring> + </arg> + <arg type="s" name="from"> + <tp:docstring> + The caller phone number. + </tp:docstring> + </arg> + </signal> + + <signal name="incomingMessage" tp:name-for-bindings="incomingMessage"> + <tp:docstring> + Notify clients that a new text message has been received. + </tp:docstring> + <arg type="s" name="callID" /> + <arg type="s" name="from" /> + <arg type="s" name="message" /> + </signal> + + <signal name="callStateChanged" tp:name-for-bindings="callStateChanged"> + <tp:docstring> + <p>Notify of a change in a call state.</p> + <p>The client must subscribe to this signal.</p> + </tp:docstring> + <arg type="s" name="callID"> + <tp:docstring> + The call ID. + </tp:docstring> + </arg> + <arg type="s" name="state" > + <tp:docstring> + The acceptable states are: + <ul> + <li>INCOMING: Initial state of incoming calls</li> + <li>RINGING: Initial state of received outgoing call</li> + <li>CURRENT: The normal active state of an answered call</li> + <li>HUNGUP: Notify that the call has been hungup by peer</li> + <li>BUSY</li> + <li>FAILURE: Error when processing a call</li> + <li>HOLD</li> + <li>UNHOLD</li> + </ul> + </tp:docstring> + </arg> + <arg type="i" name="code"> + <tp:docstring> + The optional account-type specific message code. 0 if not set. + </tp:docstring> + <tp:added version="2.0.0" /> + </arg> + </signal> + + <signal name="conferenceChanged" tp:name-for-bindings="conferenceChanged"> + <tp:added version="0.9.7"/> + <tp:docstring> + Notify of a change in the conferences state + </tp:docstring> + <arg type="s" name="confID"> + <tp:docstring> + The conference ID. + </tp:docstring> + </arg> + <arg type="s" name="state"> + <tp:docstring> + The acceptable states are: + <ul> + <li>ACTIVE_ATTACHED: Ring user is + participating to this conference</li> + <li>ACTIVE_DETACHED: This situation can + occur if a call is received while + Ring user is participating to a + conference. In this case, one can leave + the conference by answering the + call. Other participants may continue + conferencing normally.</li> + <li>HOLD: Each call in this conference + is on state HOLD</li> + </ul> + </tp:docstring> + </arg> + </signal> + + <method name="getDisplayNames" tp:name-for-bindings="getDisplayNames"> + <tp:added version="1.3.0"/> + <tp:docstring> + Get the display name of every participant in a given conference, or their number as a fallback. + </tp:docstring> + <arg type="s" name="confID" direction="in"> + <tp:docstring> + The conference ID. + </tp:docstring> + </arg> + <arg type="as" name="list" direction="out"> + <tp:docstring> + The list of the display names. + </tp:docstring> + </arg> + </method> + + <method name="getParticipantList" tp:name-for-bindings="getParticipantList"> + <tp:added version="0.9.7"/> + <tp:docstring> + Get the call IDs of every participant to a given conference. The client should keep and update the list of participants. + </tp:docstring> + <arg type="s" name="confID" direction="in"> + <tp:docstring> + The conference ID. + </tp:docstring> + </arg> + <arg type="as" name="list" direction="out"> + <tp:docstring> + The list of the call IDs. + </tp:docstring> + </arg> + </method> + + <signal name="conferenceCreated" tp:name-for-bindings="conferenceCreated"> + <tp:added version="0.9.7"/> + <tp:docstring> + Emited when a new conference is created. Ring-client is reponsible for storing the confID and call <tp:member-ref>getParticipantList</tp:member-ref> to update the display. + </tp:docstring> + <arg type="s" name="confID"> + <tp:docstring> + A new conference ID. + </tp:docstring> + </arg> + </signal> + + <signal name="conferenceRemoved" tp:name-for-bindings="conferenceRemoved"> + <tp:added version="0.9.7"/> + <tp:docstring> + Emited when a new conference is remove. Ring-client should have kept a list of current participant in order to display modification. + </tp:docstring> + <arg type="s" name="confID"> + <tp:docstring> + The conference ID. + </tp:docstring> + </arg> + </signal> + + <method name="holdConference" tp:name-for-bindings="holdConference"> + <tp:added version="0.9.7"/> + <tp:docstring> + Hold every call which is participating in this conference. + </tp:docstring> + <arg type="s" name="confID" direction="in"> + <tp:docstring> + The conference ID. + </tp:docstring> + </arg> + <arg type="b" name="holdSucceeded" direction="out"/> + </method> + + <method name="unholdConference" tp:name-for-bindings="unholdConference"> + <tp:added version="0.9.7"/> + <tp:docstring> + Hold off every call participating in this conference. + </tp:docstring> + <arg type="s" name="confID" direction="in"> + <tp:docstring> + The conference ID. + </tp:docstring> + </arg> + <arg type="b" name="unholdSucceeded" direction="out"/> + </method> + + <method name="startRecordedFilePlayback" tp:name-for-bindings="startRecordedFilePlayback"> + <tp:added version="0.9.14"/> + <arg type="s" name="filepath" direction="in"/> + <arg type="b" name="result" direction="out"/> + </method> + + <method name="stopRecordedFilePlayback" tp:name-for-bindings="stopRecordedFilePlayback"> + <tp:added version="0.9.14"/> + <tp:docstring/> + <arg type="s" name="filepath" direction="in"/> + </method> + + <signal name="voiceMailNotify" tp:name-for-bindings="voiceMailNotify"> + <tp:docstring> + Notify the clients of the voicemail number for a specific account, if applicable. + </tp:docstring> + <arg type="s" name="accountID"> + <tp:docstring> + The account ID. + </tp:docstring> + </arg> + <arg type="i" name="count"> + <tp:docstring> + The number of waiting messages. + </tp:docstring> + </arg> + </signal> + + <signal name="transferSucceeded" tp:name-for-bindings="transferSucceeded"> + <tp:docstring> + <p>Transfer has been successfully + processed. Client should remove transfered + call from call list as it is no longer + accessible in Ring-daemon (dring).</p> + </tp:docstring> + </signal> + + <signal name="transferFailed" tp:name-for-bindings="transferFailed"> + <tp:docstring> + <p>Transfer operation failed. Corresponding + call is no longer accessible in + Ring-daemon (dring).</p> + </tp:docstring> + </signal> + + <signal name="secureSdesOn" tp:name-for-bindings="secureSdesOn"> + <tp:added version="0.9.7"/> + <tp:docstring> + <p>Signal sent on SDES session success. Media transmission is encripted + for this call only. It does not apply for a conference.</p> + <p>A conference can be considered to be secured if and only if each + participant is secured.</p> + </tp:docstring> + <arg type="s" name="callID"/> + </signal> + + <signal name="secureSdesOff" tp:name-for-bindings="secureSdesOff"> + <tp:added version="0.9.7"/> + <tp:docstring> + <p>Sinal sent to notify that SDES session failed.</p> + <p>Media transmission is not encrypted.</p> + </tp:docstring> + <arg type="s" name="callID" /> + </signal> + + <!-- ZRTP Methods and Signals --> + <signal name="secureZrtpOn" tp:name-for-bindings="secureZrtpOn"> + <tp:added version="0.9.7"/> + <arg type="s" name="callID" /> + <arg type="s" name="cipher" /> + </signal> + + <signal name="secureZrtpOff" tp:name-for-bindings="secureZrtpOff"> + <tp:added version="0.9.7"/> + <arg type="s" name="callID" /> + </signal> + + <signal name="confirmGoClear" tp:name-for-bindings="confirmGoClear"> + <tp:added version="0.9.7"/> + <arg type="s" name="callID" /> + </signal> + + <signal name="recordingStateChanged" tp:name-for-bindings="recordingStateChange"> + <tp:added version="1.3.0"/> + <arg type="s" name="callID" /> + <arg type="b" name="recordingState"/> + </signal> + + <signal name="zrtpNegotiationFailed" tp:name-for-bindings="zrtpNegotiationFailed"> + <tp:added version="0.9.7"/> + <arg type="s" name="callID" /> + <arg type="s" name="reason" /> + <arg type="s" name="severity" /> + </signal> + + <signal name="zrtpNotSuppOther" tp:name-for-bindings="zrtpNotSuppOther"> + <tp:added version="0.9.7"/> + <arg type="s" name="callID" /> + </signal> + + <signal name="showSAS" tp:name-for-bindings="showSAS"> + <tp:added version="0.9.7"/> + <arg type="s" name="callID" /> + <arg type="s" name="sas" /> + <arg type="b" name="verified"/> + </signal> + + <method name="setSASVerified" tp:name-for-bindings="setSASVerified"> + <tp:added version="0.9.7"/> + <arg type="s" name="callID" direction="in"/> + </method> + + <method name="resetSASVerified" tp:name-for-bindings="resetSASVerified"> + <tp:added version="0.9.7"/> + <arg type="s" name="callID" direction="in"/> + </method> + + <method name="setConfirmGoClear" tp:name-for-bindings="setConfirmGoClear"> + <tp:added version="0.9.7"/> + <arg type="s" name="callID" direction="in"/> + </method> + + <method name="requestGoClear" tp:name-for-bindings="requestGoClear"> + <tp:added version="0.9.7"/> + <arg type="s" name="callID" direction="in"/> + </method> + + <method name="acceptEnrollment" tp:name-for-bindings="acceptEnrollment"> + <tp:added version="0.9.7"/> + <arg type="s" name="callID" direction="in"/> + <arg type="b" name="accepted" direction="in"/> + </method> + + <signal name="onRtcpReportReceived" tp:name-for-bindings="onRtcpReportReceived"> + <tp:added version="1.3.0"/> + <tp:docstring> + <p>Signal sent adter a RTCP report has been received and computed.</p> + </tp:docstring> + <arg type="s" name="callID" /> + <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="MapStringInt"/> + <arg type="a{si}" name="report" direction="out" tp:type="String_Integer_Map"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>A map containing RTCP stats: </p> + <ul> + <li>PACKET_LOSS</li> + <li>CUMULATIVE_LOSS</li> + <li>ROUND_TRIP_DELAY</li> + <li>LATENCY</li> + </ul> + </tp:docstring> + </arg> + </signal> + + <signal name="peerHold" tp:name-for-bindings="peerHold"> + <tp:added version="2.0.0"/> + <arg type="s" name="callID" /> + <arg type="b" name="peerHolding" /> + </signal> + + </interface> +</node> diff --git a/bin/dbus/configurationmanager-introspec.xml b/bin/dbus/configurationmanager-introspec.xml new file mode 100644 index 0000000000..80384a45df --- /dev/null +++ b/bin/dbus/configurationmanager-introspec.xml @@ -0,0 +1,725 @@ +<?xml version="1.0" ?> +<node name="/configurationmanager-introspec" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + <interface name="cx.ring.Ring.ConfigurationManager"> + + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + Used to handle the configuration stuff: accounts settings, account registration, user preferences, ... + </tp:docstring> + + <method name="getAccountTemplate" tp:name-for-bindings="getAccountTemplate"> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MapStringString"/> + <arg type="s" name="accountType" direction="in" tp:type="String"> + </arg> + <arg type="a{ss}" name="details" direction="out" tp:type="String_String_Map"> + </arg> + </method> + + <method name="getAccountDetails" tp:name-for-bindings="getAccountDetails"> + <tp:docstring> + Get all parameters of the specified account. + </tp:docstring> + <arg type="s" name="accountID" direction="in"> + <tp:docstring> + The account ID + </tp:docstring> + </arg> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MapStringString"/> + <arg type="a{ss}" name="details" direction="out" tp:type="String_String_Map"> + <tp:docstring> + The available keys / parameters are: + <ul> + <li>CONFIG_ACCOUNT_ENABLE: True or False (Default: True)</li> + <li>CONFIG_ACCOUNT_RESOLVE_ONCE</li> + <li>CONFIG_ACCOUNT_TYPE: SIP or IAX2 (Default: SIP)</li> + <li>HOSTNAME: The IP adress or hostname of the registrar</li> + <li>USERNAME: The username (or extension) of the account</li> + <li>PASSWORD: The password associated to the account</li> + <li>REALM</li> + <li>CONFIG_ACCOUNT_MAILBOX: Number to dial to access the voicemail box</li> + <li>CONFIG_ACCOUNT_REGISTRATION_EXPIRE: SIP header expiration value (Default: 1600)</li> + <li>LOCAL_INTERFACE: The network interface (Default: eth0)</li> + <li>PUBLISHED_SAMEAS_LOCAL: If False, the published address equals the local address. This is the default.</li> + <li>PUBLISHED_ADDRESS: The SIP published address</li> + <li>LOCAL_PORT: The SIP listening port (Default: 5060)</li> + <li>PUBLISHED_PORT: The SIP published port</li> + <li>DISPLAY_NAMEL: The display name</li> + <li>STUN_ENABLE: True or False (Default: False)</li> + <li>STUN_SERVER: The STUN server address</li> + <li>ACCOUNT_REGISTRATION_STATUS: The account registration status. Should be Registered to make calls.</li> + <li>ACCOUNT_REGISTRATION_STATE_CODE</li> + <li>ACCOUNT_REGISTRATION_STATE_DESC</li> + <li>CONFIG_DEFAULT_PRESENCE_ENABLED: enable/disable presence support - true or false</li> + <li>SRTP_KEY_EXCHANGE</li> + <li>SRTP_ENABLE: Whether or not voice communication are encrypted - True or False (Default: False)</li> + <li>SRTP_RTP_FALLBACK</li> + <li>ZRTP_DISPLAY_SAS</li> + <li>ZRTP_DISPLAY_SAS_ONCE</li> + <li>ZRTP_HELLO_HASH</li> + <li>ZRTP_NOT_SUPP_WARNING</li> + <li>TLS_LISTENER_PORT: TLS listening port (Default: 5061)</li> + <li>TLS_ENABLE: Whether or not signalling is encrypted - True or False (Default: False)</li> + <li>TLS_CA_LIST_FILE</li> + <li>TLS_CERTIFICATE_FILE</li> + <li>TLS_PRIVATE_KEY_FILE</li> + <li>TLS_METHOD</li> + <li>TLS_CIPHERS</li> + <li>TLS_SERVER_NAME</li> + <li>TLS_VERIFY_SERVER</li> + <li>TLS_VERIFY_CLIENT</li> + <li>TLS_REQUIRE_CLIENT_CERTIFICATE</li> + <li>TLS_NEGOTIATION_TIMEOUT_SEC</li> + </ul> + </tp:docstring> + </arg> + </method> + + <method name="getVolatileAccountDetails" tp:name-for-bindings="getVolatileAccountDetails"> + <arg type="s" name="accountID" direction="in"> + <tp:docstring> + The account ID + </tp:docstring> + </arg> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MapStringString"/> + <arg type="a{ss}" name="details" tp:type="String_String_Map" direction="out"> + <tp:docstring> + Account.registrationCoarseStatus ( Coarse status like, UNREGISTERED or TRYING ) + Account.registrationStatus ( Error code, like 200 (OK) or 408 (Timeout) ) + Account.registrationStatusDescription ( A technical error message (from PJSIP) ) + Account.lastSuccessfulRegister ( Timestamp of the last "REGISTERED" event ) + Account.presenceStatus ( Published presence status ) + Account.presenceNote ( Published presence note (status string) ) + </tp:docstring> + </arg> + </method> + + <method name="setAccountDetails" tp:name-for-bindings="setAccountDetails"> + <tp:docstring> + Send new account parameters, or account parameters changes, to the core. The hash table is not required to be complete, only the updated parameters may be specified. + <tp:rationale>Account settings are written to the configuration file when ring properly quits.</tp:rationale> + <tp:rationale>After calling this method, the core will emit the signal <tp:member-ref>accountsChanged</tp:member-ref> with the updated data. The client must subscribe to this signal and use it to update its internal data structure.</tp:rationale> + </tp:docstring> + <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="MapStringString"/> + <arg type="s" name="accountID" direction="in"> + </arg> + <arg type="a{ss}" name="details" direction="in" tp:type="String_String_Map"> + </arg> + </method> + + <method name="setCredentials" tp:name-for-bindings="setCredentials"> + <arg type="s" name="accountID" direction="in"> + </arg> + <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="VectorMapStringString"/> + <arg type="aa{ss}" name="credentialInformation" direction="in" tp:type="String_String_Map"> + </arg> + </method> + + <method name="getIp2IpDetails" tp:name-for-bindings="getIp2IpDetails"> + <tp:docstring> + Get configuration settings of the IP2IP_PROFILE. They are sligthly different from account settings since no VoIP accounts are involved. + </tp:docstring> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MapStringString"/> + <arg type="a{ss}" name="details" direction="out" tp:type="String_String_Map"> + <tp:docstring> + Available parameters are: + <ul> + <li>ACCOUNT_ID</li> + <li>SRTP_KEY_EXCHANGE</li> + <li>SRTP_ENABLE</li> + <li>SRTP_RTP_FALLBACK</li> + <li>ZRTP_DISPLAY_SAS</li> + <li>ZRTP_HELLO_HASH</li> + <li>ZRTP_NOT_SUPP_WARNING</li> + <li>ZRTP_DISPLAY_SAS_ONCE</li> + <li>LOCAL_INTERFACE</li> + <li>LOCAL_PORT</li> + <li>TLS_LISTENER_PORT</li> + <li>TLS_CA_LIST_FILE</li> + <li>TLS_CERTIFICATE_FILE</li> + <li>TLS_PRIVATE_KEY_FILE</li> + <li>TLS_PASSWORD</li> + <li>TLS_METHOD</li> + <li>TLS_CIPHERS</li> + <li>TLS_SERVER_NAME</li> + <li>TLS_VERIFY_SERVER</li> + <li>TLS_VERIFY_CLIENT</li> + <li>TLS_REQUIRE_CLIENT_CERTIFICATE</li> + <li>TLS_NEGOTIATION_TIMEOUT_SEC</li> + </ul> + </tp:docstring> + </arg> + </method> + + <method name="getCredentials" tp:name-for-bindings="getCredentials"> + <arg type="s" name="accountID" direction="in"> + </arg> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="VectorMapStringString"/> + <arg type="aa{ss}" name="credentialInformation" direction="out"> + </arg> + </method> + + <method name="addAccount" tp:name-for-bindings="addAccount"> + <tp:docstring> + Add a new account. When created, the signal <tp:member-ref>accountsChanged</tp:member-ref> is emitted. The clients must then call <tp:member-ref>getAccountList</tp:member-ref> to update their internal data structure. + <tp:rationale>If no details are specified, the default parameters are used.</tp:rationale> + <tp:rationale>The core tries to register the account as soon it is created.</tp:rationale> + </tp:docstring> + <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="MapStringString"/> + <arg type="a{ss}" name="details" direction="in" tp:type="String_String_Map"> + <tp:docstring> + The new account settings + </tp:docstring> + </arg> + <arg type="s" name="createdAccountId" direction="out"> + <tp:docstring> + A new account ID + </tp:docstring> + </arg> + </method> + + <method name="setAccountsOrder" tp:name-for-bindings="setAccountsOrder"> + <tp:docstring> + Update the accounts order. + <tp:rationale>When placing a call, the first registered account in the list is used.</tp:rationale> + </tp:docstring> + <arg type="s" name="order" direction="in"> + <tp:docstring> + An ordered list of account IDs, delimited by '/' + </tp:docstring> + </arg> + </method> + + <method name="removeAccount" tp:name-for-bindings="removeAccount"> + <tp:docstring> + Remove an existing account. When removed, the signal <tp:member-ref>accountsChanged</tp:member-ref> is emitted. The clients must then call <tp:member-ref>getAccountList</tp:member-ref> to update their internal data structure. + </tp:docstring> + <arg type="s" name="accoundID" direction="in"> + <tp:docstring> + The account to remove, identified by its ID + </tp:docstring> + </arg> + </method> + + <method name="getAccountList" tp:name-for-bindings="getAccountList"> + <tp:docstring> + Get a list of all created accounts, as stored by the core. + </tp:docstring> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="VectorString"/> + <arg type="as" name="list" direction="out"> + <tp:docstring> + A list of account IDs + </tp:docstring> + </arg> + </method> + + <method name="registerAllAccounts" tp:name-for-bindings="registerAllAccounts"> + <tp:docstring> + Send account registration (REGISTER) for all accounts, even if they are not enabled. + </tp:docstring> + </method> + + <method name="sendRegister" tp:name-for-bindings="sendRegister"> + <tp:docstring> + Send account registration (REGISTER) to the registrar. + Register the account if enable=true, unregister if enable=false. + </tp:docstring> + <arg type="s" name="accountID" direction="in"> + <tp:docstring> + The account ID + </tp:docstring> + </arg> + <arg type="b" name="enable" direction="in"> + <tp:docstring> + <p>To register, enable must be true.</p> + <p>To un-register, enable must be false.</p> + </tp:docstring> + </arg> + </method> + + <method name="setVolume" tp:name-for-bindings="setVolume"> + <tp:docstring> + <p>Sets the volume using a linear scale [0,100].</p> + <tp:rationale>Pulseaudio has its own mechanism to modify application volume. This method is enabled only if the ALSA API is used.</tp:rationale> + </tp:docstring> + <arg type="s" name="device" direction="in"> + <tp:docstring> + The device: mic or speaker + </tp:docstring> + </arg> + <arg type="d" name="value" direction="in"> + <tp:docstring> + The volume value (between 0 and 100) + </tp:docstring> + </arg> + </method> + + <method name="getVolume" tp:name-for-bindings="getVolume"> + <tp:docstring> + <p>Return the volume value of the given device on a linear scale [0,100].</p> + <tp:rationale>Only enabled if the ALSA API is used, Pulseaudio has its own mechanism to modify application volume.</tp:rationale> + </tp:docstring> + <arg type="s" name="device" direction="in"> + <tp:docstring> + The device: mic or speaker + </tp:docstring> + </arg> + <arg type="d" name="value" direction="out"> + <tp:docstring> + The volume value (between 0 and 100) + </tp:docstring> + </arg> + </method> + + <signal name="volumeChanged" tp:name-for-bindings="volumeChanged"> + <tp:docstring> + <p>Notify clients of a volume level change.</p> + <p>This signal occurs only if ALSA is enabled since Pulseaudio streams are managed externally. </p> + </tp:docstring> + <arg type="s" name="device"> + <tp:docstring> + The device: mic or speaker + </tp:docstring> + </arg> + <arg type="d" name="value"> + <tp:docstring> + The new volume value + </tp:docstring> + </arg> + </signal> + + <!-- For now only expose these two options to clients --> + <method name="muteDtmf" tp:name-for-bindings="muteDtmf"> + <arg type="b" name="mute" direction="in"/> + </method> + <method name="isDtmfMuted" tp:name-for-bindings="isDtmfMuted"> + <arg type="b" name="muted" direction="out"/> + </method> + + <method name="muteCapture" tp:name-for-bindings="muteCapture"> + <arg type="b" name="mute" direction="in"> + <tp:docstring> + True to mute audio capture, false to unmute. + </tp:docstring> + </arg> + </method> + + <method name="isCaptureMuted" tp:name-for-bindings="isCaptureMuted"> + <arg type="b" name="muted" direction="out"> + <tp:docstring> + Returns true if audio capture is muted, false otherwise. + </tp:docstring> + </arg> + </method> + + <method name="mutePlayback" tp:name-for-bindings="mutePlayback"> + <arg type="b" name="mute" direction="in"> + <tp:docstring> + True to mute audio playback, false otherwise. + </tp:docstring> + </arg> + </method> + + <method name="isPlaybackMuted" tp:name-for-bindings="isPlaybackMuted"> + <arg type="b" name="muted" direction="out"> + <tp:docstring> + Returns true if audio playback is muted, false otherwise. + </tp:docstring> + </arg> + </method> + + <method name="getAudioManager" tp:name-for-bindings="getAudioManager"> + <arg type="s" name="api" direction="out"> + </arg> + </method> + + <method name="setAudioManager" tp:name-for-bindings="setAudioManager"> + <arg type="s" name="api" direction="in"> + </arg> + <arg type="b" name="successful" direction="out"> + </arg> + </method> + + <method name="getSupportedAudioManagers" tp:name-for-bindings="getSupportedAudioManagers"> + <tp:docstring> + Returns a list of compiled audio backends. + </tp:docstring> + <arg type="as" name="api" direction="out"> + </arg> + </method> + + <method name="getRecordPath" tp:name-for-bindings="getRecordPath"> + <arg type="s" name="rec" direction="out"> + </arg> + </method> + + <method name="setRecordPath" tp:name-for-bindings="setRecordPath"> + <arg type="s" name="rec" direction="in"> + </arg> + </method> + + <method name="getIsAlwaysRecording" tp:name-for-bindings="getIsAlwaysRecording"> + <arg type="b" name="res" direction="out"> + </arg> + </method> + + <method name="setIsAlwaysRecording" tp:name-for-bindings="setIsAlwaysRecording"> + <arg type="b" name="enabled" direction="in"> + </arg> + </method> + + <!-- /////////////////////// --> + + <!-- Codecs-related methods --> + + <method name="getCodecList" tp:name-for-bindings="getCodecList"> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="VectorUInt"/> + <arg type="au" name="list" direction="out"> + </arg> + </method> + + <method name="getCodecDetails" tp:name-for-bindings="getCodecDetails"> + <arg type="s" name="accountID" direction="in"></arg> + <arg type="u" name="codecId" direction="in"></arg> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MapStringString"/> + <arg type="a{ss}" name="details" direction="out" tp:type="String_String_Map"> + </arg> + </method> + + <method name="setCodecDetails" tp:name-for-bindings="setCodecDetails"> + <arg type="b" name="result" direction="out"></arg> + <arg type="s" name="accountID" direction="in"></arg> + <arg type="u" name="codecId" direction="in"></arg> + <annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="MapStringString"/> + <arg type="a{ss}" name="details" direction="in" tp:type="String_String_Map"> + </arg> + </method> + + <method name="getActiveCodecList" tp:name-for-bindings="getActiveCodecList"> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="VectorUInt"/> + <arg type="s" name="accountID" direction="in"> + </arg> + <arg type="au" name="list" direction="out"> + </arg> + </method> + + <method name="setActiveCodecList" tp:name-for-bindings="setActiveCodecList"> + <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="VectorUInt"/> + <arg type="s" name="accountID" direction="in"> + </arg> + <arg type="au" name="list" direction="in"> + </arg> + </method> + + <!-- Audio devices methods --> + + <method name="getAudioPluginList" tp:name-for-bindings="getAudioPluginList"> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="VectorString"/> + <arg type="as" name="list" direction="out"> + </arg> + </method> + + <method name="setAudioPlugin" tp:name-for-bindings="setAudioPlugin"> + <arg type="s" name="audioPlugin" direction="in"> + </arg> + </method> + + <method name="getAudioOutputDeviceList" tp:name-for-bindings="getAudioOutputDeviceList"> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="VectorString"/> + <arg type="as" name="list" direction="out"> + </arg> + </method> + + <method name="setAudioOutputDevice" tp:name-for-bindings="setAudioOutputDevice"> + <arg type="i" name="index" direction="in"> + </arg> + </method> + + <method name="setAudioInputDevice" tp:name-for-bindings="setAudioInputDevice"> + <arg type="i" name="index" direction="in"> + </arg> + </method> + + <method name="setAudioRingtoneDevice" tp:name-for-bindings="setAudioRingtoneDevice"> + <arg type="i" name="index" direction="in"> + </arg> + </method> + + <method name="getAudioInputDeviceList" tp:name-for-bindings="getAudioInputDeviceList"> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="VectorString"/> + <arg type="as" name="list" direction="out"> + </arg> + </method> + + + <method name="getCurrentAudioDevicesIndex" tp:name-for-bindings="getCurrentAudioDevicesIndex"> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="VectorString"/> + <arg type="as" name="list" direction="out"> + </arg> + </method> + + <method name="getAudioInputDeviceIndex" tp:name-for-bindings="getAudioInputDeviceIndex"> + <arg type="s" name="devname" direction="in"> + </arg> + <arg type="i" name="index" direction="out"> + </arg> + </method> + + <method name="getAudioOutputDeviceIndex" tp:name-for-bindings="getAudioOutputDeviceIndex"> + <arg type="s" name="devname" direction="in"> + </arg> + <arg type="i" name="index" direction="out"> + </arg> + </method> + + <method name="getCurrentAudioOutputPlugin" tp:name-for-bindings="getCurrentAudioOutputPlugin"> + <arg type="s" name="plugin" direction="out"> + </arg> + </method> + + <!-- General Settings Panel --> + + <method name="getNoiseSuppressState" tp:name-for-bindings="getNoiseSuppressState"> + <arg type="b" name="state" direction="out"> + </arg> + </method> + + <method name="setNoiseSuppressState" tp:name-for-bindings="setNoiseSuppressState"> + <arg type="b" name="state" direction="in"> + </arg> + </method> + + <method name="isAgcEnabled" tp:name-for-bindings="isAgcEnabled"> + <arg type="b" name="enabled" direction="out"> + </arg> + </method> + + <method name="setAgcState" tp:name-for-bindings="setAgcState"> + <arg type="b" name="enabled" direction="in"> + </arg> + </method> + + <!-- General Settings Panel --> + + <method name="isIax2Enabled" tp:name-for-bindings="isIax2Enabled"> + <arg type="i" name="res" direction="out"> + </arg> + </method> + + <method name="getHistoryLimit" tp:name-for-bindings="getHistoryLimit"> + <arg type="i" name="days" direction="out"> + </arg> + </method> + + <method name="setHistoryLimit" tp:name-for-bindings="setHistoryLimit"> + <arg type="i" name="days" direction="in"> + </arg> + </method> + + <!-- Hook configuration --> + <method name="getHookSettings" tp:name-for-bindings="getHookSettings"> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MapStringString"/> + <arg type="a{ss}" name="settings" direction="out"> + </arg> + </method> + + <method name="setHookSettings" tp:name-for-bindings="setHookSettings"> + <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="MapStringString"/> + <arg type="a{ss}" name="settings" direction="in"> + </arg> + </method> + + <signal name="accountsChanged" tp:name-for-bindings="accountsChanged"> + </signal> + + <signal name="registrationStateChanged" tp:name-for-bindings="registrationStateChanged"> + <arg type="s" name="accountID"/> + <arg type="s" name="registrationState"/> + <arg type="i" name="registrationDetail"> + <tp:docstring> + The optional account-type specific message code. 0 when not available. + </tp:docstring> + </arg> + <arg type="s" name="registrationDetailStr"> + <tp:docstring> + The optional account-type specific message string. Empty string when not available. + </tp:docstring> + </arg> + </signal> + + <signal name="volatileAccountDetailsChanged" tp:name-for-bindings="volatileAccountDetailsChanged"> + <arg type="s" name="accountID"> + <tp:docstring> + The account ID + </tp:docstring> + </arg> + <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="MapStringString"/> + <arg type="a{ss}" name="details" tp:type="String_String_Map"> + <tp:docstring> + Account.registrationCoarseStatus ( Coarse status like, UNREGISTERED or TRYING ) + Account.registrationStatus ( Error code, like 200 (OK) or 408 (Timeout) ) + Account.registrationStatusDescription ( A technical error message (from PJSIP) ) + Account.lastSuccessfulRegister ( Timestamp of the last "REGISTERED" event ) + Account.presenceStatus ( Published presence status ) + Account.presenceNote ( Published presence note (status string) ) + </tp:docstring> + </arg> + </signal> + + <signal name="stunStatusFailure" tp:name-for_bindings="stunStatusFailure"> + <arg type="s" name="reason"> + </arg> + </signal> + + <signal name="stunStatusSuccess" tp:name-for_bindings="stunStatusSuccess"> + <arg type="s" name="message"> + </arg> + </signal> + + <signal name="errorAlert" tp:name-for-bindings="errorAlert"> + <arg type="i" name="code"> + </arg> + </signal> + + <!-- TLS Methods --> + <method name="getSupportedTlsMethod" tp:name-for-bindings="getSupportedTlsMethod"> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="VectorString"/> + <arg type="as" name="list" direction="out"> + </arg> + </method> + + <method name="getSupportedCiphers" tp:name-for-bindings="getSupportedCiphers"> + <tp:added version="2.0.0"/> + <tp:docstring> + Returns a list of supported encryption ciphers used to encrypt SIP messages. The list depends on the TLS library being used. + Only registered SIP accounts currently support setting custom ciphers. This method returns an empty list if TLS support is disabled in either pjproject or Ring. + </tp:docstring> + <arg type="s" name="accountID" direction="in"> + <tp:docstring> + A SIP account id, other account IDs will be rejected. + </tp:docstring> + </arg> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="VectorString"/> + <arg type="as" name="list" direction="out"> + <tp:docstring> + A list of randomly sorted cipher names. The order may or may + not be significant depending on the SSL library being used. + </tp:docstring> + </arg> + </method> + + <method name="getTlsDefaultSettings" tp:name-for-bindings="getTlsDefaultSettings"> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MapStringString"/> + <arg type="a{ss}" name="details" direction="out"> + </arg> + </method> + + <method name="getTlsSettings" tp:name-for-bindings="getTlsSettings"> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MapStringString"/> + <arg type="a{ss}" name="details" direction="out"> + </arg> + </method> + + <method name="setTlsSettings" tp:name-for-bindings="setTlsSettings"> + <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="MapStringString"/> + <arg type="a{ss}" name="details" direction="in"> + </arg> + </method> + + <method name="validateCertificate" tp:name-for-bindings="validateCertificate"> + <arg type="s" name="accountId" direction="in"></arg> + <arg type="s" name="certificatePath" direction="in"> + <tp:docstring> + <p>A certificate path</p> + </tp:docstring> + </arg> + <arg type="s" name="privateKeyPath" direction="in"> + <tp:docstring> + <p>An optional path a the private key for the certificate</p> + </tp:docstring> + </arg> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MapStringString"/> + <arg type="a{ss}" name="details" direction="out"> + <tp:docstring> + <p>A key-value list of all certificate validation</p> + The constants used as keys are defined in the "security.h" constants header file + </tp:docstring> + </arg> + </method> + + <method name="validateCertificateRaw" tp:name-for-bindings="validateCertificateRaw"> + <arg type="s" name="accountId" direction="in"></arg> + <arg type="ay" name="certificateRaw" direction="in"> + <tp:docstring> + <p>A certificate path</p> + </tp:docstring> + </arg> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MapStringString"/> + <arg type="a{ss}" name="details" direction="out"> + <tp:docstring> + <p>A key-value list of all certificate validation</p> + The constants used as keys are defined in the "security.h" constants header file + </tp:docstring> + </arg> + </method> + + <method name="getCertificateDetails" tp:name-for-bindings="getCertificateDetails"> + <arg type="s" name="certificatePath" direction="in"> + <tp:docstring> + <p>A certificate path</p> + </tp:docstring> + </arg> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MapStringString"/> + <arg type="a{ss}" name="details" direction="out"> + <tp:docstring> + <p>A key-value list of all certificate details</p> + The constants used as keys are defined in the "security.h" constants header file + </tp:docstring> + </arg> + </method> + + <method name="getCertificateDetailsRaw" tp:name-for-bindings="getCertificateDetailsRaw"> + <arg type="ay" name="certificateRaw" direction="in"> + <tp:docstring> + <p>A raw certificate</p> + </tp:docstring> + </arg> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MapStringString"/> + <arg type="a{ss}" name="details" direction="out"> + <tp:docstring> + <p>A key-value list of all certificate details</p> + The constants used as keys are defined in the "security.h" constants header file + </tp:docstring> + </arg> + </method> + + <method name="getAddrFromInterfaceName" tp:name-for-bindings="getAddrFromInterfaceName"> + <arg type="s" name="interface" direction="in"> + </arg> + <arg type="s" name="address" direction="out"> + </arg> + </method> + + <method name="getAllIpInterface" tp:name-for-bindings="getAllIpInterface"> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="VectorString"/> + <arg type="as" name="list" direction="out"> + </arg> + </method> + + <method name="getAllIpInterfaceByName" tp:name-for-bindings="getAllIpInterfaceByName"> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="VectorString"/> + <arg type="as" name="list" direction="out"> + </arg> + </method> + + <method name="getShortcuts" tp:name-for-bindings="getShortcuts"> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MapStringString"/> + <arg type="a{ss}" name="shortcutsMap" direction="out"> + </arg> + </method> + + <method name="setShortcuts" tp:name-for-bindings="setShortcuts"> + <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="MapStringString"/> + <arg type="a{ss}" name="shortcutsMap" direction="in"> + </arg> + </method> + </interface> +</node> diff --git a/bin/dbus/cx.ring.Ring.service.in b/bin/dbus/cx.ring.Ring.service.in new file mode 100644 index 0000000000..253b6798bb --- /dev/null +++ b/bin/dbus/cx.ring.Ring.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=cx.ring.Ring +Exec=@LIBEXECDIR@/dring diff --git a/bin/dbus/dbus_cpp.h b/bin/dbus/dbus_cpp.h new file mode 100644 index 0000000000..9b8a0c4648 --- /dev/null +++ b/bin/dbus/dbus_cpp.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#ifndef DBUS_CPP_WRAPPER_H_ +#define DBUS_CPP_WRAPPER_H_ + +#pragma GCC diagnostic ignored "-Wshadow" +#pragma GCC diagnostic ignored "-Wignored-qualifiers" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#include <dbus-c++/dbus.h> +#pragma GCC diagnostic warning "-Wignored-qualifiers" +#pragma GCC diagnostic warning "-Wshadow" +#pragma GCC diagnostic warning "-Wunused-parameter" + +#endif // DBUS_CPP_WRAPPER_H_ diff --git a/bin/dbus/dbuscallmanager.cpp b/bin/dbus/dbuscallmanager.cpp new file mode 100644 index 0000000000..5b5b53a747 --- /dev/null +++ b/bin/dbus/dbuscallmanager.cpp @@ -0,0 +1,288 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Philippe Proulx <philippe.proulx@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "dbuscallmanager.h" +#include "dring/callmanager_interface.h" + +DBusCallManager::DBusCallManager(DBus::Connection& connection) + : DBus::ObjectAdaptor(connection, "/cx/ring/Ring/CallManager") +{} + +auto +DBusCallManager::placeCall(const std::string& accountID, const std::string& to) -> decltype(DRing::placeCall(accountID, to)) +{ + return DRing::placeCall(accountID, to); +} + +auto +DBusCallManager::refuse(const std::string& callID) -> decltype(DRing::refuse(callID)) +{ + return DRing::refuse(callID); +} + +auto +DBusCallManager::accept(const std::string& callID) -> decltype(DRing::accept(callID)) +{ + return DRing::accept(callID); +} + +auto +DBusCallManager::hangUp(const std::string& callID) -> decltype(DRing::hangUp(callID)) +{ + return DRing::hangUp(callID); +} + +auto +DBusCallManager::hold(const std::string& callID) -> decltype(DRing::hold(callID)) +{ + return DRing::hold(callID); +} + +auto +DBusCallManager::unhold(const std::string& callID) -> decltype(DRing::unhold(callID)) +{ + return DRing::unhold(callID); +} + +auto +DBusCallManager::transfer(const std::string& callID, const std::string& to) -> decltype(DRing::transfer(callID, to)) +{ + return DRing::transfer(callID, to); +} + +auto +DBusCallManager::attendedTransfer(const std::string& transferID, const std::string& targetID) -> decltype(DRing::attendedTransfer(transferID, targetID)) +{ + return DRing::attendedTransfer(transferID, targetID); +} + +auto +DBusCallManager::getCallDetails(const std::string& callID) -> decltype(DRing::getCallDetails(callID)) +{ + return DRing::getCallDetails(callID); +} + +auto +DBusCallManager::getCallList() -> decltype(DRing::getCallList()) +{ + return DRing::getCallList(); +} + +void +DBusCallManager::removeConference(const std::string& conference_id) +{ + DRing::removeConference(conference_id); +} + +auto +DBusCallManager::joinParticipant(const std::string& sel_callID, const std::string& drag_callID) -> decltype(DRing::joinParticipant(sel_callID, drag_callID)) +{ + return DRing::joinParticipant(sel_callID, drag_callID); +} + +void +DBusCallManager::createConfFromParticipantList(const std::vector< std::string >& participants) +{ + DRing::createConfFromParticipantList(participants); +} + +auto +DBusCallManager::isConferenceParticipant(const std::string& call_id) -> decltype(DRing::isConferenceParticipant(call_id)) +{ + return DRing::isConferenceParticipant(call_id); +} + +auto +DBusCallManager::addParticipant(const std::string& callID, const std::string& confID) -> decltype(DRing::addParticipant(callID, confID)) +{ + return DRing::addParticipant(callID, confID); +} + +auto +DBusCallManager::addMainParticipant(const std::string& confID) -> decltype(DRing::addMainParticipant(confID)) +{ + return DRing::addMainParticipant(confID); +} + +auto +DBusCallManager::detachParticipant(const std::string& callID) -> decltype(DRing::detachParticipant(callID)) +{ + return DRing::detachParticipant(callID); +} + +auto +DBusCallManager::joinConference(const std::string& sel_confID, const std::string& drag_confID) -> decltype(DRing::joinConference(sel_confID, drag_confID)) +{ + return DRing::joinConference(sel_confID, drag_confID); +} + +auto +DBusCallManager::hangUpConference(const std::string& confID) -> decltype(DRing::hangUpConference(confID)) +{ + return DRing::hangUpConference(confID); +} + +auto +DBusCallManager::holdConference(const std::string& confID) -> decltype(DRing::holdConference(confID)) +{ + return DRing::holdConference(confID); +} + +auto +DBusCallManager::unholdConference(const std::string& confID) -> decltype(DRing::unholdConference(confID)) +{ + return DRing::unholdConference(confID); +} + +auto +DBusCallManager::getConferenceList() -> decltype(DRing::getConferenceList()) +{ + return DRing::getConferenceList(); +} + +auto +DBusCallManager::getParticipantList(const std::string& confID) -> decltype(DRing::getParticipantList(confID)) +{ + return DRing::getParticipantList(confID); +} + +auto +DBusCallManager::getDisplayNames(const std::string& confID) -> decltype(DRing::getDisplayNames(confID)) +{ + return DRing::getDisplayNames(confID); +} + +auto +DBusCallManager::getConferenceId(const std::string& callID) -> decltype(DRing::getConferenceId(callID)) +{ + return DRing::getConferenceId(callID); +} + +auto +DBusCallManager::getConferenceDetails(const std::string& callID) -> decltype(DRing::getConferenceDetails(callID)) +{ + return DRing::getConferenceDetails(callID); +} + +auto +DBusCallManager::startRecordedFilePlayback(const std::string& filepath) -> decltype(DRing::startRecordedFilePlayback(filepath)) +{ + return DRing::startRecordedFilePlayback(filepath); +} + +void +DBusCallManager::stopRecordedFilePlayback(const std::string& filepath) +{ + DRing::stopRecordedFilePlayback(filepath); +} + +auto +DBusCallManager::toggleRecording(const std::string& callID) -> decltype(DRing::toggleRecording(callID)) +{ + return DRing::toggleRecording(callID); +} + +void +DBusCallManager::setRecording(const std::string& callID) +{ + DRing::setRecording(callID); +} + +void +DBusCallManager::recordPlaybackSeek(const double& value) +{ + DRing::recordPlaybackSeek(value); +} + +auto +DBusCallManager::getIsRecording(const std::string& callID) -> decltype(DRing::getIsRecording(callID)) +{ + return DRing::getIsRecording(callID); +} + +void +DBusCallManager::switchInput(const std::string& callID, const std::string& input) +{ + DRing::switchInput(callID, input); +} + +auto +DBusCallManager::getCurrentAudioCodecName(const std::string& callID) -> decltype(DRing::getCurrentAudioCodecName(callID)) +{ + return DRing::getCurrentAudioCodecName(callID); +} + +void +DBusCallManager::playDTMF(const std::string& key) +{ + DRing::playDTMF(key); +} + +void +DBusCallManager::startTone(const int32_t& start, const int32_t& type) +{ + DRing::startTone(start, type); +} + +void +DBusCallManager::setSASVerified(const std::string& callID) +{ + DRing::setSASVerified(callID); +} + +void +DBusCallManager::resetSASVerified(const std::string& callID) +{ + DRing::resetSASVerified(callID); +} + +void +DBusCallManager::setConfirmGoClear(const std::string& callID) +{ + DRing::setConfirmGoClear(callID); +} + +void +DBusCallManager::requestGoClear(const std::string& callID) +{ + DRing::requestGoClear(callID); +} + +void +DBusCallManager::acceptEnrollment(const std::string& callID, const bool& accepted) +{ + DRing::acceptEnrollment(callID, accepted); +} + +void +DBusCallManager::sendTextMessage(const std::string& callID, const std::string& message) +{ + DRing::sendTextMessage(callID, message); +} diff --git a/bin/dbus/dbuscallmanager.h b/bin/dbus/dbuscallmanager.h new file mode 100644 index 0000000000..604b21b6e7 --- /dev/null +++ b/bin/dbus/dbuscallmanager.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Pierre-Luc Beaudoin <pierre-luc.beaudoin@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __RING_DBUSCALLMANAGER_H__ +#define __RING_DBUSCALLMANAGER_H__ + +#include <vector> +#include <map> +#include <string> + +#include "dbus_cpp.h" + +#if __GNUC__ >= 4 && __GNUC_MINOR__ >= 6 +/* This warning option only exists for gcc 4.6.0 and greater. */ +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#endif + +#pragma GCC diagnostic ignored "-Wignored-qualifiers" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#include "dbuscallmanager.adaptor.h" +#pragma GCC diagnostic warning "-Wignored-qualifiers" +#pragma GCC diagnostic warning "-Wunused-parameter" + +#if __GNUC__ >= 4 && __GNUC_MINOR__ >= 6 +/* This warning option only exists for gcc 4.6.0 and greater. */ +#pragma GCC diagnostic warning "-Wunused-but-set-variable" +#endif + +#include <stdexcept> + +class DBusCallManager : + public cx::ring::Ring::CallManager_adaptor, + public DBus::IntrospectableAdaptor, + public DBus::ObjectAdaptor +{ + public: + DBusCallManager(DBus::Connection& connection); + + // Methods + std::string placeCall(const std::string& accountID, const std::string& to); + bool refuse(const std::string& callID); + bool accept(const std::string& callID); + bool hangUp(const std::string& callID); + bool hold(const std::string& callID); + bool unhold(const std::string& callID); + bool transfer(const std::string& callID, const std::string& to); + bool attendedTransfer(const std::string& transferID, const std::string& targetID); + std::map<std::string, std::string> getCallDetails(const std::string& callID); + std::vector<std::string> getCallList(); + void removeConference(const std::string& conference_id); + bool joinParticipant(const std::string& sel_callID, const std::string& drag_callID); + void createConfFromParticipantList(const std::vector< std::string >& participants); + bool isConferenceParticipant(const std::string& call_id); + bool addParticipant(const std::string& callID, const std::string& confID); + bool addMainParticipant(const std::string& confID); + bool detachParticipant(const std::string& callID); + bool joinConference(const std::string& sel_confID, const std::string& drag_confID); + bool hangUpConference(const std::string& confID); + bool holdConference(const std::string& confID); + bool unholdConference(const std::string& confID); + std::vector<std::string> getConferenceList(); + std::vector<std::string> getParticipantList(const std::string& confID); + std::vector<std::string> getDisplayNames(const std::string& confID); + std::string getConferenceId(const std::string& callID); + std::map<std::string, std::string> getConferenceDetails(const std::string& callID); + bool startRecordedFilePlayback(const std::string& filepath); + void stopRecordedFilePlayback(const std::string& filepath); + bool toggleRecording(const std::string& callID); + void setRecording(const std::string& callID); + void recordPlaybackSeek(const double& value); + bool getIsRecording(const std::string& callID); + void switchInput(const std::string& callID, const std::string& input); + std::string getCurrentAudioCodecName(const std::string& callID); + void playDTMF(const std::string& key); + void startTone(const int32_t& start, const int32_t& type); + void setSASVerified(const std::string& callID); + void resetSASVerified(const std::string& callID); + void setConfirmGoClear(const std::string& callID); + void requestGoClear(const std::string& callID); + void acceptEnrollment(const std::string& callID, const bool& accepted); + void sendTextMessage(const std::string& callID, const std::string& message); +}; + +#endif // __RING_CALLMANAGER_H__ diff --git a/bin/dbus/dbusclient.cpp b/bin/dbus/dbusclient.cpp new file mode 100644 index 0000000000..7f0fcaa480 --- /dev/null +++ b/bin/dbus/dbusclient.cpp @@ -0,0 +1,259 @@ +/* + * Copyright (C) 2014-2015 Savoir-Faire Linux Inc. + * Author: Philippe Proulx <philippe.proulx@savoirfairelinux.com> + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include <cstdlib> +#include <iostream> +#include <cstring> +#include <stdexcept> + +#include "dbusclient.h" +#include "dbus_cpp.h" + +#include "dbusinstance.h" + +#include "callmanager_interface.h" +#include "dbuscallmanager.h" + +#include "dbusconfigurationmanager.h" +#include "configurationmanager_interface.h" + +#include "dbuspresencemanager.h" +#include "presencemanager_interface.h" + +#ifdef RING_VIDEO +#include "dbusvideomanager.h" +#include "videomanager_interface.h" +#endif + +class EventCallback : + public DBus::Callback_Base<void, DBus::DefaultTimeout&> +{ + public: + EventCallback(const std::function<void()>&& func) + : callback_ {std::forward<const std::function<void()>>(func)} + {} + + void call(DBus::DefaultTimeout&) const { + callback_(); + } + + private: + const std::function<void()> callback_; +}; + +DBusClient::DBusClient(int flags, bool persistent) + : dispatcher_(new DBus::BusDispatcher) +{ + try { + DBus::_init_threading(); + DBus::default_dispatcher = dispatcher_.get(); + + // timeout and expired are deleted internally by dispatcher_'s + // destructor, so we must NOT delete them ourselves. + timeout_.reset(new DBus::DefaultTimeout {10 /* ms */, true, dispatcher_.get()}); + // Poll for Deamon events + timeout_->expired = new EventCallback {DRing::pollEvents}; + + DBus::Connection sessionConnection {DBus::Connection::SessionBus()}; + sessionConnection.request_name("cx.ring.Ring"); + + callManager_.reset(new DBusCallManager {sessionConnection}); + configurationManager_.reset(new DBusConfigurationManager {sessionConnection}); + presenceManager_.reset(new DBusPresenceManager {sessionConnection}); + + DBusInstance::OnNoMoreClientFunc onNoMoreClientFunc; + if (!persistent) + onNoMoreClientFunc = [this] {this->exit();}; + + instanceManager_.reset(new DBusInstance {sessionConnection, onNoMoreClientFunc}); + +#ifdef RING_VIDEO + videoManager_.reset(new DBusVideoManager {sessionConnection}); +#endif + } catch (const DBus::Error &err) { + throw std::runtime_error {"cannot initialize DBus stuff"}; + } + + if (initLibrary(flags) < 0) + throw std::runtime_error {"cannot initialize libring"}; + + instanceManager_->started(); +} + +DBusClient::~DBusClient() +{ + // instances destruction order is important + // so we enforce it here + +#ifdef RING_VIDEO + videoManager_.reset(); +#endif + instanceManager_.reset(); + presenceManager_.reset(); + configurationManager_.reset(); + callManager_.reset(); + timeout_.reset(); +} + +int +DBusClient::initLibrary(int flags) +{ + using namespace std::placeholders; + + using std::bind; + using DRing::exportable_callback; + using DRing::CallSignal; + using DRing::ConfigurationSignal; + using DRing::PresenceSignal; + + using SharedCallback = std::shared_ptr<DRing::CallbackWrapperBase>; + + auto callM = callManager_.get(); + auto confM = configurationManager_.get(); + auto presM = presenceManager_.get(); + +#ifdef RING_VIDEO + using DRing::VideoSignal; + auto videoM = videoManager_.get(); +#endif + + // Call event handlers + const std::map<std::string, SharedCallback> callEvHandlers = { + exportable_callback<CallSignal::StateChange>(bind(&DBusCallManager::callStateChanged, callM, _1, _2, _3)), + exportable_callback<CallSignal::TransferFailed>(bind(&DBusCallManager::transferFailed, callM)), + exportable_callback<CallSignal::TransferSucceeded>(bind(&DBusCallManager::transferSucceeded, callM)), + exportable_callback<CallSignal::RecordPlaybackStopped>(bind(&DBusCallManager::recordPlaybackStopped, callM, _1)), + exportable_callback<CallSignal::VoiceMailNotify>(bind(&DBusCallManager::voiceMailNotify, callM, _1, _2)), + exportable_callback<CallSignal::IncomingMessage>(bind(&DBusCallManager::incomingMessage, callM, _1, _2, _3)), + exportable_callback<CallSignal::IncomingCall>(bind(&DBusCallManager::incomingCall, callM, _1, _2, _3)), + exportable_callback<CallSignal::RecordPlaybackFilepath>(bind(&DBusCallManager::recordPlaybackFilepath, callM, _1, _2)), + exportable_callback<CallSignal::ConferenceCreated>(bind(&DBusCallManager::conferenceCreated, callM, _1)), + exportable_callback<CallSignal::ConferenceChanged>(bind(&DBusCallManager::conferenceChanged, callM, _1, _2)), + exportable_callback<CallSignal::UpdatePlaybackScale>(bind(&DBusCallManager::updatePlaybackScale, callM, _1, _2, _3)), + exportable_callback<CallSignal::ConferenceRemoved>(bind(&DBusCallManager::conferenceRemoved, callM, _1)), + exportable_callback<CallSignal::NewCallCreated>(bind(&DBusCallManager::newCallCreated, callM, _1, _2, _3)), + exportable_callback<CallSignal::RecordingStateChanged>(bind(&DBusCallManager::recordingStateChanged, callM, _1, _2)), + exportable_callback<CallSignal::SecureSdesOn>(bind(&DBusCallManager::secureSdesOn, callM, _1)), + exportable_callback<CallSignal::SecureSdesOff>(bind(&DBusCallManager::secureSdesOff, callM, _1)), + exportable_callback<CallSignal::SecureZrtpOn>(bind(&DBusCallManager::secureZrtpOn, callM, _1, _2)), + exportable_callback<CallSignal::SecureZrtpOff>(bind(&DBusCallManager::secureZrtpOff, callM, _1)), + exportable_callback<CallSignal::ShowSAS>(bind(&DBusCallManager::showSAS, callM, _1, _2, _3)), + exportable_callback<CallSignal::ZrtpNotSuppOther>(bind(&DBusCallManager::zrtpNotSuppOther, callM, _1)), + exportable_callback<CallSignal::ZrtpNegotiationFailed>(bind(&DBusCallManager::zrtpNegotiationFailed, callM, _1, _2, _3)), + exportable_callback<CallSignal::RtcpReportReceived>(bind(&DBusCallManager::onRtcpReportReceived, callM, _1, _2)), + exportable_callback<CallSignal::PeerHold>(bind(&DBusCallManager::peerHold, callM, _1, _2)) + }; + + // Configuration event handlers + const std::map<std::string, SharedCallback> configEvHandlers = { + exportable_callback<ConfigurationSignal::VolumeChanged>(bind(&DBusConfigurationManager::volumeChanged, confM, _1, _2)), + exportable_callback<ConfigurationSignal::AccountsChanged>(bind(&DBusConfigurationManager::accountsChanged, confM)), + exportable_callback<ConfigurationSignal::StunStatusFailed>(bind(&DBusConfigurationManager::stunStatusFailure, confM, _1)), + exportable_callback<ConfigurationSignal::RegistrationStateChanged>(bind(&DBusConfigurationManager::registrationStateChanged, confM, _1, _2, _3, _4)), + exportable_callback<ConfigurationSignal::VolatileDetailsChanged>(bind(&DBusConfigurationManager::volatileAccountDetailsChanged, confM, _1, _2)), + exportable_callback<ConfigurationSignal::Error>(bind(&DBusConfigurationManager::errorAlert, confM, _1)), + }; + + // Presence event handlers + const std::map<std::string, SharedCallback> presEvHandlers = { + exportable_callback<PresenceSignal::NewServerSubscriptionRequest>(bind(&DBusPresenceManager::newServerSubscriptionRequest, presM, _1)), + exportable_callback<PresenceSignal::ServerError>(bind(&DBusPresenceManager::serverError, presM, _1, _2, _3)), + exportable_callback<PresenceSignal::NewBuddyNotification>(bind(&DBusPresenceManager::newBuddyNotification, presM, _1, _2, _3, _4)), + exportable_callback<PresenceSignal::SubscriptionStateChanged>(bind(&DBusPresenceManager::subscriptionStateChanged, presM, _1, _2, _3)), + }; + +#ifdef RING_VIDEO + // Video event handlers + const std::map<std::string, SharedCallback> videoEvHandlers = { + exportable_callback<VideoSignal::DeviceEvent>(bind(&DBusVideoManager::deviceEvent, videoM)), + exportable_callback<VideoSignal::DecodingStarted>(bind(&DBusVideoManager::startedDecoding, videoM, _1, _2, _3, _4, _5)), + exportable_callback<VideoSignal::DecodingStopped>(bind(&DBusVideoManager::stoppedDecoding, videoM, _1, _2, _3)), + }; +#endif + + if (!DRing::init(static_cast<DRing::InitFlag>(flags))) + return -1; + + registerCallHandlers(callEvHandlers); + registerConfHandlers(configEvHandlers); + registerPresHandlers(presEvHandlers); +#ifdef RING_VIDEO + registerVideoHandlers(videoEvHandlers); +#endif + + if (!DRing::start()) + return -1; + return 0; +} + +void +DBusClient::finiLibrary() noexcept +{ + DRing::fini(); +} + +int +DBusClient::event_loop() noexcept +{ + try { + dispatcher_->enter(); + } catch (const DBus::Error& err) { + std::cerr << "quitting: " << err.name() << ": " << err.what() << std::endl; + return 1; + } catch (const std::exception& err) { + std::cerr << "quitting: " << err.what() << std::endl; + return 1; + } + + return 0; +} + +int +DBusClient::exit() noexcept +{ + try { + dispatcher_->leave(); + timeout_->expired = new EventCallback([] {}); + finiLibrary(); + } catch (const DBus::Error& err) { + std::cerr << "quitting: " << err.name() << ": " << err.what() << std::endl; + return 1; + } catch (const std::exception& err) { + std::cerr << "quitting: " << err.what() << std::endl; + return 1; + } + + return 0; +} diff --git a/bin/dbus/dbusclient.h b/bin/dbus/dbusclient.h new file mode 100644 index 0000000000..52bb865e3a --- /dev/null +++ b/bin/dbus/dbusclient.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Pierre-Luc Beaudoin <pierre-luc.beaudoin@savoirfairelinux.com> + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif // HAVE_CONFIG_H + +#include "dring.h" +#include <memory> + +class DBusConfigurationManager; +class DBusCallManager; +class DBusNetworkManager; +class DBusInstance; +class DBusPresenceManager; + +#ifdef RING_VIDEO +class DBusVideoManager; +#endif + +namespace DBus { + class BusDispatcher; + class DefaultTimeout; +} + +class DBusClient { + public: + DBusClient(int flags, bool persistent); + ~DBusClient(); + + int event_loop() noexcept; + int exit() noexcept; + + private: + int initLibrary(int flags); + void finiLibrary() noexcept; + + std::unique_ptr<DBus::BusDispatcher> dispatcher_; + std::unique_ptr<DBus::DefaultTimeout> timeout_; + + std::unique_ptr<DBusCallManager> callManager_; + std::unique_ptr<DBusConfigurationManager> configurationManager_; + std::unique_ptr<DBusPresenceManager> presenceManager_; + std::unique_ptr<DBusInstance> instanceManager_; + +#ifdef RING_VIDEO + std::unique_ptr<DBusVideoManager> videoManager_; +#endif +}; diff --git a/bin/dbus/dbusconfigurationmanager.cpp b/bin/dbus/dbusconfigurationmanager.cpp new file mode 100644 index 0000000000..8013b3a408 --- /dev/null +++ b/bin/dbus/dbusconfigurationmanager.cpp @@ -0,0 +1,455 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Philippe Proulx <philippe.proulx@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "dbusconfigurationmanager.h" +#include "configurationmanager_interface.h" + +#include "media/audio/audiolayer.h" + +DBusConfigurationManager::DBusConfigurationManager(DBus::Connection& connection) + : DBus::ObjectAdaptor(connection, "/cx/ring/Ring/ConfigurationManager") +{} + +auto +DBusConfigurationManager::getAccountDetails(const std::string& accountID) -> decltype(DRing::getAccountDetails(accountID)) +{ + return DRing::getAccountDetails(accountID); +} + +auto +DBusConfigurationManager::getVolatileAccountDetails(const std::string& accountID) -> decltype(DRing::getVolatileAccountDetails(accountID)) +{ + return DRing::getVolatileAccountDetails(accountID); +} + +void +DBusConfigurationManager::setAccountDetails(const std::string& accountID, const std::map<std::string, std::string>& details) +{ + DRing::setAccountDetails(accountID, details); +} + +auto +DBusConfigurationManager::getAccountTemplate(const std::string& accountType) -> decltype(DRing::getAccountTemplate(accountType)) +{ + return DRing::getAccountTemplate(accountType); +} + +auto +DBusConfigurationManager::addAccount(const std::map<std::string, std::string>& details) -> decltype(DRing::addAccount(details)) +{ + return DRing::addAccount(details); +} + +void +DBusConfigurationManager::removeAccount(const std::string& accountID) +{ + DRing::removeAccount(accountID); +} + +auto +DBusConfigurationManager::getAccountList() -> decltype(DRing::getAccountList()) +{ + return DRing::getAccountList(); +} + +void +DBusConfigurationManager::sendRegister(const std::string& accountID, const bool& enable) +{ + DRing::sendRegister(accountID, enable); +} + +void +DBusConfigurationManager::registerAllAccounts(void) +{ + DRing::registerAllAccounts(); +} + +auto +DBusConfigurationManager::getTlsDefaultSettings() -> decltype(DRing::getTlsDefaultSettings()) +{ + return DRing::getTlsDefaultSettings(); +} + +auto +DBusConfigurationManager::getCodecList() -> decltype(DRing::getCodecList()) +{ + return DRing::getCodecList(); +} + +auto +DBusConfigurationManager::getSupportedTlsMethod() -> decltype(DRing::getSupportedTlsMethod()) +{ + return DRing::getSupportedTlsMethod(); +} + +auto +DBusConfigurationManager::getSupportedCiphers(const std::string& accountID) -> decltype(DRing::getSupportedCiphers(accountID)) +{ + return DRing::getSupportedCiphers(accountID); +} + +auto +DBusConfigurationManager::getCodecDetails(const std::string& accountID, const unsigned& codecId) -> decltype(DRing::getCodecDetails(accountID, codecId)) +{ + return DRing::getCodecDetails(accountID, codecId); +} + +auto +DBusConfigurationManager::setCodecDetails(const std::string& accountID, const unsigned& codecId, const std::map<std::string, std::string>& details) +-> decltype(DRing::setCodecDetails(accountID, codecId, details)) +{ + return DRing::setCodecDetails(accountID, codecId, details); +} + +auto +DBusConfigurationManager::getActiveCodecList(const std::string& accountID) -> decltype(DRing::getActiveCodecList(accountID)) +{ + return DRing::getActiveCodecList(accountID); +} + +void +DBusConfigurationManager::setActiveCodecList(const std::string& accountID, const std::vector<unsigned>& list) +{ + DRing::setActiveCodecList(accountID, list); +} + +auto +DBusConfigurationManager::getAudioPluginList() -> decltype(DRing::getAudioPluginList()) +{ + return DRing::getAudioPluginList(); +} + +void +DBusConfigurationManager::setAudioPlugin(const std::string& audioPlugin) +{ + DRing::setAudioPlugin(audioPlugin); +} + +auto +DBusConfigurationManager::getAudioOutputDeviceList() -> decltype(DRing::getAudioOutputDeviceList()) +{ + return DRing::getAudioOutputDeviceList(); +} + +void +DBusConfigurationManager::setAudioOutputDevice(const int32_t& index) +{ + DRing::setAudioOutputDevice(index); +} + +void +DBusConfigurationManager::setAudioInputDevice(const int32_t& index) +{ + DRing::setAudioInputDevice(index); +} + +void +DBusConfigurationManager::setAudioRingtoneDevice(const int32_t& index) +{ + DRing::setAudioRingtoneDevice(index); +} + +auto +DBusConfigurationManager::getAudioInputDeviceList() -> decltype(DRing::getAudioInputDeviceList()) +{ + return DRing::getAudioInputDeviceList(); +} + +auto +DBusConfigurationManager::getCurrentAudioDevicesIndex() -> decltype(DRing::getCurrentAudioDevicesIndex()) +{ + return DRing::getCurrentAudioDevicesIndex(); +} + +auto +DBusConfigurationManager::getAudioInputDeviceIndex(const std::string& name) -> decltype(DRing::getAudioInputDeviceIndex(name)) +{ + return DRing::getAudioInputDeviceIndex(name); +} + +auto +DBusConfigurationManager::getAudioOutputDeviceIndex(const std::string& name) -> decltype(DRing::getAudioOutputDeviceIndex(name)) +{ + return DRing::getAudioOutputDeviceIndex(name); +} + +auto +DBusConfigurationManager::getCurrentAudioOutputPlugin() -> decltype(DRing::getCurrentAudioOutputPlugin()) +{ + return DRing::getCurrentAudioOutputPlugin(); +} + +auto +DBusConfigurationManager::getNoiseSuppressState() -> decltype(DRing::getNoiseSuppressState()) +{ + return DRing::getNoiseSuppressState(); +} + +void +DBusConfigurationManager::setNoiseSuppressState(const bool& state) +{ + DRing::setNoiseSuppressState(state); +} + +auto +DBusConfigurationManager::isAgcEnabled() -> decltype(DRing::isAgcEnabled()) +{ + return DRing::isAgcEnabled(); +} + +void +DBusConfigurationManager::setAgcState(const bool& enabled) +{ + DRing::setAgcState(enabled); +} + +void +DBusConfigurationManager::muteDtmf(const bool& mute) +{ + DRing::muteDtmf(mute); +} + +auto +DBusConfigurationManager::isDtmfMuted() -> decltype(DRing::isDtmfMuted()) +{ + return DRing::isDtmfMuted(); +} + +auto +DBusConfigurationManager::isCaptureMuted() -> decltype(DRing::isCaptureMuted()) +{ + return DRing::isCaptureMuted(); +} + +void +DBusConfigurationManager::muteCapture(const bool& mute) +{ + DRing::muteCapture(mute); +} + +auto +DBusConfigurationManager::isPlaybackMuted() -> decltype(DRing::isPlaybackMuted()) +{ + return DRing::isPlaybackMuted(); +} + +void +DBusConfigurationManager::mutePlayback(const bool& mute) +{ + DRing::mutePlayback(mute); +} + +auto +DBusConfigurationManager::getAudioManager() -> decltype(DRing::getAudioManager()) +{ + return DRing::getAudioManager(); +} + +auto +DBusConfigurationManager::setAudioManager(const std::string& api) -> decltype(DRing::setAudioManager(api)) +{ + return DRing::setAudioManager(api); +} + +std::vector<std::string> +DBusConfigurationManager::getSupportedAudioManagers() +{ + return { +#if HAVE_ALSA + ALSA_API_STR, +#endif +#if HAVE_PULSE + PULSEAUDIO_API_STR, +#endif +#if HAVE_JACK + JACK_API_STR, +#endif + }; +} + +auto +DBusConfigurationManager::isIax2Enabled() -> decltype(DRing::isIax2Enabled()) +{ + return DRing::isIax2Enabled(); +} + +auto +DBusConfigurationManager::getRecordPath() -> decltype(DRing::getRecordPath()) +{ + return DRing::getRecordPath(); +} + +void +DBusConfigurationManager::setRecordPath(const std::string& recPath) +{ + DRing::setRecordPath(recPath); +} + +auto +DBusConfigurationManager::getIsAlwaysRecording() -> decltype(DRing::getIsAlwaysRecording()) +{ + return DRing::getIsAlwaysRecording(); +} + +void +DBusConfigurationManager::setIsAlwaysRecording(const bool& rec) +{ + DRing::setIsAlwaysRecording(rec); +} + +void +DBusConfigurationManager::setHistoryLimit(const int32_t& days) +{ + DRing::setHistoryLimit(days); +} + +auto +DBusConfigurationManager::getHistoryLimit() -> decltype(DRing::getHistoryLimit()) +{ + return DRing::getHistoryLimit(); +} + +void +DBusConfigurationManager::setAccountsOrder(const std::string& order) +{ + DRing::setAccountsOrder(order); +} + +auto +DBusConfigurationManager::getHookSettings() -> decltype(DRing::getHookSettings()) +{ + return DRing::getHookSettings(); +} + +void +DBusConfigurationManager::setHookSettings(const std::map<std::string, std::string>& settings) +{ + DRing::setHookSettings(settings); +} + +auto +DBusConfigurationManager::getTlsSettings() -> decltype(DRing::getTlsSettings()) +{ + return DRing::getTlsSettings(); +} + +auto +DBusConfigurationManager::validateCertificate(const std::string& accountId, const std::string& certificate, const std::string& privateKey) -> decltype(DRing::validateCertificate(accountId, certificate, privateKey)) +{ + return DRing::validateCertificate(accountId, certificate, privateKey); +} + +std::map<std::string, std::string> +DBusConfigurationManager::validateCertificateRaw(const std::string& accountId, const std::vector<uint8_t>& certificate) +{ + return DRing::validateCertificateRaw(accountId, certificate); +} + +auto +DBusConfigurationManager::getCertificateDetails(const std::string& certificate) -> decltype(DRing::getCertificateDetails(certificate)) +{ + return DRing::getCertificateDetails(certificate); +} + +auto +DBusConfigurationManager::getCertificateDetailsRaw(const std::vector<uint8_t>& certificate) -> decltype(DRing::getCertificateDetailsRaw(certificate)) +{ + return DRing::getCertificateDetailsRaw(certificate); +} + +void +DBusConfigurationManager::setTlsSettings(const std::map<std::string, std::string>& details) +{ + DRing::setTlsSettings(details); +} + +auto +DBusConfigurationManager::getIp2IpDetails() -> decltype(DRing::getIp2IpDetails()) +{ + return DRing::getIp2IpDetails(); +} + +auto +DBusConfigurationManager::getCredentials(const std::string& accountID) -> decltype(DRing::getCredentials(accountID)) +{ + return DRing::getCredentials(accountID); +} + +void +DBusConfigurationManager::setCredentials(const std::string& accountID, const std::vector<std::map<std::string, std::string>>& details) +{ + DRing::setCredentials(accountID, details); +} + +auto +DBusConfigurationManager::getAddrFromInterfaceName(const std::string& interface) -> decltype(DRing::getAddrFromInterfaceName(interface)) +{ + return DRing::getAddrFromInterfaceName(interface); +} + +auto +DBusConfigurationManager::getAllIpInterface() -> decltype(DRing::getAllIpInterface()) +{ + return DRing::getAllIpInterface(); +} + +auto +DBusConfigurationManager::getAllIpInterfaceByName() -> decltype(DRing::getAllIpInterfaceByName()) +{ + return DRing::getAllIpInterfaceByName(); +} + +auto +DBusConfigurationManager::getShortcuts() -> decltype(DRing::getShortcuts()) +{ + return DRing::getShortcuts(); +} + +void +DBusConfigurationManager::setShortcuts(const std::map<std::string, std::string> &shortcutsMap) +{ + DRing::setShortcuts(shortcutsMap); +} + +void +DBusConfigurationManager::setVolume(const std::string& device, const double& value) +{ + DRing::setVolume(device, value); +} + +auto +DBusConfigurationManager::getVolume(const std::string& device) -> decltype(DRing::getVolume(device)) +{ + return DRing::getVolume(device); +} diff --git a/bin/dbus/dbusconfigurationmanager.h b/bin/dbus/dbusconfigurationmanager.h new file mode 100644 index 0000000000..a72c094cab --- /dev/null +++ b/bin/dbus/dbusconfigurationmanager.h @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Pierre-Luc Beaudoin <pierre-luc.beaudoin@savoirfairelinux.com> + * Author: Alexandre Bourget <alexandre.bourget@savoirfairelinux.com> + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Guillaume Carmel-Archambault <guillaume.carmel-archambault@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __RING_DBUSCONFIGURATIONMANAGER_H__ +#define __RING_DBUSCONFIGURATIONMANAGER_H__ + +#include <vector> +#include <map> +#include <string> + +#include "dbus_cpp.h" + +#if __GNUC__ >= 4 && __GNUC_MINOR__ >= 6 +/* This warning option only exists for gcc 4.6.0 and greater. */ +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#endif + +#pragma GCC diagnostic ignored "-Wignored-qualifiers" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#include "dbusconfigurationmanager.adaptor.h" +#pragma GCC diagnostic warning "-Wignored-qualifiers" +#pragma GCC diagnostic warning "-Wunused-parameter" + +#if __GNUC__ >= 4 && __GNUC_MINOR__ >= 6 +/* This warning option only exists for gcc 4.6.0 and greater. */ +#pragma GCC diagnostic warning "-Wunused-but-set-variable" +#endif + +class DBusConfigurationManager : + public cx::ring::Ring::ConfigurationManager_adaptor, + public DBus::IntrospectableAdaptor, + public DBus::ObjectAdaptor +{ + public: + DBusConfigurationManager(DBus::Connection& connection); + + // Methods + std::map<std::string, std::string> getAccountDetails(const std::string& accountID); + std::map<std::string, std::string> getVolatileAccountDetails(const std::string& accountID); + void setAccountDetails(const std::string& accountID, const std::map<std::string, std::string>& details); + std::map<std::string, std::string> getAccountTemplate(const std::string& accountType); + std::string addAccount(const std::map<std::string, std::string>& details); + void removeAccount(const std::string& accoundID); + std::vector<std::string> getAccountList(); + void sendRegister(const std::string& accoundID, const bool& enable); + void registerAllAccounts(void); + std::map<std::string, std::string> getTlsDefaultSettings(); + std::vector<std::string> getSupportedCiphers(const std::string& accountID); + std::vector<unsigned> getCodecList(); + std::vector<std::string> getSupportedTlsMethod(); + std::map<std::string, std::string> getCodecDetails(const std::string& accountID, const unsigned& codecId); + bool setCodecDetails(const std::string& accountID, const unsigned& codecId, const std::map<std::string, std::string>& details); + std::vector<unsigned> getActiveCodecList(const std::string& accountID); + void setActiveCodecList(const std::string& accountID, const std::vector<unsigned>& list); + std::vector<std::string> getAudioPluginList(); + void setAudioPlugin(const std::string& audioPlugin); + std::vector<std::string> getAudioOutputDeviceList(); + void setAudioOutputDevice(const int32_t& index); + void setAudioInputDevice(const int32_t& index); + void setAudioRingtoneDevice(const int32_t& index); + std::vector<std::string> getAudioInputDeviceList(); + std::vector<std::string> getCurrentAudioDevicesIndex(); + int32_t getAudioInputDeviceIndex(const std::string& name); + int32_t getAudioOutputDeviceIndex(const std::string& name); + std::string getCurrentAudioOutputPlugin(); + bool getNoiseSuppressState(); + void setNoiseSuppressState(const bool& state); + bool isAgcEnabled(); + void setAgcState(const bool& enabled); + void muteDtmf(const bool& mute); + bool isDtmfMuted(); + bool isCaptureMuted(); + void muteCapture(const bool& mute); + bool isPlaybackMuted(); + void mutePlayback(const bool& mute); + std::string getAudioManager(); + bool setAudioManager(const std::string& api); + std::vector<std::string> getSupportedAudioManagers(); + int32_t isIax2Enabled(); + std::string getRecordPath(); + void setRecordPath(const std::string& recPath); + bool getIsAlwaysRecording(); + void setIsAlwaysRecording(const bool& rec); + void setHistoryLimit(const int32_t& days); + int32_t getHistoryLimit(); + void setAccountsOrder(const std::string& order); + std::map<std::string, std::string> getHookSettings(); + void setHookSettings(const std::map<std::string, std::string>& settings); + std::map<std::string, std::string> getTlsSettings(); + void setTlsSettings(const std::map<std::string, std::string>& details); + std::map<std::string, std::string> getIp2IpDetails(); + std::vector<std::map<std::string, std::string>> getCredentials(const std::string& accountID); + void setCredentials(const std::string& accountID, const std::vector<std::map<std::string, std::string>>& details); + std::string getAddrFromInterfaceName(const std::string& interface); + std::vector<std::string> getAllIpInterface(); + std::vector<std::string> getAllIpInterfaceByName(); + std::map<std::string, std::string> getShortcuts(); + void setShortcuts(const std::map<std::string, std::string> &shortcutsMap); + void setVolume(const std::string& device, const double& value); + double getVolume(const std::string& device); + std::map<std::string, std::string> validateCertificate(const std::string& accountId, const std::string& certificate, const std::string& privateKey); + std::map<std::string, std::string> validateCertificateRaw(const std::string& accountId, const std::vector<uint8_t>& certificate); + std::map<std::string, std::string> getCertificateDetails(const std::string& certificate); + std::map<std::string, std::string> getCertificateDetailsRaw(const std::vector<uint8_t>& certificate); +}; + +#endif // __RING_DBUSCONFIGURATIONMANAGER_H__ diff --git a/bin/dbus/dbusinstance.cpp b/bin/dbus/dbusinstance.cpp new file mode 100644 index 0000000000..8aedbc307e --- /dev/null +++ b/bin/dbus/dbusinstance.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Pierre-Luc Beaudoin <pierre-luc.beaudoin@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#include "intrin.h" +#include "dbusinstance.h" + +DBusInstance::DBusInstance(DBus::Connection& connection, + const OnNoMoreClientFunc& onNoMoreClientFunc) + : DBus::ObjectAdaptor(connection, "/cx/ring/Ring/Instance") + , onNoMoreClientFunc_(onNoMoreClientFunc) + , count_(0) +{} + +void +DBusInstance::Register(const int32_t& pid UNUSED, + const std::string& name UNUSED) +{ + ++count_; +} + + +void +DBusInstance::Unregister(const int32_t& pid UNUSED) +{ + --count_; + + if (count_ <= 0 && onNoMoreClientFunc_) { + onNoMoreClientFunc_(); + } +} diff --git a/bin/dbus/dbusinstance.h b/bin/dbus/dbusinstance.h new file mode 100644 index 0000000000..b410c21a7e --- /dev/null +++ b/bin/dbus/dbusinstance.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Pierre-Luc Beaudoin <pierre-luc.beaudoin@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __RING_DBUSINSTANCE_H__ +#define __RING_DBUSINSTANCE_H__ + +#include <functional> + +#if __GNUC__ >= 4 && __GNUC_MINOR__ >= 6 +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#endif +#include "dbus_cpp.h" + +#pragma GCC diagnostic ignored "-Wignored-qualifiers" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#include "dbusinstance.adaptor.h" +#pragma GCC diagnostic warning "-Wignored-qualifiers" +#pragma GCC diagnostic warning "-Wunused-parameter" +#if __GNUC__ >= 4 && __GNUC_MINOR__ >= 6 +#pragma GCC diagnostic warning "-Wunused-but-set-variable" +#endif + +class DBusInstance : + public cx::ring::Ring::Instance_adaptor, + public DBus::IntrospectableAdaptor, + public DBus::ObjectAdaptor +{ + public: + typedef std::function<void()> OnNoMoreClientFunc; + + DBusInstance(DBus::Connection& connection, + const OnNoMoreClientFunc& onNoMoreClientFunc); + + void Register(const int32_t& pid, const std::string& name); + void Unregister(const int32_t& pid); + + private: + OnNoMoreClientFunc onNoMoreClientFunc_; + int count_; +}; + +#endif // __RING_DBUSINSTANCE_H__ diff --git a/bin/dbus/dbuspresencemanager.cpp b/bin/dbus/dbuspresencemanager.cpp new file mode 100644 index 0000000000..21a838e348 --- /dev/null +++ b/bin/dbus/dbuspresencemanager.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Philippe Proulx <philippe.proulx@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "dbuspresencemanager.h" +#include "presencemanager_interface.h" + +DBusPresenceManager::DBusPresenceManager(DBus::Connection& connection) + : DBus::ObjectAdaptor(connection, "/cx/ring/Ring/PresenceManager") +{} + +void +DBusPresenceManager::publish(const std::string& accountID, const bool& status, const std::string& note) +{ + DRing::publish(accountID, status, note); +} + +void +DBusPresenceManager::answerServerRequest(const std::string& uri, const bool& flag) +{ + DRing::answerServerRequest(uri, flag); +} + +void +DBusPresenceManager::subscribeBuddy(const std::string& accountID, const std::string& uri, const bool& flag) +{ + DRing::subscribeBuddy(accountID, uri, flag); +} + +auto +DBusPresenceManager::getSubscriptions(const std::string& accountID) -> decltype(DRing::getSubscriptions(accountID)) +{ + return DRing::getSubscriptions(accountID); +} + +void +DBusPresenceManager::setSubscriptions(const std::string& accountID, const std::vector<std::string>& uris) +{ + DRing::setSubscriptions(accountID, uris); +} diff --git a/bin/dbus/dbuspresencemanager.h b/bin/dbus/dbuspresencemanager.h new file mode 100644 index 0000000000..9f5361f040 --- /dev/null +++ b/bin/dbus/dbuspresencemanager.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2013-2015 Savoir-Faire Linux Inc. + * Author: Patrick Keroulas <patrick.keroulas@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __RING_DBUSPRESENCEMANAGER_H__ +#define __RING_DBUSPRESENCEMANAGER_H__ + +#include <vector> +#include <map> +#include <string> + +#include "dbus_cpp.h" + +#if __GNUC__ >= 4 && __GNUC_MINOR__ >= 6 +/* This warning option only exists for gcc 4.6.0 and greater. */ +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#endif + +#pragma GCC diagnostic ignored "-Wignored-qualifiers" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#include "dbuspresencemanager.adaptor.h" +#pragma GCC diagnostic warning "-Wignored-qualifiers" +#pragma GCC diagnostic warning "-Wunused-parameter" + +#if __GNUC__ >= 4 && __GNUC_MINOR__ >= 6 +/* This warning option only exists for gcc 4.6.0 and greater. */ +#pragma GCC diagnostic warning "-Wunused-but-set-variable" +#endif + +class DBusPresenceManager : + public cx::ring::Ring::PresenceManager_adaptor, + public DBus::IntrospectableAdaptor, + public DBus::ObjectAdaptor +{ + public: + DBusPresenceManager(DBus::Connection& connection); + + // Methods + void publish(const std::string& accountID, const bool& status, const std::string& note); + void answerServerRequest(const std::string& uri, const bool& flag); + void subscribeBuddy(const std::string& accountID, const std::string& uri, const bool& flag); + std::vector<std::map<std::string, std::string>> getSubscriptions(const std::string& accountID); + void setSubscriptions(const std::string& accountID, const std::vector<std::string>& uris); +}; + +#endif // __RING_DBUSPRESENCEMANAGER_H__ diff --git a/bin/dbus/dbusvideomanager.cpp b/bin/dbus/dbusvideomanager.cpp new file mode 100644 index 0000000000..a2474ea86b --- /dev/null +++ b/bin/dbus/dbusvideomanager.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Philippe Proulx <philippe.proulx@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "dbusvideomanager.h" +#include "client/videomanager.h" + +DBusVideoManager::DBusVideoManager(DBus::Connection& connection) + : DBus::ObjectAdaptor(connection, "/cx/ring/Ring/VideoManager") +{} + +auto +DBusVideoManager::getDeviceList() -> decltype(DRing::getDeviceList()) +{ + return DRing::getDeviceList(); +} + +auto +DBusVideoManager::getCapabilities(const std::string& name) -> decltype(DRing::getCapabilities(name)) +{ + return DRing::getCapabilities(name); +} + +auto +DBusVideoManager::getSettings(const std::string& name) -> decltype(DRing::getSettings(name)) +{ + return DRing::getSettings(name); +} + +void +DBusVideoManager::applySettings(const std::string& name, const std::map<std::string, std::string>& settings) +{ + DRing::applySettings(name, settings); +} + +void +DBusVideoManager::setDefaultDevice(const std::string& dev) +{ + DRing::setDefaultDevice(dev); +} + +auto +DBusVideoManager::getDefaultDevice() -> decltype(DRing::getDefaultDevice()) +{ + return DRing::getDefaultDevice(); +} + +void +DBusVideoManager::startCamera() +{ + DRing::startCamera(); +} + +void +DBusVideoManager::stopCamera() +{ + DRing::stopCamera(); +} + +auto +DBusVideoManager::switchInput(const std::string& resource) -> decltype(DRing::switchInput(resource)) +{ + return DRing::switchInput(resource); +} + +auto +DBusVideoManager::hasCameraStarted() -> decltype(DRing::hasCameraStarted()) +{ + return DRing::hasCameraStarted(); +} diff --git a/bin/dbus/dbusvideomanager.h b/bin/dbus/dbusvideomanager.h new file mode 100644 index 0000000000..ee128a488c --- /dev/null +++ b/bin/dbus/dbusvideomanager.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2012-2015 Savoir-Faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __RING_DBUSVIDEOMANAGER_H__ +#define __RING_DBUSVIDEOMANAGER_H__ + +#include <vector> +#include <map> +#include <string> + +#include "dbus_cpp.h" + +#if __GNUC__ >= 4 && __GNUC_MINOR__ >= 6 +/* This warning option only exists for gcc 4.6.0 and greater. */ +#pragma GCC diagnostic ignored "-Wunused-but-set-variable" +#endif + +#pragma GCC diagnostic ignored "-Wignored-qualifiers" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#include "dbusvideomanager.adaptor.h" +#pragma GCC diagnostic warning "-Wignored-qualifiers" +#pragma GCC diagnostic warning "-Wunused-parameter" + +#if __GNUC__ >= 4 && __GNUC_MINOR__ >= 6 +/* This warning option only exists for gcc 4.6.0 and greater. */ +#pragma GCC diagnostic warning "-Wunused-but-set-variable" +#endif + +class DBusVideoManager : + public cx::ring::Ring::VideoManager_adaptor, + public DBus::IntrospectableAdaptor, + public DBus::ObjectAdaptor +{ + public: + DBusVideoManager(DBus::Connection& connection); + + // Methods + std::vector<std::string> getDeviceList(); + std::map<std::string, std::map<std::string, std::vector<std::string>>> getCapabilities(const std::string& name); + std::map<std::string, std::string> getSettings(const std::string& name); + void applySettings(const std::string& name, const std::map<std::string, std::string>& settings); + void setDefaultDevice(const std::string& dev); + std::string getDefaultDevice(); + void startCamera(); + void stopCamera(); + bool switchInput(const std::string& resource); + bool hasCameraStarted(); +}; + +#endif // __RING_DBUSVIDEOMANAGER_H__ diff --git a/bin/dbus/instance-introspec.xml b/bin/dbus/instance-introspec.xml new file mode 100644 index 0000000000..d8a620bc21 --- /dev/null +++ b/bin/dbus/instance-introspec.xml @@ -0,0 +1,36 @@ +<?xml version="1.0" ?> +<node name="/instance-introspec" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + <interface name="cx.ring.Ring.Instance"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>Count the number of clients actually registered to the core. When initializing your client, you need to register it against the core by using this interface.</p> + </tp:docstring> + <method name="Register" tp:name-for-bindings="Register"> + <tp:docstring> + Register a new client to the core. Increments the registration count. + </tp:docstring> + <arg type="i" name="pid" direction="in"> + <tp:docstring> + The pid of the client process + </tp:docstring> + </arg> + <arg type="s" name="name" direction="in"> + <tp:docstring> + The name of the client + </tp:docstring> + </arg> + </method> + <method name="Unregister" tp:name-for-bindings="Unregister"> + <tp:docstring> + Unregister a connected client from the core. Decrements the registration count. If no more clients are connected, ie the registration count equals 0, the core properly quits. + </tp:docstring> + <arg type="i" name="pid" direction="in"> + <tp:docstring> + The pid of the client process + </tp:docstring> + </arg> + </method> + <signal name="started" tp:name-for-bindings="started"> + <tp:docstring>Notify clients that daemon has started</tp:docstring> + </signal> + </interface> +</node> diff --git a/bin/dbus/presencemanager-introspec.xml b/bin/dbus/presencemanager-introspec.xml new file mode 100644 index 0000000000..7aad39b0ee --- /dev/null +++ b/bin/dbus/presencemanager-introspec.xml @@ -0,0 +1,183 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<node name="/presencemanager-introspec" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + <interface name="cx.ring.Ring.PresenceManager"> + <!-- METHODS !--> + <method name="publish" tp:name-for-bindings="publish"> + <tp:added version="1.3.0"/> + <arg type="s" name="accountID" direction="in"> + <tp:docstring> + The account from which the presence will be emitted + </tp:docstring> + </arg> + <arg type="b" name="status" direction="in"> + <tp:docstring> + Is this account present or not + </tp:docstring> + </arg> + <arg type="s" name="note" direction="in"> + <tp:docstring> + A message transmitted by the server to other users + </tp:docstring> + </arg> + </method> + + <method name="answerServerRequest" tp:name-for-bindings="answerServerRequest"> + <tp:docstring> + Answer a presence request from the server + </tp:docstring> + <tp:added version="1.3.0"/> + <arg type="s" name="uri" direction="in"> + <tp:docstring> + </tp:docstring> + </arg> + <arg type="b" name="flag" direction="in"> + <tp:docstring> + Is the request granted or denied + </tp:docstring> + </arg> + </method> + + <method name="subscribeBuddy" tp:name-for-bindings="subscribeBuddy"> + <tp:docstring> + Ask be be notified when 'uri' presence change + </tp:docstring> + <tp:added version="1.3.0"/> + <arg type="s" name="accountID" direction="in"> + <tp:docstring> + An account from which get request presence informations + </tp:docstring> + </arg> + <arg type="s" name="uri" direction="in"> + <tp:docstring> + A SIP uri to watch + </tp:docstring> + </arg> + <arg type="b" name="flag" direction="in"> + <tp:docstring> + </tp:docstring> + </arg> + </method> + + <method name="getSubscriptions" tp:name-for-bindings="getSubscriptions"> + <tp:added version="1.3.0"/> + <tp:rationale> + New clients connecting to existing daemon need to be aware of active + subscriptions. + </tp:rationale> + <annotation name="org.qtproject.QtDBus.QtTypeName.In0" value="VectorMapStringString"> + While there is more status than "Online" or "Offline", only those + </annotation> + <arg type="s" name="accountID" direction="in"> + </arg> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="VectorMapStringString"/> + <arg type="aa{ss}" name="credentialInformation" direction="out" tp:type="String_String_Map"> + <tp:docstring> + List of hashes map with the following key-value pairs: + * Status: "Online" or "Offline" + * LineStatus: String + </tp:docstring> + </arg> + </method> + + <method name="setSubscriptions" tp:name-for-bindings="setSubscriptions"> + <tp:added version="1.3.0"/> + <tp:rationale>Calling "subscribeClient" in a loop is too slow</tp:rationale> + <arg type="s" name="accountID" direction="in"> + </arg> + <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="VectorString"/> + <arg type="as" name="uriList" direction="in"> + <tp:docstring> + A list of SIP URIs + </tp:docstring> + </arg> + <!--TODO<annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="VectorString"/> + <arg type="as" name="invalidUris" direction="out"> + <tp:docstring> + List of invalid URIs. An URI must be a valid SIP URI. Clients should purge + the list from all invalid URIs + </tp:docstring> + </arg>--> + </method> + + <!-- SIGNALS !--> + + <signal name="newBuddyNotification" tp:name-for-bindings="newBuddyNotification"> + <tp:added version="1.3.0"/> + <tp:docstring> + Notify when a registered presence uri presence informations changes + </tp:docstring> + <arg type="s" name="accountID"> + <tp:docstring> + The associated account + </tp:docstring> + </arg> + <arg type="s" name="buddyUri"> + <tp:docstring> + The registered URI + </tp:docstring> + </arg> + <arg type="b" name="status"> + <tp:docstring> + Is the URI present or not + </tp:docstring> + </arg> + <arg type="s" name="lineStatus"> + <tp:docstring> + A string containing informations from the user (human readable) + </tp:docstring> + </arg> + </signal> + + <signal name="subscriptionStateChanged" tp:name-for-bindings="subscriptionStateChanged"> + <tp:added version="1.3.0"/> + <tp:docstring> + Notify when a the server changes the state of a subscription. + </tp:docstring> + <arg type="s" name="accountID"> + <tp:docstring> + The associated account + </tp:docstring> + </arg> + <arg type="s" name="buddyUri"> + <tp:docstring> + The registered URI + </tp:docstring> + </arg> + <arg type="b" name="state"> + <tp:docstring> + True/ false when the subscription is active/termintated. + </tp:docstring> + </arg> + </signal> + + <signal name="newServerSubscriptionRequest" tp:name-for-bindings="newServerSubscriptionRequest"> + <tp:added version="1.3.0"/> + <arg type="s" name="buddyUri"> + <tp:docstring> + Notify when an other user (or the server) request your presence informations + </tp:docstring> + </arg> + </signal> + + <signal name="serverError" tp:name-for-bindings="serverError"> + <tp:added version="1.3.0"/> + <arg type="s" name="accountID"> + <tp:docstring> + Associated account + </tp:docstring> + </arg> + <arg type="s" name="error"> + <tp:docstring> + Code returned by the server + </tp:docstring> + </arg> + <arg type="s" name="msg"> + <tp:docstring> + Error explain + </tp:docstring> + </arg> + </signal> + + </interface> +</node> diff --git a/bin/dbus/videomanager-introspec.xml b/bin/dbus/videomanager-introspec.xml new file mode 100644 index 0000000000..56cea15485 --- /dev/null +++ b/bin/dbus/videomanager-introspec.xml @@ -0,0 +1,118 @@ +<?xml version="1.0" ?> +<node name="/videomanager-introspec" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + <interface name="cx.ring.Ring.VideoManager"> + <!-- Video device methods --> + + <method name="getDeviceList" tp:name-for-bindings="getDeviceList"> + <tp:docstring>Returns a list of the detected v4l2 devices</tp:docstring> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="VectorString"/> + <arg type="as" name="list" direction="out"> + </arg> + </method> + + <method name="getCapabilities" tp:name-for-bindings="getCapabilities"> + <tp:docstring>Returns a map of map of array of strings, containing the capabilities (channel, size, rate) of a device</tp:docstring> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MapStringMapStringVectorString"/> + <arg type="s" name="name" direction="in"> + </arg> + <arg type="a{sa{sas}}" name="cap" direction="out"> + </arg> + </method> + + <method name="getSettings" tp:name-for-bindings="getSettings"> + <tp:docstring>Returns a map of settings for the given device name</tp:docstring> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="MapStringString"/> + <arg type="s" name="device" direction="in"> + </arg> + <arg type="a{ss}" name="map" direction="out"> + </arg> + </method> + + <method name="applySettings" tp:name-for-bindings="applySettings"> + <tp:docstring>Set the preferred settings for a given device name</tp:docstring> + <arg type="s" name="name" direction="in"> + </arg> + <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="MapStringString"/> + <arg type="a{ss}" name="settings" direction="in"> + </arg> + </method> + + <method name="getDefaultDevice" tp:name-for-bindings="getDefaultDevice"> + <arg type="s" name="name" direction="out"> + </arg> + </method> + + <method name="setDefaultDevice" tp:name-for-bindings="setDefaultDevice"> + <arg type="s" name="name" direction="in"> + </arg> + </method> + + <method name="startCamera" tp:name-for-bindings="startCamera"> + <tp:docstring> Starts the video camera, which renders the active v4l2 device's video to shared memory. Useful for testing/debugging camera settings</tp:docstring> + </method> + + <method name="stopCamera" tp:name-for-bindings="stopCamera"> + </method> + + <method name="switchInput" tp:name-for-bindings="switchInput"> + <arg type="s" name="resource" direction="in"> + <tp:docstring> + A media resource locator (MRL). + Currently, the following are supported: + <ul> + <li>camera://DEVICE</li> + <li>display://DISPLAY_NAME[ WIDTHxHEIGHT]</li> + <li>file://IMAGE_PATH</li> + </ul> + </tp:docstring> + </arg> + <arg type="b" name="switched" direction="out"> + <tp:docstring>Returns true if the input stream was successfully changed, false otherwise</tp:docstring> + </arg> + </method> + + <method name="hasCameraStarted" tp:name-for-bindings="hasCameraStarted"> + <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="Bool"/> + <arg type="b" name="started" direction="out"> + <tp:docstring>Returns true if the camera has already started, false otherwise</tp:docstring> + </arg> + </method> + + <signal name="deviceEvent" tp:name-for-bindings="deviceEvent"> + <tp:docstring>Signal triggered by changes in the detected v4l2 devices, e.g. a camera being unplugged.</tp:docstring> + </signal> + + <signal name="startedDecoding" tp:name-for-bindings="startedDecoding"> + <tp:docstring>Signal triggered when video is available in a shared memory buffer.</tp:docstring> + <arg type="s" name="id"> + <tp:docstring>The ID of the call associated with the video, or "local" in the case of local video</tp:docstring> + </arg> + <arg type="s" name="shmPath"> + <tp:docstring>The path of the newly created shared memory</tp:docstring> + </arg> + <arg type="i" name="width"> + <tp:docstring>The width of the video in the shared memory</tp:docstring> + </arg> + <arg type="i" name="height"> + <tp:docstring>The height of the video in the shared memory</tp:docstring> + </arg> + <arg type="b" name="isMixer"> + <tp:docstring>Whether or not this texture belongs to a video mixer or is a single texture</tp:docstring> + </arg> + </signal> + + <signal name="stoppedDecoding" tp:name-for-bindings="stoppedDecoding"> + <tp:docstring>Signal triggered when video is no longer available in a shared memory buffer.</tp:docstring> + <arg type="s" name="id"> + <tp:docstring>The ID of the call associated with the video, or "local" in the case of local video</tp:docstring> + </arg> + <arg type="s" name="shmPath"> + <tp:docstring>The path of the newly created shared memory</tp:docstring> + </arg> + <arg type="b" name="isMixer"> + <tp:docstring>Whether or not this texture belongs to a video mixer or is a single texture</tp:docstring> + </arg> + </signal> + + </interface> +</node> diff --git a/bin/main.cpp b/bin/main.cpp new file mode 100644 index 0000000000..8451ad9080 --- /dev/null +++ b/bin/main.cpp @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Bourget <alexandre.bourget@savoirfairelinux.com> + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * Author: Laurielle Lea <laurielle.lea@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#include <iostream> +#include <thread> +#include <cstring> +#include <signal.h> +#include <getopt.h> + +#include "dbus/dbusclient.h" +#include "fileutils.h" + +static int ringFlags = 0; +static std::unique_ptr<DBusClient> dbusClient; + +static void +print_title() +{ + std::cout + << "Ring Daemon " << DRing::version() + << ", by Savoir-Faire Linux 2004-2015" << std::endl + << "http://www.ring.cx/" << std::endl; +} + +static void +print_usage() +{ + std::cout << std::endl << + "-c, --console \t- Log in console (instead of syslog)" << std::endl << + "-d, --debug \t- Debug mode (more verbose)" << std::endl << + "-p, --persistent \t- Stay alive after client quits" << std::endl << + "-h, --help \t- Print help" << std::endl; +} + +// Parse command line arguments, setting debug options or printing a help +// message accordingly. +// returns true if we should quit (i.e. help was printed), false otherwise +static bool +parse_args(int argc, char *argv[], bool& persistent) +{ + static const struct option long_options[] = { + /* These options set a flag. */ + {"debug", no_argument, NULL, 'd'}, + {"console", no_argument, NULL, 'c'}, + {"persistent", no_argument, NULL, 'p'}, + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'v'}, + {0, 0, 0, 0} /* Sentinel */ + }; + + int consoleFlag = false; + int debugFlag = false; + int helpFlag = false; + int versionFlag = false; + + while (true) { + /* getopt_long stores the option index here. */ + int option_index = 0; + + auto c = getopt_long(argc, argv, "dcphv", long_options, &option_index); + + // end of the options + if (c == -1) + break; + + switch (c) { + case 'd': + debugFlag = true; + break; + + case 'c': + consoleFlag = true; + break; + + case 'p': + persistent = true; + break; + + case 'h': + case '?': + helpFlag = true; + break; + + case 'v': + versionFlag = true; + break; + + default: + break; + } + } + + if (helpFlag) { + print_usage(); + return true; + } + + if (versionFlag) { + // We've always print the title/version, so we can just exit + return true; + } + + if (consoleFlag) + ringFlags |= DRing::DRING_FLAG_CONSOLE_LOG; + + if (debugFlag) + ringFlags |= DRing::DRING_FLAG_DEBUG; + + return false; +} + +static int +run() +{ + if (dbusClient) + return dbusClient->event_loop(); + return 1; +} + +static void +interrupt() +{ + if (dbusClient) + dbusClient->exit(); +} + +static void +signal_handler(int code) +{ + std::cerr << "Caught signal " << strsignal(code) + << ", terminating..." << std::endl; + + // Unset signal handlers + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + + interrupt(); +} + +int +main(int argc, char *argv []) +{ + // make a copy as we don't want to modify argv[0], copy it to a vector to + // guarantee that memory is correctly managed/exception safe + std::string programName {argv[0]}; + std::vector<char> writable(programName.size() + 1); + std::copy(programName.begin(), programName.end(), writable.begin()); + + ring::fileutils::set_program_dir(writable.data()); + +#ifdef TOP_BUILDDIR + if (!getenv("CODECS_PATH")) + setenv("CODECS_PATH", TOP_BUILDDIR "/src/media/audio/codecs", 1); +#endif + + print_title(); + + bool persistent = false; + if (parse_args(argc, argv, persistent)) + return 0; + + // initialize client/library + try { + dbusClient.reset(new DBusClient {ringFlags, persistent}); + } catch (const std::exception& ex) { + std::cerr << "One does not simply initialize the DBus client: " << ex.what() << std::endl; + return 1; + } + + // TODO: Block signals for all threads but the main thread, decide how/if we should + // handle other signals + signal(SIGINT, signal_handler); + signal(SIGHUP, signal_handler); + signal(SIGTERM, signal_handler); + +#ifdef RING_VIDEO + std::cerr << "Warning: built with video support" << std::endl; +#endif + + return run(); +} diff --git a/bin/osxmain.cpp b/bin/osxmain.cpp new file mode 100644 index 0000000000..57f8b5ee81 --- /dev/null +++ b/bin/osxmain.cpp @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Bourget <alexandre.bourget@savoirfairelinux.com> + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * Author: Laurielle Lea <laurielle.lea@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#include <iostream> +#include <thread> +#include <cstring> +#include <signal.h> +#include <getopt.h> +#include <string> + +#include "dring.h" +#include "callmanager_interface.h" +#include "configurationmanager_interface.h" +#include "presencemanager_interface.h" +#ifdef RING_VIDEO +#include "videomanager_interface.h" +#endif +#include "fileutils.h" + +static int ringFlags = 0; + +static void +print_title() +{ + std::cout << "Ring Daemon " << DRing::version() + << ", by Savoir-Faire Linux 2004-2015" << std::endl + << "http://www.ring.cx/" << std::endl; +} + +static void +print_usage() +{ + std::cout << std::endl << + "-c, --console \t- Log in console (instead of syslog)" << std::endl << + "-d, --debug \t- Debug mode (more verbose)" << std::endl << + "-p, --persistent \t- Stay alive after client quits" << std::endl << + "-h, --help \t- Print help" << std::endl; +} + +// Parse command line arguments, setting debug options or printing a help +// message accordingly. +// returns true if we should quit (i.e. help was printed), false otherwise +static bool +parse_args(int argc, char *argv[], bool& persistent) +{ + static const struct option long_options[] = { + /* These options set a flag. */ + {"debug", no_argument, NULL, 'd'}, + {"console", no_argument, NULL, 'c'}, + {"persistent", no_argument, NULL, 'p'}, + {"help", no_argument, NULL, 'h'}, + {"version", no_argument, NULL, 'v'}, + {0, 0, 0, 0} /* Sentinel */ + }; + + int consoleFlag = false; + int debugFlag = false; + int helpFlag = false; + int versionFlag = false; + + while (true) { + /* getopt_long stores the option index here. */ + int option_index = 0; + + auto c = getopt_long(argc, argv, "dcphv", long_options, &option_index); + + // end of the options + if (c == -1) + break; + + switch (c) { + case 'd': + debugFlag = true; + break; + + case 'c': + consoleFlag = true; + break; + + case 'p': + persistent = true; + break; + + case 'h': + case '?': + helpFlag = true; + break; + + case 'v': + versionFlag = true; + break; + + default: + break; + } + } + + if (helpFlag) { + print_usage(); + return true; + } + + if (versionFlag) { + // We've always print the title/version, so we can just exit + return true; + } + + if (consoleFlag) + ringFlags |= DRing::DRING_FLAG_CONSOLE_LOG; + + if (debugFlag) + ringFlags |= DRing::DRING_FLAG_DEBUG; + + return false; +} + +static int +osxTests() +{ + using SharedCallback = std::shared_ptr<DRing::CallbackWrapperBase>; + + DRing::init(static_cast<DRing::InitFlag>(ringFlags)); + + registerCallHandlers(std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>>()); + registerConfHandlers(std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>>()); + registerPresHandlers(std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>>()); +#ifdef RING_VIDEO + registerVideoHandlers(std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>>()); +#endif + if (!DRing::start()) + return -1; + + while (true) { + DRing::pollEvents(); + sleep(1); + } + + DRing::fini(); +} + +static int +run() +{ + osxTests(); + return 0; +} + +static void +interrupt() +{} + +static void +signal_handler(int code) +{ + std::cerr << "Caught signal " << strsignal(code) + << ", terminating..." << std::endl; + + // Unset signal handlers + signal(SIGHUP, SIG_DFL); + signal(SIGINT, SIG_DFL); + signal(SIGTERM, SIG_DFL); + + interrupt(); +} + +int +main(int argc, char *argv []) +{ + // make a copy as we don't want to modify argv[0], copy it to a vector to + // guarantee that memory is correctly managed/exception safe + std::string programName {argv[0]}; + std::vector<char> writable(programName.size() + 1); + std::copy(programName.begin(), programName.end(), writable.begin()); + + ring::fileutils::set_program_dir(writable.data()); + +#ifdef TOP_BUILDDIR + if (!getenv("CODECS_PATH")) + setenv("CODECS_PATH", TOP_BUILDDIR "/src/media/audio/codecs", 1); +#endif + + print_title(); + + bool persistent = false; + if (parse_args(argc, argv, persistent)) + return 0; + + // TODO: Block signals for all threads but the main thread, decide how/if we should + // handle other signals + signal(SIGINT, signal_handler); + signal(SIGHUP, signal_handler); + signal(SIGTERM, signal_handler); + +#ifdef RING_VIDEO + std::cerr << "Warning: built with video support" << std::endl; +#endif + + return run(); +} diff --git a/bin/winmain.cpp b/bin/winmain.cpp new file mode 100644 index 0000000000..6addb4b385 --- /dev/null +++ b/bin/winmain.cpp @@ -0,0 +1,10 @@ +#include <speex/speex.h> +#include <cstdio> +#include <cmath> + +int main() +{ + const SpeexMode *mode = speex_lib_get_mode(SPEEX_MODEID_NB); + printf("Hello %s %f\n", mode->modeName, M_PI); + return 0; +} diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000000..885a4e4ad1 --- /dev/null +++ b/configure.ac @@ -0,0 +1,610 @@ +dnl Ring - configure.ac for automake 1.9 and autoconf 2.59 + +dnl Process this file with autoconf to produce a configure script. +AC_PREREQ([2.65]) +AC_INIT([Ring],[2.0.1],[ring@lists.savoirfairelinux.net],[ring]) + +AC_COPYRIGHT([[Copyright (c) Savoir-Faire Linux 2004-2015]]) +AC_REVISION([$Revision$]) + +dnl Where to find configure files +AC_CONFIG_SRCDIR(src/ring_api.cpp) +AC_CONFIG_AUX_DIR([build-aux]) +AC_CONFIG_MACRO_DIR([m4]) +AC_CANONICAL_BUILD +AC_CANONICAL_HOST + +AM_INIT_AUTOMAKE([-Wno-portability]) +m4_ifdef([AM_PROG_AR], [AM_PROG_AR]) # required by automake 1.12 +AC_CONFIG_HEADERS([config.h]) + +# Silent build by default. Use make V=1 to increase verbosity +m4_ifdef([AM_SILENT_RULES],[AM_SILENT_RULES([yes])]) + +dnl Check for programs +AC_PROG_CC +AC_PROG_CXX +AC_PROG_OBJCXX +AC_PROG_CPP +AC_PROG_INSTALL +AC_PROG_LN_S +AC_PROG_MAKE_SET + +dnl +dnl Check the operating system +dnl +HAVE_WIN64="0" +HAVE_IOS="0" +HAVE_OSX="0" + +case "${host_os}" in + "") + SYS=unknown + ;; + linux*) + SYS=linux + # Necessary for linking .a to a .so + LDFLAGS+=" -Wl,-Bsymbolic" + # Needed for plugin system + LDFLAGS+=" -ldl" + ;; + darwin*) + SYS=darwin + + AC_EGREP_CPP(yes, + [#import <TargetConditionals.h> + #if TARGET_OS_IPHONE + yes + #endif], + [HAVE_IOS="1"], + [HAVE_OSX="1"]) + ;; + mingw32*) + SYS=mingw32 + AC_CHECK_TOOL(WINDRES, windres, :) + AC_CHECK_TOOL(OBJCOPY, objcopy, :) + AH_TOP([#if defined(_WIN32) && !defined(_WIN32_WINNT)]) + AH_TOP([# define _WIN32_WINNT 0x0502 /* Windows XP SP2 */]) + AH_TOP([#endif]) + AC_DEFINE([_WIN32_IE], 0x0600, [Define to '0x0600' for IE 6.0 (and shell) APIs.]) + AC_DEFINE([_UNICODE], [1], [Define to 1 for Unicode (Wide Chars) APIs.]) + AC_DEFINE([UNICODE], [1], [Define to 1 for Unicode (Wide Chars) APIs.]) + AC_DEFINE([_ISOC99_SOURCE], [1], [Extensions to ISO C89 from ISO C99.]) + AC_DEFINE([_ISOC11_SOURCE], [1], [Extensions to ISO C99 from ISO C11.]) + AC_DEFINE([_POSIX_SOURCE], [1], [IEEE Std 1003.1.]) + AC_DEFINE([_POSIX_C_SOURCE], [200809L], [IEEE Std 1003.1.]) + AC_DEFINE([_XOPEN_SOURCE], [700], [POSIX and XPG 7th edition]) + AC_DEFINE([_XOPEN_SOURCE_EXTENDED], [1], [XPG things and X/Open Unix extensions.]) + AC_DEFINE([_BSD_SOURCE], [1], [ISO C, POSIX, and 4.3BSD things.]) + AC_DEFINE([_SVID_SOURCE], [1], [ISO C, POSIX, and SVID things.]) + + LDFLAGS="${LDFLAGS} -Wl,--nxcompat -Wl,--no-seh -Wl,--dynamicbase" + ac_default_prefix="`pwd`/_win32" + DESTDIR="`pwd`/_win32/" + AC_SUBST(WINDOWS_ARCH) + AC_SUBST(PROGRAMFILES) + ;; + *) + SYS="${host_os}" + ;; +esac +AM_CONDITIONAL(HAVE_DARWIN, test "${SYS}" = "darwin") +AM_CONDITIONAL(HAVE_LINUX, test "${SYS}" = "linux") +AM_CONDITIONAL(HAVE_WIN32, test "${SYS}" = "mingw32") +AM_CONDITIONAL(HAVE_WIN64, test "${HAVE_WIN64}" = "1") +AM_CONDITIONAL(HAVE_OSX, test "${HAVE_OSX}" = "1") + +dnl FIXME this should be deduced automatically +AC_DEFINE_UNQUOTED([HAVE_COREAUDIO], + `if test "${HAVE_OSX}" = "1"; then echo 1; else echo 0; fi`, + [Define if you have CoreAudio]) + +dnl Android is linux, but a bit different +AS_IF([test "$SYS" = linux],[ + AC_MSG_CHECKING([for an Android system]) + AC_PREPROC_IFELSE([AC_LANG_PROGRAM( + [[#ifndef __ANDROID__ + # error Not Android + #endif + ]],[[;]]) + ],[ + HAVE_ANDROID="1" + AC_MSG_RESULT([yes]) + ],[ + AC_MSG_RESULT([no]) + ]) +]) +AM_CONDITIONAL(HAVE_ANDROID, test "${HAVE_ANDROID}" = "1") + +dnl override platform specific check for dependent libraries +dnl otherwise libtool linking of shared libraries will +dnl fail on anything other than pass_all. +AC_CACHE_VAL(lt_cv_deplibs_check_method, + [lt_cv_deplibs_check_method=pass_all]) + +LT_INIT([dlopen win32-dll shared disable-static]) + +dnl Define C++ as default language +LT_LANG(C++) + +DOLT + +lt_cv_deplibs_check_method=pass_all + +dnl Check for C++11 support +AX_CXX_COMPILE_STDCXX_11([ext],[mandatory]) + +dnl Check for header files +AC_FUNC_ALLOCA +AC_HEADER_STDC +AC_CHECK_HEADERS([arpa/inet.h fcntl.h libintl.h limits.h \ + netdb.h netinet/in.h stdlib.h string.h \ + sys/ioctl.h sys/socket.h sys/time.h unistd.h]) + +dnl Check for typedefs, structures, and compiler characteristics +AC_HEADER_STAT +AC_HEADER_STDBOOL +AC_C_CONST +AC_C_INLINE +AC_TYPE_PID_T +AC_TYPE_SIZE_T +AC_HEADER_TIME +AC_C_VOLATILE +AC_CHECK_TYPES([ptrdiff_t]) + +PKG_PROG_PKG_CONFIG() + +dnl On some OS we need static linking +AS_IF([test -n "${PKG_CONFIG}" ],[ + AS_IF([test "${SYS}" = "mingw32" -o "${SYS}" = "darwin"],[ + PKG_CONFIG="${PKG_CONFIG} --static" + ]) +]) + +dnl Check if we are using clang +AC_MSG_CHECKING([if compiling with clang]) +AC_COMPILE_IFELSE( +[AC_LANG_PROGRAM([], [[ +#ifndef __clang__ + not clang +#endif +]])], +[CLANG=yes], [CLANG=no]) + +AC_MSG_RESULT([$CLANG]) + +dnl +dnl Check for the contrib directory +dnl +AC_ARG_WITH(contrib, + [AS_HELP_STRING([--with-contrib[=DIR]], + [search for 3rd party libraries in DIR/include and DIR/lib]) +]) +AC_MSG_CHECKING([for 3rd party libraries path]) +AS_IF([test -z "${with_contrib}" || test "${with_contrib}" = "yes"], [ + CONTRIB_DIR="${srcdir}/contrib/${host}" + AS_IF([test ! -d "${CONTRIB_DIR}"], [ + echo "${CONTRIB_DIR} not found" >&AS_MESSAGE_LOG_FD + CONTRIB_DIR="${srcdir}/contrib/`$CC -dumpmachine`" + AS_IF([test ! -d "${CONTRIB_DIR}"], [ + echo "${CONTRIB_DIR} not found" >&AS_MESSAGE_LOG_FD + CONTRIB_DIR="" + AC_MSG_RESULT([not found]) + ]) + ]) +], [ + AS_IF([test "${with_contrib}" != "no"], [ + CONTRIB_DIR="${with_contrib}" + ], [ + CONTRIB_DIR="" + AC_MSG_RESULT([disabled]) + ]) +]) + +AS_IF([test -n "${CONTRIB_DIR}"], [ + AS_IF([test -d "${CONTRIB_DIR}/lib"],[ + CONTRIB_DIR=`cd "${CONTRIB_DIR}" && pwd` + ], [ + echo "${CONTRIB_DIR}/lib not found" >&AS_MESSAGE_LOG_FD + CONTRIB_DIR="" + AC_MSG_RESULT([not usable]) + ]) +]) + +AS_IF([test -n "${CONTRIB_DIR}"], [ + AC_MSG_RESULT([${CONTRIB_DIR}]) + export PATH=${CONTRIB_DIR}/bin:$PATH + CPPFLAGS="${CPPFLAGS} -I${CONTRIB_DIR}/include" + CFLAGS="${CFLAGS} -I${CONTRIB_DIR}/include" + CXXFLAGS="${CXXFLAGS} -I${CONTRIB_DIR}/include" + OBJCFLAGS="${OBJCFLAGS} -I${CONTRIB_DIR}/include" + export PKG_CONFIG_PATH="${CONTRIB_DIR}/lib/pkgconfig:${CONTRIB_DIR}/lib64/pkgconfig:$PKG_CONFIG_PATH" + LDFLAGS="${LDFLAGS} -L${CONTRIB_DIR}/lib" + + AS_IF([test "${SYS}" = "darwin"], [ + export LD_LIBRARY_PATH="${CONTRIB_DIR}/lib:$LD_LIBRARY_PATH" + export DYLD_LIBRARY_PATH="${CONTRIB_DIR}/lib:$DYLD_LIBRARY_PATH" + ]) +], [ + AS_IF([test -n "${with_contrib}" && test "${with_contrib}" != "no"], [ + AC_MSG_ERROR([Third party libraries not found!]) + ]) +]) +AC_SUBST(CONTRIB_DIR) + +dnl Add extras/tools to the PATH +TOOLS_DIR="${srcdir}/extras/tools/build/bin" +AS_IF([test -d "${TOOLS_DIR}"], [ + TOOLS_DIR=`cd "${TOOLS_DIR}" && pwd` + export PATH="${TOOLS_DIR}":$PATH +]) + +dnl Check for pjproject +PKG_CHECK_MODULES(PJPROJECT, libpjproject,, AC_MSG_ERROR([Missing pjproject files])) + +PKG_CHECK_MODULES([YAMLCPP], [yaml-cpp >= 0.5.1],, AC_MSG_ERROR([yaml-cpp not found])) + +if test "${HAVE_ANDROID}" = "1"; then + dnl Check for OpenSL + AC_ARG_WITH([opensl], + AS_HELP_STRING([--without-opensl], + [Ignore presence of opensl and disable it])) + + AS_IF([test "x$with_opensl" != "xno"], + [AC_CHECK_HEADER(["SLES/OpenSLES.h"], + [have_opensl=yes], [have_opensl=no])], + [have_opensl=no]) + AC_DEFINE_UNQUOTED([HAVE_OPENSL], + `if test "x$have_opensl" = "xyes"; then echo 1; else echo 0; fi`, + [Define if you have OpenSL]) +fi +AM_CONDITIONAL([BUILD_OPENSL], test "x$have_opensl" = "xyes") + +dnl Check for alsa development package - name: libasound2-dev +ALSA_MIN_VERSION=1.0 +AC_ARG_WITH([alsa], + [AS_HELP_STRING([--without-alsa], [disable support for alsa])], + [], + [with_alsa=yes]) +AS_IF([test "x$with_alsa" = "xyes"], [ + PKG_CHECK_MODULES(ALSA, alsa >= ${ALSA_MIN_VERSION},, AC_MSG_ERROR([Missing alsa development files])) + ]); + +AC_DEFINE_UNQUOTED([HAVE_ALSA], `if test "x$with_alsa" = "xyes"; then echo 1; else echo 0; fi`, [Define if you have alsa]) +AM_CONDITIONAL(BUILD_ALSA, test "x$with_alsa" = "xyes") + + +dnl Check for pulseaudio development package - name: libpulse-dev +LIBPULSE_MIN_VERSION=0.9.15 +AC_ARG_WITH([pulse], + [AS_HELP_STRING([--without-pulse], [disable support for pulseaudio])], + [], + [with_pulse=yes]) + +AS_IF([test "x$with_pulse" = "xyes"], [ + PKG_CHECK_MODULES(PULSEAUDIO, libpulse >= ${LIBPULSE_MIN_VERSION},, AC_MSG_ERROR([Missing pulseaudio development files])) + ]); + +AC_DEFINE_UNQUOTED([HAVE_PULSE], `if test "x$with_pulse" = "xyes"; then echo 1; else echo 0; fi`, [Define if you have pulseaudio]) +AM_CONDITIONAL(BUILD_PULSE, test "x$with_pulse" = "xyes") + +AC_ARG_WITH([jack], + AS_HELP_STRING([--without-jack], [Ignore presence of jack and disable it])) + +AS_IF([test "x$with_jack" != "xno"], + [PKG_CHECK_MODULES(JACK, jack, [have_jack=yes], [have_jack=no])], + [have_jack=no]) + +AS_IF([test "x$have_jack" = "xyes"],, + [AS_IF([test "x$with_jack" = "xyes"], + [AC_MSG_ERROR([jack requested but not found]) + ]) +]) + +AC_DEFINE_UNQUOTED([HAVE_JACK], `if test "x$have_jack" = "xyes"; then echo 1; else echo 0; fi`, [Define if you have jack]) +AM_CONDITIONAL(BUILD_JACK, test "x$have_jack" = "xyes") + +dnl Check for the samplerate development package - name: libsamplerate0-dev +LIBSAMPLERATE_MIN_VERSION=0.1.2 +PKG_CHECK_MODULES(SAMPLERATE, samplerate >= ${LIBSAMPLERATE_MIN_VERSION},, AC_MSG_ERROR([Missing libsamplerate development files])) + +dnl Check for the sndfile development package - name: libsndfile-dev +PKG_CHECK_MODULES(SNDFILE, sndfile,, AC_MSG_ERROR([Missing sndfile development files])) + +dnl Coverage is default-disabled +AC_ARG_ENABLE([coverage], AS_HELP_STRING([--enable-coverage], [Enable coverage])) + +AS_IF([test "x$enable_coverage" = "xyes"], [ + CXXFLAGS="${CXXFLAGS} --coverage" + LDFLAGS="${LDFLAGS} --coverage"]) + +# DBUSCPP +dnl Check for dbuscpp, the C++ bindings for D-Bus +AC_ARG_WITH([dbus], + [AS_HELP_STRING([--without-dbus], [disable support for dbus])], + [], + [with_dbus=yes]) +AS_IF([test "x$with_dbus" = "xyes"], [ + PKG_CHECK_MODULES(DBUSCPP, dbus-c++-1,, AC_MSG_WARN([Missing dbus development files])) + + AS_AC_EXPAND(LIBEXECDIR, $libexecdir) + AC_SUBST(LIBEXECDIR) + + AC_CONFIG_FILES([bin/dbus/Makefile + bin/dbus/cx.ring.Ring.service]) + + AC_CHECK_PROG(HAVE_DBUSXML2CPP, dbusxx-xml2cpp, true, false) + if test "x$HAVE_DBUSXML2CPP" = "xfalse"; then + AC_MSG_ERROR([dbusxx-xml2cpp not found]) + fi + + AS_IF([test "x$CLANG" = "xyes"], [ + AC_MSG_ERROR([dbus does not compile when clang is used, rerun with --without-dbus]) + ]); + + AM_CONDITIONAL(RING_DBUS, true)], + AM_CONDITIONAL(RING_DBUS, false)); + + +dnl Check for libav +PKG_CHECK_MODULES(LIBAVCODEC, libavcodec >= 53.5.0,, AC_MSG_ERROR([Missing libavcodec development files])) +LIBAVCODEC_CFLAGS="${LIBAVCODEC_CFLAGS} -D__STDC_CONSTANT_MACROS" + +PKG_CHECK_MODULES(LIBAVFORMAT, libavformat >= 54.20.3,, AC_MSG_ERROR([Missing libavformat development files])) + +PKG_CHECK_MODULES(LIBSWSCALE, libswscale >= 1.1.0,, AC_MSG_ERROR([Missing libswscale development files])) + +PKG_CHECK_MODULES(LIBAVDEVICE, libavdevice >= 53.0.0,, AC_MSG_ERROR([Missing libavdevice development files])) + +PKG_CHECK_MODULES(LIBAVUTIL, libavutil >= 52.5.0,, AC_MSG_ERROR([Missing libavutil development files])) + +dnl Video is default-enabled +AC_ARG_ENABLE([video], AS_HELP_STRING([--disable-video], [Disable video])) + +AS_IF([test "x$enable_video" != "xno"], + [ + AC_DEFINE(RING_VIDEO, [], [Video support enabled]) + AM_CONDITIONAL(RING_VIDEO, true) + AS_IF([test "$SYS" = linux],[ + PKG_CHECK_MODULES(UDEV, libudev,, AC_MSG_ERROR([Missing libudev development files])) + ],[ + ]) + ], + [ + AM_CONDITIONAL(RING_VIDEO, false) + ]); + +dnl TLS support is enabled if it's installed and up to date +AC_ARG_WITH([tls], + [AS_HELP_STRING([--with-tls], + [support tls @<:@default=check@:>@])], + [], + [with_tls=check]) +AS_CASE(["$with_tls"], + [yes], [PKG_CHECK_MODULES([GNUTLS], [gnutls >= 3.1], [HAVE_GNUTLS=1])], + [no], [HAVE_GNUTLS=0], + [PKG_CHECK_MODULES([GNUTLS], [gnutls >= 3.1], [HAVE_GNUTLS=1], [HAVE_GNUTLS=0])]) + +AC_DEFINE_UNQUOTED([HAVE_TLS], `if test $HAVE_GNUTLS -eq 1; then echo 1; else echo 0; fi`, [Define if you have tls support]) +AM_CONDITIONAL(BUILD_TLS, test "$HAVE_GNUTLS" -eq 1) + +# Instant Messaging +# required dependency(ies): libxpat +AC_ARG_WITH([instant_messaging], + [AS_HELP_STRING([--without-instant_messaging], [disable support for instant-messaging])], + [], + [with_instant_messaging=yes]) + AS_IF([test "x$with_instant_messaging" = "xyes"], [ + PKG_CHECK_MODULES([EXPAT], expat >= 2.0.0, [with_instant_messaging=yes], + dnl Fallback to older version + [with_instant_messaging=no]) + ]); + +AC_DEFINE_UNQUOTED([HAVE_INSTANT_MESSAGING], `if test "x$with_instant_messaging" = "xyes"; then echo 1; else echo 0; fi`, [Define if you have instant messaging support]) +AM_CONDITIONAL(BUILD_INSTANT_MESSAGING, test "x$with_instant_messaging" = "xyes" ) + + +# PTHREAD +# required dependency(ies): libxpat +AX_PTHREAD + + +# SDES Key Exchange +# required dependency(ies): libpcre +AC_ARG_WITH([sdes], + [AS_HELP_STRING([--without-sdes], [disable support for sdes key exchange])], + [], + [with_sdes=yes]) +AS_IF([test "x$with_sdes" = "xyes"], [ + PKG_CHECK_MODULES(PCRE, [libpcre],,AC_MSG_ERROR([libpcre not found]))]); + +AC_DEFINE_UNQUOTED([HAVE_SDES], `if test "x$with_sdes" = "xyes"; then echo 1; else echo 0; fi`, [Define if you have sdes support]) +AM_CONDITIONAL(BUILD_SDES, test "x$with_sdes" = "xyes" ) + + +dnl Check for libcppunit-dev +CPPUNIT_MIN_VERSION=1.12 +PKG_CHECK_MODULES(CPPUNIT, cppunit >= ${CPPUNIT_MIN_VERSION}, AM_CONDITIONAL(BUILD_TEST, test 1 = 1 ), AM_CONDITIONAL(BUILD_TEST, test 0 = 1 )) + + +# GSM CODEC +# required dependency(ies): libgsm +dnl check for libgsm1 (doesn't use pkg-config) +dnl Check for libgsm +AC_ARG_WITH([gsm], [AS_HELP_STRING([--without-gsm], + [disable support for gsm codec])], [], [with_gsm=yes]) + +LIBGSM= +AS_IF([test "x$with_gsm" != xno], + [AC_CHECK_HEADER([gsm/gsm.h], , AC_MSG_FAILURE([Unable to find the libgsm1 headers (you may need to install the dev package). You may use --without-gsm to compile without gsm codec support.]))] + [AC_CHECK_LIB([gsm], [gsm_decode], [], [ + AC_MSG_FAILURE([libgsm link test failed. You may use --without-gsm to compile without gsm codec support.]) + ]) + ]) + +AC_DEFINE_UNQUOTED([HAVE_GSM], `if test "x$with_gsm" = "xyes"; then echo 1; else echo 0; fi`, [Define if you have libgsm]) +AM_CONDITIONAL(BUILD_GSM, test "x$with_gsm" = "xyes" ) + + +# SPEEX CODEC +# required dependency(ies): libspeex +dnl Check for libspeex +AC_ARG_WITH([speex], + [AS_HELP_STRING([--without-speex], [disable support for speex codec])], + [], + [with_speex=yes]) + +AS_IF([test "x$with_speex" != xno], [PKG_CHECK_MODULES([SPEEX], [speex])]) + +AC_DEFINE_UNQUOTED([HAVE_SPEEX], `if test "x$with_speex" = "xyes"; then echo 1; else echo 0; fi`, [Define if you have libspeex]) +AM_CONDITIONAL(BUILD_SPEEX, test "x$with_speex" = "xyes" ) + + +# SPEEX DSP +# required dependency(ies): libspeexdsp +dnl check in case the libspeexdsp is not installed +AC_ARG_WITH([speexdsp], + [AS_HELP_STRING([--without-speexdsp], + [disable support for speexdp Noise Suppression and Automatic Gain Control])], + [], + [with_speexdsp=yes]) + +AS_IF([test "x$with_speexdsp" != xno], [PKG_CHECK_MODULES([SPEEXDSP], [speexdsp])]) + +AC_DEFINE_UNQUOTED([HAVE_SPEEXDSP], `if test "x$with_speexdsp" = "xyes"; then echo 1; else echo 0; fi`, [Define if you have libspeexdsp]) +AM_CONDITIONAL(BUILD_SPEEXDSP, test "x$with_speexdsp" = "xyes" ) + +dnl iLBC is enabled if it's installed +AC_ARG_WITH([libilbc], + [AS_HELP_STRING([--with-libilbc], + [support ilbc audio @<:@default=check@:>@])], + [], + [with_libilbc=check]) +AS_CASE(["$with_libilbc"], + [yes], [PKG_CHECK_MODULES([libilbc], [libilbc], [HAVE_LIBILBC=1])], + [no], [HAVE_LIBILBC=0], + [PKG_CHECK_MODULES([libilbc], [libilbc], [HAVE_LIBILBC=1], [HAVE_LIBILBC=0])]) +AM_CONDITIONAL([BUILD_ILBC], [test "$HAVE_LIBILBC" -eq 1]) + +dnl opus is enabled if it's installed +AC_ARG_WITH([opus], + [AS_HELP_STRING([--with-opus], [support opus audio @<:@default=check@:>@])], [], [with_opus=check]) +AS_CASE(["$with_opus"], + [yes], [PKG_CHECK_MODULES([opus], [opus], [HAVE_OPUS=1])], + [no], [HAVE_OPUS=0], + [PKG_CHECK_MODULES([opus], [opus], [HAVE_OPUS=1], [HAVE_OPUS=0])]) +AM_CONDITIONAL([BUILD_OPUS], [test "$HAVE_OPUS" -eq 1]) + +AC_ARG_WITH([iax], + AS_HELP_STRING([--without-iax], [Ignore presence of iax and disable it])) + +AS_IF([test "x$with_iax" != "xno"], + [AC_CHECK_HEADER("iax/iax-client.h", [have_iax=yes], [have_iax=no])], + [have_iax=no]) + +# only fail if IAX was explicitly requested but not found +AS_IF([test "x$have_iax" = "xyes"], + [AC_DEFINE([HAVE_IAX], 1, [Define if you have libiax]) + AM_CONDITIONAL(USE_IAX, `true`)], + [AS_IF([test "x$with_iax" = "xyes"], + [AC_MSG_ERROR([iax requested but not found])]) + AC_DEFINE([HAVE_IAX], 0, [Define if you have libiax]) + AM_CONDITIONAL(USE_IAX, `false`)]) + +# dht is default-enabled, but requires gnutls +AC_ARG_ENABLE([dht], + AS_HELP_STRING([--disable-dht], [disable support for dht])) + +AS_IF([test "x$enable_dht" != "xno" -a "$HAVE_GNUTLS" -eq 1], + [AC_DEFINE([HAVE_DHT], 1, [Define to enable dht]) + PKG_CHECK_MODULES([OPENDHT], opendht, + AC_DEFINE([HAVE_DHT], 1, [Define to enable dht]) + AM_CONDITIONAL(USE_DHT, true), + AC_DEFINE([HAVE_DHT], 0, [Define to enable dht]) + AM_CONDITIONAL(USE_DHT, false) + AC_MSG_WARN([Missing OpenDHT]))], + [AC_DEFINE([HAVE_DHT], 0, [Define to enable dht]) + AM_CONDITIONAL(USE_DHT, false)]) + +dnl IPv6 mode is default-disabled +AC_ARG_ENABLE([ipv6], AS_HELP_STRING([--enable-ipv6], [Enable IPv6 support])) + +AC_DEFINE_UNQUOTED([HAVE_IPV6], `if test "x$enable_ipv6" = "xyes"; then echo 1; else echo 0; fi`, [Define if you have IPv6]) +AM_CONDITIONAL(BUILD_IPV6, test "x$enable_ipv6" = "xyes" ) + +# LIBUPNP +dnl check for libupnp +AC_ARG_WITH([upnp], [AS_HELP_STRING([--without-upnp], + [disable support for upnp])], [], [with_upnp=yes]) + +AS_IF([test "x$with_upnp" = "xyes"], + [PKG_CHECK_MODULES(LIBUPNP, [libupnp], + [AC_DEFINE([HAVE_LIBUPNP], 1, [Define if you have libupnp])], + [AC_MSG_WARN([Missing libupnp development files]) + AC_DEFINE([HAVE_LIBUPNP], 0, [Define if you have libupnp])]) + ]) + +AC_DEFINE_UNQUOTED([HAVE_SHM], `if test -z "${HAVE_LINUX_TRUE}"; then echo 1; else echo 0; fi`, + [Define if you have shared memory support]) + +# DOXYGEN +# required dependency(ies): doxygen +# check for doxygen, mostly stolen from http://log4cpp.sourceforge.net/ +# ---------------------------------------------------------------------------- +AC_DEFUN([BB_ENABLE_DOXYGEN], + [ + AC_ARG_ENABLE(doxygen, [ --enable-doxygen enable documentation generation with doxygen (disabled by default)]) + AC_ARG_ENABLE(dot, [ --enable-dot use 'dot' to generate graphs in doxygen (auto)]) + if test "x$enable_doxygen" = xyes; then + AC_PATH_PROG(DOXYGEN, doxygen, , $PATH) + test x$DOXYGEN = x && AC_MSG_ERROR([could not find doxygen]) + AC_PATH_PROG(DOT, dot, , $PATH) + test x$DOT = x -a "x$enable_dot" = xyes && AC_MSG_ERROR([could not find dot]) + fi + AM_CONDITIONAL(ENABLE_DOXYGEN, test "x$DOXYGEN" != "x") +]) + +# Actually perform the doxygen check +BB_ENABLE_DOXYGEN + + +dnl debug mode is default-disabled +AC_ARG_ENABLE([debug], AS_HELP_STRING([--enable-debug], [Build in debug mode, adds stricter warnings, disables optimization])) + +AS_IF([test "x$enable_debug" = "xyes"], + [CXXFLAGS="${CXXFLAGS} -g -Wall -Wextra -Wnon-virtual-dtor -O0"]) + +dnl What Makefiles to generate + +#TODO: split this list across where the relevant check is performed +AC_CONFIG_FILES([Makefile \ + bin/Makefile \ + src/Makefile \ + src/sip/Makefile \ + src/im/Makefile \ + src/iax/Makefile \ + src/ringdht/Makefile \ + src/media/Makefile \ + src/media/audio/Makefile \ + src/media/audio/pulseaudio/Makefile \ + src/media/audio/alsa/Makefile \ + src/media/audio/opensl/Makefile \ + src/media/audio/jack/Makefile \ + src/media/audio/coreaudio/Makefile \ + src/media/audio/sound/Makefile \ + src/config/Makefile \ + src/client/Makefile \ + src/hooks/Makefile \ + src/media/video/Makefile \ + src/media/video/v4l2/Makefile \ + src/media/video/osxvideo/Makefile \ + src/media/video/test/Makefile \ + src/upnp/Makefile \ + test/Makefile \ + ringtones/Makefile \ + man/Makefile \ + doc/Makefile \ + doc/doxygen/Makefile]) + +# Go! +AC_OUTPUT diff --git a/contrib/.gitignore b/contrib/.gitignore new file mode 100644 index 0000000000..6ee52f7fd6 --- /dev/null +++ b/contrib/.gitignore @@ -0,0 +1,3 @@ +# Ignore contrib-generated files and directories +native +x86_64-* diff --git a/contrib/bootstrap b/contrib/bootstrap new file mode 100755 index 0000000000..00d1a3e977 --- /dev/null +++ b/contrib/bootstrap @@ -0,0 +1,268 @@ +#! /bin/sh +# Copyright (C) 2003-2011 the VideoLAN team +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + +# +# Command line handling +# +usage() +{ + echo "Usage: $0 [--build=BUILD] [--host=HOST] [--prefix=PREFIX]" + echo " --build=BUILD configure for building on BUILD" + echo " --host=HOST cross-compile to build to run on HOST" + echo " --prefix=PREFIX install files in PREFIX" + echo " --disable-FOO configure to not build package FOO" + echo " --enable-FOO configure to build package FOO" +} + +BUILD= +HOST= +PREFIX= +PKGS_ENABLE= +PKGS_DISABLE= + +if test ! -f "../../contrib/src/main.mak" +then + echo "$0 must be run from a subdirectory" + exit 1 +fi + +while test -n "$1" +do + case "$1" in + --build=*) + BUILD="${1#--build=}" + ;; + --help|-h) + usage + exit 0 + ;; + --host=*) + HOST="${1#--host=}" + ;; + --prefix=*) + PREFIX="${1#--prefix=}" + ;; + --disable-*) + PKGS_DISABLE="${PKGS_DISABLE} ${1#--disable-}" + ;; + --enable-*) + PKGS_ENABLE="${PKGS_ENABLE} ${1#--enable-}" + ;; + *) + echo "Unrecognized options $1" + usage + exit 1 + ;; + esac + shift +done + +if test -z "$BUILD" +then + echo -n "Guessing build system... " + BUILD="`${CC:-cc} -dumpmachine`" + if test -z "$BUILD"; then + echo "FAIL!" + exit 1 + fi + echo "$BUILD" +fi + +if test -z "$HOST" +then + echo -n "Guessing host system... " + HOST="$BUILD" + echo "$HOST" +fi + +if test "$PREFIX" +then + # strip trailing slash + PREFIX="${PREFIX%/}" +fi + +# +# Prepare files +# +echo "Creating configuration file... config.mak" +exec 3>config.mak || exit $? +cat >&3 << EOF +# This file was automatically generated. +# Any change will be overwritten if ../bootstrap is run again. +BUILD := $BUILD +HOST := $HOST +PKGS_DISABLE := $PKGS_DISABLE +PKGS_ENABLE := $PKGS_ENABLE +EOF + +add_make() +{ + while test -n "$1" + do + echo "$1" >&3 + shift + done +} + +add_make_enabled() +{ + while test -n "$1" + do + add_make "$1 := 1" + shift + done +} + +check_ios_sdk() +{ + if test -z "$SDKROOT" + then + SDKROOT=`xcode-select -print-path`/Platforms/iPhone${PLATFORM}.platform/Developer/SDKs/iPhone${PLATFORM}${SDK_VERSION}.sdk + echo "SDKROOT not specified, assuming $SDKROOT" + else + SDKROOT="$SDKROOT" + fi + + if [ ! -d "${SDKROOT}" ] + then + echo "*** ${SDKROOT} does not exist, please install required SDK, or set SDKROOT manually. ***" + exit 1 + fi + add_make "IOS_SDK=${SDKROOT}" +} + +check_macosx_sdk() +{ + if [ -z "${OSX_VERSION}" ] + then + OSX_VERSION=`xcrun --show-sdk-version` + echo "OSX_VERSION not specified, assuming $OSX_VERSION" + fi + if test -z "$SDKROOT" + then + SDKROOT=`xcode-select -print-path`/Platforms/MacOSX.platform/Developer/SDKs/MacOSX$OSX_VERSION.sdk + echo "SDKROOT not specified, assuming $SDKROOT" + fi + + if [ ! -d "${SDKROOT}" ] + then + SDKROOT_NOT_FOUND=`xcode-select -print-path`/Platforms/MacOSX.platform/Developer/SDKs/MacOSX$OSX_VERSION.sdk + SDKROOT=`xcode-select -print-path`/SDKs/MacOSX$OSX_VERSION.sdk + echo "SDKROOT not found at $SDKROOT_NOT_FOUND, trying $SDKROOT" + fi + if [ ! -d "${SDKROOT}" ] + then + SDKROOT_NOT_FOUND="$SDKROOT" + SDKROOT=`xcrun --show-sdk-path` + echo "SDKROOT not found at $SDKROOT_NOT_FOUND, trying $SDKROOT" + fi + + if [ ! -d "${SDKROOT}" ] + then + echo "*** ${SDKROOT} does not exist, please install required SDK, or set SDKROOT manually. ***" + exit 1 + fi + + add_make "MACOSX_SDK=${SDKROOT}" + add_make "OSX_VERSION ?= ${OSX_VERSION}" +} + +check_android_sdk() +{ + [ -z "${ANDROID_NDK}" ] && echo "You must set ANDROID_NDK environment variable" && exit 1 + add_make "ANDROID_NDK := ${ANDROID_NDK}" + [ -z "${ANDROID_ABI}" ] && echo "You must set ANDROID_ABI environment variable" && exit 1 + add_make "ANDROID_ABI := ${ANDROID_ABI}" + [ -z "${ANDROID_API}" ] && echo "You should set ANDROID_API environment variable (using default android-9)" && ANDROID_API := android-9 + add_make "ANDROID_API := ${ANDROID_API}" + [ ${ANDROID_ABI} = "armeabi-v7a" ] && add_make_enabled "HAVE_NEON" + [ ${ANDROID_ABI} = "armeabi-v7a" ] && add_make_enabled "HAVE_ARMV7A" + [ ${ANDROID_ABI} = "armeabi" -a -z "${NO_ARMV6}" ] && add_make_enabled "HAVE_ARMV6" +} + +test -z "$PREFIX" || add_make "PREFIX := $PREFIX" + +# +# Checks +# +OS="${HOST#*-}" # strip architecture +case "${OS}" in + apple-darwin*) + if test -z "$BUILDFORIOS" + then + check_macosx_sdk + add_make_enabled "HAVE_MACOSX" "HAVE_DARWIN_OS" "HAVE_BSD" + else + check_ios_sdk + add_make_enabled "HAVE_IOS" "HAVE_DARWIN_OS" "HAVE_BSD" "HAVE_NEON" "HAVE_ARMV7A" + fi + ;; + *bsd*) + add_make_enabled "HAVE_BSD" + ;; + *android*) + check_android_sdk + add_make_enabled "HAVE_LINUX" "HAVE_ANDROID" + case "${HOST}" in + *arm*) + add_make "PLATFORM_SHORT_ARCH := arm" + ;; + *i686*) + add_make "PLATFORM_SHORT_ARCH := x86" + ;; + *mipsel*) + add_make "PLATFORM_SHORT_ARCH := mips" + ;; + esac + ;; + *linux*) + add_make_enabled "HAVE_LINUX" + ;; + *wince*) + add_make_enabled "HAVE_WINCE" + ;; + *mingw*) + add_make_enabled "HAVE_WIN32" + ;; + *solaris*) + add_make_enabled "HAVE_SOLARIS" + ;; +esac + +# +# Results output +# +test -e Makefile && unlink Makefile +ln -sf ../../contrib/src/main.mak Makefile || exit $? +cat << EOF +Bootstrap completed. + +Run "make" to start compilation. + +Other targets: + * make install same as "make" + * make prebuilt fetch and install prebuilt binaries + * make list list packages + * make fetch fetch required source tarballs + * make fetch-all fetch all source tarballs + * make distclean clean everything and undo bootstrap + * make mostlyclean clean everything except source tarballs + * make clean clean everything + * make package prepare prebuilt packages +EOF + +mkdir -p ../../contrib/tarballs || exit $? diff --git a/contrib/src/README b/contrib/src/README new file mode 100644 index 0000000000..581a392195 --- /dev/null +++ b/contrib/src/README @@ -0,0 +1,125 @@ +Writing rules +============== + +At the bare minimum, a package in contrib must provide two Makefile +targets in src/foo/rules.mak: + - .foo to build and install the package, and + - .sum-foo to fetch or create a source tarball and verify it, +where foo the package name. + + +Tarball +-------- + +.sum-foo typically depends on a separate target that fetches the source +code. In that case, .sum-foo needs only verify that the tarball +is correct, e.g.: + + + $(TARBALLS)/libfoo-$(FOO_VERSION).tar.bz2: + $(call download,$(FOO_URL)) + + # This will use the default rule: check SHA-512 + .sum-foo: libfoo-$(FOO_VERSION).tar.bz2 + +NOTE: contrary to the previous VLC contribs, this system always uses +a source tarball, even if the source code is downloaded from a VCS. +This serves two purposes: + - offline builds (or behind a firewall), + - source code requirements compliance. + + +Compilation +------------ + +Similarly, .foo typically depends on the source code directory. In this +case, care must be taken that the directory name only exists if the +source code is fully ready. Otherwise Makefile dependencies will break +(this is not an issue for files, only directories). + + libfoo: libfoo-$(FOO_VERSION).tar.bz2 .sum-foo + $(UNPACK) # to libfoo-$(FOO_VERSION) + ### apply patches here ### + # last command: make the target directory + $(MOVE) + + .foo: libfoo + cd $< && $(HOSTVARS) ./configure $(HOSTCONF) + cd $< && $(MAKE) install + touch $@ + +Conditional builds +------------------- + +As far as possible, build rules should determine automatically whether +a package is useful (for VLC media player) or not. Useful packages +should be listed in the PKGS special variable. See some examples: + + # FFmpeg is always useful + PKGS += ffmpeg + + # DirectX headers are useful only on Windows + ifdef HAVE_WIN32 + PKGS += directx + endif + + # x264 is only useful when stream output is enabled + ifdef BUILD_ENCODERS + PKGS += x264 + endif + +If a package is a dependency of another package, but it is not a +direct dependency of VLC, then it should NOT be added to PKGS. The +build system will automatically build it via dependencies (see below). + +Some packages may be provided by the target system. This is especially +common when building natively on Linux or BSD. When this situation is +detected, the package name should be added to the PKGS_FOUND special +variable. The build system will then skip building this package: + + # Asks pkg-config if foo version 1.2.3 or later is present: + ifeq ($(call need_pkg,'foo >= 1.2.3'),) + PKGS_FOUND += foo + endif + +Note: The need_pkg function always return 1 during cross-compilation. +This is a known bug. + + +Dependencies +------------- + +If package bar depends on package foo, the special DEPS_bar variable +should be defined as follow: + + DEPS_bar = foo $(DEPS_foo) + +Note that dependency resolution is unfortunately _not_ recursive. +Therefore $(DEPS_foo) really should be specified explicitly as shown +above. (In practice, this will not make any difference insofar as there +are no pure second-level nested dependencies. For instance, libass +depends on FontConfig, which depends on FreeType, but libass depends +directly on FreeType anyway.) + +Also note that DEPS_bar is set "recursively" with =, rather than +"immediately" with :=. This is so that $(DEPS_foo) is expanded +correctly, even if DEPS_foo it is defined after DEPS_bar. + +Implementation note: + + If you must know, the main.mak build hackery will automatically + emit a dependency from .bar onto .dep-foo: + + .bar: .dep-foo + + ...whereby .dep-foo will depend on .foo: + + .dep-foo: .foo + touch $@ + + ...unless foo was detected in the target distribution: + + .dep-foo: + touch $@ + + So you really only need to set DEPS_bar. diff --git a/contrib/src/boost-headers/SHA512SUMS b/contrib/src/boost-headers/SHA512SUMS new file mode 100644 index 0000000000..e9f8fab96c --- /dev/null +++ b/contrib/src/boost-headers/SHA512SUMS @@ -0,0 +1 @@ +a0ac451a51c5b183ee5625da66ccdbbc5a079d71c3d1bd7570e1e5f30a3f2f852a8e8237a60a3419ee72bcffdf4e1e7867d9d6ab91cf314db95df2142b70c008 boost-headers.tar.gz diff --git a/contrib/src/boost-headers/rules.mak b/contrib/src/boost-headers/rules.mak new file mode 100644 index 0000000000..ca835b1826 --- /dev/null +++ b/contrib/src/boost-headers/rules.mak @@ -0,0 +1,17 @@ +# Boost headers (needed for building yaml-cpp) +BOOST_HEADERS_URL = https://gitlab.savoirfairelinux.com/sfl-ports/boost-headers/repository/archive.tar.gz + +$(TARBALLS)/boost-headers.tar.gz: + $(call download,$(BOOST_HEADERS_URL)) + +.sum-boost-headers: boost-headers.tar.gz + +boost-headers: boost-headers.tar.gz .sum-boost-headers + $(UNPACK) + mv boost-headers.git $@ + touch $@ + +.boost-headers: boost-headers + mkdir -p ../$(HOST)/include + cp -rf $< ../$(HOST)/include/boost + touch $@ diff --git a/contrib/src/change_prefix.sh b/contrib/src/change_prefix.sh new file mode 100755 index 0000000000..43636204df --- /dev/null +++ b/contrib/src/change_prefix.sh @@ -0,0 +1,62 @@ +#!/bin/sh +# *************************************************************************** +# change_prefix.sh : allow to transfer a contrib dir +# *************************************************************************** +# Copyright © 2012 VideoLAN and its authors +# +# Authors: Rafaël Carré +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. +# *************************************************************************** + +set -e + +LANG=C +export LANG + +if test "$1" = "-h" -o "$1" = "--help" -o $# -gt 2; then + echo "Usage: $0 [old prefix] [new prefix] + +Without arguments, this script assumes old prefix = @@CONTRIB_PREFIX@@, +and new prefix = current directory. +" +fi + +if [ $# != 2 ] +then + old_prefix=@@CONTRIB_PREFIX@@ + new_prefix=`pwd` +else + old_prefix=$1 + new_prefix=$2 +fi + +# process [dir] [filemask] [text only] +process() { + for file in `find $1 -maxdepth 1 -type f -name "$2"` + do + if [ -n "$3" ] + then + file $file | sed "s/^.*: //" | grep -q 'text\|shell' || continue + fi + echo "Fixing up $file" + sed -i.orig -e "s,$old_prefix,$new_prefix,g" $file + rm -f $file.orig + done +} + +process bin/ "*" check +process lib/ "*.la" +process lib/pkgconfig/ "*.pc" diff --git a/contrib/src/expat/SHA512SUMS b/contrib/src/expat/SHA512SUMS new file mode 100644 index 0000000000..02f25a87a0 --- /dev/null +++ b/contrib/src/expat/SHA512SUMS @@ -0,0 +1 @@ +2a9ad2b44b87b84087979fe4114d661838df3b03dbdcb74d590cb74096bf35ce9d5a86617b0941a2655ea441a94537bcbcd78252da92342238823be36de2d09d expat-2.1.0.tar.gz diff --git a/contrib/src/expat/rules.mak b/contrib/src/expat/rules.mak new file mode 100644 index 0000000000..56abf6026b --- /dev/null +++ b/contrib/src/expat/rules.mak @@ -0,0 +1,24 @@ +# EXPAT + +EXPAT_VERSION := 2.1.0 +EXPAT_URL := $(SF)/expat/expat-$(EXPAT_VERSION).tar.gz + +PKGS += expat +ifeq ($(call need_pkg,"expat >= 1.95.0"),) +PKGS_FOUND += expat +endif + +$(TARBALLS)/expat-$(EXPAT_VERSION).tar.gz: + $(call download,$(EXPAT_URL)) + +.sum-expat: expat-$(EXPAT_VERSION).tar.gz + +expat: expat-$(EXPAT_VERSION).tar.gz .sum-expat + $(UNPACK) + $(UPDATE_AUTOCONFIG) + $(MOVE) + +.expat: expat + cd $< && $(HOSTVARS) ./configure $(HOSTCONF) + cd $< && $(MAKE) install + touch $@ diff --git a/contrib/src/flac/SHA512SUMS b/contrib/src/flac/SHA512SUMS new file mode 100644 index 0000000000..df50890e23 --- /dev/null +++ b/contrib/src/flac/SHA512SUMS @@ -0,0 +1 @@ +9f62a83c2041ec6f02c0df65b796a920a0cd6ba6c2c034bb69535bca5df57ed69f96fe4bb41c0d5ccc229241d90efd2c7ec3785662b5a582a8e20e2e991e6477 flac-1.3.0.tar.xz diff --git a/contrib/src/flac/libFLAC-pc.patch b/contrib/src/flac/libFLAC-pc.patch new file mode 100644 index 0000000000..8b5105aac0 --- /dev/null +++ b/contrib/src/flac/libFLAC-pc.patch @@ -0,0 +1,10 @@ +--- flac/src/libFLAC/flac.pc.in.orig 2013-05-05 14:05:30.059024229 +0200 ++++ flac/src/libFLAC/flac.pc.in 2013-05-05 14:06:25.529822137 +0200 +@@ -7,6 +7,6 @@ + Description: Free Lossless Audio Codec Library + Version: @VERSION@ + Requires.private: @OGG_PACKAGE@ +-Libs: -L${libdir} -lFLAC ++Libs: -L${libdir} -lFLAC -logg + Libs.private: -lm + Cflags: -I${includedir} diff --git a/contrib/src/flac/rules.mak b/contrib/src/flac/rules.mak new file mode 100644 index 0000000000..bf72caf895 --- /dev/null +++ b/contrib/src/flac/rules.mak @@ -0,0 +1,51 @@ +# FLAC + +FLAC_VERSION := 1.3.0 +FLAC_URL := http://downloads.xiph.org/releases/flac/flac-$(FLAC_VERSION).tar.xz + +PKGS += flac +ifeq ($(call need_pkg,"flac"),) +PKGS_FOUND += flac +endif + +$(TARBALLS)/flac-$(FLAC_VERSION).tar.xz: + $(call download,$(FLAC_URL)) + +.sum-flac: flac-$(FLAC_VERSION).tar.xz + +flac: flac-$(FLAC_VERSION).tar.xz .sum-flac + $(UNPACK) + $(APPLY) $(SRC)/flac/libFLAC-pc.patch +ifdef HAVE_DARWIN_OS + cd $(UNPACK_DIR) && sed -e 's,-dynamiclib,-dynamiclib -arch $(ARCH),' -i.orig configure +endif +ifdef HAVE_ANDROID +ifeq ($(ANDROID_ABI), x86) + # cpu.c:130:29: error: sys/ucontext.h: No such file or directory + # defining USE_OBSOLETE_SIGCONTEXT_FLAVOR allows us to bypass that + cd $(UNPACK_DIR) && sed -i.orig -e s/"# undef USE_OBSOLETE_SIGCONTEXT_FLAVOR"/"#define USE_OBSOLETE_SIGCONTEXT_FLAVOR"/g src/libFLAC/cpu.c +endif +endif + $(UPDATE_AUTOCONFIG) + $(MOVE) + +FLACCONF := $(HOSTCONF) \ + --disable-thorough-tests \ + --disable-doxygen-docs \ + --disable-xmms-plugin \ + --disable-cpplibs \ + --disable-oggtest +# TODO? --enable-sse +ifdef HAVE_DARWIN_OS +ifneq ($(findstring $(ARCH),i386 x86_64),) +FLACCONF += --disable-asm-optimizations +endif +endif + +DEPS_flac = ogg $(DEPS_ogg) + +.flac: flac + cd $< && $(HOSTVARS) ./configure $(FLACCONF) + cd $</include && $(MAKE) install + cd $</src && $(MAKE) -C share install && $(MAKE) -C libFLAC install + touch $@ diff --git a/contrib/src/gcrypt/0001-Fix-assembly-division-check.patch b/contrib/src/gcrypt/0001-Fix-assembly-division-check.patch new file mode 100644 index 0000000000..13ec1b4614 --- /dev/null +++ b/contrib/src/gcrypt/0001-Fix-assembly-division-check.patch @@ -0,0 +1,30 @@ +From ef3e66e168c4b9b86bfc4903001631e53a7125d8 Mon Sep 17 00:00:00 2001 +From: Jussi Kivilinna <jussi.kivilinna@iki.fi> +Date: Sun, 12 Jan 2014 22:01:28 +0200 +Subject: [PATCH] Fix assembly division check + +* configure.ac (gcry_cv_gcc_as_const_division_ok): Correct variable +name mismatch at '--Wa,--divide' workaround check. +-- + +Signed-off-by: Jussi Kivilinna <jussi.kivilinna@iki.fi> +--- + configure.ac | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/configure.ac b/configure.ac +index fac5f7a..7d37f94 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -1101,7 +1101,7 @@ if test $amd64_as_feature_detection = yes; then + AC_COMPILE_IFELSE([AC_LANG_SOURCE( + [[__asm__("xorl \$(123456789/12345678), %ebp;\n\t");]])], + [gcry_cv_gcc_as_const_division_with_wadivide_ok=yes])]) +- if test "$gcry_cv_gcc_as_const_division_ok_with_wadivide_ok" = "no" ; then ++ if test "$gcry_cv_gcc_as_const_division_with_wadivide_ok" = "no" ; then + # '-Wa,--divide' did not work, restore old flags. + CPPFLAGS="$_gcc_cppflags_save" + fi +-- +1.9.1 + diff --git a/contrib/src/gcrypt/SHA512SUMS b/contrib/src/gcrypt/SHA512SUMS new file mode 100644 index 0000000000..feb9cae255 --- /dev/null +++ b/contrib/src/gcrypt/SHA512SUMS @@ -0,0 +1 @@ +86003bb61c1fd37d0e54f9b1f48a9d89adb5f623818f67392f19d0de9b28ccb911a728238f8a8d23b875afb3bf7698bbea4da7b3b3a10e049311b9b45fabe472 libgcrypt-1.6.2.tar.bz2 diff --git a/contrib/src/gcrypt/fix-amd64-assembly-on-solaris.patch b/contrib/src/gcrypt/fix-amd64-assembly-on-solaris.patch new file mode 100644 index 0000000000..a73fc60c97 --- /dev/null +++ b/contrib/src/gcrypt/fix-amd64-assembly-on-solaris.patch @@ -0,0 +1,130 @@ +From: Jussi Kivilinna <jussi.kivilinna@iki.fi> +Date: Sun, 12 Jan 2014 08:53:47 +0000 (+0200) +Subject: Fix constant division for AMD64 assembly on Solaris/x86 +X-Git-Url: http://git.gnupg.org/cgi-bin/gitweb.cgi?p=libgcrypt.git;a=commitdiff_plain;h=43376891c01f4aff1fbfb23beafebb5adfd0868c + +Fix constant division for AMD64 assembly on Solaris/x86 + +* configure.ac (gcry_cv_gcc_as_const_division_ok): Add new check for +constant division in assembly and test for "-Wa,--divide" workaround. +(gcry_cv_gcc_amd64_platform_as_ok): Check for also constant division. +-- + +Appearantly on Solaris/x86 '/' character is treated as begining of line +comment by GNU as. This causes problems when compiling SHA-1 SSSE3 +implementation: + +On 02.01.2014 16:26, Richard PALO wrote: +>> COLLECT_GCC_OPTIONS='-D' 'HAVE_CONFIG_H' '-I' '.' '-I' '..' '-I' '../src' '-I' '/var/tmp/pkgsrc/security/libgcrypt/work/.buildlink/include' '-I' '/var/tmp/pkgsrc/security/libgcrypt/work/.buildlink/include/gettext' '-D' '_REENTRANT' '-O2' '-MT' 'sha1-ssse3-amd64.lo' '-MD' '-MP' '-MF' '.deps/sha1-ssse3-amd64.Tpo' '-c' '-fPIC' '-D' 'PIC' '-o' '.libs/sha1-ssse3-amd64.o' '-v' '-mtune=generic' '-march=x86-64' +>> /usr/gnu/bin/as -v -I . -I .. -I ../src -I /var/tmp/pkgsrc/security/libgcrypt/work/.buildlink/include -I /var/tmp/pkgsrc/security/libgcrypt/work/.buildlink/include/gettext -V -Qy -s --64 -o .libs/sha1-ssse3-amd64.o /var/tmp//ccAxWPXX.s +>> GNU assembler version 2.23.1 (i386-pc-solaris2.11) using BFD version (GNU Binutils) 2.23.1 +>> /var/tmp//ccAxWPXX.s: Assembler messages: +>> /var/tmp//ccAxWPXX.s:34: Error: unbalanced parenthesis in operand 1. +>> /var/tmp//ccAxWPXX.s:38: Error: unbalanced parenthesis in operand 1. +>> /var/tmp//ccAxWPXX.s:42: Error: unbalanced parenthesis in operand 1. +>> /var/tmp//ccAxWPXX.s:46: Error: unbalanced parenthesis in operand 1. +>> /var/tmp//ccAxWPXX.s:54: Error: unbalanced parenthesis in operand 1. +>> /var/tmp//ccAxWPXX.s:58: Error: unbalanced parenthesis in operand 1. +>> /var/tmp//ccAxWPXX.s:62: Error: unbalanced parenthesis in operand 1. +>> /var/tmp//ccAxWPXX.s:66: Error: unbalanced parenthesis in operand 1. +>> /var/tmp//ccAxWPXX.s:70: Error: unbalanced parenthesis in operand 1. +>> /var/tmp//ccAxWPXX.s:74: Error: unbalanced parenthesis in operand 1. +>> /var/tmp//ccAxWPXX.s:78: Error: unbalanced parenthesis in operand 1. +>> /var/tmp//ccAxWPXX.s:82: Error: unbalanced parenthesis in operand 1. +>> /var/tmp//ccAxWPXX.s:86: Error: unbalanced parenthesis in operand 1. +>> /var/tmp//ccAxWPXX.s:90: Error: unbalanced parenthesis in operand 1. +>> /var/tmp//ccAxWPXX.s:94: Error: unbalanced parenthesis in operand 1. +>> /var/tmp//ccAxWPXX.s:98: Error: unbalanced parenthesis in operand 1. +>> /var/tmp//ccAxWPXX.s:102: Error: unbalanced parenthesis in operand 1. +>> /var/tmp//ccAxWPXX.s:106: Error: unbalanced parenthesis in operand 1. +>> /var/tmp//ccAxWPXX.s:110: Error: unbalanced parenthesis in operand 1. +>> /var/tmp//ccAxWPXX.s:114: Error: unbalanced parenthesis in operand 1. +>> /var/tmp//ccAxWPXX.s:119: Error: unbalanced parenthesis in operand 1. +>> /var/tmp//ccAxWPXX.s:123: Error: unbalanced parenthesis in operand 1. +>> /var/tmp//ccAxWPXX.s:127: Error: unbalanced parenthesis in operand 1. +>> /var/tmp//ccAxWPXX.s:132: Error: unbalanced parenthesis in operand 1. +> +> +> apparently the paddd code, such as +> `paddd (.LK_XMM + ((i)/20)*16) RIP, tmp0;` +> isn't digested well, appended is the generated assembler code. + +On 02.01.2014 17:41, Richard PALO wrote: +> Hi again, after finding the following: +> https://sourceware.org/bugzilla/show_bug.cgi?id=4572 +> +> I tried using '-Wa,--divide' and that seemed to workaround the problem... +> +> perhaps the code, or at least the Makefile could be adapted accordingly? + +Patch adds detection of this feature and attempts to workaround issue with by +adding "-Wa,--divide" to CPPFLAGS. If workaround does not work (old GAS on +Solaris/x86), we'll disable AMD64 assembly. + +[v3]: + - Update CPPFLAGS after testing instead of CFLAGS. + +Reported-and-tested-by: Richard PALO <richard.palo@free.fr> +Signed-off-by: Jussi Kivilinna <jussi.kivilinna@iki.fi> +--- + +diff --git a/configure.ac b/configure.ac +index 05cdaf8..fac5f7a 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -1079,11 +1079,42 @@ fi + + + # ++# Check whether GCC assembler needs "-Wa,--divide" to correctly handle ++# constant division ++# ++if test $amd64_as_feature_detection = yes; then ++ AC_CACHE_CHECK([whether GCC assembler handles division correctly], ++ [gcry_cv_gcc_as_const_division_ok], ++ [gcry_cv_gcc_as_const_division_ok=no ++ AC_COMPILE_IFELSE([AC_LANG_SOURCE( ++ [[__asm__("xorl \$(123456789/12345678), %ebp;\n\t");]])], ++ [gcry_cv_gcc_as_const_division_ok=yes])]) ++ if test "$gcry_cv_gcc_as_const_division_ok" = "no" ; then ++ # ++ # Add '-Wa,--divide' to CPPFLAGS and try check again. ++ # ++ _gcc_cppflags_save="$CPPFLAGS" ++ CPPFLAGS="$CPPFLAGS -Wa,--divide" ++ AC_CACHE_CHECK([whether GCC assembler handles division correctly with "-Wa,--divide"], ++ [gcry_cv_gcc_as_const_division_with_wadivide_ok], ++ [gcry_cv_gcc_as_const_division_with_wadivide_ok=no ++ AC_COMPILE_IFELSE([AC_LANG_SOURCE( ++ [[__asm__("xorl \$(123456789/12345678), %ebp;\n\t");]])], ++ [gcry_cv_gcc_as_const_division_with_wadivide_ok=yes])]) ++ if test "$gcry_cv_gcc_as_const_division_ok_with_wadivide_ok" = "no" ; then ++ # '-Wa,--divide' did not work, restore old flags. ++ CPPFLAGS="$_gcc_cppflags_save" ++ fi ++ fi ++fi ++ ++ ++# + # Check whether GCC assembler supports features needed for our amd64 + # implementations + # + if test $amd64_as_feature_detection = yes; then +- AC_CACHE_CHECK([whether GCC assembler is compatible for amd64 assembly implementations], ++ AC_CACHE_CHECK([whether GCC assembler is compatible for amd64 assembly implementations], + [gcry_cv_gcc_amd64_platform_as_ok], + [gcry_cv_gcc_amd64_platform_as_ok=no + AC_COMPILE_IFELSE([AC_LANG_SOURCE( +@@ -1096,6 +1127,11 @@ if test $amd64_as_feature_detection = yes; then + "asmfunc:\n\t" + ".size asmfunc,.-asmfunc;\n\t" + ".type asmfunc,@function;\n\t" ++ /* Test if assembler allows use of '/' for constant division ++ * (Solaris/x86 issue). If previous constant division check ++ * and "-Wa,--divide" workaround failed, this causes assembly ++ * to be disable on this machine. */ ++ "xorl \$(123456789/12345678), %ebp;\n\t" + );]])], + [gcry_cv_gcc_amd64_platform_as_ok=yes])]) + if test "$gcry_cv_gcc_amd64_platform_as_ok" = "yes" ; then diff --git a/contrib/src/gcrypt/rules.mak b/contrib/src/gcrypt/rules.mak new file mode 100644 index 0000000000..939376f027 --- /dev/null +++ b/contrib/src/gcrypt/rules.mak @@ -0,0 +1,47 @@ +# GCRYPT +GCRYPT_VERSION := 1.6.2 +GCRYPT_URL := ftp://ftp.gnupg.org/gcrypt/libgcrypt/libgcrypt-$(GCRYPT_VERSION).tar.bz2 + +$(TARBALLS)/libgcrypt-$(GCRYPT_VERSION).tar.bz2: + $(call download,$(GCRYPT_URL)) + +.sum-gcrypt: libgcrypt-$(GCRYPT_VERSION).tar.bz2 + +libgcrypt: libgcrypt-$(GCRYPT_VERSION).tar.bz2 .sum-gcrypt + $(UNPACK) + $(APPLY) $(SRC)/gcrypt/fix-amd64-assembly-on-solaris.patch + $(APPLY) $(SRC)/gcrypt/0001-Fix-assembly-division-check.patch + $(MOVE) + +DEPS_gcrypt = gpg-error + +GCRYPT_CONF = \ + --enable-ciphers=aes,des,rfc2268,arcfour \ + --enable-digests=sha1,md5,rmd160,sha256,sha512 \ + --enable-pubkey-ciphers=dsa,rsa,ecc +ifdef HAVE_WIN64 +GCRYPT_CONF += --disable-asm +endif +ifdef HAVE_IOS +GCRYPT_EXTRA_CFLAGS = -fheinous-gnu-extensions +else +GCRYPT_EXTRA_CFLAGS = +endif +ifdef HAVE_MACOSX +GCRYPT_CONF += --disable-aesni-support +else +ifdef HAVE_BSD +GCRYPT_CONF += --disable-asm --disable-aesni-support +endif +endif +ifdef HAVE_ANDROID +ifeq ($(ANDROID_ABI), x86) +GCRYPT_CONF += ac_cv_sys_symbol_underscore=no +endif +endif + +.gcrypt: libgcrypt + $(RECONF) + cd $< && $(HOSTVARS) CFLAGS="$(CFLAGS) $(GCRYPT_EXTRA_CFLAGS)" ./configure $(HOSTCONF) $(GCRYPT_CONF) + cd $< && $(MAKE) install + touch $@ diff --git a/contrib/src/get-arch.sh b/contrib/src/get-arch.sh new file mode 100755 index 0000000000..2df77de83c --- /dev/null +++ b/contrib/src/get-arch.sh @@ -0,0 +1,29 @@ +#! /bin/sh + +HOST="$1" +if test -z "$HOST"; then + echo "Usage: $0 <target machine>" >&2 + exit 1 +fi + +case "$HOST" in + amd64-*) + ARCH="x86_64" + ;; + i[3456]86-*) + ARCH="i386" + ;; + powerpc-*|ppc-*) + ARCH="ppc" + ;; + powerpc64-*|ppc64-*) + ARCH="ppc64" + ;; + *-*) + ARCH="${HOST%%-*}" + ;; + *) + echo "$HOST: invalid machine specification" >&2 + exit 1 +esac +echo $ARCH diff --git a/contrib/src/gmp/SHA512SUMS b/contrib/src/gmp/SHA512SUMS new file mode 100644 index 0000000000..3cffd07d8b --- /dev/null +++ b/contrib/src/gmp/SHA512SUMS @@ -0,0 +1 @@ +06bdd312146f77bd23d1447e60b02bfea2f1e6d00798b073879e8a50a6cf7264bdbf6f31a8347dd3a0889c7a9dee2d24051b74542fc4f9f07ba2d0f744e092ad gmp-6.0.0.tar.bz2 diff --git a/contrib/src/gmp/clang.patch b/contrib/src/gmp/clang.patch new file mode 100644 index 0000000000..4431106cba --- /dev/null +++ b/contrib/src/gmp/clang.patch @@ -0,0 +1,27 @@ +# HG changeset patch +# User Torbjorn Granlund <tege@gmplib.org> +# Date 1396470504 -7200 +# Node ID 1fab0adc5ff7d9ecddcbda96f407da58347bb49c +# Parent db645603dcdb41afcf78b19b551ecd5a01c3841c +Workaround for Darwin assembler quirk. + +diff -r db645603dcdb -r 1fab0adc5ff7 mpn/x86_64/k8/redc_1.asm +--- a/mpn/x86_64/k8/redc_1.asm Mon Mar 31 23:04:32 2014 +0200 ++++ b/mpn/x86_64/k8/redc_1.asm Wed Apr 02 22:28:24 2014 +0200 +@@ -114,7 +114,7 @@ + + JUMPTABSECT + ALIGN(8) +-L(tab): JMPENT( L(0m4), L(tab)) ++L(tab): JMPENT( L(0), L(tab)) + JMPENT( L(1), L(tab)) + JMPENT( L(2), L(tab)) + JMPENT( L(3), L(tab)) +@@ -397,6 +397,7 @@ + + + ALIGN(16) ++L(0): + L(0m4): + L(lo0): mov (mp,nneg,8), %rax + mov nneg, i diff --git a/contrib/src/gmp/rules.mak b/contrib/src/gmp/rules.mak new file mode 100644 index 0000000000..fa1bbe3dc3 --- /dev/null +++ b/contrib/src/gmp/rules.mak @@ -0,0 +1,20 @@ +# GNU Multiple Precision Arithmetic + +GMP_VERSION := 6.0.0 +GMP_URL := https://gmplib.org/download/gmp-$(GMP_VERSION)/gmp-$(GMP_VERSION).tar.bz2 + +$(TARBALLS)/gmp-$(GMP_VERSION).tar.bz2: + $(call download,$(GMP_URL)) + +.sum-gmp: gmp-$(GMP_VERSION).tar.bz2 + +gmp: gmp-$(GMP_VERSION).tar.bz2 .sum-gmp + $(UNPACK) + $(APPLY) $(SRC)/gmp/thumb.patch + $(APPLY) $(SRC)/gmp/clang.patch + $(MOVE) + +.gmp: gmp + cd $< && $(HOSTVARS) ./configure $(HOSTCONF) + cd $< && $(MAKE) install + touch $@ diff --git a/contrib/src/gmp/thumb.patch b/contrib/src/gmp/thumb.patch new file mode 100644 index 0000000000..ed633cfba4 --- /dev/null +++ b/contrib/src/gmp/thumb.patch @@ -0,0 +1,22 @@ +--- gmp/mpn/generic/div_qr_1n_pi1.c.orig 2014-03-28 16:11:23.648263232 +0100 ++++ gmp/mpn/generic/div_qr_1n_pi1.c 2014-03-28 16:22:29.376932722 +0100 +@@ -131,10 +131,19 @@ + #endif + + #if defined (__arm__) && W_TYPE_SIZE == 32 ++#ifdef __thumb2__ ++#define itcc "it cc\n\t" ++#define itcs "it cs\n\t" ++#else ++#define itcc ++#define itcs ++#endif + #define add_mssaaaa(m, sh, sl, ah, al, bh, bl) \ + __asm__ ( "adds %2, %5, %6\n\t" \ + "adcs %1, %3, %4\n\t" \ ++ itcc \ + "movcc %0, #0\n\t" \ ++ itcs \ + "movcs %0, #-1" \ + : "=r" (m), "=r" (sh), "=&r" (sl) \ + : "r" (ah), "rI" (bh), "%r" (al), "rI" (bl) __CLOBBER_CC) diff --git a/contrib/src/gnutls/SHA512SUMS b/contrib/src/gnutls/SHA512SUMS new file mode 100644 index 0000000000..d6b2d98fe4 --- /dev/null +++ b/contrib/src/gnutls/SHA512SUMS @@ -0,0 +1 @@ +3205fcfe3344f777f5c8d2162de2ac338cfdfabaa55d7b829e59160cfec434651f704a9bac355f5003d1841448c4b0303dc6e06a935801aa922504b297bdd093 gnutls-3.1.25.tar.xz diff --git a/contrib/src/gnutls/downgrade-automake-requirement.patch b/contrib/src/gnutls/downgrade-automake-requirement.patch new file mode 100644 index 0000000000..62b156d988 --- /dev/null +++ b/contrib/src/gnutls/downgrade-automake-requirement.patch @@ -0,0 +1,11 @@ +--- gnutls-3.1.14/configure.ac.orig 2013-09-17 18:17:09.840217108 +0200 ++++ gnutls-3.1.14/configure.ac 2013-09-17 18:19:36.609535012 +0200 +@@ -26,7 +26,7 @@ + AC_CONFIG_MACRO_DIR([m4]) + AC_CANONICAL_HOST + +-AM_INIT_AUTOMAKE([1.12.2 no-dist-gzip dist-xz dist-lzip -Wall -Wno-override]) ++AM_INIT_AUTOMAKE([1.11.1 no-dist-gzip dist-xz -Wall -Wno-override]) + m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + AC_CONFIG_HEADERS([config.h]) + diff --git a/contrib/src/gnutls/gnutls-no-egd.patch b/contrib/src/gnutls/gnutls-no-egd.patch new file mode 100644 index 0000000000..c0e3407570 --- /dev/null +++ b/contrib/src/gnutls/gnutls-no-egd.patch @@ -0,0 +1,81 @@ +diff -ru gnutls.orig/lib/nettle/rnd.c gnutls/lib/nettle/rnd.c +--- gnutls-3.1.10/lib/nettle/Makefile.am.orig 2013-03-25 14:41:50.265377296 +0100 ++++ gnutls-3.1.10/lib/nettle/Makefile.am 2013-03-25 14:50:17.436084975 +0100 +@@ -33,7 +33,7 @@ + + noinst_LTLIBRARIES = libcrypto.la + +-libcrypto_la_SOURCES = pk.c mpi.c mac.c cipher.c rnd.c init.c egd.c egd.h \ ++libcrypto_la_SOURCES = pk.c mpi.c mac.c cipher.c rnd.c init.c \ + multi.c wmnaf.c ecc_free.c ecc.h ecc_make_key.c ecc_shared_secret.c \ + ecc_map.c ecc_mulmod.c ecc_mulmod_cached.c \ + ecc_points.c ecc_projective_dbl_point_3.c ecc_projective_isneutral.c \ +--- gnutls-3.1.10/lib/nettle/Makefile.in.orig 2013-03-25 14:41:50.268710655 +0100 ++++ gnutls-3.1.10/lib/nettle/Makefile.in 2013-03-25 14:51:42.180123726 +0100 +@@ -219,7 +219,7 @@ + LTLIBRARIES = $(noinst_LTLIBRARIES) + libcrypto_la_LIBADD = + am_libcrypto_la_OBJECTS = pk.lo mpi.lo mac.lo cipher.lo rnd.lo init.lo \ +- egd.lo multi.lo wmnaf.lo ecc_free.lo ecc_make_key.lo \ ++ multi.lo wmnaf.lo ecc_free.lo ecc_make_key.lo \ + ecc_shared_secret.lo ecc_map.lo ecc_mulmod.lo \ + ecc_mulmod_cached.lo ecc_points.lo \ + ecc_projective_dbl_point_3.lo ecc_projective_isneutral.lo \ +@@ -1536,7 +1536,7 @@ + -I$(srcdir)/../includes -I$(builddir)/../includes \ + -I$(builddir)/../../gl -I$(srcdir)/.. $(am__append_1) + noinst_LTLIBRARIES = libcrypto.la +-libcrypto_la_SOURCES = pk.c mpi.c mac.c cipher.c rnd.c init.c egd.c egd.h \ ++libcrypto_la_SOURCES = pk.c mpi.c mac.c cipher.c rnd.c init.c \ + multi.c wmnaf.c ecc_free.c ecc.h ecc_make_key.c ecc_shared_secret.c \ + ecc_map.c ecc_mulmod.c ecc_mulmod_cached.c \ + ecc_points.c ecc_projective_dbl_point_3.c ecc_projective_isneutral.c \ +@@ -1610,7 +1610,6 @@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ecc_shared_secret.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ecc_sign_hash.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ecc_verify_hash.Plo@am__quote@ +-@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/egd.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/init.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mac.Plo@am__quote@ + @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mpi.Plo@am__quote@ +--- gnutls-3.1.10/lib/nettle/rnd.c.orig 2013-03-21 21:42:28.000000000 +0100 ++++ gnutls-3.1.10/lib/nettle/rnd.c 2013-03-25 14:52:50.004027534 +0100 +@@ -205,7 +205,7 @@ + #include <sys/time.h> + #include <fcntl.h> + #include <locks.h> +-#include "egd.h" ++//#include "egd.h" + + #define DEVICE_READ_SIZE 16 + #define DEVICE_READ_SIZE_MAX 32 +@@ -276,6 +276,7 @@ + return 0; + } + ++#if 0 + static int + do_device_source_egd (int init) + { +@@ -329,6 +330,7 @@ + } + return 0; + } ++#endif + + static int + do_device_source (int init) +@@ -346,11 +348,13 @@ + + do_source = do_device_source_urandom; + ret = do_source (init); ++#if 0 + if (ret < 0) + { + do_source = do_device_source_egd; + ret = do_source (init); + } ++#endif + + if (ret < 0) + { diff --git a/contrib/src/gnutls/gnutls-pkgconfig-osx.patch b/contrib/src/gnutls/gnutls-pkgconfig-osx.patch new file mode 100644 index 0000000000..37cbc670e8 --- /dev/null +++ b/contrib/src/gnutls/gnutls-pkgconfig-osx.patch @@ -0,0 +1,51 @@ +--- a/m4/intlmacosx.m4.orig 2014-06-25 17:40:22.000000000 -0400 ++++ b/m4/intlmacosx.m4 2014-06-25 17:40:29.000000000 -0400 +@@ -43,9 +43,25 @@ + AC_DEFINE([HAVE_CFLOCALECOPYCURRENT], [1], + [Define to 1 if you have the MacOS X function CFLocaleCopyCurrent in the CoreFoundation framework.]) + fi ++ AC_CACHE_CHECK([for SecTrustCopyAnchorCertificates], ++ [SecTrustCopyAnchorCertificates], ++ [gt_save_LIBS="$LIBS" ++ LIBS="$LIBS -Wl,-framework -Wl,Security" ++ AC_TRY_LINK([#include <Security/SecTrust.h>], ++ [SecTrustCopyAnchorCertificates(NULL)], ++ [gt_cv_func_SecTrustCopyAnchorCertificates=yes], ++ [gt_cv_func_SecTrustCopyAnchorCertificates=no]) ++ LIBS="$gt_save_LIBS"]) ++ if test $gt_cv_func_SecTrustCopyAnchorCertificates = yes; then ++ AC_DEFINE([HAVE_SecTrustCopyAnchorCertificates], [1], ++ [Define to 1 if you have the MacOS X function SecTrustCopyAnchorCertificates in the Security framework.]) ++ fi + INTL_MACOSX_LIBS= + if test $gt_cv_func_CFPreferencesCopyAppValue = yes || test $gt_cv_func_CFLocaleCopyCurrent = yes; then +- INTL_MACOSX_LIBS="-Wl,-framework -Wl,CoreFoundation" ++ INTL_MACOSX_LIBS+="-Wl,-framework -Wl,CoreFoundation " ++ fi ++ if test $gt_cv_func_SecTrustCopyAnchorCertificates = yes; then ++ INTL_MACOSX_LIBS+="-Wl,-framework -Wl,Security " + fi + AC_SUBST([INTL_MACOSX_LIBS]) + ]) + +--- a/lib/gnutls.pc.in.orig 2014-06-25 17:42:26.000000000 -0400 ++++ b/lib/gnutls.pc.in 2014-06-25 17:42:35.000000000 -0400 +@@ -19,6 +19,6 @@ + Version: @VERSION@ + Libs: -L${libdir} -lgnutls +-Libs.private: @LTLIBNETTLE@ @LTLIBZ@ @LTLIBINTL@ @LIBSOCKET@ @LTLIBPTHREAD@ @LTLIBICONV@ @P11_KIT_LIBS@ @LIB_SELECT@ @TSS_LIBS@ @LIB_CLOCK_GETTIME@ @GMP_LIBS@ ++Libs.private: @LTLIBNETTLE@ @LTLIBZ@ @LTLIBINTL@ @LIBSOCKET@ @LTLIBPTHREAD@ @LTLIBICONV@ @P11_KIT_LIBS@ @LIB_SELECT@ @TSS_LIBS@ @LIB_CLOCK_GETTIME@ @GMP_LIBS@ @INTL_MACOSX_LIBS@ + @GNUTLS_REQUIRES_PRIVATE@ + Cflags: -I${includedir} + +--- a/libdane/gnutls-dane.pc.in.orig 2014-06-25 17:57:29.000000000 -0400 ++++ b/libdane/gnutls-dane.pc.in 2014-06-25 17:57:39.000000000 -0400 +@@ -19,7 +19,7 @@ + Description: DANE security library for the GNU system + URL: http://www.gnu.org/software/gnutls/ + Version: @VERSION@ +-Libs: -L${libdir} -lgnutls-dane ++Libs: -L${libdir} -lgnutls-dane @INTL_MACOSX_LIBS@ + Libs.private: @UNBOUND_LIBS@ + Requires.private: gnutls + Cflags: -I${includedir} diff --git a/contrib/src/gnutls/gnutls-win32.patch b/contrib/src/gnutls/gnutls-win32.patch new file mode 100644 index 0000000000..c0c540ab43 --- /dev/null +++ b/contrib/src/gnutls/gnutls-win32.patch @@ -0,0 +1,28 @@ +--- gnutls-2.12.20/gl/gai_strerror.c.orig 2012-03-01 16:45:12.000000000 +0100 ++++ gnutls-2.12.20/gl/gai_strerror.c 2012-09-27 14:39:30.273584236 +0200 +@@ -75,7 +75,7 @@ + { EAI_IDN_ENCODE, N_("Parameter string not correctly encoded") } + #endif + }; +- ++#ifndef _WIN32 + const char * + gai_strerror (int code) + { +@@ -89,4 +89,5 @@ + # ifdef _LIBC + libc_hidden_def (gai_strerror) + # endif ++#endif + #endif /* !HAVE_DECL_GAI_STRERROR */ +--- gnutls-3.1.14/lib/gnutls.pc.in.orig 2013-09-17 18:14:16.270374773 +0200 ++++ gnutls-3.1.14/lib/gnutls.pc.in 2013-09-17 18:16:10.232464936 +0200 +@@ -18,7 +18,7 @@ + Description: Transport Security Layer implementation for the GNU system + URL: http://www.gnutls.org/ + Version: @VERSION@ +-Libs: -L${libdir} -lgnutls ++Libs: -L${libdir} -lgnutls -lws2_32 -lcrypt32 @LTLIBINTL@ + Libs.private: @LTLIBNETTLE@ @LTLIBZ@ @LTLIBINTL@ @LIBSOCKET@ @LTLIBPTHREAD@ @LTLIBICONV@ @P11_KIT_LIBS@ @LIB_SELECT@ @TSS_LIBS@ @LIB_CLOCK_GETTIME@ @GMP_LIBS@ + @GNUTLS_REQUIRES_PRIVATE@ + Cflags: -I${includedir} diff --git a/contrib/src/gnutls/mac-keychain-lookup.patch b/contrib/src/gnutls/mac-keychain-lookup.patch new file mode 100644 index 0000000000..81e21c215b --- /dev/null +++ b/contrib/src/gnutls/mac-keychain-lookup.patch @@ -0,0 +1,74 @@ +diff -ru gnutls-old/lib/Makefile.am gnutls/lib/Makefile.am +--- gnutls-old/lib/Makefile.am 2013-06-02 19:33:57.000000000 +0200 ++++ gnutls/lib/Makefile.am 2013-11-10 13:28:18.000000000 +0100 +@@ -152,6 +152,10 @@ + DISTCLEANFILES += $(defexec_DATA) + endif + ++if MACOSX ++libgnutls_la_LDFLAGS += -Wl,-framework,Security,-framework,CoreFoundation ++endif ++ + if WINDOWS + thirdparty_libadd += -lcrypt32 + endif +diff -ru gnutls-old/lib/system.c gnutls/lib/system.c +--- gnutls-old/lib/system.c 2013-04-10 22:25:51.000000000 +0200 ++++ gnutls/lib/system.c 2013-11-10 13:30:31.000000000 +0100 +@@ -57,6 +57,15 @@ + #undef send + #undef select + ++#ifdef __APPLE__ ++#include "TargetConditionals.h" ++#ifdef TARGET_OS_MAC ++#define _UINT64 ++#include <Security/Security.h> ++#include <Security/SecCertificate.h> ++#endif ++#endif ++ + /* System specific function wrappers. + */ + +@@ -550,6 +559,40 @@ + + return r; + } ++#elif defined(__APPLE__) ++#if TARGET_OS_MAC ++static ++int add_system_trust(gnutls_x509_trust_list_t list, unsigned int tl_flags, unsigned int tl_vflags) ++{ ++ CFArrayRef anchors; ++ int ret = 0; ++ if (SecTrustCopyAnchorCertificates(&anchors) != 0) ++ return -1; ++ ++ CFIndex count = CFArrayGetCount(anchors); ++ for (int i = 0; i < count; i++) { ++ SecCertificateRef certref = (SecCertificateRef)CFArrayGetValueAtIndex(anchors, i); ++ ++ CSSM_DATA certData; ++ SecCertificateGetData(certref, &certData); ++ gnutls_datum data = { ++ .data = certData.Data, ++ .size = certData.Length, ++ }; ++ ++ if (!gnutls_x509_trust_list_add_trust_mem(list, &data, NULL, GNUTLS_X509_FMT_DER, tl_flags, tl_vflags)) ++ printf("cannot add x509 credentials\n"); ++ else ++ ret++; ++ } ++ CFRelease(anchors); ++ ++ return ret; ++} ++ ++#else ++#define add_system_trust(x,y,z) GNUTLS_E_UNIMPLEMENTED_FEATURE ++#endif + #else + + #define add_system_trust(x,y,z) GNUTLS_E_UNIMPLEMENTED_FEATURE diff --git a/contrib/src/gnutls/no-create-time-h.patch b/contrib/src/gnutls/no-create-time-h.patch new file mode 100644 index 0000000000..d355c7cc6b --- /dev/null +++ b/contrib/src/gnutls/no-create-time-h.patch @@ -0,0 +1,11 @@ +--- gnutls/gl/Makefile.am 2011-04-07 17:30:44.000000000 -0700 ++++ gnutls/gl/Makefile.am 2012-03-02 19:51:53.576555217 -0800 +@@ -891,7 +891,7 @@ EXTRA_DIST += sys_stat.in.h + + ## begin gnulib module time + +-BUILT_SOURCES += time.h ++#BUILT_SOURCES += time.h + + # We need the following in order to create <time.h> when the system + # doesn't have one that works with the given compiler. diff --git a/contrib/src/gnutls/read-file-limits.h.patch b/contrib/src/gnutls/read-file-limits.h.patch new file mode 100644 index 0000000000..b13b1a88f4 --- /dev/null +++ b/contrib/src/gnutls/read-file-limits.h.patch @@ -0,0 +1,12 @@ +--- gnutls/gl/read-file.c.orig 2012-03-06 20:59:29.600593329 -0500 ++++ gnutls/gl/read-file.c 2012-03-06 20:59:44.568593328 -0500 +@@ -35,6 +35,9 @@ + /* Get errno. */ + #include <errno.h> + ++/* Get SIZE_MAX */ ++#include <limits.h> ++ + /* Read a STREAM and return a newly allocated string with the content, + and set *LENGTH to the length of the string. The string is + zero-terminated, but the terminating zero byte is not counted in diff --git a/contrib/src/gnutls/rules.mak b/contrib/src/gnutls/rules.mak new file mode 100644 index 0000000000..c91416696a --- /dev/null +++ b/contrib/src/gnutls/rules.mak @@ -0,0 +1,60 @@ +# GnuTLS + +GNUTLS_VERSION := 3.1.25 +GNUTLS_URL := ftp://ftp.gnutls.org/gcrypt/gnutls/v3.1/gnutls-$(GNUTLS_VERSION).tar.xz + +PKGS += gnutls +ifeq ($(call need_pkg,"gnutls >= 3.0.20"),) +PKGS_FOUND += gnutls +endif + +$(TARBALLS)/gnutls-$(GNUTLS_VERSION).tar.xz: + $(call download,$(GNUTLS_URL)) + +.sum-gnutls: gnutls-$(GNUTLS_VERSION).tar.xz + +gnutls: gnutls-$(GNUTLS_VERSION).tar.xz .sum-gnutls + $(UNPACK) +ifdef HAVE_WIN32 + $(APPLY) $(SRC)/gnutls/gnutls-win32.patch +endif +ifdef HAVE_ANDROID + $(APPLY) $(SRC)/gnutls/no-create-time-h.patch +endif +ifdef HAVE_MACOSX + $(APPLY) $(SRC)/gnutls/gnutls-pkgconfig-osx.patch +endif + $(APPLY) $(SRC)/gnutls/gnutls-no-egd.patch + $(APPLY) $(SRC)/gnutls/read-file-limits.h.patch + $(APPLY) $(SRC)/gnutls/downgrade-automake-requirement.patch + $(APPLY) $(SRC)/gnutls/mac-keychain-lookup.patch + $(call pkg_static,"lib/gnutls.pc.in") + $(UPDATE_AUTOCONFIG) + $(MOVE) + +GNUTLS_CONF := \ + --disable-gtk-doc \ + --without-p11-kit \ + --disable-cxx \ + --disable-srp-authentication \ + --disable-psk-authentication-FIXME \ + --with-included-libtasn1 \ + --disable-openpgp-authentication \ + --disable-openssl-compatibility \ + --disable-guile \ + --disable-nls \ + --without-libintl-prefix \ + $(HOSTCONF) + +DEPS_gnutls = nettle $(DEPS_nettle) iconv $(DEPS_iconv) + +.gnutls: gnutls + $(RECONF) +ifdef HAVE_ANDROID + cd $< && $(HOSTVARS) gl_cv_header_working_stdint_h=yes ./configure $(GNUTLS_CONF) +else + cd $< && $(HOSTVARS) ./configure $(GNUTLS_CONF) +endif + cd $</gl && $(MAKE) install + cd $</lib && $(MAKE) install + touch $@ diff --git a/contrib/src/gpg-error/SHA512SUMS b/contrib/src/gpg-error/SHA512SUMS new file mode 100644 index 0000000000..aabc9e816f --- /dev/null +++ b/contrib/src/gpg-error/SHA512SUMS @@ -0,0 +1 @@ +7ceb654b9690b27ea904861a47eb2da16d68da11f69e150cb413970b3dd756093d1fcf1fabeabb7aadc888bfa03edc64115dbc572a97428ddcf9f0af0c1b210b libgpg-error-1.15.tar.bz2 diff --git a/contrib/src/gpg-error/gpgerror-android.patch b/contrib/src/gpg-error/gpgerror-android.patch new file mode 100644 index 0000000000..3b7b43dd3b --- /dev/null +++ b/contrib/src/gpg-error/gpgerror-android.patch @@ -0,0 +1,14 @@ +--- libgpg-error/src/gpgrt-int.h.orig 2014-09-17 15:23:22.992333706 +0200 ++++ libgpg-error/src/gpgrt-int.h 2014-09-17 15:23:52.772783925 +0200 +@@ -105,9 +105,9 @@ + + int _gpgrt_fflush (gpgrt_stream_t stream); + int _gpgrt_fseek (gpgrt_stream_t stream, long int offset, int whence); +-int _gpgrt_fseeko (gpgrt_stream_t stream, off_t offset, int whence); ++int _gpgrt_fseeko (gpgrt_stream_t stream, gpgrt_off_t offset, int whence); + long int _gpgrt_ftell (gpgrt_stream_t stream); +-off_t _gpgrt_ftello (gpgrt_stream_t stream); ++gpgrt_off_t _gpgrt_ftello (gpgrt_stream_t stream); + void _gpgrt_rewind (gpgrt_stream_t stream); + + int _gpgrt_fgetc (gpgrt_stream_t stream); diff --git a/contrib/src/gpg-error/missing-unistd-include.patch b/contrib/src/gpg-error/missing-unistd-include.patch new file mode 100644 index 0000000000..19dc806f15 --- /dev/null +++ b/contrib/src/gpg-error/missing-unistd-include.patch @@ -0,0 +1,11 @@ +diff -ru libgpg-error/tests/t-lock.c libgpg-error-fixed/tests/t-lock.c +--- libgpg-error/tests/t-lock.c 2014-01-24 21:50:09.000000000 +0100 ++++ libgpg-error-fixed/tests/t-lock.c 2014-08-17 22:10:24.000000000 +0200 +@@ -24,6 +24,7 @@ + #include <stdio.h> + #include <stdlib.h> + #include <string.h> ++#include <unistd.h> + #include <assert.h> + #ifdef _WIN32 + # include <windows.h> diff --git a/contrib/src/gpg-error/rules.mak b/contrib/src/gpg-error/rules.mak new file mode 100644 index 0000000000..7fb6bddb1f --- /dev/null +++ b/contrib/src/gpg-error/rules.mak @@ -0,0 +1,26 @@ +# GPGERROR +GPGERROR_VERSION := 1.15 +GPGERROR_URL := ftp://ftp.gnupg.org/gcrypt/libgpg-error/libgpg-error-$(GPGERROR_VERSION).tar.bz2 + +$(TARBALLS)/libgpg-error-$(GPGERROR_VERSION).tar.bz2: + $(call download,$(GPGERROR_URL)) + +.sum-gpg-error: libgpg-error-$(GPGERROR_VERSION).tar.bz2 + +libgpg-error: libgpg-error-$(GPGERROR_VERSION).tar.bz2 .sum-gpg-error + $(UNPACK) +ifdef HAVE_WIN32 + $(APPLY) $(SRC)/gpg-error/windres-make.patch +endif + $(APPLY) $(SRC)/gpg-error/missing-unistd-include.patch + $(APPLY) $(SRC)/gpg-error/gpgerror-android.patch + $(MOVE) + cp $@/src/syscfg/lock-obj-pub.arm-unknown-linux-androideabi.h $@/src/syscfg/lock-obj-pub.linux-android.h +ifdef HAVE_IOS + cp $@/src/syscfg/lock-obj-pub.arm-unknown-linux-androideabi.h $@/src/syscfg/lock-obj-pub.$(HOST).h +endif + +.gpg-error: libgpg-error + cd $< && $(HOSTVARS) ./configure $(HOSTCONF) --disable-nls --disable-shared --disable-languages + cd $< && $(MAKE) install + touch $@ diff --git a/contrib/src/gpg-error/windres-make.patch b/contrib/src/gpg-error/windres-make.patch new file mode 100644 index 0000000000..53ddbecf1b --- /dev/null +++ b/contrib/src/gpg-error/windres-make.patch @@ -0,0 +1,22 @@ +--- libgpg-error/src/Makefile.am 2010-04-14 11:16:44.000000000 +0200 ++++ libgpg-error.new/src/Makefile.am 2010-08-09 11:21:56.000000000 +0200 +@@ -60,7 +60,7 @@ + arch_sources = w32-gettext.c + + RCCOMPILE = $(RC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ +- -DLOCALEDIR=\"$(localedir)\" $(AM_CPPFLAGS) $(CPPFLAGS) ++ -DLOCALEDIR=\"$(localedir)\" $(AM_CPPFLAGS) + LTRCCOMPILE = $(LIBTOOL) --mode=compile --tag=RC $(RCCOMPILE) + + SUFFIXES = .rc .lo +--- libgpg-error/src/Makefile.in.orig 2011-11-22 23:23:14.450340031 -0500 ++++ libgpg-error/src/Makefile.in 2011-11-22 23:23:20.650370779 -0500 +@@ -306,7 +306,7 @@ + @HAVE_W32_SYSTEM_FALSE@arch_sources = + @HAVE_W32_SYSTEM_TRUE@arch_sources = w32-gettext.c + @HAVE_W32_SYSTEM_TRUE@RCCOMPILE = $(RC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ +-@HAVE_W32_SYSTEM_TRUE@ -DLOCALEDIR=\"$(localedir)\" $(AM_CPPFLAGS) $(CPPFLAGS) ++@HAVE_W32_SYSTEM_TRUE@ -DLOCALEDIR=\"$(localedir)\" $(AM_CPPFLAGS) + + @HAVE_W32_SYSTEM_TRUE@LTRCCOMPILE = $(LIBTOOL) --mode=compile --tag=RC $(RCCOMPILE) + @HAVE_W32_SYSTEM_TRUE@SUFFIXES = .rc .lo diff --git a/contrib/src/gsm/SHA512SUMS b/contrib/src/gsm/SHA512SUMS new file mode 100644 index 0000000000..0aed31f5c4 --- /dev/null +++ b/contrib/src/gsm/SHA512SUMS @@ -0,0 +1 @@ +0d0cf9e1e81e64cd84f588c1e4f0cb74b849d45e41fdebf860f63588084c73c7c5198bfe73a6c976bda5735ee516661d3db00afbb5cc5886a7ee3a7b31d673aa libgsm_1.0.13.tar.gz diff --git a/contrib/src/gsm/gsm-cross.patch b/contrib/src/gsm/gsm-cross.patch new file mode 100644 index 0000000000..1b8d6e2576 --- /dev/null +++ b/contrib/src/gsm/gsm-cross.patch @@ -0,0 +1,38 @@ +diff -ruN gsm/Makefile gsm.new/Makefile +--- gsm/Makefile 2006-04-26 21:14:26.000000000 +0200 ++++ gsm.new/Makefile 2009-03-29 20:12:39.000000000 +0200 +@@ -43,10 +43,8 @@ + # CC = /usr/lang/acc + # CCFLAGS = -c -O + +-CC = gcc -ansi -pedantic + CCFLAGS = -c -O2 -DNeedFunctionPrototypes=1 + +-LD = $(CC) + + # LD = gcc + # LDFLAGS = +@@ -98,14 +96,11 @@ + SHELL = /bin/sh + LN = ln + BASENAME = basename +-AR = ar + ARFLAGS = cr + RMFLAGS = + FIND = find + COMPRESS = compress + COMPRESSFLAGS = +-# RANLIB = true +-RANLIB = ranlib + + # + # You shouldn't have to configure below this line if you're porting. +@@ -279,7 +274,7 @@ + + # Target rules + +-all: $(LIBGSM) $(TOAST) $(TCAT) $(UNTOAST) ++all: $(LIBGSM) + @-echo $(ROOT): Done. + + tst: $(TST)/lin2cod $(TST)/cod2lin $(TOAST) $(TST)/test-result diff --git a/contrib/src/gsm/rules.mak b/contrib/src/gsm/rules.mak new file mode 100644 index 0000000000..2ca2b440f2 --- /dev/null +++ b/contrib/src/gsm/rules.mak @@ -0,0 +1,24 @@ +# GSM +GSM_VERSION := 1.0.13 +GSM_URL := $(CONTRIB_VIDEOLAN)/libgsm_$(GSM_VERSION).tar.gz + +PKGS += gsm + +$(TARBALLS)/libgsm_$(GSM_VERSION).tar.gz: + $(call download,$(GSM_URL)) + +.sum-gsm: libgsm_$(GSM_VERSION).tar.gz + +gsm: libgsm_$(GSM_VERSION).tar.gz .sum-gsm + $(UNPACK) + mv gsm-1.0-* libgsm_$(GSM_VERSION) + $(APPLY) $(SRC)/gsm/gsm-cross.patch + sed -e 's/^CFLAGS.*=/CFLAGS+=/' -i.orig libgsm_$(GSM_VERSION)/Makefile + $(MOVE) + +.gsm: gsm + cd $< && $(HOSTVARS) $(MAKE) + mkdir -p "$(PREFIX)/include/gsm" "$(PREFIX)/lib" + cp $</inc/gsm.h "$(PREFIX)/include/gsm/" + cp $</lib/libgsm.a "$(PREFIX)/lib/" + touch $@ diff --git a/contrib/src/iax/rules.mak b/contrib/src/iax/rules.mak new file mode 100644 index 0000000000..063b976bed --- /dev/null +++ b/contrib/src/iax/rules.mak @@ -0,0 +1,24 @@ +#IAX + +IAX_URL = https://gitlab.savoirfairelinux.com/sflphone/libiax2/repository/archive.tar.gz + +PKGS += iax + +$(TARBALLS)/iax-git.tar.gz: + $(call download,$(IAX_URL)) + +.sum-iax: iax-git.tar.gz + $(warning $@ not implemented) + touch $@ + +iax: iax-git.tar.gz .sum-iax + $(UNPACK) + mv libiax2.git $@ + touch $@ + + +.iax: iax + $(RECONF) + cd $< && $(HOSTVARS) ./configure $(HOSTCONF) + cd $< && $(MAKE) install + touch $@ diff --git a/contrib/src/iconv/SHA512SUMS b/contrib/src/iconv/SHA512SUMS new file mode 100644 index 0000000000..78e4babf14 --- /dev/null +++ b/contrib/src/iconv/SHA512SUMS @@ -0,0 +1 @@ +b96774fefc4fa1d07948fcc667027701373c34ebf9c4101000428e048addd85a5bb5e05e59f80eb783a3054a3a8a3c0da909450053275bbbf3ffde511eb3f387 libiconv-1.14.tar.gz diff --git a/contrib/src/iconv/bins.patch b/contrib/src/iconv/bins.patch new file mode 100644 index 0000000000..75e11a1ebe --- /dev/null +++ b/contrib/src/iconv/bins.patch @@ -0,0 +1,82 @@ +--- iconv/Makefile.in.orig 2013-08-20 14:01:59.451167175 +0200 ++++ iconv/Makefile.in 2013-08-20 14:02:24.007166142 +0200 +@@ -33,7 +33,6 @@ + cd lib && $(MAKE) all + cd preload && $(MAKE) all + cd srclib && $(MAKE) all +- cd src && $(MAKE) all + cd po && $(MAKE) all + cd man && $(MAKE) all + if test -d tests; then cd tests && $(MAKE) all; fi +@@ -53,7 +52,6 @@ + cd lib && $(MAKE) install prefix='$(prefix)' exec_prefix='$(exec_prefix)' libdir='$(libdir)' + cd preload && $(MAKE) install prefix='$(prefix)' exec_prefix='$(exec_prefix)' libdir='$(libdir)' + cd srclib && $(MAKE) install prefix='$(prefix)' exec_prefix='$(exec_prefix)' libdir='$(libdir)' +- cd src && $(MAKE) install prefix='$(prefix)' exec_prefix='$(exec_prefix)' libdir='$(libdir)' + if [ ! -d $(DESTDIR)$(includedir) ] ; then $(mkinstalldirs) $(DESTDIR)$(includedir) ; fi + $(INSTALL_DATA) include/iconv.h.inst $(DESTDIR)$(includedir)/iconv.h + cd po && $(MAKE) install prefix='$(prefix)' exec_prefix='$(exec_prefix)' datarootdir='$(datarootdir)' datadir='$(datadir)' +@@ -64,7 +62,6 @@ + cd lib && $(MAKE) install-strip prefix='$(prefix)' exec_prefix='$(exec_prefix)' libdir='$(libdir)' + cd preload && $(MAKE) install-strip prefix='$(prefix)' exec_prefix='$(exec_prefix)' libdir='$(libdir)' + cd srclib && $(MAKE) install-strip prefix='$(prefix)' exec_prefix='$(exec_prefix)' libdir='$(libdir)' +- cd src && $(MAKE) install-strip prefix='$(prefix)' exec_prefix='$(exec_prefix)' libdir='$(libdir)' + if [ ! -d $(DESTDIR)$(includedir) ] ; then $(mkinstalldirs) $(DESTDIR)$(includedir) ; fi + $(INSTALL_DATA) include/iconv.h.inst $(DESTDIR)$(includedir)/iconv.h + cd po && $(MAKE) install-strip prefix='$(prefix)' exec_prefix='$(exec_prefix)' datarootdir='$(datarootdir)' datadir='$(datadir)' +@@ -75,7 +72,6 @@ + cd lib && $(MAKE) installdirs prefix='$(prefix)' exec_prefix='$(exec_prefix)' libdir='$(libdir)' + cd preload && $(MAKE) installdirs prefix='$(prefix)' exec_prefix='$(exec_prefix)' libdir='$(libdir)' + cd srclib && $(MAKE) installdirs prefix='$(prefix)' exec_prefix='$(exec_prefix)' libdir='$(libdir)' +- cd src && $(MAKE) installdirs prefix='$(prefix)' exec_prefix='$(exec_prefix)' libdir='$(libdir)' + if [ ! -d $(DESTDIR)$(includedir) ] ; then $(mkinstalldirs) $(DESTDIR)$(includedir) ; fi + cd po && $(MAKE) installdirs prefix='$(prefix)' exec_prefix='$(exec_prefix)' datarootdir='$(datarootdir)' datadir='$(datadir)' + cd man && $(MAKE) installdirs prefix='$(prefix)' exec_prefix='$(exec_prefix)' datarootdir='$(datarootdir)' datadir='$(datadir)' mandir='$(mandir)' +@@ -85,7 +81,6 @@ + cd lib && $(MAKE) uninstall prefix='$(prefix)' exec_prefix='$(exec_prefix)' libdir='$(libdir)' + cd preload && $(MAKE) uninstall prefix='$(prefix)' exec_prefix='$(exec_prefix)' libdir='$(libdir)' + cd srclib && $(MAKE) uninstall prefix='$(prefix)' exec_prefix='$(exec_prefix)' libdir='$(libdir)' +- cd src && $(MAKE) uninstall prefix='$(prefix)' exec_prefix='$(exec_prefix)' libdir='$(libdir)' + $(RM) $(DESTDIR)$(includedir)/iconv.h + cd po && $(MAKE) uninstall prefix='$(prefix)' exec_prefix='$(exec_prefix)' datarootdir='$(datarootdir)' datadir='$(datadir)' + cd man && $(MAKE) uninstall prefix='$(prefix)' exec_prefix='$(exec_prefix)' datarootdir='$(datarootdir)' datadir='$(datadir)' mandir='$(mandir)' +@@ -95,7 +90,6 @@ + cd lib && $(MAKE) check + cd preload && $(MAKE) check + cd srclib && $(MAKE) check +- cd src && $(MAKE) check + cd po && $(MAKE) check + cd man && $(MAKE) check + if test -d tests; then cd tests && $(MAKE) check; fi +@@ -106,7 +100,6 @@ + cd lib && $(MAKE) mostlyclean + cd preload && $(MAKE) mostlyclean + cd srclib && $(MAKE) mostlyclean +- cd src && $(MAKE) mostlyclean + cd po && $(MAKE) mostlyclean + cd man && $(MAKE) mostlyclean + if test -d tests; then cd tests && $(MAKE) mostlyclean; fi +@@ -118,7 +111,6 @@ + cd lib && $(MAKE) clean + cd preload && $(MAKE) clean + cd srclib && $(MAKE) clean +- cd src && $(MAKE) clean + cd po && $(MAKE) clean + cd man && $(MAKE) clean + if test -d tests; then cd tests && $(MAKE) clean; fi +@@ -130,7 +122,6 @@ + cd lib && if test -f Makefile; then $(MAKE) distclean; fi + cd preload && if test -f Makefile; then $(MAKE) distclean; fi + cd srclib && if test -f Makefile; then $(MAKE) distclean; fi +- cd src && if test -f Makefile; then $(MAKE) distclean; fi + cd po && if test -f Makefile; then $(MAKE) distclean; fi + cd man && if test -f Makefile; then $(MAKE) distclean; fi + if test -d tests; then cd tests && if test -f Makefile; then $(MAKE) distclean; fi; fi +@@ -145,7 +136,6 @@ + cd lib && if test -f Makefile; then $(MAKE) maintainer-clean; fi + cd preload && if test -f Makefile; then $(MAKE) maintainer-clean; fi + cd srclib && if test -f Makefile; then $(MAKE) maintainer-clean; fi +- cd src && if test -f Makefile; then $(MAKE) maintainer-clean; fi + cd po && if test -f Makefile; then $(MAKE) maintainer-clean; fi + cd man && if test -f Makefile; then $(MAKE) maintainer-clean; fi + if test -d tests; then cd tests && if test -f Makefile; then $(MAKE) maintainer-clean; fi; fi diff --git a/contrib/src/iconv/libiconv-android-ios.patch b/contrib/src/iconv/libiconv-android-ios.patch new file mode 100644 index 0000000000..5dafffb451 --- /dev/null +++ b/contrib/src/iconv/libiconv-android-ios.patch @@ -0,0 +1,34 @@ +--- iconv/Makefile.in.orig 2013-08-25 00:49:42.477424777 +0200 ++++ iconv/Makefile.in 2013-08-25 00:50:28.117422857 +0200 +@@ -32,10 +32,6 @@ + all : lib/localcharset.h force + cd lib && $(MAKE) all + cd preload && $(MAKE) all +- cd srclib && $(MAKE) all +- cd po && $(MAKE) all +- cd man && $(MAKE) all +- if test -d tests; then cd tests && $(MAKE) all; fi + + lib/localcharset.h : + builddir="`pwd`"; cd libcharset && $(MAKE) all && $(MAKE) install-lib libdir="$$builddir/lib" includedir="$$builddir/lib" +@@ -51,11 +47,8 @@ + cd libcharset && $(MAKE) install prefix='$(prefix)' exec_prefix='$(exec_prefix)' libdir='$(libdir)' + cd lib && $(MAKE) install prefix='$(prefix)' exec_prefix='$(exec_prefix)' libdir='$(libdir)' + cd preload && $(MAKE) install prefix='$(prefix)' exec_prefix='$(exec_prefix)' libdir='$(libdir)' +- cd srclib && $(MAKE) install prefix='$(prefix)' exec_prefix='$(exec_prefix)' libdir='$(libdir)' + if [ ! -d $(DESTDIR)$(includedir) ] ; then $(mkinstalldirs) $(DESTDIR)$(includedir) ; fi + $(INSTALL_DATA) include/iconv.h.inst $(DESTDIR)$(includedir)/iconv.h +- cd po && $(MAKE) install prefix='$(prefix)' exec_prefix='$(exec_prefix)' datarootdir='$(datarootdir)' datadir='$(datadir)' +- cd man && $(MAKE) install prefix='$(prefix)' exec_prefix='$(exec_prefix)' datarootdir='$(datarootdir)' datadir='$(datadir)' mandir='$(mandir)' + + install-strip : lib/localcharset.h force + cd libcharset && $(MAKE) install-strip prefix='$(prefix)' exec_prefix='$(exec_prefix)' libdir='$(libdir)' +@@ -64,8 +57,6 @@ + cd srclib && $(MAKE) install-strip prefix='$(prefix)' exec_prefix='$(exec_prefix)' libdir='$(libdir)' + if [ ! -d $(DESTDIR)$(includedir) ] ; then $(mkinstalldirs) $(DESTDIR)$(includedir) ; fi + $(INSTALL_DATA) include/iconv.h.inst $(DESTDIR)$(includedir)/iconv.h +- cd po && $(MAKE) install-strip prefix='$(prefix)' exec_prefix='$(exec_prefix)' datarootdir='$(datarootdir)' datadir='$(datadir)' +- cd man && $(MAKE) install-strip prefix='$(prefix)' exec_prefix='$(exec_prefix)' datarootdir='$(datarootdir)' datadir='$(datadir)' mandir='$(mandir)' + + installdirs : force + cd libcharset && $(MAKE) installdirs prefix='$(prefix)' exec_prefix='$(exec_prefix)' libdir='$(libdir)' diff --git a/contrib/src/iconv/libiconv-win64.patch b/contrib/src/iconv/libiconv-win64.patch new file mode 100644 index 0000000000..8e65f95bcd --- /dev/null +++ b/contrib/src/iconv/libiconv-win64.patch @@ -0,0 +1,1293 @@ +diff -ruN libiconv/lib/iconv.c libiconv.new/lib/iconv.c +--- libiconv/lib/iconv.c 2009-06-21 13:17:33.000000000 +0200 ++++ libiconv.new/lib/iconv.c 2009-09-02 23:24:06.000000000 +0200 +@@ -21,6 +21,7 @@ + #include <iconv.h> + + #include <stdlib.h> ++#include <stdint.h> + #include <string.h> + #include "config.h" + #include "localcharset.h" +@@ -168,7 +169,7 @@ + }; + #define stringpool2 ((const char *) &stringpool2_contents) + static const struct alias sysdep_aliases[] = { +-#define S(tag,name,encoding_index) { (int)(long)&((struct stringpool2_t *)0)->stringpool_##tag, encoding_index }, ++#define S(tag,name,encoding_index) { (int)(intptr_t)&((struct stringpool2_t *)0)->stringpool_##tag, encoding_index }, + #include "aliases2.h" + #undef S + }; +--- iconv/lib/aliases.h.orig 2013-03-10 12:13:55.861853467 +0100 ++++ iconv.new/lib/aliases.h 2013-03-10 12:12:46.810972684 +0100 +@@ -810,852 +810,852 @@ + { + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + #line 308 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str7, ei_sjis}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str7, ei_sjis}, + {-1}, + #line 288 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str9, ei_iso646_cn}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str9, ei_iso646_cn}, + {-1}, + #line 209 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str11, ei_cp1131}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str11, ei_cp1131}, + #line 354 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str12, ei_johab}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str12, ei_johab}, + #line 207 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str13, ei_cp866}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str13, ei_cp866}, + {-1}, + #line 244 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str15, ei_cp1133}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str15, ei_cp1133}, + {-1}, {-1}, + #line 174 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str18, ei_cp1251}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str18, ei_cp1251}, + #line 205 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str19, ei_cp866}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str19, ei_cp866}, + #line 189 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str20, ei_cp1256}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str20, ei_cp1256}, + #line 203 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str21, ei_cp862}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str21, ei_cp862}, + #line 180 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str22, ei_cp1253}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str22, ei_cp1253}, + {-1}, + #line 323 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str24, ei_cp936}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str24, ei_cp936}, + {-1}, + #line 186 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str26, ei_cp1255}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str26, ei_cp1255}, + #line 201 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str27, ei_cp862}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str27, ei_cp862}, + #line 177 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str28, ei_cp1252}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str28, ei_cp1252}, + {-1}, + #line 51 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str30, ei_c99}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str30, ei_c99}, + {-1}, + #line 311 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str32, ei_cp932}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str32, ei_cp932}, + {-1}, + #line 195 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str34, ei_cp1258}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str34, ei_cp1258}, + {-1}, {-1}, {-1}, {-1}, {-1}, + #line 57 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str40, ei_iso8859_1}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str40, ei_iso8859_1}, + #line 60 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str41, ei_iso8859_1}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str41, ei_iso8859_1}, + #line 134 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str42, ei_iso8859_10}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str42, ei_iso8859_10}, + #line 76 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str43, ei_iso8859_3}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str43, ei_iso8859_3}, + {-1}, + #line 126 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str45, ei_iso8859_9}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str45, ei_iso8859_9}, + #line 68 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str46, ei_iso8859_2}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str46, ei_iso8859_2}, + {-1}, {-1}, + #line 151 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str49, ei_iso8859_14}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str49, ei_iso8859_14}, + {-1}, {-1}, {-1}, + #line 318 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str53, ei_euc_cn}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str53, ei_euc_cn}, + {-1}, {-1}, {-1}, + #line 62 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str57, ei_iso8859_1}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str57, ei_iso8859_1}, + #line 139 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str58, ei_iso8859_11}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str58, ei_iso8859_11}, + #line 102 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str59, ei_iso8859_6}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str59, ei_iso8859_6}, + #line 166 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str60, ei_iso8859_16}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str60, ei_iso8859_16}, + #line 78 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str61, ei_iso8859_3}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str61, ei_iso8859_3}, + #line 145 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str62, ei_iso8859_13}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str62, ei_iso8859_13}, + {-1}, {-1}, + #line 93 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str65, ei_iso8859_5}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str65, ei_iso8859_5}, + #line 159 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str66, ei_iso8859_15}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str66, ei_iso8859_15}, + #line 70 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str67, ei_iso8859_2}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str67, ei_iso8859_2}, + {-1}, {-1}, + #line 317 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str70, ei_euc_cn}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str70, ei_euc_cn}, + {-1}, {-1}, + #line 120 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str73, ei_iso8859_8}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str73, ei_iso8859_8}, + #line 53 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str74, ei_iso8859_1}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str74, ei_iso8859_1}, + #line 137 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str75, ei_iso8859_11}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str75, ei_iso8859_11}, + #line 94 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str76, ei_iso8859_6}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str76, ei_iso8859_6}, + #line 160 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str77, ei_iso8859_16}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str77, ei_iso8859_16}, + #line 71 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str78, ei_iso8859_3}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str78, ei_iso8859_3}, + #line 140 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str79, ei_iso8859_13}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str79, ei_iso8859_13}, + {-1}, + #line 128 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str81, ei_iso8859_9}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str81, ei_iso8859_9}, + #line 87 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str82, ei_iso8859_5}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str82, ei_iso8859_5}, + #line 154 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str83, ei_iso8859_15}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str83, ei_iso8859_15}, + #line 63 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str84, ei_iso8859_2}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str84, ei_iso8859_2}, + #line 286 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str85, ei_iso646_cn}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str85, ei_iso646_cn}, + #line 227 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str86, ei_hp_roman8}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str86, ei_hp_roman8}, + {-1}, + #line 84 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str88, ei_iso8859_4}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str88, ei_iso8859_4}, + {-1}, + #line 114 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str90, ei_iso8859_8}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str90, ei_iso8859_8}, + #line 351 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str91, ei_cp949}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str91, ei_cp949}, + #line 54 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str92, ei_iso8859_1}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str92, ei_iso8859_1}, + #line 138 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str93, ei_iso8859_11}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str93, ei_iso8859_11}, + #line 95 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str94, ei_iso8859_6}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str94, ei_iso8859_6}, + #line 161 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str95, ei_iso8859_16}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str95, ei_iso8859_16}, + #line 72 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str96, ei_iso8859_3}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str96, ei_iso8859_3}, + #line 141 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str97, ei_iso8859_13}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str97, ei_iso8859_13}, + #line 121 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str98, ei_iso8859_9}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str98, ei_iso8859_9}, + #line 162 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str99, ei_iso8859_16}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str99, ei_iso8859_16}, + #line 88 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str100, ei_iso8859_5}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str100, ei_iso8859_5}, + #line 155 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str101, ei_iso8859_15}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str101, ei_iso8859_15}, + #line 64 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str102, ei_iso8859_2}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str102, ei_iso8859_2}, + #line 59 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str103, ei_iso8859_1}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str103, ei_iso8859_1}, + {-1}, + #line 133 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str105, ei_iso8859_10}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str105, ei_iso8859_10}, + #line 236 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str106, ei_pt154}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str106, ei_pt154}, + #line 75 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str107, ei_iso8859_3}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str107, ei_iso8859_3}, + #line 115 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str108, ei_iso8859_8}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str108, ei_iso8859_8}, + {-1}, + #line 156 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str110, ei_iso8859_15}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str110, ei_iso8859_15}, + #line 125 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str111, ei_iso8859_9}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str111, ei_iso8859_9}, + #line 183 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str112, ei_cp1254}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str112, ei_cp1254}, + #line 67 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str113, ei_iso8859_2}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str113, ei_iso8859_2}, + #line 328 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str114, ei_iso2022_cn}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str114, ei_iso2022_cn}, + {-1}, + #line 122 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str116, ei_iso8859_9}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str116, ei_iso8859_9}, + #line 293 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str117, ei_gb2312}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str117, ei_gb2312}, + #line 16 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str118, ei_ascii}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str118, ei_ascii}, + #line 150 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str119, ei_iso8859_14}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str119, ei_iso8859_14}, + #line 13 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str120, ei_ascii}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str120, ei_ascii}, + #line 252 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str121, ei_tis620}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str121, ei_tis620}, + #line 282 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str122, ei_jisx0212}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str122, ei_jisx0212}, + {-1}, + #line 255 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str124, ei_viscii}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str124, ei_viscii}, + #line 107 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str125, ei_iso8859_7}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str125, ei_iso8859_7}, + #line 22 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str126, ei_ascii}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str126, ei_ascii}, + #line 294 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str127, ei_isoir165}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str127, ei_isoir165}, + {-1}, + #line 257 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str129, ei_viscii}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str129, ei_viscii}, + #line 163 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str130, ei_iso8859_16}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str130, ei_iso8859_16}, + #line 212 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str131, ei_mac_roman}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str131, ei_mac_roman}, + {-1}, {-1}, {-1}, {-1}, + #line 117 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str136, ei_iso8859_8}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str136, ei_iso8859_8}, + #line 291 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str137, ei_gb2312}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str137, ei_gb2312}, + {-1}, + #line 206 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str139, ei_cp866}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str139, ei_cp866}, + {-1}, {-1}, + #line 327 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str142, ei_iso2022_cn}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str142, ei_iso2022_cn}, + #line 324 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str143, ei_cp936}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str143, ei_cp936}, + #line 158 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str144, ei_iso8859_15}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str144, ei_iso8859_15}, + {-1}, + #line 283 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str146, ei_jisx0212}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str146, ei_jisx0212}, + #line 202 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str147, ei_cp862}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str147, ei_cp862}, + {-1}, {-1}, + #line 21 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str150, ei_ascii}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str150, ei_ascii}, + #line 86 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str151, ei_iso8859_4}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str151, ei_iso8859_4}, + #line 153 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str152, ei_iso8859_14}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str152, ei_iso8859_14}, + #line 148 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str153, ei_iso8859_14}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str153, ei_iso8859_14}, + #line 149 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str154, ei_iso8859_14}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str154, ei_iso8859_14}, + {-1}, + #line 352 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str156, ei_cp949}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str156, ei_cp949}, + #line 199 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str157, ei_cp850}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str157, ei_cp850}, + {-1}, + #line 330 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str159, ei_hz}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str159, ei_hz}, + #line 58 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str160, ei_iso8859_1}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str160, ei_iso8859_1}, + {-1}, + #line 152 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str162, ei_iso8859_14}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str162, ei_iso8859_14}, + #line 109 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str163, ei_iso8859_7}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str163, ei_iso8859_7}, + #line 171 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str164, ei_cp1250}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str164, ei_cp1250}, + #line 319 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str165, ei_euc_cn}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str165, ei_euc_cn}, + #line 197 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str166, ei_cp850}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str166, ei_cp850}, + {-1}, + #line 79 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str168, ei_iso8859_4}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str168, ei_iso8859_4}, + #line 146 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str169, ei_iso8859_14}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str169, ei_iso8859_14}, + #line 341 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str170, ei_cp950}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str170, ei_cp950}, + #line 91 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str171, ei_iso8859_5}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str171, ei_iso8859_5}, + {-1}, {-1}, {-1}, {-1}, + #line 131 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str176, ei_iso8859_10}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str176, ei_iso8859_10}, + {-1}, {-1}, + #line 24 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str179, ei_ucs2}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str179, ei_ucs2}, + #line 258 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str180, ei_tcvn}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str180, ei_tcvn}, + #line 124 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str181, ei_iso8859_9}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str181, ei_iso8859_9}, + {-1}, {-1}, {-1}, + #line 269 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str185, ei_jisx0201}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str185, ei_jisx0201}, + #line 80 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str186, ei_iso8859_4}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str186, ei_iso8859_4}, + #line 147 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str187, ei_iso8859_14}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str187, ei_iso8859_14}, + #line 165 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str188, ei_iso8859_16}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str188, ei_iso8859_16}, + #line 299 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str189, ei_ksc5601}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str189, ei_ksc5601}, + {-1}, + #line 66 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str191, ei_iso8859_2}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str191, ei_iso8859_2}, + {-1}, {-1}, {-1}, {-1}, + #line 329 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str196, ei_iso2022_cn_ext}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str196, ei_iso2022_cn_ext}, + #line 83 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str197, ei_iso8859_4}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str197, ei_iso8859_4}, + {-1}, {-1}, + #line 157 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str200, ei_iso8859_15}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str200, ei_iso8859_15}, + #line 275 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str201, ei_jisx0208}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str201, ei_jisx0208}, + #line 296 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str202, ei_ksc5601}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str202, ei_ksc5601}, + {-1}, + #line 136 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str204, ei_iso8859_10}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str204, ei_iso8859_10}, + {-1}, {-1}, + #line 256 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str207, ei_viscii}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str207, ei_viscii}, + {-1}, + #line 144 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str209, ei_iso8859_13}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str209, ei_iso8859_13}, + {-1}, + #line 264 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str211, ei_iso646_jp}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str211, ei_iso646_jp}, + #line 234 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str212, ei_pt154}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str212, ei_pt154}, + #line 247 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str213, ei_tis620}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str213, ei_tis620}, + {-1}, + #line 74 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str215, ei_iso8859_3}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str215, ei_iso8859_3}, + #line 30 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str216, ei_ucs2be}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str216, ei_ucs2be}, + #line 233 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str217, ei_koi8_t}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str217, ei_koi8_t}, + #line 239 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str218, ei_rk1048}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str218, ei_rk1048}, + {-1}, {-1}, + #line 129 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str221, ei_iso8859_10}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str221, ei_iso8859_10}, + #line 251 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str222, ei_tis620}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str222, ei_tis620}, + #line 14 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str223, ei_ascii}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str223, ei_ascii}, + #line 61 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str224, ei_iso8859_1}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str224, ei_iso8859_1}, + {-1}, + #line 135 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str226, ei_iso8859_10}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str226, ei_iso8859_10}, + {-1}, + #line 77 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str228, ei_iso8859_3}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str228, ei_iso8859_3}, + {-1}, + #line 246 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str230, ei_tis620}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str230, ei_tis620}, + {-1}, + #line 127 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str232, ei_iso8859_9}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str232, ei_iso8859_9}, + {-1}, + #line 69 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str234, ei_iso8859_2}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str234, ei_iso8859_2}, + #line 249 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str235, ei_tis620}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str235, ei_tis620}, + #line 242 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str236, ei_rk1048}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str236, ei_rk1048}, + #line 92 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str237, ei_iso8859_5}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str237, ei_iso8859_5}, + #line 241 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str238, ei_rk1048}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str238, ei_rk1048}, + #line 130 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str239, ei_iso8859_10}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str239, ei_iso8859_10}, + {-1}, + #line 29 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str241, ei_ucs2be}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str241, ei_ucs2be}, + #line 38 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str242, ei_utf16}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str242, ei_utf16}, + {-1}, {-1}, + #line 173 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str245, ei_cp1250}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str245, ei_cp1250}, + {-1}, {-1}, + #line 26 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str248, ei_ucs2}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str248, ei_ucs2}, + #line 168 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str249, ei_koi8_r}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str249, ei_koi8_r}, + #line 164 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str250, ei_iso8859_16}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str250, ei_iso8859_16}, + {-1}, + #line 41 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str252, ei_utf32}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str252, ei_utf32}, + {-1}, + #line 35 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str254, ei_ucs4}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str254, ei_ucs4}, + #line 23 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str255, ei_utf8}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str255, ei_utf8}, + {-1}, {-1}, {-1}, + #line 90 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str259, ei_iso8859_5}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str259, ei_iso8859_5}, + {-1}, + #line 167 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str261, ei_koi8_r}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str261, ei_koi8_r}, + #line 179 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str262, ei_cp1252}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str262, ei_cp1252}, + #line 33 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str263, ei_ucs4}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str263, ei_ucs4}, + #line 82 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str264, ei_iso8859_4}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str264, ei_iso8859_4}, + {-1}, + #line 245 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str266, ei_cp1133}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str266, ei_cp1133}, + #line 208 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str267, ei_cp866}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str267, ei_cp866}, + {-1}, {-1}, + #line 298 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str270, ei_ksc5601}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str270, ei_ksc5601}, + #line 357 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str271, ei_local_char}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str271, ei_local_char}, + {-1}, + #line 349 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str273, ei_euc_kr}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str273, ei_euc_kr}, + {-1}, {-1}, {-1}, + #line 335 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str277, ei_ces_big5}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str277, ei_ces_big5}, + #line 253 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str278, ei_cp874}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str278, ei_cp874}, + #line 230 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str279, ei_armscii_8}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str279, ei_armscii_8}, + {-1}, {-1}, + #line 340 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str282, ei_ces_big5}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str282, ei_ces_big5}, + #line 31 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str283, ei_ucs2le}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str283, ei_ucs2le}, + {-1}, {-1}, + #line 198 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str286, ei_cp850}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str286, ei_cp850}, + #line 12 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str287, ei_ascii}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str287, ei_ascii}, + {-1}, {-1}, + #line 348 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str290, ei_euc_kr}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str290, ei_euc_kr}, + {-1}, {-1}, + #line 321 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str293, ei_euc_cn}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str293, ei_euc_cn}, + #line 336 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str294, ei_ces_big5}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str294, ei_ces_big5}, + #line 250 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str295, ei_tis620}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str295, ei_tis620}, + {-1}, {-1}, {-1}, + #line 339 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str299, ei_ces_big5}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str299, ei_ces_big5}, + {-1}, {-1}, + #line 218 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str302, ei_mac_cyrillic}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str302, ei_mac_cyrillic}, + #line 322 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str303, ei_ces_gbk}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str303, ei_ces_gbk}, + #line 248 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str304, ei_tis620}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str304, ei_tis620}, + #line 176 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str305, ei_cp1251}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str305, ei_cp1251}, + {-1}, + #line 237 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str307, ei_pt154}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str307, ei_pt154}, + #line 108 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str308, ei_iso8859_7}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str308, ei_iso8859_7}, + {-1}, + #line 142 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str310, ei_iso8859_13}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str310, ei_iso8859_13}, + #line 110 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str311, ei_iso8859_7}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str311, ei_iso8859_7}, + {-1}, {-1}, {-1}, + #line 301 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str315, ei_ksc5601}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str315, ei_ksc5601}, + {-1}, {-1}, + #line 85 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str318, ei_iso8859_4}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str318, ei_iso8859_4}, + {-1}, {-1}, + #line 25 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str321, ei_ucs2}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str321, ei_ucs2}, + {-1}, {-1}, {-1}, + #line 37 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str325, ei_ucs4le}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str325, ei_ucs4le}, + #line 235 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str326, ei_pt154}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str326, ei_pt154}, + {-1}, {-1}, {-1}, + #line 266 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str330, ei_iso646_jp}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str330, ei_iso646_jp}, + {-1}, {-1}, {-1}, + #line 356 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str334, ei_iso2022_kr}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str334, ei_iso2022_kr}, + {-1}, + #line 226 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str336, ei_hp_roman8}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str336, ei_hp_roman8}, + #line 56 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str337, ei_iso8859_1}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str337, ei_iso8859_1}, + {-1}, {-1}, + #line 277 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str340, ei_jisx0208}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str340, ei_jisx0208}, + {-1}, {-1}, {-1}, + #line 101 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str344, ei_iso8859_6}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str344, ei_iso8859_6}, + {-1}, {-1}, + #line 19 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str347, ei_ascii}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str347, ei_ascii}, + {-1}, {-1}, + #line 40 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str350, ei_utf16le}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str350, ei_utf16le}, + #line 15 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str351, ei_ascii}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str351, ei_ascii}, + {-1}, {-1}, + #line 192 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str354, ei_cp1257}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str354, ei_cp1257}, + #line 215 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str355, ei_mac_iceland}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str355, ei_mac_iceland}, + #line 43 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str356, ei_utf32le}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str356, ei_utf32le}, + #line 300 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str357, ei_ksc5601}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str357, ei_ksc5601}, + {-1}, + #line 100 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str359, ei_iso8859_6}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str359, ei_iso8859_6}, + {-1}, {-1}, + #line 355 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str362, ei_iso2022_kr}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str362, ei_iso2022_kr}, + #line 34 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str363, ei_ucs4}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str363, ei_ucs4}, + {-1}, {-1}, {-1}, + #line 27 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str367, ei_ucs2be}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str367, ei_ucs2be}, + #line 290 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str368, ei_gb2312}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str368, ei_gb2312}, + #line 265 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str369, ei_iso646_jp}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str369, ei_iso646_jp}, + {-1}, + #line 243 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str371, ei_mulelao}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str371, ei_mulelao}, + #line 284 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str372, ei_jisx0212}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str372, ei_jisx0212}, + #line 111 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str373, ei_iso8859_7}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str373, ei_iso8859_7}, + {-1}, + #line 260 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str375, ei_tcvn}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str375, ei_tcvn}, + #line 292 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str376, ei_gb2312}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str376, ei_gb2312}, + {-1}, + #line 326 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str378, ei_gb18030}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str378, ei_gb18030}, + #line 259 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str379, ei_tcvn}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str379, ei_tcvn}, + {-1}, {-1}, {-1}, {-1}, + #line 285 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str384, ei_iso646_cn}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str384, ei_iso646_cn}, + #line 238 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str385, ei_pt154}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str385, ei_pt154}, + #line 98 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str386, ei_iso8859_6}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str386, ei_iso8859_6}, + {-1}, + #line 46 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str388, ei_utf7}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str388, ei_utf7}, + {-1}, {-1}, + #line 18 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str391, ei_ascii}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str391, ei_ascii}, + #line 32 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str392, ei_ucs2le}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str392, ei_ucs2le}, + #line 113 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str393, ei_iso8859_7}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str393, ei_iso8859_7}, + {-1}, + #line 295 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str395, ei_isoir165}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str395, ei_isoir165}, + #line 240 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str396, ei_rk1048}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str396, ei_rk1048}, + {-1}, + #line 17 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str398, ei_ascii}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str398, ei_ascii}, + {-1}, {-1}, {-1}, {-1}, + #line 169 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str403, ei_koi8_u}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str403, ei_koi8_u}, + {-1}, {-1}, + #line 47 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str406, ei_ucs2internal}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str406, ei_ucs2internal}, + {-1}, {-1}, + #line 36 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str409, ei_ucs4be}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str409, ei_ucs4be}, + #line 103 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str410, ei_iso8859_7}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str410, ei_iso8859_7}, + #line 307 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str411, ei_sjis}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str411, ei_sjis}, + #line 320 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str412, ei_euc_cn}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str412, ei_euc_cn}, + #line 262 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str413, ei_iso646_jp}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str413, ei_iso646_jp}, + {-1}, + #line 45 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str415, ei_utf7}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str415, ei_utf7}, + #line 175 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str416, ei_cp1251}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str416, ei_cp1251}, + #line 190 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str417, ei_cp1256}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str417, ei_cp1256}, + #line 181 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str418, ei_cp1253}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str418, ei_cp1253}, + {-1}, + #line 187 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str420, ei_cp1255}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str420, ei_cp1255}, + #line 178 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str421, ei_cp1252}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str421, ei_cp1252}, + #line 325 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str422, ei_cp936}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str422, ei_cp936}, + {-1}, + #line 196 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str424, ei_cp1258}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str424, ei_cp1258}, + #line 350 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str425, ei_euc_kr}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str425, ei_euc_kr}, + #line 297 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str426, ei_ksc5601}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str426, ei_ksc5601}, + {-1}, + #line 104 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str428, ei_iso8859_7}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str428, ei_iso8859_7}, + #line 306 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str429, ei_sjis}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str429, ei_sjis}, + {-1}, {-1}, {-1}, + #line 274 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str433, ei_jisx0208}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str433, ei_jisx0208}, + #line 39 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str434, ei_utf16be}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str434, ei_utf16be}, + {-1}, {-1}, {-1}, {-1}, + #line 143 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str439, ei_iso8859_13}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str439, ei_iso8859_13}, + #line 42 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str440, ei_utf32be}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str440, ei_utf32be}, + {-1}, {-1}, {-1}, {-1}, + #line 224 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str445, ei_mac_thai}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str445, ei_mac_thai}, + {-1}, {-1}, + #line 49 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str448, ei_ucs4internal}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str448, ei_ucs4internal}, + #line 112 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str449, ei_iso8859_7}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str449, ei_iso8859_7}, + {-1}, + #line 210 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str451, ei_mac_roman}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str451, ei_mac_roman}, + #line 304 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str452, ei_euc_jp}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str452, ei_euc_jp}, + {-1}, {-1}, {-1}, + #line 333 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str456, ei_euc_tw}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str456, ei_euc_tw}, + #line 287 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str457, ei_iso646_cn}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str457, ei_iso646_cn}, + #line 132 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str458, ei_iso8859_10}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str458, ei_iso8859_10}, + #line 97 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str459, ei_iso8859_6}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str459, ei_iso8859_6}, + {-1}, + #line 276 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str461, ei_jisx0208}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str461, ei_jisx0208}, + {-1}, + #line 184 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str463, ei_cp1254}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str463, ei_cp1254}, + #line 73 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str464, ei_iso8859_3}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str464, ei_iso8859_3}, + {-1}, + #line 89 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str466, ei_iso8859_5}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str466, ei_iso8859_5}, + #line 20 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str467, ei_ascii}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str467, ei_ascii}, + {-1}, {-1}, + #line 116 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str470, ei_iso8859_8}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str470, ei_iso8859_8}, + #line 331 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str471, ei_hz}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str471, ei_hz}, + {-1}, + #line 332 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str473, ei_euc_tw}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str473, ei_euc_tw}, + #line 289 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str474, ei_iso646_cn}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str474, ei_iso646_cn}, + #line 229 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str475, ei_nextstep}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str475, ei_nextstep}, + #line 316 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str476, ei_iso2022_jp2}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str476, ei_iso2022_jp2}, + {-1}, + #line 123 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str478, ei_iso8859_9}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str478, ei_iso8859_9}, + {-1}, + #line 170 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str480, ei_koi8_ru}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str480, ei_koi8_ru}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + #line 211 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str487, ei_mac_roman}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str487, ei_mac_roman}, + {-1}, + #line 172 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str489, ei_cp1250}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str489, ei_cp1250}, + {-1}, {-1}, + #line 279 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str492, ei_jisx0212}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str492, ei_jisx0212}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + #line 314 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str500, ei_iso2022_jp1}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str500, ei_iso2022_jp1}, + #line 216 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str501, ei_mac_croatian}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str501, ei_mac_croatian}, + #line 225 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str502, ei_hp_roman8}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str502, ei_hp_roman8}, + {-1}, {-1}, + #line 315 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str505, ei_iso2022_jp2}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str505, ei_iso2022_jp2}, + {-1}, {-1}, {-1}, + #line 81 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str509, ei_iso8859_4}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str509, ei_iso8859_4}, + #line 346 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str510, ei_big5hkscs2008}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str510, ei_big5hkscs2008}, + {-1}, {-1}, {-1}, {-1}, + #line 99 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str515, ei_iso8859_6}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str515, ei_iso8859_6}, + {-1}, {-1}, + #line 303 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str518, ei_euc_jp}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str518, ei_euc_jp}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + #line 338 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str525, ei_ces_big5}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str525, ei_ces_big5}, + {-1}, + #line 345 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str527, ei_big5hkscs2008}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str527, ei_big5hkscs2008}, + {-1}, {-1}, {-1}, + #line 214 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str531, ei_mac_centraleurope}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str531, ei_mac_centraleurope}, + #line 204 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str532, ei_cp862}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str532, ei_cp862}, + {-1}, {-1}, + #line 302 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str535, ei_euc_jp}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str535, ei_euc_jp}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + #line 337 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str542, ei_ces_big5}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str542, ei_ces_big5}, + {-1}, {-1}, {-1}, + #line 310 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str546, ei_sjis}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str546, ei_sjis}, + {-1}, {-1}, {-1}, + #line 263 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str550, ei_iso646_jp}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str550, ei_iso646_jp}, + {-1}, {-1}, {-1}, + #line 268 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str554, ei_jisx0201}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str554, ei_jisx0201}, + #line 267 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str555, ei_jisx0201}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str555, ei_jisx0201}, + #line 119 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str556, ei_iso8859_8}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str556, ei_iso8859_8}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + #line 223 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str563, ei_mac_arabic}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str563, ei_mac_arabic}, + #line 278 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str564, ei_jisx0208}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str564, ei_jisx0208}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + #line 271 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str571, ei_jisx0208}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str571, ei_jisx0208}, + {-1}, {-1}, {-1}, + #line 44 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str575, ei_utf7}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str575, ei_utf7}, + {-1}, + #line 220 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str577, ei_mac_greek}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str577, ei_mac_greek}, + {-1}, + #line 313 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str579, ei_iso2022_jp}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str579, ei_iso2022_jp}, + #line 185 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str580, ei_cp1254}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str580, ei_cp1254}, + #line 281 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str581, ei_jisx0212}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str581, ei_jisx0212}, + {-1}, {-1}, + #line 193 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str584, ei_cp1257}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str584, ei_cp1257}, + {-1}, + #line 272 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str586, ei_jisx0208}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str586, ei_jisx0208}, + {-1}, {-1}, {-1}, + #line 182 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str590, ei_cp1253}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str590, ei_cp1253}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + #line 228 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str599, ei_hp_roman8}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str599, ei_hp_roman8}, + #line 52 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str600, ei_java}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str600, ei_java}, + #line 188 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str601, ei_cp1255}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str601, ei_cp1255}, + {-1}, {-1}, + #line 213 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str604, ei_mac_roman}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str604, ei_mac_roman}, + {-1}, {-1}, + #line 312 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str607, ei_iso2022_jp}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str607, ei_iso2022_jp}, + #line 334 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str608, ei_euc_tw}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str608, ei_euc_tw}, + {-1}, {-1}, {-1}, {-1}, {-1}, + #line 232 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str614, ei_georgian_ps}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str614, ei_georgian_ps}, + #line 28 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str615, ei_ucs2be}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str615, ei_ucs2be}, + {-1}, + #line 309 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str617, ei_sjis}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str617, ei_sjis}, + {-1}, {-1}, + #line 200 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str620, ei_cp850}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str620, ei_cp850}, + #line 219 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str621, ei_mac_ukraine}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str621, ei_mac_ukraine}, + #line 55 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str622, ei_iso8859_1}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str622, ei_iso8859_1}, + #line 96 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str623, ei_iso8859_6}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str623, ei_iso8859_6}, + #line 106 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str624, ei_iso8859_7}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str624, ei_iso8859_7}, + {-1}, + #line 231 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str626, ei_georgian_academy}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str626, ei_georgian_academy}, + #line 65 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str627, ei_iso8859_2}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str627, ei_iso8859_2}, + {-1}, + #line 280 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str629, ei_jisx0212}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str629, ei_jisx0212}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + #line 273 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str657, ei_jisx0208}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str657, ei_jisx0208}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + #line 358 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str664, ei_local_wchar_t}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str664, ei_local_wchar_t}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + #line 217 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str673, ei_mac_romania}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str673, ei_mac_romania}, + {-1}, {-1}, + #line 254 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str676, ei_cp874}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str676, ei_cp874}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, + #line 305 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str689, ei_euc_jp}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str689, ei_euc_jp}, + {-1}, + #line 191 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str691, ei_cp1256}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str691, ei_cp1256}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, + #line 48 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str723, ei_ucs2swapped}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str723, ei_ucs2swapped}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + #line 261 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str739, ei_tcvn}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str739, ei_tcvn}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + #line 118 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str746, ei_iso8859_8}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str746, ei_iso8859_8}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + #line 50 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str765, ei_ucs4swapped}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str765, ei_ucs4swapped}, + {-1}, {-1}, + #line 353 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str768, ei_johab}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str768, ei_johab}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + #line 221 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str786, ei_mac_turkish}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str786, ei_mac_turkish}, + {-1}, {-1}, {-1}, + #line 105 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str790, ei_iso8859_7}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str790, ei_iso8859_7}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, +@@ -1663,31 +1663,31 @@ + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + #line 194 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str842, ei_cp1257}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str842, ei_cp1257}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + #line 343 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str888, ei_big5hkscs2001}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str888, ei_big5hkscs2001}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + #line 347 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str896, ei_big5hkscs2008}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str896, ei_big5hkscs2008}, + {-1}, + #line 270 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str898, ei_jisx0201}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str898, ei_jisx0201}, + {-1}, + #line 342 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str900, ei_big5hkscs1999}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str900, ei_big5hkscs1999}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + #line 222 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str908, ei_mac_hebrew}, ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str908, ei_mac_hebrew}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, {-1}, + #line 344 "lib/aliases.gperf" +- {(int)(long)&((struct stringpool_t *)0)->stringpool_str935, ei_big5hkscs2004} ++ {(int)(intptr_t)&((struct stringpool_t *)0)->stringpool_str935, ei_big5hkscs2004} + }; + + #ifdef __GNUC__ +--- iconv/srclib/malloca.c.orig 2013-03-10 12:16:34.030538010 +0100 ++++ iconv/srclib/malloca.c 2013-03-10 12:16:32.980524613 +0100 +@@ -77,7 +77,7 @@ + + if (p != NULL) + { +- size_t slot; ++ uintptr_t slot; + + p += HEADER_SIZE; + +@@ -85,7 +85,7 @@ + ((int *) p)[-1] = MAGIC_NUMBER; + + /* Enter p into the hash table. */ +- slot = (unsigned long) p % HASH_TABLE_SIZE; ++ slot = (uintptr_t) p % HASH_TABLE_SIZE; + ((struct header *) (p - HEADER_SIZE))->next = mmalloca_results[slot]; + mmalloca_results[slot] = p; + +@@ -118,7 +118,7 @@ + { + /* Looks like a mmalloca() result. To see whether it really is one, + perform a lookup in the hash table. */ +- size_t slot = (unsigned long) p % HASH_TABLE_SIZE; ++ uintptr_t slot = (uintptr_t) p % HASH_TABLE_SIZE; + void **chain = &mmalloca_results[slot]; + for (; *chain != NULL;) + { +--- iconv.new/srclib/malloca.h 2011-08-07 15:42:06.000000000 +0200 ++++ iconv/srclib/malloca.h 2013-03-10 12:50:30.804222836 +0100 +@@ -22,6 +22,7 @@ + #include <alloca.h> + #include <stddef.h> + #include <stdlib.h> ++#include <stdint.h> + + + #ifdef __cplusplus diff --git a/contrib/src/iconv/libiconv-winrt.patch b/contrib/src/iconv/libiconv-winrt.patch new file mode 100644 index 0000000000..c40495d6d2 --- /dev/null +++ b/contrib/src/iconv/libiconv-winrt.patch @@ -0,0 +1,11 @@ +--- iconv/libcharset/lib/localcharset.c 2014-03-10 20:39:14.105914067 +0100 ++++ iconv/libcharset/lib/localcharset.c.new 2014-03-10 20:38:25.601822680 +0100 +@@ -465,7 +465,7 @@ + GetConsoleOutputCP() encoding if it is using a TrueType font. + But in GUI programs and for output sent to files and pipes, GetACP() + encoding is the best bet. */ +- sprintf (buf, "CP%u", GetACP ()); ++ sprintf (buf, "CP%u", 65001); + codeset = buf; + + #elif defined OS2 diff --git a/contrib/src/iconv/rules.mak b/contrib/src/iconv/rules.mak new file mode 100644 index 0000000000..3596463523 --- /dev/null +++ b/contrib/src/iconv/rules.mak @@ -0,0 +1,40 @@ +# libiconv +LIBICONV_VERSION=1.14 +LIBICONV_URL=$(GNU)/libiconv/libiconv-$(LIBICONV_VERSION).tar.gz + +PKGS += iconv +# iconv cannot be detect with pkg-config, but it is mandated by POSIX. +# Hard-code based on the operating system. +ifndef HAVE_WIN32 +PKGS_FOUND += iconv +endif + +$(TARBALLS)/libiconv-$(LIBICONV_VERSION).tar.gz: + $(call download,$(LIBICONV_URL)) + +.sum-iconv: libiconv-$(LIBICONV_VERSION).tar.gz + +iconv: libiconv-$(LIBICONV_VERSION).tar.gz .sum-iconv + $(UNPACK) + $(APPLY) $(SRC)/iconv/win32.patch + $(APPLY) $(SRC)/iconv/bins.patch +ifdef HAVE_WIN64 + $(APPLY) $(SRC)/iconv/libiconv-win64.patch +endif +ifdef HAVE_ANDROID + $(APPLY) $(SRC)/iconv/libiconv-android-ios.patch +endif +ifdef HAVE_IOS + $(APPLY) $(SRC)/iconv/libiconv-android-ios.patch +endif +ifdef HAVE_WINRT + $(APPLY) $(SRC)/iconv/libiconv-winrt.patch +endif + $(UPDATE_AUTOCONFIG) && cd $(UNPACK_DIR) && mv config.guess config.sub build-aux + $(UPDATE_AUTOCONFIG) && cd $(UNPACK_DIR) && mv config.guess config.sub libcharset/build-aux + $(MOVE) + +.iconv: iconv + cd $< && $(HOSTVARS) ./configure $(HOSTCONF) --disable-nls + cd $< && $(MAKE) install + touch $@ diff --git a/contrib/src/iconv/win32.patch b/contrib/src/iconv/win32.patch new file mode 100644 index 0000000000..cb5570dd1a --- /dev/null +++ b/contrib/src/iconv/win32.patch @@ -0,0 +1,17 @@ +--- iconv/srclib/stdio-write.c.orig 2013-08-15 11:50:20.508093720 +0200 ++++ iconv/srclib/stdio-write.c 2013-08-15 11:51:36.344096864 +0200 +@@ -20,6 +20,14 @@ + /* Specification. */ + #include <stdio.h> + ++#ifdef _WIN32 ++#include <winapifamily.h> ++ ++#if !WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) ++#define GetFileType(h) FILE_TYPE_UNKNOWN ++#endif ++#endif ++ + /* Replace these functions only if module 'nonblocking' or module 'sigpipe' is + requested. */ + #if GNULIB_NONBLOCKING || GNULIB_SIGPIPE diff --git a/contrib/src/jack/SHA512SUMS b/contrib/src/jack/SHA512SUMS new file mode 100644 index 0000000000..84f4f8a23d --- /dev/null +++ b/contrib/src/jack/SHA512SUMS @@ -0,0 +1 @@ +a52655ca895e37a49a14cb72811f25c65c54bc26ae6d4ce35331b8b85f87905f1be25a88b2efb96c47b344abd0a6bf9371e4043e05ccf91cb08287835e5cc8ac jack1-0.121.3.tar.gz diff --git a/contrib/src/jack/config-osx.patch b/contrib/src/jack/config-osx.patch new file mode 100644 index 0000000000..fd1004f9ba --- /dev/null +++ b/contrib/src/jack/config-osx.patch @@ -0,0 +1,25 @@ +From 7bbaa8a2541987ae8db90819f4a94b9f3b133b24 Mon Sep 17 00:00:00 2001 +From: Tristan Matthews <tristan.matthews@savoirfairelinux.com> +Date: Fri, 31 Oct 2014 16:29:26 -0400 +Subject: [PATCH 1/1] config: patch to build on OS X + +--- + config/os/macosx/pThreadUtilities.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/config/os/macosx/pThreadUtilities.h b/config/os/macosx/pThreadUtilities.h +index bd1d1e8..cdb59b4 100644 +--- a/config/os/macosx/pThreadUtilities.h ++++ b/config/os/macosx/pThreadUtilities.h +@@ -66,7 +66,7 @@ + #define __PTHREADUTILITIES_H__ + + #import "pthread.h" +-#import <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacTypes.h> ++#import <MacTypes.h> + + #define THREAD_SET_PRIORITY 0 + #define THREAD_SCHEDULED_PRIORITY 1 +-- +1.9.3 + diff --git a/contrib/src/jack/rules.mak b/contrib/src/jack/rules.mak new file mode 100644 index 0000000000..6798c3cbad --- /dev/null +++ b/contrib/src/jack/rules.mak @@ -0,0 +1,29 @@ +# JACK + +JACK_VERSION := 0.121.3 +JACK_URL := https://github.com/jackaudio/jack1/archive/$(JACK_VERSION).tar.gz + +# disabled by default for now +#PKGS += jack +ifeq ($(call need_pkg,"jack"),) +PKGS_FOUND += jack +endif + +$(TARBALLS)/jack1-$(JACK_VERSION).tar.gz: + $(call download,$(JACK_URL)) + +.sum-jack: jack1-$(JACK_VERSION).tar.gz + +jack: jack1-$(JACK_VERSION).tar.gz .sum-jack + $(UNPACK) +ifdef HAVE_MACOSX + $(APPLY) $(SRC)/jack/config-osx.patch +endif + $(UPDATE_AUTOCONFIG) + $(MOVE) + +.jack: jack + $(RECONF) + cd $< && $(HOSTVARS) ./configure $(HOSTCONF) + cd $< && $(MAKE) install + touch $@ diff --git a/contrib/src/libav/osx.patch b/contrib/src/libav/osx.patch new file mode 100644 index 0000000000..b2632429d2 --- /dev/null +++ b/contrib/src/libav/osx.patch @@ -0,0 +1,765 @@ +diff --git a/Makefile b/Makefile +index cc016b3..183617e 100644 +--- a/Makefile ++++ b/Makefile +@@ -1,6 +1,7 @@ + include config.mak + + vpath %.c $(SRC_PATH) ++vpath %.m $(SRC_PATH) + vpath %.h $(SRC_PATH) + vpath %.S $(SRC_PATH) + vpath %.asm $(SRC_PATH) +@@ -26,6 +27,8 @@ IFLAGS := -I. -I$(SRC_PATH) + CPPFLAGS := $(IFLAGS) $(CPPFLAGS) + CFLAGS += $(ECFLAGS) + CCFLAGS = $(CPPFLAGS) $(CFLAGS) ++OBJCFLAGS += $(EOBJCFLAGS) ++OBJCCFLAGS = $(CPPFLAGS) $(CFLAGS) $(OBJCFLAGS) + ASFLAGS := $(CPPFLAGS) $(ASFLAGS) + YASMFLAGS += $(IFLAGS:%=%/) -Pconfig.asm + HOSTCCFLAGS = $(IFLAGS) $(HOSTCPPFLAGS) $(HOSTCFLAGS) +@@ -38,6 +41,7 @@ endef + + COMPILE_C = $(call COMPILE,CC) + COMPILE_S = $(call COMPILE,AS) ++COMPILE_M = $(call COMPILE,OBJCC) + COMPILE_HOSTC = $(call COMPILE,HOSTCC) + + %.o: %.c +@@ -46,6 +50,9 @@ COMPILE_HOSTC = $(call COMPILE,HOSTCC) + %.o: %.S + $(COMPILE_S) + ++%.o: %.m ++ $(COMPILE_M) ++ + %_host.o: %.c + $(COMPILE_HOSTC) + +diff --git a/configure b/configure +index 13245f7..a293cc0 100755 +--- a/configure ++++ b/configure +@@ -238,6 +238,7 @@ Toolchain options: + --ar=AR use archive tool AR [$ar_default] + --as=AS use assembler AS [$as_default] + --cc=CC use C compiler CC [$cc_default] ++ --objcc=OCC use ObjC compiler OCC [$cc_default] + --dep-cc=DEPCC use dependency generator DEPCC [$cc_default] + --ld=LD use linker LD + --pkg-config=PKGCONFIG use pkg-config tool PKGCONFIG [$pkg_config_default] +@@ -250,6 +251,7 @@ Toolchain options: + --host-libs=HLIBS use libs HLIBS when linking for host + --host-os=OS compiler host OS [$target_os] + --extra-cflags=ECFLAGS add ECFLAGS to CFLAGS [$CFLAGS] ++ --extra-objcflags=FLAGS add FLAGS to OBJCFLAGS [$CFLAGS] + --extra-ldflags=ELDFLAGS add ELDFLAGS to LDFLAGS [$LDFLAGS] + --extra-ldexeflags=ELDFLAGS add ELDFLAGS to LDEXEFLAGS [$LDEXEFLAGS] + --extra-libs=ELIBS add ELIBS [$ELIBS] +@@ -683,6 +685,10 @@ add_asflags(){ + append ASFLAGS $($asflags_filter "$@") + } + ++add_objcflags(){ ++ append OBJCFLAGS $($objcflags_filter "$@") ++} ++ + add_ldflags(){ + append LDFLAGS $($ldflags_filter "$@") + } +@@ -737,6 +743,13 @@ check_cc(){ + check_cmd $cc $CPPFLAGS $CFLAGS "$@" $CC_C $(cc_o $TMPO) $TMPC + } + ++check_objcc(){ ++ log check_objcc "$@" ++ cat > $TMPC ++ log_file $TMPC ++ check_cmd $objcc $CPPFLAGS $CFLAGS $OBJCFLAGS "$@" $OBJCC_C $(cc_o $TMPO) $TMPC ++} ++ + check_cpp(){ + log check_cpp "$@" + cat > $TMPC +@@ -835,6 +848,19 @@ check_cflags(){ + test_cflags "$@" && add_cflags "$@" + } + ++test_objcflags(){ ++ log test_cflags "$@" ++ set -- $($cflags_filter "$@") ++ check_objcc "$@" <<EOF ++int x; ++EOF ++} ++ ++check_objcflags(){ ++ log check_cflags "$@" ++ test_objcflags "$@" && add_objcflags "$@" ++} ++ + test_ldflags(){ + log test_ldflags "$@" + check_ld "$@" <<EOF +@@ -1377,6 +1403,8 @@ HAVE_LIST_PUB=" + " + + HEADERS_LIST=" ++ AVFoundation_AVFoundation_h ++ CoreVideo_CoreVideo_h + alsa_asoundlib_h + altivec_h + arpa_inet_h +@@ -1640,6 +1668,7 @@ CMDLINE_SET=" + as + build_suffix + cc ++ objcc + cpu + cross_prefix + dep_cc +@@ -1949,7 +1978,7 @@ zmbv_encoder_deps="zlib" + dxva2_deps="dxva2api_h" + vaapi_deps="va_va_h" + vda_deps="VideoDecodeAcceleration_VDADecoder_h pthreads" +-vda_extralibs="-framework CoreFoundation -framework VideoDecodeAcceleration -framework QuartzCore" ++vda_extralibs="-framework CoreFoundation -framework CoreMedia -framework CoreVideo -framework VideoDecodeAcceleration -framework QuartzCore" + vdpau_deps="vdpau_vdpau_h vdpau_vdpau_x11_h" + + h263_vaapi_hwaccel_deps="vaapi" +@@ -2109,6 +2138,7 @@ xwma_demuxer_select="riffdec" + # indevs / outdevs + alsa_indev_deps="alsa_asoundlib_h snd_pcm_htimestamp" + alsa_outdev_deps="alsa_asoundlib_h" ++avfoundation_indev_deps="AVFoundation_AVFoundation_h CoreVideo_CoreVideo_h" + bktr_indev_deps_any="dev_bktr_ioctl_bt848_h machine_ioctl_bt848_h dev_video_bktr_ioctl_bt848_h dev_ic_bt8xx_h" + dv1394_indev_deps="dv1394" + dv1394_indev_select="dv_demuxer" +@@ -2285,6 +2315,9 @@ AS_O='-o $@' + CC_C='-c' + CC_E='-E -o $@' + CC_O='-o $@' ++OBJCC_C='-c' ++OBJCC_E='-E -o $@' ++OBJCC_O='-o $@' + LD_O='-o $@' + LD_LIB='-l%' + LD_PATH='-L' +@@ -2580,6 +2613,7 @@ esac + + ar_default="${cross_prefix}${ar_default}" + cc_default="${cross_prefix}${cc_default}" ++occ_default="${cross_prefix}${occ_default}" + nm_default="${cross_prefix}${nm_default}" + pkg_config_default="${cross_prefix}${pkg_config_default}" + ranlib="${cross_prefix}${ranlib}" +@@ -3032,16 +3066,22 @@ test -n "$cc_type" && enable $cc_type || + warn "Unknown C compiler $cc, unable to select optimal CFLAGS" + + : ${as_default:=$cc} ++: ${objcc_default:=$cc} + : ${dep_cc_default:=$cc} + : ${ld_default:=$cc} + : ${host_ld_default:=$host_cc} +-set_default ar as dep_cc ld host_ld ++set_default ar as objcc dep_cc ld host_ld + + probe_cc as "$as" + asflags_filter=$_flags_filter + add_asflags $_flags $_cflags + set_ccvars AS + ++probe_cc objcc "$objcc" ++objcflags_filter=$_flags_filter ++add_objcflags $_flags $_cflags ++set_ccvars OBJC ++ + probe_cc ld "$ld" + ldflags_filter=$_flags_filter + add_ldflags $_flags $_ldflags +@@ -4268,6 +4308,15 @@ check_header linux/fb.h + check_header linux/videodev2.h + check_struct linux/videodev2.h "struct v4l2_frmivalenum" discrete + ++check_header AVFoundation/AVFoundation.h && ++ check_objcflags -fobjc-arc && ++ add_extralibs -framework Foundation -framework AVFoundation || \ ++ disable AVFoundation_AVFoundation_h ++ ++check_header CoreVideo/CoreVideo.h && ++ check_objcflags -fobjc-arc && ++ add_extralibs -framework CoreVideo || ++ + check_header sys/videoio.h + + check_func_headers "windows.h vfw.h" capCreateCaptureWindow "$vfwcap_indev_extralibs" +@@ -4689,6 +4738,7 @@ ARCH=$arch + INTRINSICS=$intrinsics + CC=$cc + AS=$as ++OBJCC=$objcc + LD=$ld + DEPCC=$dep_cc + DEPCCFLAGS=$DEPCCFLAGS \$(CPPFLAGS) +@@ -4704,9 +4754,13 @@ STRIP=$strip + LN_S=$ln_s + CPPFLAGS=$CPPFLAGS + CFLAGS=$CFLAGS ++OBJCFLAGS=$OBJCFLAGS + ASFLAGS=$ASFLAGS + AS_C=$AS_C + AS_O=$AS_O ++OBJCC_C=$OBJCC_C ++OBJCC_E=$OBJCC_E ++OBJCC_O=$OBJCC_O + CC_C=$CC_C + CC_E=$CC_E + CC_O=$CC_O +diff --git a/libavdevice/Makefile b/libavdevice/Makefile +index 2a21832..682a39a 100644 +--- a/libavdevice/Makefile ++++ b/libavdevice/Makefile +@@ -11,6 +11,7 @@ OBJS-$(CONFIG_ALSA_INDEV) += alsa-audio-common.o \ + alsa-audio-dec.o + OBJS-$(CONFIG_ALSA_OUTDEV) += alsa-audio-common.o \ + alsa-audio-enc.o ++OBJS-$(CONFIG_AVFOUNDATION_INDEV) += avfoundation_dec.o + OBJS-$(CONFIG_BKTR_INDEV) += bktr.o + OBJS-$(CONFIG_DV1394_INDEV) += dv1394.o + OBJS-$(CONFIG_FBDEV_INDEV) += fbdev.o +diff --git a/libavdevice/alldevices.c b/libavdevice/alldevices.c +index 5dbe277..8439b5b 100644 +--- a/libavdevice/alldevices.c ++++ b/libavdevice/alldevices.c +@@ -48,6 +48,7 @@ void avdevice_register_all(void) + + /* devices */ + REGISTER_INOUTDEV(ALSA, alsa); ++ REGISTER_INDEV (AVFOUNDATION, avfoundation); + REGISTER_INDEV (BKTR, bktr); + REGISTER_INDEV (DV1394, dv1394); + REGISTER_INDEV (FBDEV, fbdev); +diff --git a/libavdevice/avfoundation_dec.m b/libavdevice/avfoundation_dec.m +new file mode 100644 +index 0000000..6b60782 +--- /dev/null ++++ b/libavdevice/avfoundation_dec.m +@@ -0,0 +1,517 @@ ++/* ++ * AVFoundation input device ++ * Copyright (c) 2015 Luca Barbato ++ * Alexandre Lision ++ * ++ * This file is part of Libav. ++ * ++ * Libav is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2.1 of the License, or (at your option) any later version. ++ * ++ * Libav is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with Libav; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ ++#import <AVFoundation/AVFoundation.h> ++#include <pthread.h> ++ ++#include "libavformat/avformat.h" ++#include "libavutil/log.h" ++#include "libavutil/opt.h" ++#include "libavutil/pixdesc.h" ++#include "libavformat/internal.h" ++#include "libavutil/time.h" ++#include "libavutil/mathematics.h" ++ ++#include "avdevice.h" ++ ++struct AVFPixelFormatSpec { ++ enum AVPixelFormat ff_id; ++ OSType avf_id; ++}; ++ ++static const struct AVFPixelFormatSpec avf_pixel_formats[] = { ++ { AV_PIX_FMT_MONOBLACK, kCVPixelFormatType_1Monochrome }, ++ { AV_PIX_FMT_RGB555BE, kCVPixelFormatType_16BE555 }, ++ { AV_PIX_FMT_RGB555LE, kCVPixelFormatType_16LE555 }, ++ { AV_PIX_FMT_RGB565BE, kCVPixelFormatType_16BE565 }, ++ { AV_PIX_FMT_RGB565LE, kCVPixelFormatType_16LE565 }, ++ { AV_PIX_FMT_RGB24, kCVPixelFormatType_24RGB }, ++ { AV_PIX_FMT_BGR24, kCVPixelFormatType_24BGR }, ++ { AV_PIX_FMT_ARGB, kCVPixelFormatType_32ARGB }, ++ { AV_PIX_FMT_BGRA, kCVPixelFormatType_32BGRA }, ++ { AV_PIX_FMT_ABGR, kCVPixelFormatType_32ABGR }, ++ { AV_PIX_FMT_RGBA, kCVPixelFormatType_32RGBA }, ++ { AV_PIX_FMT_BGR48BE, kCVPixelFormatType_48RGB }, ++ { AV_PIX_FMT_UYVY422, kCVPixelFormatType_422YpCbCr8 }, ++ { AV_PIX_FMT_YUVA444P, kCVPixelFormatType_4444YpCbCrA8R }, ++ { AV_PIX_FMT_YUVA444P16LE, kCVPixelFormatType_4444AYpCbCr16 }, ++ { AV_PIX_FMT_YUV444P, kCVPixelFormatType_444YpCbCr8 }, ++ { AV_PIX_FMT_YUV422P16, kCVPixelFormatType_422YpCbCr16 }, ++ { AV_PIX_FMT_YUV422P10, kCVPixelFormatType_422YpCbCr10 }, ++ { AV_PIX_FMT_YUV444P10, kCVPixelFormatType_444YpCbCr10 }, ++ { AV_PIX_FMT_YUV420P, kCVPixelFormatType_420YpCbCr8Planar }, ++ { AV_PIX_FMT_NV12, kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange }, ++ { AV_PIX_FMT_YUYV422, kCVPixelFormatType_422YpCbCr8_yuvs }, ++#if __MAC_OS_X_VERSION_MIN_REQUIRED >= 1080 ++ { AV_PIX_FMT_GRAY8, kCVPixelFormatType_OneComponent8 }, ++#endif ++ { AV_PIX_FMT_NONE, 0 } ++}; ++ ++typedef struct AVFoundationCaptureContext { ++ AVClass *class; ++ int list_devices; ++ CFTypeRef session; /** AVCaptureSession*/ ++ char* video_size; /**< String describing video size, ++ set by a private option. */ ++ enum AVPixelFormat pixel_format; /**< Set by a private option. */ ++ int list_format; /**< Set by a private option. */ ++ char* framerate; /**< Set by a private option. */ ++ ++ int video_stream_index; ++ ++ int64_t first_pts; ++ int frames_captured; ++ int audio_frames_captured; ++ pthread_mutex_t frame_lock; ++ pthread_cond_t frame_wait_cond; ++ ++ CFTypeRef avf_delegate; /** AVFFrameReceiver */ ++ CFTypeRef video_output; /** AVCaptureVideoDataOutput */ ++ CVImageBufferRef current_frame; /** CVImageBufferRef */ ++ ++} AVFoundationCaptureContext; ++ ++#define AUDIO_DEVICES 1 ++#define VIDEO_DEVICES 2 ++#define ALL_DEVICES AUDIO_DEVICES|VIDEO_DEVICES ++ ++#define OFFSET(x) offsetof(AVFoundationCaptureContext, x) ++#define DEC AV_OPT_FLAG_DECODING_PARAM ++static const AVOption options[] = { ++ { "list_devices", "List available devices and exit", OFFSET(list_devices), AV_OPT_TYPE_INT, {.i64 = 0 }, 0, INT_MAX, DEC, "list_devices" }, ++ { "all", "Show all the supported devices", OFFSET(list_devices), AV_OPT_TYPE_CONST, {.i64 = ALL_DEVICES }, 0, INT_MAX, DEC, "list_devices" }, ++ { "audio", "Show only the audio devices", OFFSET(list_devices), AV_OPT_TYPE_CONST, {.i64 = AUDIO_DEVICES }, 0, INT_MAX, DEC, "list_devices" }, ++ { "video", "Show only the video devices", OFFSET(list_devices), AV_OPT_TYPE_CONST, {.i64 = VIDEO_DEVICES }, 0, INT_MAX, DEC, "list_devices" }, ++ { NULL }, ++}; ++ ++ ++static void list_capture_devices_by_type(AVFormatContext *s, NSString *type) ++{ ++ NSArray *devices = [AVCaptureDevice devicesWithMediaType:type]; ++ ++ av_log(s, AV_LOG_INFO, "Type: %s\n", [type UTF8String]); ++ for (AVCaptureDevice *device in devices) { ++ ++ av_log(s, AV_LOG_INFO, "uniqueID: %s\nname: %s\nformat:\n", ++ [[device uniqueID] UTF8String], ++ [[device localizedName] UTF8String]); ++ ++ for (AVCaptureDeviceFormat *format in device.formats) ++ av_log(s, AV_LOG_INFO, "\t%s\n", ++ [[NSString stringWithFormat:@"%@", format] UTF8String]); ++ } ++} ++ ++static int avfoundation_list_capture_devices(AVFormatContext *s) ++{ ++ AVFoundationCaptureContext *ctx = s->priv_data; ++ ++ if (ctx->list_devices & AUDIO_DEVICES) ++ list_capture_devices_by_type(s, AVMediaTypeAudio); ++ ++ if (ctx->list_devices & VIDEO_DEVICES) ++ list_capture_devices_by_type(s, AVMediaTypeVideo); ++ ++ return AVERROR_EXIT; ++} ++ ++static void lock_frames(AVFoundationCaptureContext* ctx) ++{ ++ pthread_mutex_lock(&ctx->frame_lock); ++} ++ ++static void unlock_frames(AVFoundationCaptureContext* ctx) ++{ ++ pthread_mutex_unlock(&ctx->frame_lock); ++} ++ ++/** FrameReceiver class - delegate for AVCaptureSession ++ */ ++@interface AVFFrameReceiver : NSObject <AVCaptureVideoDataOutputSampleBufferDelegate> ++{ ++ AVFoundationCaptureContext* _context; ++} ++ ++- (id)initWithContext:(AVFoundationCaptureContext*)context; ++ ++- (void) captureOutput:(AVCaptureOutput *)captureOutput ++ didOutputSampleBuffer:(CMSampleBufferRef)videoFrame ++ fromConnection:(AVCaptureConnection *)connection; ++ ++@end ++ ++@implementation AVFFrameReceiver ++ ++- (id)initWithContext:(AVFoundationCaptureContext*)context ++{ ++ if (self = [super init]) { ++ _context = context; ++ } ++ return self; ++} ++ ++- (void) captureOutput:(AVCaptureOutput *)captureOutput ++ didOutputSampleBuffer:(CMSampleBufferRef)videoFrame ++ fromConnection:(AVCaptureConnection *)connection ++{ ++ lock_frames(_context); ++ ++ if (_context->current_frame != nil) { ++ CFRelease(_context->current_frame); ++ } ++ ++ _context->current_frame = CFRetain(CMSampleBufferGetImageBuffer(videoFrame)); ++ ++ pthread_cond_signal(&_context->frame_wait_cond); ++ ++ unlock_frames(_context); ++ ++ ++_context->frames_captured; ++} ++ ++@end ++ ++NSString *pat = @"\\[[^\\].]*\\]"; ++ ++static int setup_stream(AVFormatContext *s, AVCaptureDevice *device) ++{ ++ NSLog(@"setting up stream for device %@ ID\n", [device uniqueID]); ++ ++ AVFoundationCaptureContext *ctx = s->priv_data; ++ NSError *__autoreleasing error = nil; ++ AVCaptureDeviceInput *input; ++ AVCaptureSession *session = (__bridge AVCaptureSession*)ctx->session; ++ input = [AVCaptureDeviceInput deviceInputWithDevice:device ++ error:&error]; ++ // add the input devices ++ if (!input) { ++ av_log(s, AV_LOG_ERROR, "%s\n", ++ [[error localizedDescription] UTF8String]); ++ return AVERROR_UNKNOWN; ++ } ++ ++ if ([session canAddInput:input]) { ++ [session addInput:input]; ++ } else { ++ av_log(s, AV_LOG_ERROR, "can't add video input to capture session\n"); ++ return -1; ++ } ++ ++ // add the output devices ++ if ([device hasMediaType:AVMediaTypeVideo]) { ++ ++ AVCaptureVideoDataOutput* out = [[AVCaptureVideoDataOutput alloc] init]; ++ if (!out) { ++ av_log(s, AV_LOG_ERROR, "Failed to init AV video output\n"); ++ return -1; ++ } ++ ++ [out setAlwaysDiscardsLateVideoFrames:YES]; ++ ++ ++ // select pixel format ++ struct AVFPixelFormatSpec pxl_fmt_spec; ++ pxl_fmt_spec.ff_id = AV_PIX_FMT_NONE; ++ ++ av_log(s, AV_LOG_ERROR, "Supported pixel formats:\n"); ++ for (NSNumber *pxl_fmt in [out availableVideoCVPixelFormatTypes]) { ++ struct AVFPixelFormatSpec pxl_fmt_dummy; ++ pxl_fmt_dummy.ff_id = AV_PIX_FMT_NONE; ++ for (int i = 0; avf_pixel_formats[i].ff_id != AV_PIX_FMT_NONE; i++) { ++ if ([pxl_fmt intValue] == avf_pixel_formats[i].avf_id) { ++ pxl_fmt_dummy = avf_pixel_formats[i]; ++ break; ++ } ++ } ++ ++ if (pxl_fmt_dummy.ff_id != AV_PIX_FMT_NONE) { ++ av_log(s, AV_LOG_ERROR, " %s: %d \n", av_get_pix_fmt_name(pxl_fmt_dummy.ff_id), ++ pxl_fmt_dummy.avf_id); ++ ++ // select first supported pixel format instead of user selected (or default) pixel format ++ if (pxl_fmt_spec.ff_id == AV_PIX_FMT_NONE) { ++ pxl_fmt_spec = pxl_fmt_dummy; ++ } ++ } ++ } ++ ++ // fail if there is no appropriate pixel format or print a warning about overriding the pixel format ++ if (pxl_fmt_spec.ff_id == AV_PIX_FMT_NONE) { ++ return 1; ++ } else { ++ av_log(s, AV_LOG_WARNING, "Overriding selected pixel format to use %s instead.\n", ++ av_get_pix_fmt_name(pxl_fmt_spec.ff_id)); ++ } ++ ctx->pixel_format = pxl_fmt_spec.ff_id; ++ NSNumber *pixel_format = [NSNumber numberWithUnsignedInt:pxl_fmt_spec.avf_id]; ++ NSDictionary *capture_dict = [NSDictionary dictionaryWithObject:pixel_format ++ forKey:(id)kCVPixelBufferPixelFormatTypeKey]; ++ [out setVideoSettings:capture_dict]; ++ ++ AVFFrameReceiver* delegate = [[AVFFrameReceiver alloc] initWithContext:ctx]; ++ ++ dispatch_queue_t queue = dispatch_queue_create("avf_queue", NULL); ++ [out setSampleBufferDelegate:delegate queue:queue]; ++ ++ ctx->avf_delegate = (__bridge_retained CFTypeRef) delegate; ++ ++ if ([session canAddOutput:out]) { ++ [session addOutput:out]; ++ ctx->video_output = (__bridge_retained CFTypeRef) out; ++ } else { ++ av_log(s, AV_LOG_ERROR, "can't add video output to capture session\n"); ++ return -1; ++ } ++ NSLog(@"%@", device); ++ } ++ ++/** if ([device hasMediaType:AVMediaTypeAudio]) { ++ AVCaptureAudioDataOutput *out = ++ [[AVCaptureAudioDataOutput alloc] init]; ++ ++ out.audioSettings = nil; ++ [session addOutput:out]; ++ ++ NSLog(@"%@ %@", device, out.audioSettings); ++ } ++*/ ++ return 0; ++} ++ ++static int get_video_config(AVFormatContext *s) ++{ ++ AVFoundationCaptureContext *ctx = (AVFoundationCaptureContext*)s->priv_data; ++ CVImageBufferRef image_buffer; ++ CGSize image_buffer_size; ++ AVStream* stream = avformat_new_stream(s, NULL); ++ ++ if (!stream) { ++ av_log(s, AV_LOG_ERROR, "Failed to create AVStream\n"); ++ return -1; ++ } ++ ++ // Take stream info from the first frame. ++ while (ctx->frames_captured < 1) { ++ CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, YES); ++ } ++ ++ lock_frames(ctx); ++ ++ ctx->video_stream_index = stream->index; ++ ++ avpriv_set_pts_info(stream, 64, 1, 1000000); ++ ++ image_buffer = ctx->current_frame; ++ image_buffer_size = CVImageBufferGetEncodedSize(image_buffer); ++ ++ av_log(s, AV_LOG_ERROR, "Update stream info...\n"); ++ stream->codec->codec_id = AV_CODEC_ID_RAWVIDEO; ++ stream->codec->codec_type = AVMEDIA_TYPE_VIDEO; ++ stream->codec->width = (int)image_buffer_size.width; ++ stream->codec->height = (int)image_buffer_size.height; ++ stream->codec->pix_fmt = ctx->pixel_format; ++ ++ CFRelease(ctx->current_frame); ++ ctx->current_frame = nil; ++ ++ unlock_frames(ctx); ++ ++ return 0; ++} ++ ++static void destroy_context(AVFoundationCaptureContext* ctx) ++{ ++ NSLog(@"Destroying context"); ++ ++ AVCaptureSession *session = (__bridge AVCaptureSession*)ctx->session; ++ [session stopRunning]; ++ ++ ctx->session = NULL; ++ ++ pthread_mutex_destroy(&ctx->frame_lock); ++ pthread_cond_destroy(&ctx->frame_wait_cond); ++ ++ if (ctx->current_frame) { ++ CFRelease(ctx->current_frame); ++ } ++} ++ ++static int setup_default_stream(AVFormatContext *s) ++{ ++ AVCaptureDevice *device; ++ for (NSString *type in @[AVMediaTypeVideo]) { ++ device = [AVCaptureDevice defaultDeviceWithMediaType:type]; ++ if (device) ++ return setup_stream(s, device); ++ } ++ ++ return -1; ++} ++ ++static int setup_streams(AVFormatContext *s) ++{ ++ NSLog(@"setting streams"); ++ AVFoundationCaptureContext *ctx = s->priv_data; ++ int ret; ++ NSError *__autoreleasing error = nil; ++ NSArray *matches; ++ NSString *filename; ++ AVCaptureDevice *device; ++ NSRegularExpression *exp; ++ ++ pthread_mutex_init(&ctx->frame_lock, NULL); ++ pthread_cond_init(&ctx->frame_wait_cond, NULL); ++ ++ ctx->session = (__bridge_retained CFTypeRef)[[AVCaptureSession alloc] init]; ++ ++ if (s->filename[0] != '[') { ++ ret = setup_default_stream(s); ++ } else { ++ exp = [NSRegularExpression regularExpressionWithPattern:pat ++ options:0 ++ error:&error]; ++ if (!exp) { ++ av_log(s, AV_LOG_ERROR, "%s\n", ++ [[error localizedDescription] UTF8String]); ++ return AVERROR(ENOMEM); ++ } ++ ++ filename = [NSString stringWithFormat:@"%s", s->filename]; ++ av_log(s, AV_LOG_INFO, "device name: %s\n",[filename UTF8String]); ++ ++ matches = [exp matchesInString:filename options:0 ++ range:NSMakeRange(0, [filename length])]; ++ ++ if (matches.count > 0) { ++ for (NSTextCheckingResult *match in matches) { ++ NSRange range = [match rangeAtIndex:0]; ++ NSString *uniqueID = [filename substringWithRange:NSMakeRange(range.location + 1, range.length-2)]; ++ av_log(s, AV_LOG_INFO, "opening device with ID: %s\n",[uniqueID UTF8String]); ++ if (!(device = [AVCaptureDevice deviceWithUniqueID:uniqueID])) { ++ // report error ++ av_log(s, AV_LOG_ERROR, "Device with name %s not found",[filename UTF8String]); ++ return AVERROR(EINVAL); ++ } ++ ret = setup_stream(s, device); ++ if (ret < 0) { ++ // avfoundation_close ++ return ret; ++ } ++ } ++ } else { ++ av_log(s, AV_LOG_ERROR, "No matches for %s",[filename UTF8String]); ++ ret = setup_default_stream(s); ++ } ++ } ++ ++ if (ret < 0) { ++ av_log(s, AV_LOG_ERROR, "No device could be added"); ++ return ret; ++ } ++ ++ av_log(s, AV_LOG_INFO, "Starting session!\n"); ++ [(__bridge AVCaptureSession*)ctx->session startRunning]; ++ ++ av_log(s, AV_LOG_INFO, "Checking video config\n"); ++ if (get_video_config(s)) { ++ destroy_context(ctx); ++ return AVERROR(EIO); ++ } ++ ++ return 0; ++} ++ ++ ++static int avfoundation_read_header(AVFormatContext *s) ++{ ++ AVFoundationCaptureContext *ctx = s->priv_data; ++ ctx->first_pts = av_gettime(); ++ if (ctx->list_devices) ++ return avfoundation_list_capture_devices(s); ++ ++ return setup_streams(s); ++} ++ ++static int avfoundation_read_packet(AVFormatContext *s, AVPacket *pkt) ++{ ++ AVFoundationCaptureContext* ctx = (AVFoundationCaptureContext*)s->priv_data; ++ ++ do { ++ lock_frames(ctx); ++ ++ if (ctx->current_frame != nil) { ++ if (av_new_packet(pkt, (int)CVPixelBufferGetDataSize(ctx->current_frame)) < 0) { ++ return AVERROR(EIO); ++ } ++ ++ pkt->pts = pkt->dts = av_rescale_q(av_gettime() - ctx->first_pts, ++ AV_TIME_BASE_Q, ++ (AVRational){1, 1000000}); ++ pkt->stream_index = ctx->video_stream_index; ++ pkt->flags |= AV_PKT_FLAG_KEY; ++ ++ CVPixelBufferLockBaseAddress(ctx->current_frame, 0); ++ ++ void* data = CVPixelBufferGetBaseAddress(ctx->current_frame); ++ memcpy(pkt->data, data, pkt->size); ++ ++ CVPixelBufferUnlockBaseAddress(ctx->current_frame, 0); ++ CFRelease(ctx->current_frame); ++ ctx->current_frame = nil; ++ } else { ++ pkt->data = NULL; ++ pthread_cond_wait(&ctx->frame_wait_cond, &ctx->frame_lock); ++ } ++ ++ unlock_frames(ctx); ++ } while (!pkt->data); ++ ++ return 0; ++} ++ ++static int avfoundation_read_close(AVFormatContext *s) ++{ ++ NSLog(@"Closing session..."); ++ AVFoundationCaptureContext *ctx = s->priv_data; ++ destroy_context(ctx); ++ return 0; ++} ++ ++static const AVClass avfoundation_class = { ++ .class_name = "AVFoundation AVCaptureDevice indev", ++ .item_name = av_default_item_name, ++ .option = options, ++ .version = LIBAVUTIL_VERSION_INT, ++}; ++ ++AVInputFormat ff_avfoundation_demuxer = { ++ .name = "avfoundation", ++ .long_name = NULL_IF_CONFIG_SMALL("AVFoundation AVCaptureDevice grab"), ++ .priv_data_size = sizeof(AVFoundationCaptureContext), ++ .read_header = avfoundation_read_header, ++ .read_packet = avfoundation_read_packet, ++ .read_close = avfoundation_read_close, ++ .flags = AVFMT_NOFILE, ++ .priv_class = &avfoundation_class, ++}; diff --git a/contrib/src/libav/rules.mak b/contrib/src/libav/rules.mak new file mode 100644 index 0000000000..f010f45532 --- /dev/null +++ b/contrib/src/libav/rules.mak @@ -0,0 +1,174 @@ +#Libav 11-1 (git version packaged for ubuntu 14.10) +LIBAV_HASH := f851477889ae48e2f17073cf7486e1d5561b7ae4 +LIBAV_GITURL := git://git.libav.org/libav.git + +PKGS += libav + +#disable everything +#ensure to add this option first +LIBAVCONF = \ + --disable-everything + +LIBAVCONF += \ + --cc="$(CC)" \ + --pkg-config="$(PKG_CONFIG)" \ + --enable-zlib \ + --enable-gpl \ + --enable-swscale \ + --enable-protocols + + +#enable muxers/demuxers +LIBAVCONF += \ + --enable-demuxers \ + --enable-muxers + +#enable parsers +LIBAVCONF += \ + --enable-parser=h263 \ + --enable-parser=h264 \ + --enable-parser=mpeg4video \ + --enable-parser=opus \ + --enable-parser=vp8 + +#librairies +LIBAVCONF += \ + --enable-libx264 \ + --enable-libopus \ + --enable-libspeex \ + --enable-libvpx + +#encoders/decoders +LIBAVCONF += \ + --enable-encoder=adpcm_g722 \ + --enable-decoder=adpcm_g722 \ + --enable-encoder=rawvideo \ + --enable-decoder=rawvideo \ + --enable-encoder=libx264 \ + --enable-decoder=h264 \ + --enable-encoder=pcm_alaw \ + --enable-decoder=pcm_alaw \ + --enable-encoder=pcm_mulaw \ + --enable-decoder=pcm_mulaw \ + --enable-encoder=libopus \ + --enable-decoder=libopus \ + --enable-encoder=mpeg4 \ + --enable-decoder=mpeg4 \ + --enable-encoder=libvpx_vp8 \ + --enable-decoder=vp8 \ + --enable-encoder=h263 \ + --enable-decoder=h263 \ + --enable-encoder=libspeex \ + --enable-decoder=libspeex + +# Linux +ifdef HAVE_LINUX +LIBAVCONF += \ + --enable-x11grab +endif + +DEPS_libav = zlib x264 vpx $(DEPS_vpx) + +ifdef HAVE_CROSS_COMPILE +LIBAVCONF += --enable-cross-compile +ifndef HAVE_DARWIN_OS +LIBAVCONF += --cross-prefix=$(HOST)- +endif +endif + +# ARM stuff +ifeq ($(ARCH),arm) +ifdef HAVE_ARMV7A +endif +ifndef HAVE_DARWIN_OS +LIBAVCONF += --arch=arm +endif +ifdef HAVE_NEON +LIBAVCONF += --enable-neon +endif +ifdef HAVE_ARMV7A +LIBAVCONF += --cpu=cortex-a8 +LIBAVCONF += --enable-thumb +endif +ifdef HAVE_ARMV6 +LIBAVCONF += --cpu=armv6 --disable-neon +endif +endif + +# x86 stuff +ifeq ($(ARCH),i386) +ifndef HAVE_DARWIN_OS +LIBAVCONF += --arch=x86 +endif +endif + +# Darwin +ifdef HAVE_DARWIN_OS +LIBAVCONF += --arch=$(ARCH) --target-os=darwin --enable-indev=avfoundation +ifeq ($(ARCH),x86_64) +LIBAVCONF += --cpu=core2 +endif +endif +ifdef HAVE_IOS +LIBAVCONF += --enable-pic +ifdef HAVE_NEON +LIBAVCONF += --as="$(AS)" +endif +endif +#ifdef HAVE_MACOSX +#LIBAVCONF += --enable-vda +#endif + +# Linux +ifdef HAVE_LINUX +LIBAVCONF += --target-os=linux --enable-pic +ifndef HAVE_ANDROID +LIBAVCONF += --enable-indev=v4l2 --enable-indev=x11grab --enable-x11grab +endif +endif + +# Windows +ifdef HAVE_WIN32 +#ifndef HAVE_MINGW_W64 +#DEPS_libav += directx +#endif + +LIBAVCONF += --target-os=mingw32 --enable-memalign-hack +LIBAVCONF += --enable-w32threads --disable-decoder=dca +#LIBAVCONF += --enable-dxva2 + +ifdef HAVE_WIN64 +LIBAVCONF += --cpu=athlon64 --arch=x86_64 +else # !WIN64 +LIBAVCONF+= --cpu=i686 --arch=x86 +endif + +else # !Windows +LIBAVCONF += --enable-pthreads +endif + +ifeq ($(call need_pkg,"libavcodec >= 53.5.0 libavformat >= 54.20.3 libswscale libavdevice >= 53.0.0 libavutil >= 51.0.0"),) +PKGS_FOUND += libav +endif + +$(TARBALLS)/libav-$(LIBAV_HASH).tar.xz: + $(call download_git,$(LIBAV_GITURL),master,$(LIBAV_HASH)) + +.sum-libav: libav-$(LIBAV_HASH).tar.xz + $(warning Not implemented.) + touch $@ + +libav: libav-$(LIBAV_HASH).tar.xz .sum-libav + rm -Rf $@ $@-$(LIBAV_HASH) + mkdir -p $@-$(LIBAV_HASH) + (cd $@-$(LIBAV_HASH) && tar xv --strip-components=1 -f ../$<) + $(UPDATE_AUTOCONFIG) + $(APPLY) $(SRC)/libav/osx.patch + $(MOVE) + +.libav: libav + cd $< && $(HOSTVARS) ./configure \ + --extra-ldflags="$(LDFLAGS)" $(LIBAVCONF) \ + --prefix="$(PREFIX)" --enable-static --disable-shared + cd $< && $(MAKE) install-libs install-headers + touch $@ diff --git a/contrib/src/main.mak b/contrib/src/main.mak new file mode 100644 index 0000000000..046be757a4 --- /dev/null +++ b/contrib/src/main.mak @@ -0,0 +1,467 @@ +# Main makefile for VLC 3rd party libraries ("contrib") +# Copyright (C) 2003-2011 the VideoLAN team +# +# This file is under the same license as the vlc package. + +all: install + +# bootstrap configuration +include config.mak + +TOPSRC ?= ../../contrib +TOPDST ?= .. +SRC := $(TOPSRC)/src +TARBALLS := $(TOPSRC)/tarballs + +PATH :=$(abspath ../../extras/tools/build/bin):$(PATH) +export PATH + +PKGS_ALL := $(patsubst $(SRC)/%/rules.mak,%,$(wildcard $(SRC)/*/rules.mak)) +DATE := $(shell date +%Y%m%d) +VPATH := $(TARBALLS) + +# Common download locations +GNU := http://ftp.gnu.org/gnu +SF := http://heanet.dl.sourceforge.net/sourceforge +CONTRIB_VIDEOLAN ?= http://downloads.videolan.org/pub/contrib +GNUTELEPHONY := https://github.com/dyfet + +# +# Machine-dependent variables +# + +PREFIX ?= $(TOPDST)/$(HOST) +PREFIX := $(abspath $(PREFIX)) +ifneq ($(HOST),$(BUILD)) +HAVE_CROSS_COMPILE = 1 +endif +ARCH := $(shell $(SRC)/get-arch.sh $(HOST)) + +ifeq ($(ARCH)-$(HAVE_WIN32),x86_64-1) +HAVE_WIN64 := 1 +endif + +ifdef HAVE_CROSS_COMPILE +need_pkg = 1 +else +need_pkg = $(shell $(PKG_CONFIG) $(1) || echo 1) +endif + +# +# Default values for tools +# +ifndef HAVE_CROSS_COMPILE +ifneq ($(findstring $(origin CC),undefined default),) +CC := gcc +endif +ifneq ($(findstring $(origin CXX),undefined default),) +CXX := g++ +endif +ifneq ($(findstring $(origin LD),undefined default),) +LD := ld +endif +ifneq ($(findstring $(origin AR),undefined default),) +AR := ar +endif +ifneq ($(findstring $(origin RANLIB),undefined default),) +RANLIB := ranlib +endif +ifneq ($(findstring $(origin STRIP),undefined default),) +STRIP := strip +endif +else +ifneq ($(findstring $(origin CC),undefined default),) +CC := $(HOST)-gcc +endif +ifneq ($(findstring $(origin CXX),undefined default),) +CXX := $(HOST)-g++ +endif +ifneq ($(findstring $(origin LD),undefined default),) +LD := $(HOST)-ld +endif +ifneq ($(findstring $(origin AR),undefined default),) +AR := $(HOST)-ar +endif +ifneq ($(findstring $(origin RANLIB),undefined default),) +RANLIB := $(HOST)-ranlib +endif +ifneq ($(findstring $(origin STRIP),undefined default),) +STRIP := $(HOST)-strip +endif +endif + +ifdef HAVE_ANDROID +CC := $(HOST)-gcc --sysroot=$(ANDROID_NDK)/platforms/$(ANDROID_API)/arch-$(PLATFORM_SHORT_ARCH) +CXX := $(HOST)-g++ --sysroot=$(ANDROID_NDK)/platforms/$(ANDROID_API)/arch-$(PLATFORM_SHORT_ARCH) +endif + +ifdef HAVE_MACOSX +MIN_OSX_VERSION=10.8 +CC=xcrun cc +CXX=xcrun c++ +AR=xcrun ar +LD=xcrun ld +STRIP=xcrun strip +RANLIB=xcrun ranlib +EXTRA_CFLAGS += -isysroot $(MACOSX_SDK) -mmacosx-version-min=$(MIN_OSX_VERSION) -DMACOSX_DEPLOYMENT_TARGET=$(MIN_OSX_VERSION) +EXTRA_CXXFLAGS += -std=c++11 -stdlib=libc++ +EXTRA_LDFLAGS += -Wl,-syslibroot,$(MACOSX_SDK) -mmacosx-version-min=$(MIN_OSX_VERSION) -isysroot $(MACOSX_SDK) -DMACOSX_DEPLOYMENT_TARGET=$(MIN_OSX_VERSION) +ifeq ($(ARCH),x86_64) +EXTRA_CFLAGS += -m64 +EXTRA_LDFLAGS += -m64 +else +EXTRA_CFLAGS += -m32 +EXTRA_LDFLAGS += -m32 +endif + +XCODE_FLAGS = -sdk macosx$(OSX_VERSION) +ifeq ($(shell xcodebuild -version 2>/dev/null | tee /dev/null|head -1|cut -d\ -f2|cut -d. -f1),3) +XCODE_FLAGS += ARCHS=$(ARCH) +# XCode 3 doesn't support -arch +else +XCODE_FLAGS += -arch $(ARCH) +endif + +endif + +CCAS=$(CC) -c + +ifdef HAVE_IOS +CC=xcrun clang +CXX=xcrun clang++ +ifdef HAVE_NEON +AS=perl $(abspath ../../extras/tools/build/bin/gas-preprocessor.pl) $(CC) +CCAS=gas-preprocessor.pl $(CC) -c +else +CCAS=$(CC) -c +endif +AR=xcrun ar +LD=xcrun ld +STRIP=xcrun strip +RANLIB=xcrun ranlib +EXTRA_CFLAGS += $(CFLAGS) +EXTRA_LDFLAGS += $(LDFLAGS) +endif + +ifdef HAVE_WIN32 +ifneq ($(shell $(CC) $(CFLAGS) -E -dM -include _mingw.h - < /dev/null | grep -E __MINGW64_VERSION_MAJOR),) +HAVE_MINGW_W64 := 1 +endif +endif + +ifdef HAVE_SOLARIS +ifeq ($(ARCH),x86_64) +EXTRA_CFLAGS += -m64 +EXTRA_LDFLAGS += -m64 +else +EXTRA_CFLAGS += -m32 +EXTRA_LDFLAGS += -m32 +endif +endif + +cppcheck = $(shell $(CC) $(CFLAGS) -E -dM - < /dev/null | grep -E $(1)) + +EXTRA_CFLAGS += -I$(PREFIX)/include +CPPFLAGS := $(CPPFLAGS) $(EXTRA_CFLAGS) +CFLAGS := $(CFLAGS) $(EXTRA_CFLAGS) -g +CXXFLAGS := $(CXXFLAGS) $(EXTRA_CFLAGS) $(EXTRA_CXXFLAGS) -g +EXTRA_LDFLAGS += -L$(PREFIX)/lib +LDFLAGS := $(LDFLAGS) $(EXTRA_LDFLAGS) +# Do not export those! Use HOSTVARS. + +# Do the FPU detection, after we have figured out our compilers and flags. +ifneq ($(findstring $(ARCH),aarch64 i386 ppc ppc64 sparc sparc64 x86_64),) +# This should be consistent with include/vlc_cpu.h +HAVE_FPU = 1 +else ifneq ($(findstring $(ARCH),arm),) +ifneq ($(call cppcheck, __VFP_FP__)),) +ifeq ($(call cppcheck, __SOFTFP__),) +HAVE_FPU = 1 +endif +endif +else ifneq ($(call cppcheck, __mips_hard_float),) +HAVE_FPU = 1 +endif + +ACLOCAL_AMFLAGS += -I$(PREFIX)/share/aclocal +export ACLOCAL_AMFLAGS + +PKG_CONFIG ?= pkg-config +ifdef HAVE_CROSS_COMPILE +# This inhibits .pc file from within the cross-compilation toolchain sysroot. +# Hopefully, nobody ever needs that. +PKG_CONFIG_PATH := /usr/share/pkgconfig +PKG_CONFIG_LIBDIR := /usr/$(HOST)/lib/pkgconfig +export PKG_CONFIG_LIBDIR +endif +PKG_CONFIG_PATH := $(PKG_CONFIG_PATH):$(PREFIX)/lib/pkgconfig +export PKG_CONFIG_PATH + +ifndef GIT +ifeq ($(shell git --version >/dev/null 2>&1 || echo FAIL),) +GIT = git +endif +endif +GIT ?= $(error git not found!) + +ifndef SVN +ifeq ($(shell svn --version >/dev/null 2>&1 || echo FAIL),) +SVN = svn +endif +endif +SVN ?= $(error subversion client (svn) not found!) + +ifeq ($(shell curl --version >/dev/null 2>&1 || echo FAIL),) +download = curl -f -L -- "$(1)" > "$@" +else ifeq ($(shell wget --version >/dev/null 2>&1 || echo FAIL),) +download = rm -f $@.tmp && \ + wget --passive -c -p -O $@.tmp "$(1)" && \ + touch $@.tmp && \ + mv $@.tmp $@ +else ifeq ($(which fetch >/dev/null 2>&1 || echo FAIL),) +download = rm -f $@.tmp && \ + fetch -p -o $@.tmp "$(1)" && \ + touch $@.tmp && \ + mv $@.tmp $@ +else +download = $(error Neither curl nor wget found!) +endif + +ifeq ($(shell which bzcat >/dev/null 2>&1 || echo FAIL),) +BZCAT = bzcat +else +BZCAT ?= $(error Bunzip2 client (bzcat) not found!) +endif + +ifeq ($(shell gzcat --version >/dev/null 2>&1 || echo FAIL),) +ZCAT = gzcat +else ifeq ($(shell zcat --version >/dev/null 2>&1 || echo FAIL),) +ZCAT = zcat +else +ZCAT ?= $(error Gunzip client (zcat) not found!) +endif + +ifeq ($(shell sha512sum --version >/dev/null 2>&1 || echo FAIL),) +SHA512SUM = sha512sum --check +else ifeq ($(shell shasum --version >/dev/null 2>&1 || echo FAIL),) +SHA512SUM = shasum -a 512 --check +else ifeq ($(shell openssl version >/dev/null 2>&1 || echo FAIL),) +SHA512SUM = openssl dgst -sha512 +else +SHA512SUM = $(error SHA-512 checksumming not found!) +endif + +# +# Common helpers +# +HOSTCONF := --prefix="$(PREFIX)" +HOSTCONF += --datarootdir="$(PREFIX)/share" +HOSTCONF += --includedir="$(PREFIX)/include" +HOSTCONF += --libdir="$(PREFIX)/lib" +HOSTCONF += --build="$(BUILD)" --host="$(HOST)" --target="$(HOST)" +HOSTCONF += --program-prefix="" +# libtool stuff: +HOSTCONF += --disable-dependency-tracking + +ifdef HAVE_LINUX +HOSTCONF += --enable-static --disable-shared +endif + +ifdef HAVE_WIN32 +HOSTCONF += --without-pic +PIC := +else +HOSTCONF += --with-pic +PIC := -fPIC +endif + +HOSTTOOLS := \ + CC="$(CC)" CXX="$(CXX)" LD="$(LD)" \ + AR="$(AR)" CCAS="$(CCAS)" RANLIB="$(RANLIB)" STRIP="$(STRIP)" \ + PATH="$(PREFIX)/bin:$(PATH)" +# this part is different from VideoLan main.mak +HOSTVARS_NOPIC := $(HOSTTOOLS) \ + CPPFLAGS="$(CPPFLAGS)" \ + CFLAGS="$(CFLAGS)" \ + CXXFLAGS="$(CXXFLAGS)" \ + LDFLAGS="$(LDFLAGS)" +HOSTVARS := $(HOSTTOOLS) \ + CPPFLAGS="$(CPPFLAGS) $(PIC)" \ + CFLAGS="$(CFLAGS) $(PIC)" \ + CXXFLAGS="$(CXXFLAGS) $(PIC)" \ + LDFLAGS="$(LDFLAGS)" + +download_git = \ + rm -Rf $(@:.tar.xz=) && \ + $(GIT) clone $(2:%=--branch %) $(1) $(@:.tar.xz=) && \ + (cd $(@:.tar.xz=) && $(GIT) checkout $(3:%= %)) && \ + rm -Rf $(@:%.tar.xz=%)/.git && \ + (cd $(dir $@) && \ + tar cvJ $(notdir $(@:.tar.xz=))) > $@ && \ + rm -Rf $(@:.tar.xz=) +checksum = \ + $(foreach f,$(filter $(TARBALLS)/%,$^), \ + grep -- " $(f:$(TARBALLS)/%=%)$$" \ + "$(SRC)/$(patsubst .sum-%,%,$@)/$(2)SUMS" &&) \ + (cd $(TARBALLS) && $(1) /dev/stdin) < \ + "$(SRC)/$(patsubst .sum-%,%,$@)/$(2)SUMS" +CHECK_SHA512 = $(call checksum,$(SHA512SUM),SHA512) +UNPACK = $(RM) -R $@ \ + $(foreach f,$(filter %.tar.gz %.tgz,$^), && tar xvzf $(f)) \ + $(foreach f,$(filter %.tar.bz2,$^), && tar xvjf $(f)) \ + $(foreach f,$(filter %.tar.xz,$^), && tar xvJf $(f)) \ + $(foreach f,$(filter %.zip,$^), && unzip $(f)) +UNPACK_DIR = $(basename $(basename $(notdir $<))) +APPLY = (cd $(UNPACK_DIR) && patch -fp1) < +pkg_static = (cd $(UNPACK_DIR) && ../../../contrib/src/pkg-static.sh $(1)) +MOVE = mv $(UNPACK_DIR) $@ && touch $@ + +AUTOMAKE_DATA_DIRS=$(foreach n,$(foreach n,$(subst :, ,$(shell echo $$PATH)),$(abspath $(n)/../share)),$(wildcard $(n)/automake*)) +UPDATE_AUTOCONFIG = for dir in $(AUTOMAKE_DATA_DIRS); do \ + if test -f "$${dir}/config.sub" -a -f "$${dir}/config.guess"; then \ + cp "$${dir}/config.sub" "$${dir}/config.guess" $(UNPACK_DIR); \ + break; \ + fi; \ + done + +RECONF = mkdir -p -- $(PREFIX)/share/aclocal && \ + cd $< && autoreconf -fiv $(ACLOCAL_AMFLAGS) +CMAKE = cmake . -DCMAKE_TOOLCHAIN_FILE=$(abspath toolchain.cmake) \ + -DCMAKE_INSTALL_PREFIX=$(PREFIX) + +# +# Per-package build rules +# +include $(SRC)/*/rules.mak + +ifeq ($(PKGS_DISABLE), all) +PKGS := +endif +# +# Targets +# +ifneq ($(filter $(PKGS_DISABLE),$(PKGS_ENABLE)),) +$(error Same package(s) disabled and enabled at the same time) +endif +# Apply automatic selection (= remove distro packages): +PKGS_AUTOMATIC := $(filter-out $(PKGS_FOUND),$(PKGS)) +# Apply manual selection (from bootstrap): +PKGS_MANUAL := $(sort $(PKGS_ENABLE) $(filter-out $(PKGS_DISABLE),$(PKGS_AUTOMATIC))) +# Resolve dependencies: +PKGS_DEPS := $(filter-out $(PKGS_FOUND) $(PKGS_MANUAL),$(sort $(foreach p,$(PKGS_MANUAL),$(DEPS_$(p))))) +PKGS := $(sort $(PKGS_MANUAL) $(PKGS_DEPS)) + +fetch: $(PKGS:%=.sum-%) +fetch-all: $(PKGS_ALL:%=.sum-%) +install: $(PKGS:%=.%) + +mostlyclean: + -$(RM) $(foreach p,$(PKGS_ALL),.$(p) .sum-$(p) .dep-$(p)) + -$(RM) toolchain.cmake + -$(RM) -R "$(PREFIX)" + -$(RM) -R */ + +clean: mostlyclean + -$(RM) $(TARBALLS)/*.* + +distclean: clean + $(RM) config.mak + unlink Makefile + +# TODO: set up the correct url +#PREBUILT_URL=$(URL)/contrib/$(HOST)/ring-contrib-$(HOST)-latest.tar.bz2 + +ring-contrib-$(HOST)-latest.tar.bz2: + $(call download,$(PREBUILT_URL)) + +prebuilt: sflphone-contrib-$(HOST)-latest.tar.bz2 + -$(UNPACK) + mv $(HOST) $(TOPDST) + cd $(TOPDST)/$(HOST) && $(SRC)/change_prefix.sh + +package: install + rm -Rf tmp/ + mkdir -p tmp/ + cp -r $(PREFIX) tmp/ + # remove useless files + cd tmp/$(notdir $(PREFIX)); \ + cd share; rm -Rf man doc gtk-doc info lua projectM gettext; cd ..; \ + rm -Rf man sbin etc lib/lua lib/sidplay + cd tmp/$(notdir $(PREFIX)) && $(abspath $(SRC))/change_prefix.sh $(PREFIX) @@CONTRIB_PREFIX@@ + (cd tmp && tar c $(notdir $(PREFIX))/) | bzip2 -c > ../ring-contrib-$(HOST)-$(DATE).tar.bz2 + +list: + @echo All packages: + @echo ' $(PKGS_ALL)' | fmt + @echo Distribution-provided packages: + @echo ' $(PKGS_FOUND)' | fmt + @echo Automatically selected packages: + @echo ' $(PKGS_AUTOMATIC)' | fmt + @echo Manually deselected packages: + @echo ' $(PKGS_DISABLE)' | fmt + @echo Manually selected packages: + @echo ' $(PKGS_ENABLE)' | fmt + @echo Depended-on packages: + @echo ' $(PKGS_DEPS)' | fmt + @echo To-be-built packages: + @echo ' $(PKGS)' | fmt + +.PHONY: all fetch fetch-all install mostlyclean clean distclean package list prebuilt + +# CMake toolchain +toolchain.cmake: + $(RM) $@ +ifdef HAVE_WIN32 + echo "set(CMAKE_SYSTEM_NAME Windows)" >> $@ + echo "set(CMAKE_RC_COMPILER $(HOST)-windres)" >> $@ +endif +ifdef HAVE_DARWIN_OS + echo "set(CMAKE_SYSTEM_NAME Darwin)" >> $@ + echo "set(CMAKE_C_FLAGS $(CFLAGS))" >> $@ + echo "set(CMAKE_CXX_FLAGS $(CFLAGS))" >> $@ + echo "set(CMAKE_LD_FLAGS $(LDFLAGS))" >> $@ + echo "set(CMAKE_AR ar CACHE FILEPATH "Archiver")" >> $@ +ifdef HAVE_IOS + echo "set(CMAKE_OSX_SYSROOT $(IOS_SDK))" >> $@ +else + echo "set(CMAKE_OSX_SYSROOT $(MACOSX_SDK))" >> $@ +endif +endif +ifdef HAVE_CROSS_COMPILE + echo "set(_CMAKE_TOOLCHAIN_PREFIX $(HOST)-)" >> $@ +ifdef HAVE_ANDROID +# cmake will overwrite our --sysroot with a native (host) one on Darwin +# Set it to "" right away to short-circuit this behaviour + echo "set(CMAKE_CXX_SYSROOT_FLAG \"\")" >> $@ + echo "set(CMAKE_C_SYSROOT_FLAG \"\")" >> $@ +endif +endif + echo "set(CMAKE_C_COMPILER $(CC))" >> $@ + echo "set(CMAKE_CXX_COMPILER $(CXX))" >> $@ + echo "set(CMAKE_FIND_ROOT_PATH $(PREFIX))" >> $@ + echo "set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)" >> $@ + echo "set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)" >> $@ + echo "set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)" >> $@ + +# Default pattern rules +.sum-%: $(SRC)/%/SHA512SUMS + $(CHECK_SHA512) + touch $@ + +.sum-%: + $(error Download and check target not defined for $*) + +# Dummy dependency on found packages +$(patsubst %,.dep-%,$(PKGS_FOUND)): .dep-%: + touch $@ + +# Real dependency on missing packages +$(patsubst %,.dep-%,$(filter-out $(PKGS_FOUND),$(PKGS_ALL))): .dep-%: .% + touch -r $< $@ + +.SECONDEXPANSION: + +# Dependency propagation (convert 'DEPS_foo = bar' to '.foo: .bar') +$(foreach p,$(PKGS_ALL),.$(p)): .%: $$(foreach d,$$(DEPS_$$*),.dep-$$(d)) + +.DELETE_ON_ERROR: diff --git a/contrib/src/nettle/SHA512SUMS b/contrib/src/nettle/SHA512SUMS new file mode 100644 index 0000000000..38275a3336 --- /dev/null +++ b/contrib/src/nettle/SHA512SUMS @@ -0,0 +1 @@ +297c69e90bbd448f72e854abe5cc7868c08d710e1c1bcd6a14adf06e25629d58a3ef4d65ab588d001ec7091aa583032312ad15b416ea5479e5bf0ea63717f473 nettle-2.7.1.tar.gz diff --git a/contrib/src/nettle/rules.mak b/contrib/src/nettle/rules.mak new file mode 100644 index 0000000000..f832eda440 --- /dev/null +++ b/contrib/src/nettle/rules.mak @@ -0,0 +1,25 @@ +# Nettle + +NETTLE_VERSION := 2.7.1 +NETTLE_URL := ftp://ftp.gnu.org/gnu/nettle/nettle-$(NETTLE_VERSION).tar.gz + +ifeq ($(call need_pkg,"nettle >= 2.7"),) +PKGS_FOUND += nettle +endif + +$(TARBALLS)/nettle-$(NETTLE_VERSION).tar.gz: + $(call download,$(NETTLE_URL)) + +.sum-nettle: nettle-$(NETTLE_VERSION).tar.gz + +nettle: nettle-$(NETTLE_VERSION).tar.gz .sum-nettle + $(UNPACK) + $(UPDATE_AUTOCONFIG) + $(MOVE) + +DEPS_nettle = gmp $(DEPS_gmp) + +.nettle: nettle + cd $< && $(HOSTVARS) ./configure $(HOSTCONF) + cd $< && $(MAKE) install + touch $@ diff --git a/contrib/src/ogg/SHA512SUMS b/contrib/src/ogg/SHA512SUMS new file mode 100644 index 0000000000..19d4dbc4b8 --- /dev/null +++ b/contrib/src/ogg/SHA512SUMS @@ -0,0 +1 @@ +980d8916b5d6bf22376105869a9b9d5312cee81f0ff1f6aa9f34359e92a93b7b34555a4c4f124922d87ef7e48a4b9451e35d9b536929b9144fcc22bcf6debbf0 libogg-1.3.1.tar.xz diff --git a/contrib/src/ogg/libogg-1.1.patch b/contrib/src/ogg/libogg-1.1.patch new file mode 100644 index 0000000000..bf74b0cb83 --- /dev/null +++ b/contrib/src/ogg/libogg-1.1.patch @@ -0,0 +1,56 @@ +--- libogg/configure.in 2010-11-01 21:13:42.000000000 +0100 ++++ libogg.new/configure.in 2010-11-03 23:59:54.267733114 +0100 +@@ -28,17 +28,17 @@ + case $host in + *-*-irix*) + DEBUG="-g -signed" +- CFLAGS="-O2 -w -signed" ++ EXTRA_CFLAGS="-O2 -w -signed" + PROFILE="-p -g3 -O2 -signed" + ;; + sparc-sun-solaris*) + DEBUG="-v -g" +- CFLAGS="-xO4 -fast -w -fsimple -native -xcg92" ++ EXTRA_CFLAGS="-xO4 -fast -w -fsimple -native -xcg92" + PROFILE="-v -xpg -g -xO4 -fast -native -fsimple -xcg92 -Dsuncc" + ;; + *) + DEBUG="-g" +- CFLAGS="-O" ++ EXTRA_CFLAGS="-O" + PROFILE="-g -p" + ;; + esac +@@ -46,27 +46,27 @@ + case $host in + *-*-linux*) + DEBUG="-g -Wall -fsigned-char" +- CFLAGS="-O20 -Wall -ffast-math -fsigned-char" ++ EXTRA_CFLAGS="-O20 -Wall -ffast-math -fsigned-char" + PROFILE="-Wall -W -pg -g -O20 -ffast-math -fsigned-char" + ;; + sparc-sun-*) + DEBUG="-g -Wall -fsigned-char" +- CFLAGS="-O20 -ffast-math -fsigned-char" ++ EXTRA_CFLAGS="-O20 -ffast-math -fsigned-char" + PROFILE="-pg -g -O20 -fsigned-char" + ;; + *-*-darwin*) + DEBUG="-fno-common -g -Wall -fsigned-char" +- CFLAGS="-fno-common -O4 -Wall -fsigned-char -ffast-math" ++ EXTRA_CFLAGS="-fno-common -O3 -Wall -fsigned-char -ffast-math" + PROFILE="-fno-common -O4 -Wall -pg -g -fsigned-char -ffast-math" + ;; + *) + DEBUG="-g -Wall -fsigned-char" +- CFLAGS="-O20 -fsigned-char" ++ EXTRA_CFLAGS="-O20 -fsigned-char" + PROFILE="-O20 -g -pg -fsigned-char" + ;; + esac + fi +-CFLAGS="$CFLAGS $cflags_save" ++CFLAGS="$EXTRA_CFLAGS $cflags_save" + DEBUG="$DEBUG $cflags_save" + PROFILE="$PROFILE $cflags_save" + diff --git a/contrib/src/ogg/libogg-disable-check.patch b/contrib/src/ogg/libogg-disable-check.patch new file mode 100644 index 0000000000..5801b372be --- /dev/null +++ b/contrib/src/ogg/libogg-disable-check.patch @@ -0,0 +1,12 @@ +diff -ru libogg/src/Makefile.am libogg/src/Makefile.am +--- libogg/src/Makefile.am 2011-08-04 19:07:42.000000000 +0200 ++++ libogg-f/src/Makefile.am 2012-03-21 13:05:23.000000000 +0100 +@@ -9,7 +9,7 @@ + + # build and run the self tests on 'make check' + +-noinst_PROGRAMS = test_bitwise test_framing ++noinst_PROGRAMS = + + test_bitwise_SOURCES = bitwise.c + test_bitwise_CFLAGS = -D_V_SELFTEST diff --git a/contrib/src/ogg/rules.mak b/contrib/src/ogg/rules.mak new file mode 100644 index 0000000000..ac53b58e61 --- /dev/null +++ b/contrib/src/ogg/rules.mak @@ -0,0 +1,29 @@ +# libogg + +OGG_VERSION := 1.3.1 + +OGG_URL := http://downloads.xiph.org/releases/ogg/libogg-$(OGG_VERSION).tar.xz +#OGG_URL := $(CONTRIB_VIDEOLAN)/libogg-$(OGG_VERSION).tar.xz + +PKGS += ogg +ifeq ($(call need_pkg,"ogg >= 1.0"),) +PKGS_FOUND += ogg +endif + +$(TARBALLS)/libogg-$(OGG_VERSION).tar.xz: + $(call download,$(OGG_URL)) + +.sum-ogg: libogg-$(OGG_VERSION).tar.xz + +ogg: libogg-$(OGG_VERSION).tar.xz .sum-ogg + $(UNPACK) + $(APPLY) $(SRC)/ogg/libogg-1.1.patch + $(APPLY) $(SRC)/ogg/libogg-disable-check.patch + $(UPDATE_AUTOCONFIG) + $(MOVE) + +.ogg: ogg + $(RECONF) + cd $< && $(HOSTVARS) ./configure $(HOSTCONF) + cd $< && $(MAKE) install + touch $@ diff --git a/contrib/src/opendht/rules.mak b/contrib/src/opendht/rules.mak new file mode 100644 index 0000000000..f69af07d57 --- /dev/null +++ b/contrib/src/opendht/rules.mak @@ -0,0 +1,31 @@ +# OPENDHT +OPENDHT_VERSION := 97f8b633f090f36588e6dfe1569cfc9a305c14b7 +OPENDHT_URL := https://github.com/savoirfairelinux/opendht/archive/$(OPENDHT_VERSION).tar.gz + +PKGS += opendht +ifeq ($(call need_pkg,'opendht'),) +PKGS_FOUND += opendht +endif + +# Avoid building distro-provided dependencies in case opendht was built manually +ifneq ($(call need_pkg,"gnutls >= 3.1"),) +DEPS_opendht = gnutls $(DEPS_gnutls) +endif + +$(TARBALLS)/opendht-$(OPENDHT_VERSION).tar.gz: + $(call download,$(OPENDHT_URL)) + +.sum-opendht: opendht-$(OPENDHT_VERSION).tar.gz + $(warning $@ not implemented) + touch $@ + +opendht: opendht-$(OPENDHT_VERSION).tar.gz .sum-opendht + $(UNPACK) + $(UPDATE_AUTOCONFIG) && cd $(UNPACK_DIR) + $(MOVE) + +.opendht: opendht + mkdir -p $</m4 && $(RECONF) + cd $< && $(HOSTVARS) ./configure $(HOSTCONF) + cd $< && $(MAKE) install + touch $@ diff --git a/contrib/src/opus/SHA512SUMS b/contrib/src/opus/SHA512SUMS new file mode 100644 index 0000000000..ec92aedaf3 --- /dev/null +++ b/contrib/src/opus/SHA512SUMS @@ -0,0 +1 @@ +b603efe66d65ef38dbcd0d2bbf213a1d15fa456aee00eca73e99abe4ec78668ed82e661ca7a69e9af4e0bc39e1aa76c4151b7f9840ff621ddcfd69f596cf2ba9 opus-1.1.tar.gz diff --git a/contrib/src/opus/rules.mak b/contrib/src/opus/rules.mak new file mode 100644 index 0000000000..56f09336f2 --- /dev/null +++ b/contrib/src/opus/rules.mak @@ -0,0 +1,30 @@ +# opus + +OPUS_VERSION := 1.1 + +OPUS_URL := http://downloads.xiph.org/releases/opus/opus-$(OPUS_VERSION).tar.gz + +PKGS += opus +ifeq ($(call need_pkg,"opus >= 0.9.14"),) +PKGS_FOUND += opus +endif + +$(TARBALLS)/opus-$(OPUS_VERSION).tar.gz: + $(call download,$(OPUS_URL)) + +.sum-opus: opus-$(OPUS_VERSION).tar.gz + +opus: opus-$(OPUS_VERSION).tar.gz .sum-opus + $(UNPACK) + $(UPDATE_AUTOCONFIG) + $(MOVE) + +OPUS_CONF= --disable-extra-programs --disable-doc +ifndef HAVE_FPU +OPUS_CONF += --enable-fixed-point +endif + +.opus: opus + cd $< && $(HOSTVARS) ./configure $(HOSTCONF) $(OPUS_CONF) + cd $< && $(MAKE) install + touch $@ diff --git a/contrib/src/pcre/SHA512SUMS b/contrib/src/pcre/SHA512SUMS new file mode 100644 index 0000000000..5c2c2935b0 --- /dev/null +++ b/contrib/src/pcre/SHA512SUMS @@ -0,0 +1 @@ +e94c652b40de60b391e28afbddc67b5a034650f6f62027e52f2d7aef53caf5f0da9f2d4f6872d1558f965dd9d4d696e5e23d2a50f20a4fbc9f0a707fb6f55fae pcre-8.35.tar.bz2 diff --git a/contrib/src/pcre/rules.mak b/contrib/src/pcre/rules.mak new file mode 100644 index 0000000000..d30cee780d --- /dev/null +++ b/contrib/src/pcre/rules.mak @@ -0,0 +1,27 @@ +# Perl Compatible Regular Expression + +PCRE_VERSION := 8.35 +PCRE_URL := ftp://ftp.csx.cam.ac.uk/pub/software/programming/pcre/pcre-$(PCRE_VERSION).tar.bz2 + +PKGS += pcre + +# OS X ships with improperly packaged libpcre, so we can't rely on pkg-config +ifndef HAVE_MACOSX +ifeq ($(call need_pkg,"libpcre"),) +PKGS_FOUND += pcre +endif +endif + +$(TARBALLS)/pcre-$(PCRE_VERSION).tar.bz2: + $(call download,$(PCRE_URL)) + +.sum-pcre: pcre-$(PCRE_VERSION).tar.bz2 + +pcre: pcre-$(PCRE_VERSION).tar.bz2 .sum-pcre + $(UNPACK) + $(MOVE) + +.pcre: pcre + cd $< && $(HOSTVARS) ./configure $(HOSTCONF) + cd $< && $(MAKE) install + touch $@ diff --git a/contrib/src/pjproject/SHA512SUMS b/contrib/src/pjproject/SHA512SUMS new file mode 100644 index 0000000000..8c708f76f2 --- /dev/null +++ b/contrib/src/pjproject/SHA512SUMS @@ -0,0 +1 @@ +ead780282a1b3df8ca326992d30b83236c3fdb9dbafb134b33d3f59484ce7253ca8014f2d7d8b98968a696b75e8a29a545b3aa34a66acdf55a21942ca9b30370 pjproject-2.2.1.tar.bz2 diff --git a/contrib/src/pjproject/aconfigureupdate.patch b/contrib/src/pjproject/aconfigureupdate.patch new file mode 100644 index 0000000000..a010207430 --- /dev/null +++ b/contrib/src/pjproject/aconfigureupdate.patch @@ -0,0 +1,484 @@ +From 5ddeabda001689893f43e60a96436904f0597457 Mon Sep 17 00:00:00 2001 +From: Vittorio Giovara <vittorio.giovara@savoirfairelinux.com> +Date: Mon, 9 Jun 2014 18:17:42 -0400 +Subject: [PATCH] update aconfigure + +--- + aconfigure | 207 +++++++++++++++++++++++++++++++------------------------------ + 1 file changed, 106 insertions(+), 101 deletions(-) + +diff --git a/aconfigure b/aconfigure +index d4fc521..03f727f 100755 +--- a/aconfigure ++++ b/aconfigure +@@ -1,11 +1,9 @@ + #! /bin/sh + # Guess values for system-dependent variables and create Makefiles. +-# Generated by GNU Autoconf 2.68 for pjproject 2.x. ++# Generated by GNU Autoconf 2.69 for pjproject 2.x. + # + # +-# Copyright (C) 1992, 1993, 1994, 1995, 1996, 1998, 1999, 2000, 2001, +-# 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010 Free Software +-# Foundation, Inc. ++# Copyright (C) 1992-1996, 1998-2012 Free Software Foundation, Inc. + # + # + # This configure script is free software; the Free Software Foundation +@@ -134,6 +132,31 @@ export LANGUAGE + # CDPATH. + (unset CDPATH) >/dev/null 2>&1 && unset CDPATH + ++# Use a proper internal environment variable to ensure we don't fall ++ # into an infinite loop, continuously re-executing ourselves. ++ if test x"${_as_can_reexec}" != xno && test "x$CONFIG_SHELL" != x; then ++ _as_can_reexec=no; export _as_can_reexec; ++ # We cannot yet assume a decent shell, so we have to provide a ++# neutralization value for shells without unset; and this also ++# works around shells that cannot unset nonexistent variables. ++# Preserve -v and -x to the replacement shell. ++BASH_ENV=/dev/null ++ENV=/dev/null ++(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV ++case $- in # (((( ++ *v*x* | *x*v* ) as_opts=-vx ;; ++ *v* ) as_opts=-v ;; ++ *x* ) as_opts=-x ;; ++ * ) as_opts= ;; ++esac ++exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} ++# Admittedly, this is quite paranoid, since all the known shells bail ++# out after a failed `exec'. ++$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 ++as_fn_exit 255 ++ fi ++ # We don't want this to propagate to other subprocesses. ++ { _as_can_reexec=; unset _as_can_reexec;} + if test "x$CONFIG_SHELL" = x; then + as_bourne_compatible="if test -n \"\${ZSH_VERSION+set}\" && (emulate sh) >/dev/null 2>&1; then : + emulate sh +@@ -167,7 +190,8 @@ if ( set x; as_fn_ret_success y && test x = \"\$1\" ); then : + else + exitcode=1; echo positional parameters were not saved. + fi +-test x\$exitcode = x0 || exit 1" ++test x\$exitcode = x0 || exit 1 ++test -x / || exit 1" + as_suggested=" as_lineno_1=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_1a=\$LINENO + as_lineno_2=";as_suggested=$as_suggested$LINENO;as_suggested=$as_suggested" as_lineno_2a=\$LINENO + eval 'test \"x\$as_lineno_1'\$as_run'\" != \"x\$as_lineno_2'\$as_run'\" && +@@ -212,21 +236,25 @@ IFS=$as_save_IFS + + + if test "x$CONFIG_SHELL" != x; then : +- # We cannot yet assume a decent shell, so we have to provide a +- # neutralization value for shells without unset; and this also +- # works around shells that cannot unset nonexistent variables. +- # Preserve -v and -x to the replacement shell. +- BASH_ENV=/dev/null +- ENV=/dev/null +- (unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV +- export CONFIG_SHELL +- case $- in # (((( +- *v*x* | *x*v* ) as_opts=-vx ;; +- *v* ) as_opts=-v ;; +- *x* ) as_opts=-x ;; +- * ) as_opts= ;; +- esac +- exec "$CONFIG_SHELL" $as_opts "$as_myself" ${1+"$@"} ++ export CONFIG_SHELL ++ # We cannot yet assume a decent shell, so we have to provide a ++# neutralization value for shells without unset; and this also ++# works around shells that cannot unset nonexistent variables. ++# Preserve -v and -x to the replacement shell. ++BASH_ENV=/dev/null ++ENV=/dev/null ++(unset BASH_ENV) >/dev/null 2>&1 && unset BASH_ENV ENV ++case $- in # (((( ++ *v*x* | *x*v* ) as_opts=-vx ;; ++ *v* ) as_opts=-v ;; ++ *x* ) as_opts=-x ;; ++ * ) as_opts= ;; ++esac ++exec $CONFIG_SHELL $as_opts "$as_myself" ${1+"$@"} ++# Admittedly, this is quite paranoid, since all the known shells bail ++# out after a failed `exec'. ++$as_echo "$0: could not re-execute with $CONFIG_SHELL" >&2 ++exit 255 + fi + + if test x$as_have_required = xno; then : +@@ -328,6 +356,14 @@ $as_echo X"$as_dir" | + + + } # as_fn_mkdir_p ++ ++# as_fn_executable_p FILE ++# ----------------------- ++# Test if FILE is an executable regular file. ++as_fn_executable_p () ++{ ++ test -f "$1" && test -x "$1" ++} # as_fn_executable_p + # as_fn_append VAR VALUE + # ---------------------- + # Append the text in VALUE to the end of the definition contained in VAR. Take +@@ -449,6 +485,10 @@ as_cr_alnum=$as_cr_Letters$as_cr_digits + chmod +x "$as_me.lineno" || + { $as_echo "$as_me: error: cannot create $as_me.lineno; rerun with a POSIX shell" >&2; as_fn_exit 1; } + ++ # If we had to re-execute with $CONFIG_SHELL, we're ensured to have ++ # already done that, so ensure we don't try to do so again and fall ++ # in an infinite loop. This has already happened in practice. ++ _as_can_reexec=no; export _as_can_reexec + # Don't try to exec as it changes $[0], causing all sort of problems + # (the dirname of $[0] is not the place where we might find the + # original and so on. Autoconf is especially sensitive to this). +@@ -483,16 +523,16 @@ if (echo >conf$$.file) 2>/dev/null; then + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. +- # In both cases, we have to default to `cp -p'. ++ # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || +- as_ln_s='cp -p' ++ as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else +- as_ln_s='cp -p' ++ as_ln_s='cp -pR' + fi + else +- as_ln_s='cp -p' ++ as_ln_s='cp -pR' + fi + rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file + rmdir conf$$.dir 2>/dev/null +@@ -504,28 +544,8 @@ else + as_mkdir_p=false + fi + +-if test -x / >/dev/null 2>&1; then +- as_test_x='test -x' +-else +- if ls -dL / >/dev/null 2>&1; then +- as_ls_L_option=L +- else +- as_ls_L_option= +- fi +- as_test_x=' +- eval sh -c '\'' +- if test -d "$1"; then +- test -d "$1/."; +- else +- case $1 in #( +- -*)set "./$1";; +- esac; +- case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #(( +- ???[sx]*):;;*)false;;esac;fi +- '\'' sh +- ' +-fi +-as_executable_p=$as_test_x ++as_test_x='test -x' ++as_executable_p=as_fn_executable_p + + # Sed expression to map a string onto a valid CPP name. + as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" +@@ -1252,8 +1272,6 @@ target=$target_alias + if test "x$host_alias" != x; then + if test "x$build_alias" = x; then + cross_compiling=maybe +- $as_echo "$as_me: WARNING: if you wanted to set the --build type, don't use --host. +- If a cross compiler is detected then cross compile mode will be used" >&2 + elif test "x$build_alias" != "x$host_alias"; then + cross_compiling=yes + fi +@@ -1567,9 +1585,9 @@ test -n "$ac_init_help" && exit $ac_status + if $ac_init_version; then + cat <<\_ACEOF + pjproject configure 2.x +-generated by GNU Autoconf 2.68 ++generated by GNU Autoconf 2.69 + +-Copyright (C) 2010 Free Software Foundation, Inc. ++Copyright (C) 2012 Free Software Foundation, Inc. + This configure script is free software; the Free Software Foundation + gives unlimited permission to copy, distribute and modify it. + _ACEOF +@@ -1683,7 +1701,7 @@ $as_echo "$ac_try_echo"; } >&5 + test ! -s conftest.err + } && test -s conftest$ac_exeext && { + test "$cross_compiling" = yes || +- $as_test_x conftest$ac_exeext ++ test -x conftest$ac_exeext + }; then : + ac_retval=0 + else +@@ -1970,7 +1988,7 @@ This file contains any messages produced by compilers while + running configure, to aid debugging if configure makes a mistake. + + It was created by pjproject $as_me 2.x, which was +-generated by GNU Autoconf 2.68. Invocation command line was ++generated by GNU Autoconf 2.69. Invocation command line was + + $ $0 $@ + +@@ -2495,7 +2513,7 @@ do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do +- if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ++ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 +@@ -2535,7 +2553,7 @@ do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do +- if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ++ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="gcc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 +@@ -2588,7 +2606,7 @@ do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do +- if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ++ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="${ac_tool_prefix}cc" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 +@@ -2629,7 +2647,7 @@ do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do +- if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ++ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + if test "$as_dir/$ac_word$ac_exec_ext" = "/usr/ucb/cc"; then + ac_prog_rejected=yes + continue +@@ -2687,7 +2705,7 @@ do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do +- if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ++ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CC="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 +@@ -2731,7 +2749,7 @@ do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do +- if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ++ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CC="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 +@@ -3177,8 +3195,7 @@ cat confdefs.h - <<_ACEOF >conftest.$ac_ext + /* end confdefs.h. */ + #include <stdarg.h> + #include <stdio.h> +-#include <sys/types.h> +-#include <sys/stat.h> ++struct stat; + /* Most of the following tests are stolen from RCS 5.7's src/conf.sh. */ + struct buf { int x; }; + FILE * (*rcsopen) (struct buf *, struct stat *, int); +@@ -3291,7 +3308,7 @@ do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do +- if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ++ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_CXX="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 +@@ -3335,7 +3352,7 @@ do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do +- if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ++ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_CXX="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 +@@ -3544,7 +3561,7 @@ do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do +- if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ++ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_RANLIB="${ac_tool_prefix}ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 +@@ -3584,7 +3601,7 @@ do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do +- if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ++ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_RANLIB="ranlib" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 +@@ -3638,7 +3655,7 @@ do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do +- if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ++ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_AR="$ac_tool_prefix$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 +@@ -3682,7 +3699,7 @@ do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do +- if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ++ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_ac_ct_AR="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 +@@ -4464,7 +4481,7 @@ do + for ac_prog in grep ggrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_GREP="$as_dir/$ac_prog$ac_exec_ext" +- { test -f "$ac_path_GREP" && $as_test_x "$ac_path_GREP"; } || continue ++ as_fn_executable_p "$ac_path_GREP" || continue + # Check for GNU ac_path_GREP and select it if it is found. + # Check for GNU $ac_path_GREP + case `"$ac_path_GREP" --version 2>&1` in +@@ -4530,7 +4547,7 @@ do + for ac_prog in egrep; do + for ac_exec_ext in '' $ac_executable_extensions; do + ac_path_EGREP="$as_dir/$ac_prog$ac_exec_ext" +- { test -f "$ac_path_EGREP" && $as_test_x "$ac_path_EGREP"; } || continue ++ as_fn_executable_p "$ac_path_EGREP" || continue + # Check for GNU ac_path_EGREP and select it if it is found. + # Check for GNU $ac_path_EGREP + case `"$ac_path_EGREP" --version 2>&1` in +@@ -6489,7 +6506,7 @@ do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do +- if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ++ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_SDL_CONFIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 +@@ -6535,7 +6552,7 @@ do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do +- if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ++ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_path_SDL_CONFIG="$as_dir/$ac_word$ac_exec_ext" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 +@@ -6640,7 +6657,7 @@ do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do +- if { test -f "$as_dir/$ac_word$ac_exec_ext" && $as_test_x "$as_dir/$ac_word$ac_exec_ext"; }; then ++ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then + ac_cv_prog_PKG_CONFIG="$ac_prog" + $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 + break 2 +@@ -8449,16 +8466,16 @@ if (echo >conf$$.file) 2>/dev/null; then + # ... but there are two gotchas: + # 1) On MSYS, both `ln -s file dir' and `ln file dir' fail. + # 2) DJGPP < 2.04 has no symlinks; `ln -s' creates a wrapper executable. +- # In both cases, we have to default to `cp -p'. ++ # In both cases, we have to default to `cp -pR'. + ln -s conf$$.file conf$$.dir 2>/dev/null && test ! -f conf$$.exe || +- as_ln_s='cp -p' ++ as_ln_s='cp -pR' + elif ln conf$$.file conf$$ 2>/dev/null; then + as_ln_s=ln + else +- as_ln_s='cp -p' ++ as_ln_s='cp -pR' + fi + else +- as_ln_s='cp -p' ++ as_ln_s='cp -pR' + fi + rm -f conf$$ conf$$.exe conf$$.dir/conf$$.file conf$$.file + rmdir conf$$.dir 2>/dev/null +@@ -8518,28 +8535,16 @@ else + as_mkdir_p=false + fi + +-if test -x / >/dev/null 2>&1; then +- as_test_x='test -x' +-else +- if ls -dL / >/dev/null 2>&1; then +- as_ls_L_option=L +- else +- as_ls_L_option= +- fi +- as_test_x=' +- eval sh -c '\'' +- if test -d "$1"; then +- test -d "$1/."; +- else +- case $1 in #( +- -*)set "./$1";; +- esac; +- case `ls -ld'$as_ls_L_option' "$1" 2>/dev/null` in #(( +- ???[sx]*):;;*)false;;esac;fi +- '\'' sh +- ' +-fi +-as_executable_p=$as_test_x ++ ++# as_fn_executable_p FILE ++# ----------------------- ++# Test if FILE is an executable regular file. ++as_fn_executable_p () ++{ ++ test -f "$1" && test -x "$1" ++} # as_fn_executable_p ++as_test_x='test -x' ++as_executable_p=as_fn_executable_p + + # Sed expression to map a string onto a valid CPP name. + as_tr_cpp="eval sed 'y%*$as_cr_letters%P$as_cr_LETTERS%;s%[^_$as_cr_alnum]%_%g'" +@@ -8561,7 +8566,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 + # values after options handling. + ac_log=" + This file was extended by pjproject $as_me 2.x, which was +-generated by GNU Autoconf 2.68. Invocation command line was ++generated by GNU Autoconf 2.69. Invocation command line was + + CONFIG_FILES = $CONFIG_FILES + CONFIG_HEADERS = $CONFIG_HEADERS +@@ -8623,10 +8628,10 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" + ac_cs_version="\\ + pjproject config.status 2.x +-configured by $0, generated by GNU Autoconf 2.68, ++configured by $0, generated by GNU Autoconf 2.69, + with options \\"\$ac_cs_config\\" + +-Copyright (C) 2010 Free Software Foundation, Inc. ++Copyright (C) 2012 Free Software Foundation, Inc. + This config.status script is free software; the Free Software Foundation + gives unlimited permission to copy, distribute and modify it." + +@@ -8714,7 +8719,7 @@ fi + _ACEOF + cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 + if \$ac_cs_recheck; then +- set X '$SHELL' '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion ++ set X $SHELL '$0' $ac_configure_args \$ac_configure_extra_args --no-create --no-recursion + shift + \$as_echo "running CONFIG_SHELL=$SHELL \$*" >&6 + CONFIG_SHELL='$SHELL' +-- +1.8.3.2 + diff --git a/contrib/src/pjproject/endianness.patch b/contrib/src/pjproject/endianness.patch new file mode 100644 index 0000000000..84b9499448 --- /dev/null +++ b/contrib/src/pjproject/endianness.patch @@ -0,0 +1,19 @@ +diff --git a/pjlib/include/pj/config.h b/pjlib/include/pj/config.h +index 10f86fd..4ace1bc 100644 +--- a/pjlib/include/pj/config.h ++++ b/pjlib/include/pj/config.h +@@ -245,7 +245,13 @@ + # define PJ_M_NAME "armv4" + # define PJ_HAS_PENTIUM 0 + # if !PJ_IS_LITTLE_ENDIAN && !PJ_IS_BIG_ENDIAN +-# error Endianness must be declared for this processor ++# if defined(__GNUC__) ++# include <endian.h> ++# define PJ_IS_LITTLE_ENDIAN __BYTE_ORDER__ == __LITTLE_ENDIAN__ ++# define PJ_IS_BIG_ENDIAN __BYTE_ORDER__ == __BIG_ENDIAN__ ++# else ++# error Endianness must be declared for this processor ++# endif + # endif + + #elif defined (PJ_M_POWERPC) || defined(__powerpc) || defined(__powerpc__) || \ diff --git a/contrib/src/pjproject/gnutls.patch b/contrib/src/pjproject/gnutls.patch new file mode 100644 index 0000000000..2b2903fd6e --- /dev/null +++ b/contrib/src/pjproject/gnutls.patch @@ -0,0 +1,3257 @@ +From 5cb3786d107bdea372ab45e6c35f882fd867d5e0 Mon Sep 17 00:00:00 2001 +From: Vittorio Giovara <vittorio.giovara@savoirfairelinux.com> +Date: Mon, 9 Jun 2014 14:20:55 -0400 +Subject: [PATCH 1/1] ssl_sock: add gnutls backend + +This backend is mutually exclusive with the OpenSSL one, but completely +compatible, and conformant to the PJSIP API. Also avoids any license issues +when linking statically. + +The configure script is updated to select either OpenSSL or GnuTLS +with --enable-ssl[='...'] and a new symbol (PJ_HAS_TLS_SOCK) is introduced +to identify which backend is in use. + +Written by Vittorio Giovara <vittorio.giovara@savoirfairelinux.com> and +Philippe Proulx <philippe.proulx@savoirfairelinux.com> on behalf of +Savoir-Faire Linux. + +squashed the following commit: +ssl_sock_gtls: avoid NULL dereference +--- + aconfigure | 183 ++- + aconfigure.ac | 100 +- + pjlib/build/Makefile | 2 +- + pjlib/include/pj/compat/os_auto.h.in | 3 + + pjlib/include/pj/config.h | 4 +- + pjlib/src/pj/ssl_sock_common.c | 5 + + pjlib/src/pj/ssl_sock_gtls.c | 2782 ++++++++++++++++++++++++++++++++++ + pjlib/src/pj/ssl_sock_ossl.c | 6 +- + 8 files changed, 3023 insertions(+), 62 deletions(-) + create mode 100644 pjlib/src/pj/ssl_sock_gtls.c + +diff --git a/aconfigure b/aconfigure +index a296266..03f727f 100755 +--- a/aconfigure ++++ b/aconfigure +@@ -637,6 +637,8 @@ ac_no_opencore_amrnb + libcrypto_present + libssl_present + openssl_h_present ++libgnutls_present ++gnutls_h_present + ac_no_ssl + ac_v4l2_ldflags + ac_v4l2_cflags +@@ -1457,8 +1459,8 @@ Optional Features: + package and samples location using IPPROOT and + IPPSAMPLES env var or with --with-ipp and + --with-ipp-samples options +- --disable-ssl Exclude SSL support the build (default: autodetect) +- ++ --enable-ssl=backend Select 'gnutls' or 'openssl' (default) to provide ++ SSL support (autodetect) + --disable-opencore-amr Exclude OpenCORE AMR support from the build + (default: autodetect) + +@@ -7380,33 +7382,159 @@ fi + + # Check whether --enable-ssl was given. + if test "${enable_ssl+set}" = set; then : +- enableval=$enable_ssl; +- if test "$enable_ssl" = "no"; then +- ac_no_ssl=1 +- { $as_echo "$as_me:${as_lineno-$LINENO}: result: Checking if SSL support is disabled... yes" >&5 ++ enableval=$enable_ssl; if test "x$enableval" = "xgnutls"; then ++ ssl_backend="gnutls" ++ else ++ ssl_backend="openssl" ++ fi ++ ++fi ++ ++ ++if test "x$enable_ssl" = "xno"; then ++ ac_no_ssl=1 ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: Checking if SSL support is disabled... yes" >&5 + $as_echo "Checking if SSL support is disabled... yes" >&6; } +- fi ++else ++ if test "x$with_ssl" != "xno" -a "x$with_ssl" != "x"; then ++ CFLAGS="$CFLAGS -I$with_ssl/include" ++ LDFLAGS="$LDFLAGS -L$with_ssl/lib" ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: Using SSL prefix... $with_ssl" >&5 ++$as_echo "Using SSL prefix... $with_ssl" >&6; } ++ fi ++ if test "x$ssl_backend" = "xgnutls"; then ++ for ac_prog in pkg-config "python pkgconfig.py" ++do ++ # Extract the first word of "$ac_prog", so it can be a program name with args. ++set dummy $ac_prog; ac_word=$2 ++{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5 ++$as_echo_n "checking for $ac_word... " >&6; } ++if ${ac_cv_prog_PKG_CONFIG+:} false; then : ++ $as_echo_n "(cached) " >&6 ++else ++ if test -n "$PKG_CONFIG"; then ++ ac_cv_prog_PKG_CONFIG="$PKG_CONFIG" # Let the user override the test. ++else ++as_save_IFS=$IFS; IFS=$PATH_SEPARATOR ++for as_dir in $PATH ++do ++ IFS=$as_save_IFS ++ test -z "$as_dir" && as_dir=. ++ for ac_exec_ext in '' $ac_executable_extensions; do ++ if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then ++ ac_cv_prog_PKG_CONFIG="$ac_prog" ++ $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5 ++ break 2 ++ fi ++done ++ done ++IFS=$as_save_IFS + ++fi ++fi ++PKG_CONFIG=$ac_cv_prog_PKG_CONFIG ++if test -n "$PKG_CONFIG"; then ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $PKG_CONFIG" >&5 ++$as_echo "$PKG_CONFIG" >&6; } + else ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5 ++$as_echo "no" >&6; } ++fi ++ + +- { $as_echo "$as_me:${as_lineno-$LINENO}: result: checking for OpenSSL installations.." >&5 ++ test -n "$PKG_CONFIG" && break ++done ++test -n "$PKG_CONFIG" || PKG_CONFIG="none" ++ ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: checking for GnuTLS installations.." >&5 ++$as_echo "checking for GnuTLS installations.." >&6; } ++ ++ ++ ac_fn_c_check_header_mongrel "$LINENO" "gnutls/gnutls.h" "ac_cv_header_gnutls_gnutls_h" "$ac_includes_default" ++if test "x$ac_cv_header_gnutls_gnutls_h" = xyes; then : ++ gnutls_h_present=1 ++fi ++ ++ ++ ++ if test "$PKG_CONFIG" != "none"; then ++ if $PKG_CONFIG --exists gnutls; then ++ LIBS="$LIBS `$PKG_CONFIG --libs gnutls`" ++ libgnutls_present=1 ++ else ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for gnutls_certificate_set_x509_system_trust in -lgnutls" >&5 ++$as_echo_n "checking for gnutls_certificate_set_x509_system_trust in -lgnutls... " >&6; } ++if ${ac_cv_lib_gnutls_gnutls_certificate_set_x509_system_trust+:} false; then : ++ $as_echo_n "(cached) " >&6 ++else ++ ac_check_lib_save_LIBS=$LIBS ++LIBS="-lgnutls $LIBS" ++cat confdefs.h - <<_ACEOF >conftest.$ac_ext ++/* end confdefs.h. */ ++ ++/* Override any GCC internal prototype to avoid an error. ++ Use char because int might match the return type of a GCC ++ builtin and then its argument prototype would still apply. */ ++#ifdef __cplusplus ++extern "C" ++#endif ++char gnutls_certificate_set_x509_system_trust (); ++int ++main () ++{ ++return gnutls_certificate_set_x509_system_trust (); ++ ; ++ return 0; ++} ++_ACEOF ++if ac_fn_c_try_link "$LINENO"; then : ++ ac_cv_lib_gnutls_gnutls_certificate_set_x509_system_trust=yes ++else ++ ac_cv_lib_gnutls_gnutls_certificate_set_x509_system_trust=no ++fi ++rm -f core conftest.err conftest.$ac_objext \ ++ conftest$ac_exeext conftest.$ac_ext ++LIBS=$ac_check_lib_save_LIBS ++fi ++{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_gnutls_gnutls_certificate_set_x509_system_trust" >&5 ++$as_echo "$ac_cv_lib_gnutls_gnutls_certificate_set_x509_system_trust" >&6; } ++if test "x$ac_cv_lib_gnutls_gnutls_certificate_set_x509_system_trust" = xyes; then : ++ libgnutls_present=1 && ++ LIBS="$LIBS -lgnutls" ++fi ++ ++ fi ++ else ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: *** Warning: neither pkg-config nor python is available, disabling gnutls. ***" >&5 ++$as_echo "*** Warning: neither pkg-config nor python is available, disabling gnutls. ***" >&6; } ++ fi ++ ++ if test "x$gnutls_h_present" = "x1" -a "x$libgnutls_present" = "x1"; then ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: GnuTLS library found, SSL support enabled" >&5 ++$as_echo "GnuTLS library found, SSL support enabled" >&6; } ++ # PJSIP_HAS_TLS_TRANSPORT setting follows PJ_HAS_SSL_SOCK ++ #AC_DEFINE(PJSIP_HAS_TLS_TRANSPORT, 1) ++ $as_echo "#define PJ_HAS_SSL_SOCK 1" >>confdefs.h ++ ++ $as_echo "#define PJ_HAS_TLS_SOCK 1" >>confdefs.h ++ ++ else ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: ** No GnuTLS libraries found, disabling SSL support **" >&5 ++$as_echo "** No GnuTLS libraries found, disabling SSL support **" >&6; } ++ fi ++ else ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: checking for OpenSSL installations.." >&5 + $as_echo "checking for OpenSSL installations.." >&6; } +- if test "x$with_ssl" != "xno" -a "x$with_ssl" != "x"; then +- CFLAGS="$CFLAGS -I$with_ssl/include" +- LDFLAGS="$LDFLAGS -L$with_ssl/lib" +- { $as_echo "$as_me:${as_lineno-$LINENO}: result: Using SSL prefix... $with_ssl" >&5 +-$as_echo "Using SSL prefix... $with_ssl" >&6; } +- fi + + + +- ac_fn_c_check_header_mongrel "$LINENO" "openssl/ssl.h" "ac_cv_header_openssl_ssl_h" "$ac_includes_default" ++ ac_fn_c_check_header_mongrel "$LINENO" "openssl/ssl.h" "ac_cv_header_openssl_ssl_h" "$ac_includes_default" + if test "x$ac_cv_header_openssl_ssl_h" = xyes; then : + openssl_h_present=1 + fi + + +- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ERR_load_BIO_strings in -lcrypto" >&5 ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ERR_load_BIO_strings in -lcrypto" >&5 + $as_echo_n "checking for ERR_load_BIO_strings in -lcrypto... " >&6; } + if ${ac_cv_lib_crypto_ERR_load_BIO_strings+:} false; then : + $as_echo_n "(cached) " >&6 +@@ -7446,7 +7574,7 @@ if test "x$ac_cv_lib_crypto_ERR_load_BIO_strings" = xyes; then : + libcrypto_present=1 && LIBS="$LIBS -lcrypto" + fi + +- { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_library_init in -lssl" >&5 ++ { $as_echo "$as_me:${as_lineno-$LINENO}: checking for SSL_library_init in -lssl" >&5 + $as_echo_n "checking for SSL_library_init in -lssl... " >&6; } + if ${ac_cv_lib_ssl_SSL_library_init+:} false; then : + $as_echo_n "(cached) " >&6 +@@ -7486,22 +7614,23 @@ if test "x$ac_cv_lib_ssl_SSL_library_init" = xyes; then : + libssl_present=1 && LIBS="$LIBS -lssl" + fi + +- if test "x$openssl_h_present" = "x1" -a "x$libssl_present" = "x1" -a "x$libcrypto_present" = "x1"; then +- { $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL library found, SSL support enabled" >&5 ++ if test "x$openssl_h_present" = "x1" -a "x$libssl_present" = "x1" -a "x$libcrypto_present" = "x1"; then ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: OpenSSL library found, SSL support enabled" >&5 + $as_echo "OpenSSL library found, SSL support enabled" >&6; } +- # PJSIP_HAS_TLS_TRANSPORT setting follows PJ_HAS_SSL_SOCK +- #AC_DEFINE(PJSIP_HAS_TLS_TRANSPORT, 1) +- $as_echo "#define PJ_HAS_SSL_SOCK 1" >>confdefs.h ++ # PJSIP_HAS_TLS_TRANSPORT setting follows PJ_HAS_SSL_SOCK ++ #AC_DEFINE(PJSIP_HAS_TLS_TRANSPORT, 1) ++ $as_echo "#define PJ_HAS_SSL_SOCK 1" >>confdefs.h + +- else +- { $as_echo "$as_me:${as_lineno-$LINENO}: result: ** OpenSSL libraries not found, disabling SSL support **" >&5 +-$as_echo "** OpenSSL libraries not found, disabling SSL support **" >&6; } +- fi ++ $as_echo "#define PJ_HAS_TLS_SOCK 0" >>confdefs.h + ++ else ++ { $as_echo "$as_me:${as_lineno-$LINENO}: result: ** No OpenSSL libraries found, disabling SSL support **" >&5 ++$as_echo "** No OpenSSL libraries found, disabling SSL support **" >&6; } ++ fi ++ fi + fi + + +- + # Check whether --with-opencore-amrnb was given. + if test "${with_opencore_amrnb+set}" = set; then : + withval=$with_opencore_amrnb; as_fn_error $? "This option is obsolete and replaced by --with-opencore-amr=DIR" "$LINENO" 5 +diff --git a/aconfigure.ac b/aconfigure.ac +index cd71a7a..465285e 100644 +--- a/aconfigure.ac ++++ b/aconfigure.ac +@@ -1346,38 +1346,76 @@ fi + + dnl # Include SSL support + AC_SUBST(ac_no_ssl) +-AC_ARG_ENABLE(ssl, +- AC_HELP_STRING([--disable-ssl], +- [Exclude SSL support the build (default: autodetect)]) +- , +- [ +- if test "$enable_ssl" = "no"; then +- [ac_no_ssl=1] +- AC_MSG_RESULT([Checking if SSL support is disabled... yes]) +- fi +- ], +- [ +- AC_MSG_RESULT([checking for OpenSSL installations..]) +- if test "x$with_ssl" != "xno" -a "x$with_ssl" != "x"; then +- CFLAGS="$CFLAGS -I$with_ssl/include" +- LDFLAGS="$LDFLAGS -L$with_ssl/lib" +- AC_MSG_RESULT([Using SSL prefix... $with_ssl]) ++AC_ARG_ENABLE([ssl], ++ AC_HELP_STRING([--enable-ssl[=backend]], ++ [Select 'gnutls' or 'openssl' (default) to provide SSL support (autodetect)]), ++ [ if test "x$enableval" = "xgnutls"; then ++ [ssl_backend="gnutls"] ++ else ++ [ssl_backend="openssl"] + fi +- AC_SUBST(openssl_h_present) +- AC_SUBST(libssl_present) +- AC_SUBST(libcrypto_present) +- AC_CHECK_HEADER(openssl/ssl.h,[openssl_h_present=1]) +- AC_CHECK_LIB(crypto,ERR_load_BIO_strings,[libcrypto_present=1 && LIBS="$LIBS -lcrypto"]) +- AC_CHECK_LIB(ssl,SSL_library_init,[libssl_present=1 && LIBS="$LIBS -lssl"]) +- if test "x$openssl_h_present" = "x1" -a "x$libssl_present" = "x1" -a "x$libcrypto_present" = "x1"; then +- AC_MSG_RESULT([OpenSSL library found, SSL support enabled]) +- # PJSIP_HAS_TLS_TRANSPORT setting follows PJ_HAS_SSL_SOCK +- #AC_DEFINE(PJSIP_HAS_TLS_TRANSPORT, 1) +- AC_DEFINE(PJ_HAS_SSL_SOCK, 1) +- else +- AC_MSG_RESULT([** OpenSSL libraries not found, disabling SSL support **]) +- fi +- ]) ++ ]) ++ ++if test "x$enable_ssl" = "xno"; then ++ [ac_no_ssl=1] ++ AC_MSG_RESULT([Checking if SSL support is disabled... yes]) ++else ++ if test "x$with_ssl" != "xno" -a "x$with_ssl" != "x"; then ++ CFLAGS="$CFLAGS -I$with_ssl/include" ++ LDFLAGS="$LDFLAGS -L$with_ssl/lib" ++ AC_MSG_RESULT([Using SSL prefix... $with_ssl]) ++ fi ++ if test "x$ssl_backend" = "xgnutls"; then ++ AC_CHECK_PROGS(PKG_CONFIG, ++ pkg-config "python pkgconfig.py", ++ none) ++ AC_MSG_RESULT([checking for GnuTLS installations..]) ++ AC_SUBST(gnutls_h_present) ++ AC_SUBST(libgnutls_present) ++ AC_CHECK_HEADER(gnutls/gnutls.h, [gnutls_h_present=1]) ++ ++ if test "$PKG_CONFIG" != "none"; then ++ if $PKG_CONFIG --exists gnutls; then ++ LIBS="$LIBS `$PKG_CONFIG --libs gnutls`" ++ libgnutls_present=1 ++ else ++ AC_CHECK_LIB(gnutls, ++ gnutls_certificate_set_x509_system_trust, ++ [libgnutls_present=1 && ++ LIBS="$LIBS -lgnutls"]) ++ fi ++ else ++ AC_MSG_RESULT([*** Warning: neither pkg-config nor python is available, disabling gnutls. ***]) ++ fi ++ ++ if test "x$gnutls_h_present" = "x1" -a "x$libgnutls_present" = "x1"; then ++ AC_MSG_RESULT([GnuTLS library found, SSL support enabled]) ++ # PJSIP_HAS_TLS_TRANSPORT setting follows PJ_HAS_SSL_SOCK ++ #AC_DEFINE(PJSIP_HAS_TLS_TRANSPORT, 1) ++ AC_DEFINE(PJ_HAS_SSL_SOCK, 1) ++ AC_DEFINE(PJ_HAS_TLS_SOCK, 1) ++ else ++ AC_MSG_RESULT([** No GnuTLS libraries found, disabling SSL support **]) ++ fi ++ else ++ AC_MSG_RESULT([checking for OpenSSL installations..]) ++ AC_SUBST(openssl_h_present) ++ AC_SUBST(libssl_present) ++ AC_SUBST(libcrypto_present) ++ AC_CHECK_HEADER(openssl/ssl.h, [openssl_h_present=1]) ++ AC_CHECK_LIB(crypto,ERR_load_BIO_strings, [libcrypto_present=1 && LIBS="$LIBS -lcrypto"]) ++ AC_CHECK_LIB(ssl,SSL_library_init, [libssl_present=1 && LIBS="$LIBS -lssl"]) ++ if test "x$openssl_h_present" = "x1" -a "x$libssl_present" = "x1" -a "x$libcrypto_present" = "x1"; then ++ AC_MSG_RESULT([OpenSSL library found, SSL support enabled]) ++ # PJSIP_HAS_TLS_TRANSPORT setting follows PJ_HAS_SSL_SOCK ++ #AC_DEFINE(PJSIP_HAS_TLS_TRANSPORT, 1) ++ AC_DEFINE(PJ_HAS_SSL_SOCK, 1) ++ AC_DEFINE(PJ_HAS_TLS_SOCK, 0) ++ else ++ AC_MSG_RESULT([** No OpenSSL libraries found, disabling SSL support **]) ++ fi ++ fi ++fi + + dnl # Obsolete option --with-opencore-amrnb + AC_ARG_WITH(opencore-amrnb, +diff --git a/pjlib/build/Makefile b/pjlib/build/Makefile +index a75fa65..529e0ff 100644 +--- a/pjlib/build/Makefile ++++ b/pjlib/build/Makefile +@@ -35,7 +35,7 @@ export PJLIB_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \ + guid.o hash.o ip_helper_generic.o list.o lock.o log.o os_time_common.o \ + os_info.o pool.o pool_buf.o pool_caching.o pool_dbg.o rand.o \ + rbtree.o sock_common.o sock_qos_common.o sock_qos_bsd.o \ +- ssl_sock_common.o ssl_sock_ossl.o ssl_sock_dump.o \ ++ ssl_sock_common.o ssl_sock_ossl.o ssl_sock_gtls.o ssl_sock_dump.o \ + string.o timer.o types.o + export PJLIB_CFLAGS += $(_CFLAGS) + export PJLIB_CXXFLAGS += $(_CXXFLAGS) +diff --git a/pjlib/include/pj/compat/os_auto.h.in b/pjlib/include/pj/compat/os_auto.h.in +index 18df2bf..9295740 100644 +--- a/pjlib/include/pj/compat/os_auto.h.in ++++ b/pjlib/include/pj/compat/os_auto.h.in +@@ -206,6 +206,9 @@ + #ifndef PJ_HAS_SSL_SOCK + #undef PJ_HAS_SSL_SOCK + #endif ++#ifndef PJ_HAS_TLS_SOCK ++#undef PJ_HAS_TLS_SOCK ++#endif + + + #endif /* __PJ_COMPAT_OS_AUTO_H__ */ +diff --git a/pjlib/include/pj/config.h b/pjlib/include/pj/config.h +index 31020a3..90aefe2 100644 +--- a/pjlib/include/pj/config.h ++++ b/pjlib/include/pj/config.h +@@ -854,13 +854,15 @@ + + /** + * Enable secure socket. For most platforms, this is implemented using +- * OpenSSL, so this will require OpenSSL to be installed. For Symbian ++ * OpenSSL, so this will require OpenSSL or GnuTLS to be installed. For Symbian + * platform, this is implemented natively using CSecureSocket. + * + * Default: 0 (for now) + */ + #ifndef PJ_HAS_SSL_SOCK + # define PJ_HAS_SSL_SOCK 0 ++ // When set to 1 secure sockets will use the GnuTLS backend ++# define PJ_HAS_TLS_SOCK 0 + #endif + + +diff --git a/pjlib/src/pj/ssl_sock_common.c b/pjlib/src/pj/ssl_sock_common.c +index 768a640..b116f1b 100644 +--- a/pjlib/src/pj/ssl_sock_common.c ++++ b/pjlib/src/pj/ssl_sock_common.c +@@ -34,7 +34,12 @@ PJ_DEF(void) pj_ssl_sock_param_default(pj_ssl_sock_param *param) + param->async_cnt = 1; + param->concurrency = -1; + param->whole_data = PJ_TRUE; ++#if defined(PJ_HAS_TLS_SOCK) && PJ_HAS_TLS_SOCK == 1 ++ // GnuTLS is allowed to send bigger chunks ++ param->send_buffer_size = 65536; ++#else + param->send_buffer_size = 8192; ++#endif + #if !defined(PJ_SYMBIAN) || PJ_SYMBIAN==0 + param->read_buffer_size = 1500; + #endif +diff --git a/pjlib/src/pj/ssl_sock_gtls.c b/pjlib/src/pj/ssl_sock_gtls.c +new file mode 100644 +index 0000000..7b4b941 +--- /dev/null ++++ b/pjlib/src/pj/ssl_sock_gtls.c +@@ -0,0 +1,2782 @@ ++/* $Id$ */ ++/* ++ * Copyright (C) 2014 Savoir-Faire Linux. (http://www.savoirfairelinux.com) ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA ++ */ ++ ++#include <pj/ssl_sock.h> ++#include <pj/activesock.h> ++#include <pj/compat/socket.h> ++#include <pj/assert.h> ++#include <pj/errno.h> ++#include <pj/list.h> ++#include <pj/lock.h> ++#include <pj/log.h> ++#include <pj/math.h> ++#include <pj/os.h> ++#include <pj/pool.h> ++#include <pj/string.h> ++#include <pj/timer.h> ++#include <pj/file_io.h> ++ ++ ++/* Only build when PJ_HAS_SSL_SOCK and PJ_HAS_TLS_SOCK are enabled */ ++#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && \ ++ defined(PJ_HAS_TLS_SOCK) && PJ_HAS_TLS_SOCK != 0 ++ ++#define THIS_FILE "ssl_sock_gtls.c" ++ ++/* Workaround for ticket #985 */ ++#define DELAYED_CLOSE_TIMEOUT 200 ++ ++/* Maximum ciphers */ ++#define MAX_CIPHERS 100 ++ ++/* Standard trust locations */ ++#define TRUST_STORE_FILE1 "/etc/ssl/certs/ca-certificates.crt" ++#define TRUST_STORE_FILE2 "/etc/ssl/certs/ca-bundle.crt" ++ ++/* Debugging output level for GnuTLS only */ ++#define GNUTLS_LOG_LEVEL 0 ++ ++/* GnuTLS includes */ ++#include <gnutls/gnutls.h> ++#include <gnutls/x509.h> ++#include <gnutls/abstract.h> ++ ++#ifdef _MSC_VER ++# pragma comment( lib, "gnutls") ++#endif ++ ++ ++/* TLS state enumeration. */ ++enum tls_connection_state { ++ TLS_STATE_NULL, ++ TLS_STATE_HANDSHAKING, ++ TLS_STATE_ESTABLISHED ++}; ++ ++/* Internal timer types. */ ++enum timer_id { ++ TIMER_NONE, ++ TIMER_HANDSHAKE_TIMEOUT, ++ TIMER_CLOSE ++}; ++ ++/* Structure of SSL socket read buffer. */ ++typedef struct read_data_t { ++ void *data; ++ pj_size_t len; ++} read_data_t; ++ ++/* ++ * Get the offset of pointer to read-buffer of SSL socket from read-buffer ++ * of active socket. Note that both SSL socket and active socket employ ++ * different but correlated read-buffers (as much as async_cnt for each), ++ * and to make it easier/faster to find corresponding SSL socket's read-buffer ++ * from known active socket's read-buffer, the pointer of corresponding ++ * SSL socket's read-buffer is stored right after the end of active socket's ++ * read-buffer. ++ */ ++#define OFFSET_OF_READ_DATA_PTR(ssock, asock_rbuf) \ ++ (read_data_t**) \ ++ ((pj_int8_t *)(asock_rbuf) + \ ++ ssock->param.read_buffer_size) ++ ++/* Structure of SSL socket write data. */ ++typedef struct write_data_t { ++ PJ_DECL_LIST_MEMBER(struct write_data_t); ++ pj_ioqueue_op_key_t key; ++ pj_size_t record_len; ++ pj_ioqueue_op_key_t *app_key; ++ pj_size_t plain_data_len; ++ pj_size_t data_len; ++ unsigned flags; ++ union { ++ char content[1]; ++ const char *ptr; ++ } data; ++} write_data_t; ++ ++ ++/* Structure of SSL socket write buffer (circular buffer). */ ++typedef struct send_buf_t { ++ char *buf; ++ pj_size_t max_len; ++ char *start; ++ pj_size_t len; ++} send_buf_t; ++ ++ ++/* Circular buffer object */ ++typedef struct circ_buf_t { ++ pj_size_t cap; /* maximum number of elements (must be power of 2) */ ++ pj_size_t readp; /* index of oldest element */ ++ pj_size_t writep; /* index at which to write new element */ ++ pj_size_t size; /* number of elements */ ++ pj_uint8_t *buf; /* data buffer */ ++ pj_pool_t *pool; /* where new allocations will take place */ ++} circ_buf_t; ++ ++ ++/* Secure socket structure definition. */ ++struct pj_ssl_sock_t { ++ pj_pool_t *pool; ++ pj_ssl_sock_t *parent; ++ pj_ssl_sock_param param; ++ pj_ssl_cert_t *cert; ++ ++ pj_ssl_cert_info local_cert_info; ++ pj_ssl_cert_info remote_cert_info; ++ ++ pj_bool_t is_server; ++ enum tls_connection_state connection_state; ++ pj_ioqueue_op_key_t handshake_op_key; ++ pj_timer_entry timer; ++ pj_status_t verify_status; ++ ++ int last_err; ++ ++ pj_sock_t sock; ++ pj_activesock_t *asock; ++ ++ pj_sockaddr local_addr; ++ pj_sockaddr rem_addr; ++ int addr_len; ++ ++ pj_bool_t read_started; ++ pj_size_t read_size; ++ pj_uint32_t read_flags; ++ void **asock_rbuf; ++ read_data_t *ssock_rbuf; ++ ++ write_data_t write_pending; /* list of pending writes */ ++ write_data_t write_pending_empty; /* cache for write_pending */ ++ pj_bool_t flushing_write_pend; /* flag of flushing is ongoing */ ++ send_buf_t send_buf; ++ write_data_t send_pending; /* list of pending write to network */ ++ ++ gnutls_session_t session; ++ gnutls_certificate_credentials_t xcred; ++ ++ circ_buf_t circ_buf_input; ++ pj_lock_t *circ_buf_input_mutex; ++ ++ circ_buf_t circ_buf_output; ++ pj_lock_t *circ_buf_output_mutex; ++ ++ int tls_init_count; /* library initialization counter */ ++}; ++ ++ ++/* Certificate/credential structure definition. */ ++struct pj_ssl_cert_t { ++ pj_str_t CA_file; ++ pj_str_t cert_file; ++ pj_str_t privkey_file; ++ pj_str_t privkey_pass; ++}; ++ ++/* GnuTLS available ciphers */ ++static unsigned tls_available_ciphers; ++ ++/* Array of id/names for available ciphers */ ++static struct tls_ciphers_t { ++ pj_ssl_cipher id; ++ const char *name; ++} tls_ciphers[MAX_CIPHERS]; ++ ++/* Last error reported somehow */ ++static int tls_last_error; ++ ++ ++/* ++ ******************************************************************* ++ * Circular buffer functions. ++ ******************************************************************* ++ */ ++ ++static pj_status_t circ_init(pj_pool_factory *factory, ++ circ_buf_t *cb, pj_size_t cap) ++{ ++ cb->cap = cap; ++ cb->readp = 0; ++ cb->writep = 0; ++ cb->size = 0; ++ ++ /* Initial pool holding the buffer elements */ ++ cb->pool = pj_pool_create(factory, "tls-circ%p", cap, cap, NULL); ++ if (!cb->pool) ++ return PJ_ENOMEM; ++ ++ /* Allocate circular buffer */ ++ cb->buf = pj_pool_alloc(cb->pool, cap); ++ if (!cb->buf) { ++ pj_pool_release(cb->pool); ++ return PJ_ENOMEM; ++ } ++ ++ return PJ_SUCCESS; ++} ++ ++static void circ_deinit(circ_buf_t *cb) ++{ ++ if (cb->pool) { ++ pj_pool_release(cb->pool); ++ cb->pool = NULL; ++ } ++} ++ ++static pj_bool_t circ_empty(const circ_buf_t *cb) ++{ ++ return cb->size == 0; ++} ++ ++static pj_size_t circ_size(const circ_buf_t *cb) ++{ ++ return cb->size; ++} ++ ++static pj_size_t circ_avail(const circ_buf_t *cb) ++{ ++ return cb->cap - cb->size; ++} ++ ++static void circ_read(circ_buf_t *cb, pj_uint8_t *dst, pj_size_t len) ++{ ++ pj_size_t size_after = cb->cap - cb->readp; ++ pj_size_t tbc = PJ_MIN(size_after, len); ++ pj_size_t rem = len - tbc; ++ ++ pj_memcpy(dst, cb->buf + cb->readp, tbc); ++ pj_memcpy(dst + tbc, cb->buf, rem); ++ ++ cb->readp += len; ++ cb->readp &= (cb->cap - 1); ++ ++ cb->size -= len; ++} ++ ++static pj_status_t circ_write(circ_buf_t *cb, ++ const pj_uint8_t *src, pj_size_t len) ++{ ++ /* Overflow condition: resize */ ++ if (len > circ_avail(cb)) { ++ /* Minimum required capacity */ ++ pj_size_t min_cap = len + cb->size; ++ ++ /* Next 32-bit power of two */ ++ min_cap--; ++ min_cap |= min_cap >> 1; ++ min_cap |= min_cap >> 2; ++ min_cap |= min_cap >> 4; ++ min_cap |= min_cap >> 8; ++ min_cap |= min_cap >> 16; ++ min_cap++; ++ ++ /* Create a new pool to hold a bigger buffer, using the same factory */ ++ pj_pool_t *pool = pj_pool_create(cb->pool->factory, "tls-circ%p", ++ min_cap, min_cap, NULL); ++ if (!pool) ++ return PJ_ENOMEM; ++ ++ /* Allocate our new buffer */ ++ pj_uint8_t *buf = pj_pool_alloc(pool, min_cap); ++ if (!buf) { ++ pj_pool_release(pool); ++ return PJ_ENOMEM; ++ } ++ ++ /* Save old size, which we shall restore after the next read */ ++ pj_size_t old_size = cb->size; ++ ++ /* Copy old data into beginning of new buffer */ ++ circ_read(cb, buf, cb->size); ++ ++ /* Restore old size now */ ++ cb->size = old_size; ++ ++ /* Release the previous pool */ ++ pj_pool_release(cb->pool); ++ ++ /* Update circular buffer members */ ++ cb->pool = pool; ++ cb->buf = buf; ++ cb->readp = 0; ++ cb->writep = cb->size; ++ cb->cap = min_cap; ++ } ++ ++ pj_size_t size_after = cb->cap - cb->writep; ++ pj_size_t tbc = PJ_MIN(size_after, len); ++ pj_size_t rem = len - tbc; ++ ++ pj_memcpy(cb->buf + cb->writep, src, tbc); ++ pj_memcpy(cb->buf, src + tbc, rem); ++ ++ cb->writep += len; ++ cb->writep &= (cb->cap - 1); ++ ++ cb->size += len; ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* ++ ******************************************************************* ++ * Static/internal functions. ++ ******************************************************************* ++ */ ++ ++/* Convert from GnuTLS error to pj_status_t. */ ++static pj_status_t tls_status_from_err(pj_ssl_sock_t *ssock, int err) ++{ ++ pj_status_t status; ++ ++ switch (err) { ++ case GNUTLS_E_SUCCESS: ++ status = PJ_SUCCESS; ++ break; ++ case GNUTLS_E_MEMORY_ERROR: ++ status = PJ_ENOMEM; ++ break; ++ case GNUTLS_E_LARGE_PACKET: ++ status = PJ_ETOOBIG; ++ break; ++ case GNUTLS_E_NO_CERTIFICATE_FOUND: ++ status = PJ_ENOTFOUND; ++ break; ++ case GNUTLS_E_SESSION_EOF: ++ status = PJ_EEOF; ++ break; ++ case GNUTLS_E_HANDSHAKE_TOO_LARGE: ++ status = PJ_ETOOBIG; ++ break; ++ case GNUTLS_E_EXPIRED: ++ status = PJ_EGONE; ++ break; ++ case GNUTLS_E_TIMEDOUT: ++ status = PJ_ETIMEDOUT; ++ break; ++ case GNUTLS_E_PREMATURE_TERMINATION: ++ status = PJ_ECANCELLED; ++ break; ++ case GNUTLS_E_INTERNAL_ERROR: ++ case GNUTLS_E_UNIMPLEMENTED_FEATURE: ++ status = PJ_EBUG; ++ break; ++ case GNUTLS_E_AGAIN: ++ case GNUTLS_E_INTERRUPTED: ++ case GNUTLS_E_REHANDSHAKE: ++ status = PJ_EPENDING; ++ break; ++ case GNUTLS_E_TOO_MANY_EMPTY_PACKETS: ++ case GNUTLS_E_TOO_MANY_HANDSHAKE_PACKETS: ++ case GNUTLS_E_RECORD_LIMIT_REACHED: ++ status = PJ_ETOOMANY; ++ break; ++ case GNUTLS_E_UNSUPPORTED_VERSION_PACKET: ++ case GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM: ++ case GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE: ++ case GNUTLS_E_X509_UNSUPPORTED_ATTRIBUTE: ++ case GNUTLS_E_X509_UNSUPPORTED_EXTENSION: ++ case GNUTLS_E_X509_UNSUPPORTED_CRITICAL_EXTENSION: ++ status = PJ_ENOTSUP; ++ break; ++ case GNUTLS_E_INVALID_SESSION: ++ case GNUTLS_E_INVALID_REQUEST: ++ case GNUTLS_E_INVALID_PASSWORD: ++ case GNUTLS_E_ILLEGAL_PARAMETER: ++ case GNUTLS_E_RECEIVED_ILLEGAL_EXTENSION: ++ case GNUTLS_E_UNEXPECTED_PACKET: ++ case GNUTLS_E_UNEXPECTED_PACKET_LENGTH: ++ case GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET: ++ case GNUTLS_E_UNWANTED_ALGORITHM: ++ case GNUTLS_E_USER_ERROR: ++ status = PJ_EINVAL; ++ break; ++ default: ++ status = PJ_EUNKNOWN; ++ break; ++ } ++ ++ /* Not thread safe */ ++ tls_last_error = err; ++ if (ssock) ++ ssock->last_err = err; ++ return status; ++} ++ ++ ++/* Get error string from GnuTLS using tls_last_error */ ++static pj_str_t tls_strerror(pj_status_t status, ++ char *buf, pj_size_t bufsize) ++{ ++ pj_str_t errstr; ++ const char *tmp = gnutls_strerror(tls_last_error); ++ ++#if defined(PJ_HAS_ERROR_STRING) && (PJ_HAS_ERROR_STRING != 0) ++ if (tmp) { ++ pj_ansi_strncpy(buf, tmp, bufsize); ++ errstr = pj_str(buf); ++ return errstr; ++ } ++#endif /* PJ_HAS_ERROR_STRING */ ++ ++ errstr.ptr = buf; ++ errstr.slen = pj_ansi_snprintf(buf, bufsize, "GnuTLS error %d: %s", ++ tls_last_error, tmp); ++ if (errstr.slen < 1 || errstr.slen >= (int) bufsize) ++ errstr.slen = bufsize - 1; ++ ++ return errstr; ++} ++ ++ ++/* GnuTLS way of reporting internal operations. */ ++static void tls_print_logs(int level, const char* msg) ++{ ++ PJ_LOG(3, (THIS_FILE, "GnuTLS [%d]: %s", level, msg)); ++} ++ ++ ++/* Initialize GnuTLS. */ ++static pj_status_t tls_init(void) ++{ ++ /* Register error subsystem */ ++ pj_status_t status = pj_register_strerror(PJ_ERRNO_START_USER + ++ PJ_ERRNO_SPACE_SIZE * 6, ++ PJ_ERRNO_SPACE_SIZE, ++ &tls_strerror); ++ pj_assert(status == PJ_SUCCESS); ++ ++ /* Init GnuTLS library */ ++ int ret = gnutls_global_init(); ++ if (ret < 0) ++ return tls_status_from_err(NULL, ret); ++ ++ gnutls_global_set_log_level(GNUTLS_LOG_LEVEL); ++ gnutls_global_set_log_function(tls_print_logs); ++ ++ /* Init available ciphers */ ++ if (!tls_available_ciphers) { ++ unsigned int i; ++ ++ for (i = 0; ; i++) { ++ unsigned char id[2]; ++ const char *suite = gnutls_cipher_suite_info(i, (unsigned char *)id, ++ NULL, NULL, NULL, NULL); ++ tls_ciphers[i].id = 0; ++ /* usually the array size is bigger than the number of available ++ * ciphers anyway, so by checking here we can exit the loop as soon ++ * as either all ciphers have been added or the array is full */ ++ if (suite && i < PJ_ARRAY_SIZE(tls_ciphers)) { ++ tls_ciphers[i].id = (pj_ssl_cipher) ++ (pj_uint32_t) ((id[0] << 8) | id[1]); ++ tls_ciphers[i].name = suite; ++ } else ++ break; ++ } ++ ++ tls_available_ciphers = i; ++ } ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* Shutdown GnuTLS */ ++static void tls_deinit(void) ++{ ++ gnutls_global_deinit(); ++} ++ ++ ++/* Callback invoked every time a certificate has to be validated. */ ++static int tls_cert_verify_cb(gnutls_session_t session) ++{ ++ pj_ssl_sock_t *ssock; ++ unsigned int status; ++ int ret; ++ ++ /* Get SSL socket instance */ ++ ssock = (pj_ssl_sock_t *)gnutls_session_get_ptr(session); ++ pj_assert(ssock); ++ ++ /* Support only x509 format */ ++ ret = gnutls_certificate_type_get(session) != GNUTLS_CRT_X509; ++ if (ret < 0) { ++ ssock->verify_status |= PJ_SSL_CERT_EINVALID_FORMAT; ++ return GNUTLS_E_CERTIFICATE_ERROR; ++ } ++ ++ /* Store verification status */ ++ ret = gnutls_certificate_verify_peers2(session, &status); ++ if (ret < 0) { ++ ssock->verify_status |= PJ_SSL_CERT_EUNKNOWN; ++ return GNUTLS_E_CERTIFICATE_ERROR; ++ } ++ if (ssock->param.verify_peer) { ++ if (status & GNUTLS_CERT_INVALID) { ++ if (status & GNUTLS_CERT_SIGNER_NOT_FOUND) ++ ssock->verify_status |= PJ_SSL_CERT_EISSUER_NOT_FOUND; ++ else if (status & GNUTLS_CERT_EXPIRED || ++ status & GNUTLS_CERT_NOT_ACTIVATED) ++ ssock->verify_status |= PJ_SSL_CERT_EVALIDITY_PERIOD; ++ else if (status & GNUTLS_CERT_SIGNER_NOT_CA || ++ status & GNUTLS_CERT_INSECURE_ALGORITHM) ++ ssock->verify_status |= PJ_SSL_CERT_EUNTRUSTED; ++ else if (status & GNUTLS_CERT_UNEXPECTED_OWNER || ++ status & GNUTLS_CERT_MISMATCH) ++ ssock->verify_status |= PJ_SSL_CERT_EISSUER_MISMATCH; ++ else if (status & GNUTLS_CERT_REVOKED) ++ ssock->verify_status |= PJ_SSL_CERT_EREVOKED; ++ else ++ ssock->verify_status |= PJ_SSL_CERT_EUNKNOWN; ++ ++ return GNUTLS_E_CERTIFICATE_ERROR; ++ } ++ ++ /* When verification is not requested just return ok here, however ++ * applications can still get the verification status. */ ++ gnutls_x509_crt_t cert; ++ unsigned int cert_list_size; ++ const gnutls_datum_t *cert_list; ++ int ret; ++ ++ ret = gnutls_x509_crt_init(&cert); ++ if (ret < 0) ++ goto out; ++ ++ cert_list = gnutls_certificate_get_peers(session, &cert_list_size); ++ if (cert_list == NULL) { ++ ret = GNUTLS_E_NO_CERTIFICATE_FOUND; ++ goto out; ++ } ++ ++ /* TODO: verify whole chain perhaps? */ ++ ret = gnutls_x509_crt_import(cert, &cert_list[0], GNUTLS_X509_FMT_DER); ++ if (ret < 0) ++ ret = gnutls_x509_crt_import(cert, &cert_list[0], ++ GNUTLS_X509_FMT_PEM); ++ if (ret < 0) { ++ ssock->verify_status |= PJ_SSL_CERT_EINVALID_FORMAT; ++ goto out; ++ } ++ ret = gnutls_x509_crt_check_hostname(cert, ssock->param.server_name.ptr); ++ if (ret < 0) ++ goto out; ++ ++ gnutls_x509_crt_deinit(cert); ++ ++ /* notify GnuTLS to continue handshake normally */ ++ return GNUTLS_E_SUCCESS; ++ ++out: ++ tls_last_error = ret; ++ ssock->verify_status |= PJ_SSL_CERT_EUNKNOWN; ++ return GNUTLS_E_CERTIFICATE_ERROR; ++ } ++ ++ return GNUTLS_E_SUCCESS; ++} ++ ++ ++/* gnutls_handshake() and gnutls_record_send() will call this function to ++ * send/write (encrypted) data */ ++static ssize_t tls_data_push(gnutls_transport_ptr_t ptr, ++ const void *data, size_t len) ++{ ++ pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)ptr; ++ ++ pj_lock_acquire(ssock->circ_buf_output_mutex); ++ if (circ_write(&ssock->circ_buf_output, data, len) != PJ_SUCCESS) { ++ pj_lock_release(ssock->circ_buf_output_mutex); ++ ++ errno = ENOMEM; ++ return -1; ++ } ++ ++ pj_lock_release(ssock->circ_buf_output_mutex); ++ ++ return len; ++} ++ ++ ++/* gnutls_handshake() and gnutls_record_recv() will call this function to ++ * receive/read (encrypted) data */ ++static ssize_t tls_data_pull(gnutls_transport_ptr_t ptr, ++ void *data, pj_size_t len) ++{ ++ pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)ptr; ++ ++ pj_lock_acquire(ssock->circ_buf_input_mutex); ++ ++ if (circ_empty(&ssock->circ_buf_input)) { ++ pj_lock_release(ssock->circ_buf_input_mutex); ++ ++ /* Data buffers not yet filled */ ++ errno = EAGAIN; ++ return -1; ++ } ++ ++ pj_size_t circ_buf_size = circ_size(&ssock->circ_buf_input); ++ pj_size_t read_size = PJ_MIN(circ_buf_size, len); ++ ++ circ_read(&ssock->circ_buf_input, data, read_size); ++ ++ pj_lock_release(ssock->circ_buf_input_mutex); ++ ++ return read_size; ++} ++ ++ ++/* Append a string to the priority string, only once. */ ++static pj_status_t tls_str_append_once(pj_str_t *dst, pj_str_t *src) ++{ ++ if (pj_strstr(dst, src) == NULL) { ++ /* Check buffer size */ ++ if (dst->slen + src->slen + 3 > 1024) ++ return PJ_ETOOMANY; ++ ++ pj_strcat2(dst, ":+"); ++ pj_strcat(dst, src); ++ } ++ return PJ_SUCCESS; ++} ++ ++ ++/* Generate priority string with user preference order. */ ++static pj_status_t tls_priorities_set(pj_ssl_sock_t *ssock) ++{ ++ char buf[1024]; ++ pj_str_t cipher_list; ++ pj_str_t compression = pj_str("COMP-NULL"); ++ pj_str_t server = pj_str(":%SERVER_PRECEDENCE"); ++ int i, j, ret; ++ const char *priority; ++ const char *err; ++ ++ pj_strset(&cipher_list, buf, 0); ++ ++ /* For each level, enable only the requested protocol */ ++ switch (ssock->param.proto) { ++ case PJ_SSL_SOCK_PROTO_DEFAULT: ++ case PJ_SSL_SOCK_PROTO_TLS1: ++ // set lowest compatibility mode, ask for TLS client hello ++ if (ssock->param.ciphers_num == 0) ++ priority = "NORMAL:-VERS-SSL3.0:%LATEST_RECORD_VERSION"; ++ else ++ priority = "NONE:+VERS-TLS1.2:+VERS-TLS1.1:+VERS-TLS1.0:%LATEST_RECORD_VERSION"; ++ break; ++ case PJ_SSL_SOCK_PROTO_SSL3: ++ if (ssock->param.ciphers_num == 0) ++ priority = "NORMAL:-VERS-TLS1.0:-VERS-TLS1.1:-VERS-TLS1.2"; ++ else ++ priority = "NONE:+VERS-SSL3.0"; ++ break; ++ case PJ_SSL_SOCK_PROTO_SSL23: ++ /* GnuTLS does not support any SSLv2 suite, let's just enable SSLv3 ++ * with maximum compatibility */ ++ if (ssock->param.ciphers_num == 0) ++ priority = "NORMAL:-VERS-TLS1.0:-VERS-TLS1.1:-VERS-TLS1.2:%COMPAT"; ++ else ++ priority = "NONE:+VERS-SSL3.0:%COMPAT"; ++ break; ++ default: ++ return PJ_ENOTSUP; ++ } ++ ++ pj_strcat2(&cipher_list, priority); ++ for (i = 0; i < ssock->param.ciphers_num; i++) { ++ for (j = 0; ; j++) { ++ pj_ssl_cipher c; ++ const char *suite; ++ unsigned char id[2]; ++ gnutls_protocol_t proto; ++ gnutls_kx_algorithm_t kx; ++ gnutls_mac_algorithm_t mac; ++ gnutls_cipher_algorithm_t algo; ++ ++ suite = gnutls_cipher_suite_info(j, (unsigned char *)id, ++ &kx, &algo, &mac, &proto); ++ if (!suite) ++ break; ++ ++ c = (pj_ssl_cipher) (pj_uint32_t) ((id[0] << 8) | id[1]); ++ if (ssock->param.ciphers[i] == c) { ++ char temp[256]; ++ pj_str_t cipher_entry; ++ ++ /* Protocol version */ ++ pj_strset(&cipher_entry, temp, 0); ++ pj_strcat2(&cipher_entry, "VERS-"); ++ pj_strcat2(&cipher_entry, gnutls_protocol_get_name(proto)); ++ ret = tls_str_append_once(&cipher_list, &cipher_entry); ++ if (ret != PJ_SUCCESS) ++ return ret; ++ ++ /* Cipher */ ++ pj_strset(&cipher_entry, temp, 0); ++ pj_strcat2(&cipher_entry, gnutls_cipher_get_name(algo)); ++ ret = tls_str_append_once(&cipher_list, &cipher_entry); ++ if (ret != PJ_SUCCESS) ++ return ret; ++ ++ /* Mac */ ++ pj_strset(&cipher_entry, temp, 0); ++ pj_strcat2(&cipher_entry, gnutls_mac_get_name(mac)); ++ ret = tls_str_append_once(&cipher_list, &cipher_entry); ++ if (ret != PJ_SUCCESS) ++ return ret; ++ ++ /* Key exchange */ ++ pj_strset(&cipher_entry, temp, 0); ++ pj_strcat2(&cipher_entry, gnutls_kx_get_name(kx)); ++ ret = tls_str_append_once(&cipher_list, &cipher_entry); ++ if (ret != PJ_SUCCESS) ++ return ret; ++ ++ /* Compression is always disabled */ ++ /* Signature is level-default */ ++ break; ++ } ++ } ++ } ++ ++ /* Disable compression, it's a TLS-only extension after all */ ++ tls_str_append_once(&cipher_list, &compression); ++ ++ /* Server will be the one deciding which crypto to use */ ++ if (ssock->is_server) { ++ if (cipher_list.slen + server.slen + 1 > sizeof(buf)) ++ return PJ_ETOOMANY; ++ else ++ pj_strcat(&cipher_list, &server); ++ } ++ ++ /* End the string and print it */ ++ cipher_list.ptr[cipher_list.slen] = '\0'; ++ PJ_LOG(5, (ssock->pool->obj_name, "Priority string: %s", cipher_list.ptr)); ++ ++ /* Set our priority string */ ++ ret = gnutls_priority_set_direct(ssock->session, ++ cipher_list.ptr, &err); ++ if (ret < 0) { ++ tls_last_error = GNUTLS_E_INVALID_REQUEST; ++ return PJ_EINVAL; ++ } ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* Load root CA file or load the installed ones. */ ++static pj_status_t tls_trust_set(pj_ssl_sock_t *ssock) ++{ ++ int ntrusts = 0; ++ int err; ++ ++ err = gnutls_certificate_set_x509_system_trust(ssock->xcred); ++ if (err > 0) ++ ntrusts += err; ++ err = gnutls_certificate_set_x509_trust_file(ssock->xcred, ++ TRUST_STORE_FILE1, ++ GNUTLS_X509_FMT_PEM); ++ if (err > 0) ++ ntrusts += err; ++ ++ err = gnutls_certificate_set_x509_trust_file(ssock->xcred, ++ TRUST_STORE_FILE2, ++ GNUTLS_X509_FMT_PEM); ++ if (err > 0) ++ ntrusts += err; ++ ++ if (ntrusts > 0) ++ return PJ_SUCCESS; ++ else if (!ntrusts) ++ return PJ_ENOTFOUND; ++ else ++ return PJ_EINVAL; ++} ++ ++ ++/* Create and initialize new GnuTLS context and instance */ ++static pj_status_t tls_open(pj_ssl_sock_t *ssock) ++{ ++ pj_ssl_cert_t *cert; ++ pj_status_t status; ++ int ret; ++ ++ pj_assert(ssock); ++ ++ cert = ssock->cert; ++ ++ /* Even if reopening is harmless, having one instance only simplifies ++ * deallocating it later on */ ++ if (!ssock->tls_init_count) { ++ ssock->tls_init_count++; ++ ret = tls_init(); ++ if (ret < 0) ++ return ret; ++ } else ++ return PJ_SUCCESS; ++ ++ /* Start this socket session */ ++ ret = gnutls_init(&ssock->session, ssock->is_server ? GNUTLS_SERVER ++ : GNUTLS_CLIENT); ++ if (ret < 0) ++ goto out; ++ ++ /* Set the ssock object to be retrieved by transport (send/recv) and by ++ * user data from this session */ ++ gnutls_transport_set_ptr(ssock->session, ++ (gnutls_transport_ptr_t) (uintptr_t) ssock); ++ gnutls_session_set_ptr(ssock->session, ++ (gnutls_transport_ptr_t) (uintptr_t) ssock); ++ ++ /* Initialize input circular buffer */ ++ status = circ_init(ssock->pool->factory, &ssock->circ_buf_input, 512); ++ if (status != PJ_SUCCESS) ++ return status; ++ ++ /* Initialize output circular buffer */ ++ status = circ_init(ssock->pool->factory, &ssock->circ_buf_output, 512); ++ if (status != PJ_SUCCESS) ++ return status; ++ ++ /* Set the callback that allows GnuTLS to PUSH and PULL data ++ * TO and FROM the transport layer */ ++ gnutls_transport_set_push_function(ssock->session, tls_data_push); ++ gnutls_transport_set_pull_function(ssock->session, tls_data_pull); ++ ++ /* Determine which cipher suite to support */ ++ status = tls_priorities_set(ssock); ++ if (status != PJ_SUCCESS) ++ return status; ++ ++ /* Allocate credentials for handshaking and transmission */ ++ ret = gnutls_certificate_allocate_credentials(&ssock->xcred); ++ if (ret < 0) ++ goto out; ++ gnutls_certificate_set_verify_function(ssock->xcred, tls_cert_verify_cb); ++ ++ /* Load system trust file(s) */ ++ status = tls_trust_set(ssock); ++ if (status != PJ_SUCCESS) ++ return status; ++ ++ /* Load user-provided CA, certificate and key if available */ ++ if (cert) { ++ /* Load CA if one is specified. */ ++ if (cert->CA_file.slen) { ++ ret = gnutls_certificate_set_x509_trust_file(ssock->xcred, ++ cert->CA_file.ptr, ++ GNUTLS_X509_FMT_PEM); ++ if (ret < 0) ++ ret = gnutls_certificate_set_x509_trust_file(ssock->xcred, ++ cert->CA_file.ptr, ++ GNUTLS_X509_FMT_DER); ++ if (ret < 0) ++ goto out; ++ } ++ ++ /* Load certificate, key and pass if one is specified */ ++ if (cert->cert_file.slen && cert->privkey_file.slen) { ++ const char *prikey_file = cert->privkey_file.ptr; ++ const char *prikey_pass = cert->privkey_pass.slen ++ ? cert->privkey_pass.ptr ++ : NULL; ++ ret = gnutls_certificate_set_x509_key_file2(ssock->xcred, ++ cert->cert_file.ptr, ++ prikey_file, ++ GNUTLS_X509_FMT_PEM, ++ prikey_pass, ++ 0); ++ if (ret != GNUTLS_E_SUCCESS) ++ ret = gnutls_certificate_set_x509_key_file2(ssock->xcred, ++ cert->cert_file.ptr, ++ prikey_file, ++ GNUTLS_X509_FMT_DER, ++ prikey_pass, ++ 0); ++ if (ret < 0) ++ goto out; ++ } ++ } ++ ++ /* Require client certificate if asked */ ++ if (ssock->is_server && ssock->param.require_client_cert) ++ gnutls_certificate_server_set_request(ssock->session, ++ GNUTLS_CERT_REQUIRE); ++ ++ /* Finally set credentials for this session */ ++ ret = gnutls_credentials_set(ssock->session, ++ GNUTLS_CRD_CERTIFICATE, ssock->xcred); ++ if (ret < 0) ++ goto out; ++ ++ ret = GNUTLS_E_SUCCESS; ++out: ++ return tls_status_from_err(ssock, ret); ++} ++ ++ ++/* Destroy GnuTLS credentials and session. */ ++static void tls_close(pj_ssl_sock_t *ssock) ++{ ++ if (ssock->session) { ++ gnutls_bye(ssock->session, GNUTLS_SHUT_RDWR); ++ gnutls_deinit(ssock->session); ++ ssock->session = NULL; ++ } ++ ++ if (ssock->xcred) { ++ gnutls_certificate_free_credentials(ssock->xcred); ++ ssock->xcred = NULL; ++ } ++ ++ /* Free GnuTLS library */ ++ if (ssock->tls_init_count) { ++ ssock->tls_init_count--; ++ tls_deinit(); ++ } ++ ++ /* Destroy circular buffers */ ++ circ_deinit(&ssock->circ_buf_input); ++ circ_deinit(&ssock->circ_buf_output); ++} ++ ++ ++/* Reset socket state. */ ++static void tls_sock_reset(pj_ssl_sock_t *ssock) ++{ ++ ssock->connection_state = TLS_STATE_NULL; ++ ++ tls_close(ssock); ++ ++ if (ssock->asock) { ++ pj_activesock_close(ssock->asock); ++ ssock->asock = NULL; ++ ssock->sock = PJ_INVALID_SOCKET; ++ } ++ if (ssock->sock != PJ_INVALID_SOCKET) { ++ pj_sock_close(ssock->sock); ++ ssock->sock = PJ_INVALID_SOCKET; ++ } ++ ++ ssock->last_err = tls_last_error = GNUTLS_E_SUCCESS; ++} ++ ++ ++/* Get Common Name field string from a general name string */ ++static void tls_cert_get_cn(const pj_str_t *gen_name, pj_str_t *cn) ++{ ++ pj_str_t CN_sign = {"CN=", 3}; ++ char *p, *q; ++ ++ pj_bzero(cn, sizeof(cn)); ++ ++ p = pj_strstr(gen_name, &CN_sign); ++ if (!p) ++ return; ++ ++ p += 3; /* shift pointer to value part */ ++ pj_strset(cn, p, gen_name->slen - (p - gen_name->ptr)); ++ q = pj_strchr(cn, ','); ++ if (q) ++ cn->slen = q - p; ++} ++ ++ ++/* Get certificate info; in case the certificate info is already populated, ++ * this function will check if the contents need updating by inspecting the ++ * issuer and the serial number. */ ++static void tls_cert_get_info(pj_pool_t *pool, pj_ssl_cert_info *ci, ++ gnutls_x509_crt_t cert) ++{ ++ pj_bool_t update_needed; ++ char buf[512] = { 0 }; ++ size_t bufsize = sizeof(buf); ++ pj_uint8_t serial_no[64] = { 0 }; /* should be >= sizeof(ci->serial_no) */ ++ size_t serialsize = sizeof(serial_no); ++ size_t len = sizeof(buf); ++ int i, ret, seq = 0; ++ pj_ssl_cert_name_type type; ++ ++ pj_assert(pool && ci && cert); ++ ++ /* Get issuer */ ++ gnutls_x509_crt_get_issuer_dn(cert, buf, &bufsize); ++ ++ /* Get serial no */ ++ gnutls_x509_crt_get_serial(cert, serial_no, &serialsize); ++ ++ /* Check if the contents need to be updated */ ++ update_needed = pj_strcmp2(&ci->issuer.info, buf) || ++ pj_memcmp(ci->serial_no, serial_no, serialsize); ++ if (!update_needed) ++ return; ++ ++ /* Update cert info */ ++ ++ pj_bzero(ci, sizeof(pj_ssl_cert_info)); ++ ++ /* Version */ ++ ci->version = gnutls_x509_crt_get_version(cert); ++ ++ /* Issuer */ ++ pj_strdup2(pool, &ci->issuer.info, buf); ++ tls_cert_get_cn(&ci->issuer.info, &ci->issuer.cn); ++ ++ /* Serial number */ ++ pj_memcpy(ci->serial_no, serial_no, sizeof(ci->serial_no)); ++ ++ /* Subject */ ++ bufsize = sizeof(buf); ++ gnutls_x509_crt_get_dn(cert, buf, &bufsize); ++ pj_strdup2(pool, &ci->subject.info, buf); ++ tls_cert_get_cn(&ci->subject.info, &ci->subject.cn); ++ ++ /* Validity */ ++ ci->validity.end.sec = gnutls_x509_crt_get_expiration_time(cert); ++ ci->validity.start.sec = gnutls_x509_crt_get_activation_time(cert); ++ ci->validity.gmt = 0; ++ ++ /* Subject Alternative Name extension */ ++ if (ci->version >= 3) { ++ char out[256] = { 0 }; ++ /* Get the number of all alternate names so that we can allocate ++ * the correct number of bytes in subj_alt_name */ ++ while (gnutls_x509_crt_get_subject_alt_name(cert, seq, out, &len, ++ NULL) != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) ++ seq++; ++ ++ ci->subj_alt_name.entry = pj_pool_calloc(pool, seq, ++ sizeof(*ci->subj_alt_name.entry)); ++ if (!ci->subj_alt_name.entry) { ++ tls_last_error = GNUTLS_E_MEMORY_ERROR; ++ return; ++ } ++ ++ /* Now populate the alternative names */ ++ for (i = 0; i < seq; i++) { ++ len = sizeof(out) - 1; ++ ret = gnutls_x509_crt_get_subject_alt_name(cert, i, out, &len, NULL); ++ switch (ret) { ++ case GNUTLS_SAN_IPADDRESS: ++ type = PJ_SSL_CERT_NAME_IP; ++ pj_inet_ntop2(len == sizeof(pj_in6_addr) ? pj_AF_INET6() ++ : pj_AF_INET(), ++ out, buf, sizeof(buf)); ++ break; ++ case GNUTLS_SAN_URI: ++ type = PJ_SSL_CERT_NAME_URI; ++ break; ++ case GNUTLS_SAN_RFC822NAME: ++ type = PJ_SSL_CERT_NAME_RFC822; ++ break; ++ case GNUTLS_SAN_DNSNAME: ++ type = PJ_SSL_CERT_NAME_DNS; ++ break; ++ default: ++ type = PJ_SSL_CERT_NAME_UNKNOWN; ++ break; ++ } ++ ++ if (len && type != PJ_SSL_CERT_NAME_UNKNOWN) { ++ ci->subj_alt_name.entry[ci->subj_alt_name.cnt].type = type; ++ pj_strdup2(pool, ++ &ci->subj_alt_name.entry[ci->subj_alt_name.cnt].name, ++ type == PJ_SSL_CERT_NAME_IP ? buf : out); ++ ci->subj_alt_name.cnt++; ++ } ++ } ++ /* TODO: if no DNS alt. names were found, we could check against ++ * the commonName as per RFC3280. */ ++ } ++} ++ ++ ++/* Update local & remote certificates info. This function should be ++ * called after handshake or renegotiation successfully completed. */ ++static void tls_cert_update(pj_ssl_sock_t *ssock) ++{ ++ gnutls_x509_crt_t cert = NULL; ++ const gnutls_datum_t *us; ++ const gnutls_datum_t *certs; ++ unsigned int certslen = 0; ++ int ret = GNUTLS_CERT_INVALID; ++ ++ pj_assert(ssock->connection_state == TLS_STATE_ESTABLISHED); ++ ++ /* Get active local certificate */ ++ us = gnutls_certificate_get_ours(ssock->session); ++ if (!us) ++ goto us_out; ++ ++ ret = gnutls_x509_crt_init(&cert); ++ if (ret < 0) ++ goto us_out; ++ ret = gnutls_x509_crt_import(cert, us, GNUTLS_X509_FMT_DER); ++ if (ret < 0) ++ ret = gnutls_x509_crt_import(cert, us, GNUTLS_X509_FMT_PEM); ++ if (ret < 0) ++ goto us_out; ++ ++ tls_cert_get_info(ssock->pool, &ssock->local_cert_info, cert); ++ const pj_str_t local_crt_raw = {(char*)us->data, (pj_ssize_t)us->size}; ++ pj_strdup(ssock->pool, &ssock->local_cert_info.cert_raw, &local_crt_raw); ++ ++us_out: ++ tls_last_error = ret; ++ if (cert) ++ gnutls_x509_crt_deinit(cert); ++ else ++ pj_bzero(&ssock->local_cert_info, sizeof(pj_ssl_cert_info)); ++ ++ cert = NULL; ++ ++ /* Get active remote certificate */ ++ certs = gnutls_certificate_get_peers(ssock->session, &certslen); ++ if (certs == NULL || certslen == 0) ++ goto peer_out; ++ ++ ret = gnutls_x509_crt_init(&cert); ++ if (ret < 0) ++ goto peer_out; ++ ++ ret = gnutls_x509_crt_import(cert, certs, GNUTLS_X509_FMT_PEM); ++ if (ret < 0) ++ ret = gnutls_x509_crt_import(cert, certs, GNUTLS_X509_FMT_DER); ++ if (ret < 0) ++ goto peer_out; ++ ++ tls_cert_get_info(ssock->pool, &ssock->remote_cert_info, cert); ++ const pj_str_t remote_crt_raw = {(char*)certs->data, (pj_ssize_t)certs->size}; ++ pj_strdup(ssock->pool, &ssock->remote_cert_info.cert_raw, &remote_crt_raw); ++ ++peer_out: ++ tls_last_error = ret; ++ if (cert) ++ gnutls_x509_crt_deinit(cert); ++ else ++ pj_bzero(&ssock->remote_cert_info, sizeof(pj_ssl_cert_info)); ++} ++ ++ ++/* When handshake completed: ++ * - notify application ++ * - if handshake failed, reset SSL state ++ * - return PJ_FALSE when SSL socket instance is destroyed by application. */ ++static pj_bool_t on_handshake_complete(pj_ssl_sock_t *ssock, ++ pj_status_t status) ++{ ++ pj_bool_t ret = PJ_TRUE; ++ ++ /* Cancel handshake timer */ ++ if (ssock->timer.id == TIMER_HANDSHAKE_TIMEOUT) { ++ pj_timer_heap_cancel(ssock->param.timer_heap, &ssock->timer); ++ ssock->timer.id = TIMER_NONE; ++ } ++ ++ /* Update certificates info on successful handshake */ ++ if (status == PJ_SUCCESS) ++ tls_cert_update(ssock); ++ ++ /* Accepting */ ++ if (ssock->is_server) { ++ if (status != PJ_SUCCESS) { ++ /* Handshake failed in accepting, destroy our self silently. */ ++ ++ char errmsg[PJ_ERR_MSG_SIZE]; ++ char buf[PJ_INET6_ADDRSTRLEN + 10]; ++ ++ pj_strerror(status, errmsg, sizeof(errmsg)); ++ PJ_LOG(3, (ssock->pool->obj_name, ++ "Handshake failed in accepting %s: %s", ++ pj_sockaddr_print(&ssock->rem_addr, buf, sizeof(buf), 3), ++ errmsg)); ++ ++ /* Workaround for ticket #985 */ ++#if (defined(PJ_WIN32) && PJ_WIN32 != 0) || (defined(PJ_WIN64) && PJ_WIN64 != 0) ++ if (ssock->param.timer_heap) { ++ pj_time_val interval = {0, DELAYED_CLOSE_TIMEOUT}; ++ ++ tls_sock_reset(ssock); ++ ++ ssock->timer.id = TIMER_CLOSE; ++ pj_time_val_normalize(&interval); ++ if (pj_timer_heap_schedule(ssock->param.timer_heap, ++ &ssock->timer, &interval) != 0) ++ { ++ ssock->timer.id = TIMER_NONE; ++ pj_ssl_sock_close(ssock); ++ } ++ } else ++#endif /* PJ_WIN32 */ ++ { ++ pj_ssl_sock_close(ssock); ++ } ++ ++ return PJ_FALSE; ++ } ++ /* Notify application the newly accepted SSL socket */ ++ if (ssock->param.cb.on_accept_complete) ++ ret = (*ssock->param.cb.on_accept_complete) ++ (ssock->parent, ssock, (pj_sockaddr_t*)&ssock->rem_addr, ++ pj_sockaddr_get_len((pj_sockaddr_t*)&ssock->rem_addr)); ++ ++ } else { /* Connecting */ ++ /* On failure, reset SSL socket state first, as app may try to ++ * reconnect in the callback. */ ++ if (status != PJ_SUCCESS) { ++ /* Server disconnected us, possibly due to negotiation failure */ ++ tls_sock_reset(ssock); ++ } ++ if (ssock->param.cb.on_connect_complete) { ++ ++ ret = (*ssock->param.cb.on_connect_complete)(ssock, status); ++ } ++ } ++ ++ return ret; ++} ++ ++static write_data_t *alloc_send_data(pj_ssl_sock_t *ssock, pj_size_t len) ++{ ++ send_buf_t *send_buf = &ssock->send_buf; ++ pj_size_t avail_len, skipped_len = 0; ++ char *reg1, *reg2; ++ pj_size_t reg1_len, reg2_len; ++ write_data_t *p; ++ ++ /* Check buffer availability */ ++ avail_len = send_buf->max_len - send_buf->len; ++ if (avail_len < len) ++ return NULL; ++ ++ /* If buffer empty, reset start pointer and return it */ ++ if (send_buf->len == 0) { ++ send_buf->start = send_buf->buf; ++ send_buf->len = len; ++ p = (write_data_t*)send_buf->start; ++ goto init_send_data; ++ } ++ ++ /* Free space may be wrapped/splitted into two regions, so let's ++ * analyze them if any region can hold the write data. */ ++ reg1 = send_buf->start + send_buf->len; ++ if (reg1 >= send_buf->buf + send_buf->max_len) ++ reg1 -= send_buf->max_len; ++ reg1_len = send_buf->max_len - send_buf->len; ++ if (reg1 + reg1_len > send_buf->buf + send_buf->max_len) { ++ reg1_len = send_buf->buf + send_buf->max_len - reg1; ++ reg2 = send_buf->buf; ++ reg2_len = send_buf->start - send_buf->buf; ++ } else { ++ reg2 = NULL; ++ reg2_len = 0; ++ } ++ ++ /* More buffer availability check, note that the write data must be in ++ * a contigue buffer. */ ++ avail_len = PJ_MAX(reg1_len, reg2_len); ++ if (avail_len < len) ++ return NULL; ++ ++ /* Get the data slot */ ++ if (reg1_len >= len) { ++ p = (write_data_t*)reg1; ++ } else { ++ p = (write_data_t*)reg2; ++ skipped_len = reg1_len; ++ } ++ ++ /* Update buffer length */ ++ send_buf->len += len + skipped_len; ++ ++init_send_data: ++ /* Init the new send data */ ++ pj_bzero(p, sizeof(*p)); ++ pj_list_init(p); ++ pj_list_push_back(&ssock->send_pending, p); ++ ++ return p; ++} ++ ++static void free_send_data(pj_ssl_sock_t *ssock, write_data_t *wdata) ++{ ++ send_buf_t *buf = &ssock->send_buf; ++ write_data_t *spl = &ssock->send_pending; ++ ++ pj_assert(!pj_list_empty(&ssock->send_pending)); ++ ++ /* Free slot from the buffer */ ++ if (spl->next == wdata && spl->prev == wdata) { ++ /* This is the only data, reset the buffer */ ++ buf->start = buf->buf; ++ buf->len = 0; ++ } else if (spl->next == wdata) { ++ /* This is the first data, shift start pointer of the buffer and ++ * adjust the buffer length. ++ */ ++ buf->start = (char*)wdata->next; ++ if (wdata->next > wdata) { ++ buf->len -= ((char*)wdata->next - buf->start); ++ } else { ++ /* Overlapped */ ++ pj_size_t right_len, left_len; ++ right_len = buf->buf + buf->max_len - (char*)wdata; ++ left_len = (char*)wdata->next - buf->buf; ++ buf->len -= (right_len + left_len); ++ } ++ } else if (spl->prev == wdata) { ++ /* This is the last data, just adjust the buffer length */ ++ if (wdata->prev < wdata) { ++ pj_size_t jump_len; ++ jump_len = (char*)wdata - ++ ((char*)wdata->prev + wdata->prev->record_len); ++ buf->len -= (wdata->record_len + jump_len); ++ } else { ++ /* Overlapped */ ++ pj_size_t right_len, left_len; ++ right_len = buf->buf + buf->max_len - ++ ((char*)wdata->prev + wdata->prev->record_len); ++ left_len = (char*)wdata + wdata->record_len - buf->buf; ++ buf->len -= (right_len + left_len); ++ } ++ } ++ /* For data in the middle buffer, just do nothing on the buffer. The slot ++ * will be freed later when freeing the first/last data. */ ++ ++ /* Remove the data from send pending list */ ++ pj_list_erase(wdata); ++} ++ ++#if 0 ++/* Just for testing send buffer alloc/free */ ++#include <pj/rand.h> ++pj_status_t pj_ssl_sock_ossl_test_send_buf(pj_pool_t *pool) ++{ ++ enum { MAX_CHUNK_NUM = 20 }; ++ unsigned chunk_size, chunk_cnt, i; ++ write_data_t *wdata[MAX_CHUNK_NUM] = {0}; ++ pj_time_val now; ++ pj_ssl_sock_t *ssock = NULL; ++ pj_ssl_sock_param param; ++ pj_status_t status; ++ ++ pj_gettimeofday(&now); ++ pj_srand((unsigned)now.sec); ++ ++ pj_ssl_sock_param_default(¶m); ++ status = pj_ssl_sock_create(pool, ¶m, &ssock); ++ if (status != PJ_SUCCESS) { ++ return status; ++ } ++ ++ if (ssock->send_buf.max_len == 0) { ++ ssock->send_buf.buf = (char *) ++ pj_pool_alloc(ssock->pool, ++ ssock->param.send_buffer_size); ++ ssock->send_buf.max_len = ssock->param.send_buffer_size; ++ ssock->send_buf.start = ssock->send_buf.buf; ++ ssock->send_buf.len = 0; ++ } ++ ++ chunk_size = ssock->param.send_buffer_size / MAX_CHUNK_NUM / 2; ++ chunk_cnt = 0; ++ for (i = 0; i < MAX_CHUNK_NUM; i++) { ++ wdata[i] = alloc_send_data(ssock, pj_rand() % chunk_size + 321); ++ if (wdata[i]) ++ chunk_cnt++; ++ else ++ break; ++ } ++ ++ while (chunk_cnt) { ++ i = pj_rand() % MAX_CHUNK_NUM; ++ if (wdata[i]) { ++ free_send_data(ssock, wdata[i]); ++ wdata[i] = NULL; ++ chunk_cnt--; ++ } ++ } ++ ++ if (ssock->send_buf.len != 0) ++ status = PJ_EBUG; ++ ++ pj_ssl_sock_close(ssock); ++ return status; ++} ++#endif ++ ++/* Flush write circular buffer to network socket. */ ++static pj_status_t flush_circ_buf_output(pj_ssl_sock_t *ssock, ++ pj_ioqueue_op_key_t *send_key, ++ pj_size_t orig_len, unsigned flags) ++{ ++ pj_ssize_t len; ++ write_data_t *wdata; ++ pj_size_t needed_len; ++ pj_status_t status; ++ ++ pj_lock_acquire(ssock->circ_buf_output_mutex); ++ ++ /* Check if there is data in the circular buffer, flush it if any */ ++ if (circ_empty(&ssock->circ_buf_output)) { ++ pj_lock_release(ssock->circ_buf_output_mutex); ++ ++ return PJ_SUCCESS; ++ } ++ ++ len = circ_size(&ssock->circ_buf_output); ++ ++ /* Calculate buffer size needed, and align it to 8 */ ++ needed_len = len + sizeof(write_data_t); ++ needed_len = ((needed_len + 7) >> 3) << 3; ++ ++ /* Allocate buffer for send data */ ++ wdata = alloc_send_data(ssock, needed_len); ++ if (wdata == NULL) { ++ pj_lock_release(ssock->circ_buf_output_mutex); ++ return PJ_ENOMEM; ++ } ++ ++ /* Copy the data and set its properties into the send data */ ++ pj_ioqueue_op_key_init(&wdata->key, sizeof(pj_ioqueue_op_key_t)); ++ wdata->key.user_data = wdata; ++ wdata->app_key = send_key; ++ wdata->record_len = needed_len; ++ wdata->data_len = len; ++ wdata->plain_data_len = orig_len; ++ wdata->flags = flags; ++ circ_read(&ssock->circ_buf_output, (pj_uint8_t *)&wdata->data, len); ++ ++ /* Ticket #1573: Don't hold mutex while calling PJLIB socket send(). */ ++ pj_lock_release(ssock->circ_buf_output_mutex); ++ ++ /* Send it */ ++ if (ssock->param.sock_type == pj_SOCK_STREAM()) { ++ status = pj_activesock_send(ssock->asock, &wdata->key, ++ wdata->data.content, &len, ++ flags); ++ } else { ++ status = pj_activesock_sendto(ssock->asock, &wdata->key, ++ wdata->data.content, &len, ++ flags, ++ (pj_sockaddr_t*)&ssock->rem_addr, ++ ssock->addr_len); ++ } ++ ++ if (status != PJ_EPENDING) { ++ /* When the sending is not pending, remove the wdata from send ++ * pending list. */ ++ pj_lock_acquire(ssock->circ_buf_output_mutex); ++ free_send_data(ssock, wdata); ++ pj_lock_release(ssock->circ_buf_output_mutex); ++ } ++ ++ return status; ++} ++ ++static void on_timer(pj_timer_heap_t *th, struct pj_timer_entry *te) ++{ ++ pj_ssl_sock_t *ssock = (pj_ssl_sock_t*)te->user_data; ++ int timer_id = te->id; ++ ++ te->id = TIMER_NONE; ++ ++ PJ_UNUSED_ARG(th); ++ ++ switch (timer_id) { ++ case TIMER_HANDSHAKE_TIMEOUT: ++ PJ_LOG(1, (ssock->pool->obj_name, "TLS timeout after %d.%ds", ++ ssock->param.timeout.sec, ssock->param.timeout.msec)); ++ ++ on_handshake_complete(ssock, PJ_ETIMEDOUT); ++ break; ++ case TIMER_CLOSE: ++ pj_ssl_sock_close(ssock); ++ break; ++ default: ++ pj_assert(!"Unknown timer"); ++ break; ++ } ++} ++ ++ ++/* Try to perform an asynchronous handshake */ ++static pj_status_t tls_try_handshake(pj_ssl_sock_t *ssock) ++{ ++ int ret; ++ pj_status_t status; ++ ++ /* Perform SSL handshake */ ++ ret = gnutls_handshake(ssock->session); ++ ++ status = flush_circ_buf_output(ssock, &ssock->handshake_op_key, 0, 0); ++ if (status != PJ_SUCCESS) ++ return status; ++ ++ if (ret == GNUTLS_E_SUCCESS) { ++ /* System are GO */ ++ ssock->connection_state = TLS_STATE_ESTABLISHED; ++ status = PJ_SUCCESS; ++ } else if (!gnutls_error_is_fatal(ret)) { ++ /* Non fatal error, retry later (busy or again) */ ++ status = PJ_EPENDING; ++ } else { ++ /* Fatal error invalidates session, no fallback */ ++ status = PJ_EINVAL; ++ } ++ ++ tls_last_error = ret; ++ ++ return status; ++} ++ ++ ++/* ++ ******************************************************************* ++ * Active socket callbacks. ++ ******************************************************************* ++ */ ++ ++/* PJ_TRUE asks the socket to read more data, PJ_FALSE takes it off the queue */ ++static pj_bool_t asock_on_data_read(pj_activesock_t *asock, void *data, ++ pj_size_t size, pj_status_t status, ++ pj_size_t *remainder) ++{ ++ pj_ssl_sock_t *ssock = (pj_ssl_sock_t *) ++ pj_activesock_get_user_data(asock); ++ ++ pj_size_t app_remainder = 0; ++ ++ if (data && size > 0) { ++ /* Push data into input circular buffer (for GnuTLS) */ ++ pj_lock_acquire(ssock->circ_buf_input_mutex); ++ circ_write(&ssock->circ_buf_input, data, size); ++ pj_lock_release(ssock->circ_buf_input_mutex); ++ } ++ ++ /* Check if SSL handshake hasn't finished yet */ ++ if (ssock->connection_state == TLS_STATE_HANDSHAKING) { ++ pj_bool_t ret = PJ_TRUE; ++ ++ if (status == PJ_SUCCESS) ++ status = tls_try_handshake(ssock); ++ ++ /* Not pending is either success or failed */ ++ if (status != PJ_EPENDING) ++ ret = on_handshake_complete(ssock, status); ++ ++ return ret; ++ } ++ ++ /* See if there is any decrypted data for the application */ ++ if (ssock->read_started) { ++ do { ++ /* Get read data structure at the end of the data */ ++ read_data_t *app_read_data = *(OFFSET_OF_READ_DATA_PTR(ssock, data)); ++ int app_data_size = (int)(ssock->read_size - app_read_data->len); ++ ++ /* Decrypt received data using GnuTLS (will read our input ++ * circular buffer) */ ++ int decrypted_size = gnutls_record_recv(ssock->session, ++ app_read_data->data + ++ app_read_data->len, ++ app_data_size); ++ ++ if (decrypted_size > 0 || status != PJ_SUCCESS) { ++ if (ssock->param.cb.on_data_read) { ++ pj_bool_t ret; ++ app_remainder = 0; ++ ++ if (decrypted_size > 0) ++ app_read_data->len += decrypted_size; ++ ++ ret = (*ssock->param.cb.on_data_read)(ssock, ++ app_read_data->data, ++ app_read_data->len, ++ status, ++ &app_remainder); ++ ++ if (!ret) { ++ /* We've been destroyed */ ++ return PJ_FALSE; ++ } ++ ++ /* Application may have left some data to be consumed ++ * later as remainder */ ++ app_read_data->len = app_remainder; ++ } ++ ++ /* Active socket signalled connection closed/error, this has ++ * been signalled to the application along with any remaining ++ * buffer. So, let's just reset SSL socket now. */ ++ if (status != PJ_SUCCESS) { ++ tls_sock_reset(ssock); ++ return PJ_FALSE; ++ } ++ } else if (decrypted_size == 0) { ++ /* Nothing more to read */ ++ ++ return PJ_TRUE; ++ } else if (decrypted_size == GNUTLS_E_AGAIN || ++ decrypted_size == GNUTLS_E_INTERRUPTED) { ++ return PJ_TRUE; ++ } else if (decrypted_size == GNUTLS_E_REHANDSHAKE) { ++ /* Seems like we are renegotiating */ ++ pj_status_t try_handshake_status = tls_try_handshake(ssock); ++ ++ /* Not pending is either success or failed */ ++ if (try_handshake_status != PJ_EPENDING) { ++ if (!on_handshake_complete(ssock, try_handshake_status)) { ++ return PJ_FALSE; ++ } ++ } ++ ++ if (try_handshake_status != PJ_SUCCESS && ++ try_handshake_status != PJ_EPENDING) { ++ return PJ_FALSE; ++ } ++ } else if (!gnutls_error_is_fatal(decrypted_size)) { ++ /* non-fatal error, let's just continue */ ++ } else { ++ return PJ_FALSE; ++ } ++ } while (PJ_TRUE); ++ } ++ ++ return PJ_TRUE; ++} ++ ++ ++/* Callback every time new data is available from the active socket */ ++static pj_bool_t asock_on_data_sent(pj_activesock_t *asock, ++ pj_ioqueue_op_key_t *send_key, ++ pj_ssize_t sent) ++{ ++ pj_ssl_sock_t *ssock = (pj_ssl_sock_t *)pj_activesock_get_user_data(asock); ++ ++ PJ_UNUSED_ARG(send_key); ++ PJ_UNUSED_ARG(sent); ++ ++ if (ssock->connection_state == TLS_STATE_HANDSHAKING) { ++ /* Initial handshaking */ ++ pj_status_t status = tls_try_handshake(ssock); ++ ++ /* Not pending is either success or failed */ ++ if (status != PJ_EPENDING) ++ return on_handshake_complete(ssock, status); ++ ++ } else if (send_key != &ssock->handshake_op_key) { ++ /* Some data has been sent, notify application */ ++ write_data_t *wdata = (write_data_t*)send_key->user_data; ++ if (ssock->param.cb.on_data_sent) { ++ pj_bool_t ret; ++ pj_ssize_t sent_len; ++ ++ sent_len = sent > 0 ? wdata->plain_data_len : sent; ++ ++ ret = (*ssock->param.cb.on_data_sent)(ssock, wdata->app_key, ++ sent_len); ++ if (!ret) { ++ /* We've been destroyed */ ++ return PJ_FALSE; ++ } ++ } ++ ++ /* Update write buffer state */ ++ pj_lock_acquire(ssock->circ_buf_output_mutex); ++ free_send_data(ssock, wdata); ++ pj_lock_release(ssock->circ_buf_output_mutex); ++ } else { ++ /* SSL re-negotiation is on-progress, just do nothing */ ++ /* FIXME: check if this is valid for GnuTLS too */ ++ } ++ ++ return PJ_TRUE; ++} ++ ++ ++/* Callback every time a new connection has been accepted (server) */ ++static pj_bool_t asock_on_accept_complete(pj_activesock_t *asock, ++ pj_sock_t newsock, ++ const pj_sockaddr_t *src_addr, ++ int src_addr_len) ++{ ++ pj_ssl_sock_t *ssock_parent = (pj_ssl_sock_t *) ++ pj_activesock_get_user_data(asock); ++ ++ pj_ssl_sock_t *ssock; ++ pj_activesock_cb asock_cb; ++ pj_activesock_cfg asock_cfg; ++ unsigned int i; ++ pj_status_t status; ++ ++ PJ_UNUSED_ARG(src_addr_len); ++ ++ /* Create new SSL socket instance */ ++ status = pj_ssl_sock_create(ssock_parent->pool, &ssock_parent->param, ++ &ssock); ++ if (status != PJ_SUCCESS) ++ goto on_return; ++ ++ /* Update new SSL socket attributes */ ++ ssock->sock = newsock; ++ ssock->parent = ssock_parent; ++ ssock->is_server = PJ_TRUE; ++ if (ssock_parent->cert) { ++ status = pj_ssl_sock_set_certificate(ssock, ssock->pool, ++ ssock_parent->cert); ++ if (status != PJ_SUCCESS) ++ goto on_return; ++ } ++ ++ /* Apply QoS, if specified */ ++ status = pj_sock_apply_qos2(ssock->sock, ssock->param.qos_type, ++ &ssock->param.qos_params, 1, ++ ssock->pool->obj_name, NULL); ++ if (status != PJ_SUCCESS && !ssock->param.qos_ignore_error) ++ goto on_return; ++ ++ /* Update local address */ ++ ssock->addr_len = src_addr_len; ++ status = pj_sock_getsockname(ssock->sock, &ssock->local_addr, ++ &ssock->addr_len); ++ if (status != PJ_SUCCESS) { ++ /* This fails on few envs, e.g: win IOCP, just tolerate this and ++ * use parent local address instead. ++ */ ++ pj_sockaddr_cp(&ssock->local_addr, &ssock_parent->local_addr); ++ } ++ ++ /* Set remote address */ ++ pj_sockaddr_cp(&ssock->rem_addr, src_addr); ++ ++ /* Create SSL context */ ++ status = tls_open(ssock); ++ if (status != PJ_SUCCESS) ++ goto on_return; ++ ++ /* Prepare read buffer */ ++ ssock->asock_rbuf = (void **)pj_pool_calloc(ssock->pool, ++ ssock->param.async_cnt, ++ sizeof(void*)); ++ if (!ssock->asock_rbuf) ++ return PJ_ENOMEM; ++ ++ for (i = 0; i < ssock->param.async_cnt; ++i) { ++ ssock->asock_rbuf[i] = (void *)pj_pool_alloc( ++ ssock->pool, ++ ssock->param.read_buffer_size + ++ sizeof(read_data_t*)); ++ if (!ssock->asock_rbuf[i]) ++ return PJ_ENOMEM; ++ } ++ ++ /* Create active socket */ ++ pj_activesock_cfg_default(&asock_cfg); ++ asock_cfg.async_cnt = ssock->param.async_cnt; ++ asock_cfg.concurrency = ssock->param.concurrency; ++ asock_cfg.whole_data = PJ_TRUE; ++ ++ pj_bzero(&asock_cb, sizeof(asock_cb)); ++ asock_cb.on_data_read = asock_on_data_read; ++ asock_cb.on_data_sent = asock_on_data_sent; ++ ++ status = pj_activesock_create(ssock->pool, ++ ssock->sock, ++ ssock->param.sock_type, ++ &asock_cfg, ++ ssock->param.ioqueue, ++ &asock_cb, ++ ssock, ++ &ssock->asock); ++ ++ if (status != PJ_SUCCESS) ++ goto on_return; ++ ++ /* Start reading */ ++ status = pj_activesock_start_read2(ssock->asock, ssock->pool, ++ (unsigned)ssock->param.read_buffer_size, ++ ssock->asock_rbuf, ++ PJ_IOQUEUE_ALWAYS_ASYNC); ++ if (status != PJ_SUCCESS) ++ goto on_return; ++ ++ /* Prepare write/send state */ ++ pj_assert(ssock->send_buf.max_len == 0); ++ ssock->send_buf.buf = (char *)pj_pool_alloc(ssock->pool, ++ ssock->param.send_buffer_size); ++ if (!ssock->send_buf.buf) ++ return PJ_ENOMEM; ++ ++ ssock->send_buf.max_len = ssock->param.send_buffer_size; ++ ssock->send_buf.start = ssock->send_buf.buf; ++ ssock->send_buf.len = 0; ++ ++ /* Start handshake timer */ ++ if (ssock->param.timer_heap && ++ (ssock->param.timeout.sec != 0 || ssock->param.timeout.msec != 0)) { ++ pj_assert(ssock->timer.id == TIMER_NONE); ++ ssock->timer.id = TIMER_HANDSHAKE_TIMEOUT; ++ status = pj_timer_heap_schedule(ssock->param.timer_heap, ++ &ssock->timer, ++ &ssock->param.timeout); ++ if (status != PJ_SUCCESS) ++ ssock->timer.id = TIMER_NONE; ++ } ++ ++ /* Start SSL handshake */ ++ ssock->connection_state = TLS_STATE_HANDSHAKING; ++ ++ status = tls_try_handshake(ssock); ++ ++on_return: ++ if (ssock && status != PJ_EPENDING) ++ on_handshake_complete(ssock, status); ++ ++ /* Must return PJ_TRUE whatever happened, as active socket must ++ * continue listening. ++ */ ++ return PJ_TRUE; ++} ++ ++ ++/* Callback every time a new connection has been completed (client) */ ++static pj_bool_t asock_on_connect_complete (pj_activesock_t *asock, ++ pj_status_t status) ++{ ++ pj_ssl_sock_t *ssock = (pj_ssl_sock_t*) ++ pj_activesock_get_user_data(asock); ++ ++ unsigned int i; ++ int ret; ++ ++ if (status != PJ_SUCCESS) ++ goto on_return; ++ ++ /* Update local address */ ++ ssock->addr_len = sizeof(pj_sockaddr); ++ status = pj_sock_getsockname(ssock->sock, &ssock->local_addr, ++ &ssock->addr_len); ++ if (status != PJ_SUCCESS) ++ goto on_return; ++ ++ /* Create SSL context */ ++ status = tls_open(ssock); ++ if (status != PJ_SUCCESS) ++ goto on_return; ++ ++ /* Prepare read buffer */ ++ ssock->asock_rbuf = (void **)pj_pool_calloc(ssock->pool, ++ ssock->param.async_cnt, ++ sizeof(void *)); ++ if (!ssock->asock_rbuf) ++ return PJ_ENOMEM; ++ ++ for (i = 0; i < ssock->param.async_cnt; ++i) { ++ ssock->asock_rbuf[i] = (void *)pj_pool_alloc( ++ ssock->pool, ++ ssock->param.read_buffer_size + ++ sizeof(read_data_t *)); ++ if (!ssock->asock_rbuf[i]) ++ return PJ_ENOMEM; ++ } ++ ++ /* Start read */ ++ status = pj_activesock_start_read2(ssock->asock, ssock->pool, ++ (unsigned) ssock->param.read_buffer_size, ++ ssock->asock_rbuf, ++ PJ_IOQUEUE_ALWAYS_ASYNC); ++ if (status != PJ_SUCCESS) ++ goto on_return; ++ ++ /* Prepare write/send state */ ++ pj_assert(ssock->send_buf.max_len == 0); ++ ssock->send_buf.buf = (char *)pj_pool_alloc(ssock->pool, ++ ssock->param.send_buffer_size); ++ if (!ssock->send_buf.buf) ++ return PJ_ENOMEM; ++ ++ ssock->send_buf.max_len = ssock->param.send_buffer_size; ++ ssock->send_buf.start = ssock->send_buf.buf; ++ ssock->send_buf.len = 0; ++ ++ /* Set server name to connect */ ++ if (ssock->param.server_name.slen) { ++ /* Server name is null terminated already */ ++ ret = gnutls_server_name_set(ssock->session, GNUTLS_NAME_DNS, ++ ssock->param.server_name.ptr, ++ ssock->param.server_name.slen); ++ if (ret < 0) { ++ PJ_LOG(3, (ssock->pool->obj_name, ++ "gnutls_server_name_set() failed: %s", ++ gnutls_strerror(ret))); ++ } ++ } ++ ++ /* Start handshake */ ++ ssock->connection_state = TLS_STATE_HANDSHAKING; ++ ++ status = tls_try_handshake(ssock); ++ if (status != PJ_EPENDING) ++ goto on_return; ++ ++ return PJ_TRUE; ++ ++on_return: ++ return on_handshake_complete(ssock, status); ++} ++ ++static void tls_ciphers_fill(void) ++{ ++ if (!tls_available_ciphers) { ++ tls_init(); ++ tls_deinit(); ++ } ++} ++ ++/* ++ ******************************************************************* ++ * API ++ ******************************************************************* ++ */ ++ ++/* Load credentials from files. */ ++PJ_DEF(pj_status_t) pj_ssl_cert_load_from_files(pj_pool_t *pool, ++ const pj_str_t *CA_file, ++ const pj_str_t *cert_file, ++ const pj_str_t *privkey_file, ++ const pj_str_t *privkey_pass, ++ pj_ssl_cert_t **p_cert) ++{ ++ pj_ssl_cert_t *cert; ++ ++ PJ_ASSERT_RETURN(pool && CA_file && cert_file && privkey_file, PJ_EINVAL); ++ ++ cert = PJ_POOL_ZALLOC_T(pool, pj_ssl_cert_t); ++ pj_strdup_with_null(pool, &cert->CA_file, CA_file); ++ pj_strdup_with_null(pool, &cert->cert_file, cert_file); ++ pj_strdup_with_null(pool, &cert->privkey_file, privkey_file); ++ pj_strdup_with_null(pool, &cert->privkey_pass, privkey_pass); ++ ++ *p_cert = cert; ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* Store credentials. */ ++PJ_DECL(pj_status_t) pj_ssl_sock_set_certificate(pj_ssl_sock_t *ssock, ++ pj_pool_t *pool, ++ const pj_ssl_cert_t *cert) ++{ ++ pj_ssl_cert_t *cert_; ++ ++ PJ_ASSERT_RETURN(ssock && pool && cert, PJ_EINVAL); ++ ++ cert_ = PJ_POOL_ZALLOC_T(pool, pj_ssl_cert_t); ++ pj_memcpy(cert_, cert, sizeof(cert)); ++ pj_strdup_with_null(pool, &cert_->CA_file, &cert->CA_file); ++ pj_strdup_with_null(pool, &cert_->cert_file, &cert->cert_file); ++ pj_strdup_with_null(pool, &cert_->privkey_file, &cert->privkey_file); ++ pj_strdup_with_null(pool, &cert_->privkey_pass, &cert->privkey_pass); ++ ++ ssock->cert = cert_; ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* Get available ciphers. */ ++PJ_DEF(pj_status_t) pj_ssl_cipher_get_availables(pj_ssl_cipher ciphers[], ++ unsigned *cipher_num) ++{ ++ unsigned int i; ++ ++ PJ_ASSERT_RETURN(ciphers && cipher_num, PJ_EINVAL); ++ ++ tls_ciphers_fill(); ++ ++ if (!tls_available_ciphers) { ++ *cipher_num = 0; ++ return PJ_ENOTFOUND; ++ } ++ ++ *cipher_num = PJ_MIN(*cipher_num, tls_available_ciphers); ++ ++ for (i = 0; i < *cipher_num; ++i) ++ ciphers[i] = tls_ciphers[i].id; ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* Get cipher name string. */ ++PJ_DEF(const char *)pj_ssl_cipher_name(pj_ssl_cipher cipher) ++{ ++ unsigned int i; ++ ++ tls_ciphers_fill(); ++ ++ for (i = 0; i < tls_available_ciphers; ++i) { ++ if (cipher == tls_ciphers[i].id) ++ return tls_ciphers[i].name; ++ } ++ ++ return NULL; ++} ++ ++ ++/* Get cipher identifier. */ ++PJ_DEF(pj_ssl_cipher) pj_ssl_cipher_id(const char *cipher_name) ++{ ++ unsigned int i; ++ ++ tls_ciphers_fill(); ++ ++ for (i = 0; i < tls_available_ciphers; ++i) { ++ if (!pj_ansi_stricmp(tls_ciphers[i].name, cipher_name)) ++ return tls_ciphers[i].id; ++ } ++ ++ return PJ_TLS_UNKNOWN_CIPHER; ++} ++ ++ ++/* Check if the specified cipher is supported by the TLS backend. */ ++PJ_DEF(pj_bool_t) pj_ssl_cipher_is_supported(pj_ssl_cipher cipher) ++{ ++ unsigned int i; ++ ++ tls_ciphers_fill(); ++ ++ for (i = 0; i < tls_available_ciphers; ++i) { ++ if (cipher == tls_ciphers[i].id) ++ return PJ_TRUE; ++ } ++ ++ return PJ_FALSE; ++} ++ ++/* Create SSL socket instance. */ ++PJ_DEF(pj_status_t) pj_ssl_sock_create(pj_pool_t *pool, ++ const pj_ssl_sock_param *param, ++ pj_ssl_sock_t **p_ssock) ++{ ++ pj_ssl_sock_t *ssock; ++ pj_status_t status; ++ ++ PJ_ASSERT_RETURN(pool && param && p_ssock, PJ_EINVAL); ++ PJ_ASSERT_RETURN(param->sock_type == pj_SOCK_STREAM(), PJ_ENOTSUP); ++ ++ pool = pj_pool_create(pool->factory, "tls%p", 512, 512, NULL); ++ ++ /* Create secure socket */ ++ ssock = PJ_POOL_ZALLOC_T(pool, pj_ssl_sock_t); ++ ssock->pool = pool; ++ ssock->sock = PJ_INVALID_SOCKET; ++ ssock->connection_state = TLS_STATE_NULL; ++ pj_list_init(&ssock->write_pending); ++ pj_list_init(&ssock->write_pending_empty); ++ pj_list_init(&ssock->send_pending); ++ pj_timer_entry_init(&ssock->timer, 0, ssock, &on_timer); ++ pj_ioqueue_op_key_init(&ssock->handshake_op_key, ++ sizeof(pj_ioqueue_op_key_t)); ++ ++ /* Create secure socket mutex */ ++ status = pj_lock_create_recursive_mutex(pool, pool->obj_name, ++ &ssock->circ_buf_output_mutex); ++ if (status != PJ_SUCCESS) ++ return status; ++ ++ /* Create input circular buffer mutex */ ++ status = pj_lock_create_simple_mutex(pool, pool->obj_name, ++ &ssock->circ_buf_input_mutex); ++ if (status != PJ_SUCCESS) ++ return status; ++ ++ /* Create output circular buffer mutex */ ++ status = pj_lock_create_simple_mutex(pool, pool->obj_name, ++ &ssock->circ_buf_output_mutex); ++ if (status != PJ_SUCCESS) ++ return status; ++ ++ /* Init secure socket param */ ++ ssock->param = *param; ++ ssock->param.read_buffer_size = ((ssock->param.read_buffer_size + 7) >> 3) << 3; ++ ++ if (param->ciphers_num > 0) { ++ unsigned int i; ++ ssock->param.ciphers = (pj_ssl_cipher *) ++ pj_pool_calloc(pool, param->ciphers_num, ++ sizeof(pj_ssl_cipher)); ++ if (!ssock->param.ciphers) ++ return PJ_ENOMEM; ++ ++ for (i = 0; i < param->ciphers_num; ++i) ++ ssock->param.ciphers[i] = param->ciphers[i]; ++ } ++ ++ /* Server name must be null-terminated */ ++ pj_strdup_with_null(pool, &ssock->param.server_name, ¶m->server_name); ++ ++ /* Finally */ ++ *p_ssock = ssock; ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* ++ * Close the secure socket. This will unregister the socket from the ++ * ioqueue and ultimately close the socket. ++ */ ++PJ_DEF(pj_status_t) pj_ssl_sock_close(pj_ssl_sock_t *ssock) ++{ ++ pj_pool_t *pool; ++ ++ PJ_ASSERT_RETURN(ssock, PJ_EINVAL); ++ ++ if (!ssock->pool) ++ return PJ_SUCCESS; ++ ++ if (ssock->timer.id != TIMER_NONE) { ++ pj_timer_heap_cancel(ssock->param.timer_heap, &ssock->timer); ++ ssock->timer.id = TIMER_NONE; ++ } ++ ++ tls_sock_reset(ssock); ++ ++ pj_lock_destroy(ssock->circ_buf_output_mutex); ++ pj_lock_destroy(ssock->circ_buf_input_mutex); ++ ++ pool = ssock->pool; ++ ssock->pool = NULL; ++ if (pool) ++ pj_pool_release(pool); ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* Associate arbitrary data with the secure socket. */ ++PJ_DEF(pj_status_t) pj_ssl_sock_set_user_data(pj_ssl_sock_t *ssock, ++ void *user_data) ++{ ++ PJ_ASSERT_RETURN(ssock, PJ_EINVAL); ++ ++ ssock->param.user_data = user_data; ++ return PJ_SUCCESS; ++} ++ ++ ++/* Retrieve the user data previously associated with this secure socket. */ ++PJ_DEF(void *)pj_ssl_sock_get_user_data(pj_ssl_sock_t *ssock) ++{ ++ PJ_ASSERT_RETURN(ssock, NULL); ++ ++ return ssock->param.user_data; ++} ++ ++ ++/* Retrieve the local address and port used by specified SSL socket. */ ++PJ_DEF(pj_status_t) pj_ssl_sock_get_info (pj_ssl_sock_t *ssock, ++ pj_ssl_sock_info *info) ++{ ++ pj_bzero(info, sizeof(*info)); ++ ++ /* Established flag */ ++ info->established = (ssock->connection_state == TLS_STATE_ESTABLISHED); ++ ++ /* Protocol */ ++ info->proto = ssock->param.proto; ++ ++ /* Local address */ ++ pj_sockaddr_cp(&info->local_addr, &ssock->local_addr); ++ ++ if (info->established) { ++ int i; ++ gnutls_cipher_algorithm_t lookup; ++ gnutls_cipher_algorithm_t cipher; ++ ++ /* Current cipher */ ++ cipher = gnutls_cipher_get(ssock->session); ++ for (i = 0; ; i++) { ++ unsigned char id[2]; ++ const char *suite = gnutls_cipher_suite_info(i, (unsigned char *)id, ++ NULL, &lookup, NULL, ++ NULL); ++ if (suite) { ++ if (lookup == cipher) { ++ info->cipher = (pj_uint32_t) ((id[0] << 8) | id[1]); ++ break; ++ } ++ } else ++ break; ++ } ++ ++ /* Remote address */ ++ pj_sockaddr_cp(&info->remote_addr, &ssock->rem_addr); ++ ++ /* Certificates info */ ++ info->local_cert_info = &ssock->local_cert_info; ++ info->remote_cert_info = &ssock->remote_cert_info; ++ ++ /* Verification status */ ++ info->verify_status = ssock->verify_status; ++ } ++ ++ /* Last known GnuTLS error code */ ++ info->last_native_err = ssock->last_err; ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* Starts read operation on this secure socket. */ ++PJ_DEF(pj_status_t) pj_ssl_sock_start_read(pj_ssl_sock_t *ssock, ++ pj_pool_t *pool, ++ unsigned buff_size, ++ pj_uint32_t flags) ++{ ++ void **readbuf; ++ unsigned int i; ++ ++ PJ_ASSERT_RETURN(ssock && pool && buff_size, PJ_EINVAL); ++ PJ_ASSERT_RETURN(ssock->connection_state == TLS_STATE_ESTABLISHED, ++ PJ_EINVALIDOP); ++ ++ readbuf = (void**) pj_pool_calloc(pool, ssock->param.async_cnt, ++ sizeof(void *)); ++ if (!readbuf) ++ return PJ_ENOMEM; ++ ++ for (i = 0; i < ssock->param.async_cnt; ++i) { ++ readbuf[i] = pj_pool_alloc(pool, buff_size); ++ if (!readbuf[i]) ++ return PJ_ENOMEM; ++ } ++ ++ return pj_ssl_sock_start_read2(ssock, pool, buff_size, readbuf, flags); ++} ++ ++ ++/* ++ * Same as #pj_ssl_sock_start_read(), except that the application ++ * supplies the buffers for the read operation so that the acive socket ++ * does not have to allocate the buffers. ++ */ ++PJ_DEF(pj_status_t) pj_ssl_sock_start_read2 (pj_ssl_sock_t *ssock, ++ pj_pool_t *pool, ++ unsigned buff_size, ++ void *readbuf[], ++ pj_uint32_t flags) ++{ ++ unsigned int i; ++ ++ PJ_ASSERT_RETURN(ssock && pool && buff_size && readbuf, PJ_EINVAL); ++ PJ_ASSERT_RETURN(ssock->connection_state == TLS_STATE_ESTABLISHED, ++ PJ_EINVALIDOP); ++ ++ /* Create SSL socket read buffer */ ++ ssock->ssock_rbuf = (read_data_t*)pj_pool_calloc(pool, ++ ssock->param.async_cnt, ++ sizeof(read_data_t)); ++ if (!ssock->ssock_rbuf) ++ return PJ_ENOMEM; ++ ++ /* Store SSL socket read buffer pointer in the activesock read buffer */ ++ for (i = 0; i < ssock->param.async_cnt; ++i) { ++ read_data_t **p_ssock_rbuf = ++ OFFSET_OF_READ_DATA_PTR(ssock, ssock->asock_rbuf[i]); ++ ++ ssock->ssock_rbuf[i].data = readbuf[i]; ++ ssock->ssock_rbuf[i].len = 0; ++ ++ *p_ssock_rbuf = &ssock->ssock_rbuf[i]; ++ } ++ ++ ssock->read_size = buff_size; ++ ssock->read_started = PJ_TRUE; ++ ssock->read_flags = flags; ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* ++ * Same as pj_ssl_sock_start_read(), except that this function is used ++ * only for datagram sockets, and it will trigger \a on_data_recvfrom() ++ * callback instead. ++ */ ++PJ_DEF(pj_status_t) pj_ssl_sock_start_recvfrom (pj_ssl_sock_t *ssock, ++ pj_pool_t *pool, ++ unsigned buff_size, ++ pj_uint32_t flags) ++{ ++ PJ_UNUSED_ARG(ssock); ++ PJ_UNUSED_ARG(pool); ++ PJ_UNUSED_ARG(buff_size); ++ PJ_UNUSED_ARG(flags); ++ ++ return PJ_ENOTSUP; ++} ++ ++ ++/* ++ * Same as #pj_ssl_sock_start_recvfrom() except that the recvfrom() ++ * operation takes the buffer from the argument rather than creating ++ * new ones. ++ */ ++PJ_DEF(pj_status_t) pj_ssl_sock_start_recvfrom2 (pj_ssl_sock_t *ssock, ++ pj_pool_t *pool, ++ unsigned buff_size, ++ void *readbuf[], ++ pj_uint32_t flags) ++{ ++ PJ_UNUSED_ARG(ssock); ++ PJ_UNUSED_ARG(pool); ++ PJ_UNUSED_ARG(buff_size); ++ PJ_UNUSED_ARG(readbuf); ++ PJ_UNUSED_ARG(flags); ++ ++ return PJ_ENOTSUP; ++} ++ ++ ++/* ++ * Write the plain data to GnuTLS, it will be encrypted by gnutls_record_send() ++ * and sent via tls_data_push. Note that re-negotitation may be on progress, so ++ * sending data should be delayed until re-negotiation is completed. ++ */ ++static pj_status_t tls_write(pj_ssl_sock_t *ssock, ++ pj_ioqueue_op_key_t *send_key, ++ const void *data, pj_ssize_t size, unsigned flags) ++{ ++ pj_status_t status; ++ int nwritten; ++ pj_ssize_t total_written = 0; ++ ++ /* Ask GnuTLS to encrypt our plaintext now. GnuTLS will use the push ++ * callback to actually write the encrypted bytes into our output circular ++ * buffer. GnuTLS may refuse to "send" everything at once, but since we are ++ * not really sending now, we will just call it again now until it succeeds ++ * (or fails in a fatal way). */ ++ while (total_written < size) { ++ /* Try encrypting using GnuTLS */ ++ nwritten = gnutls_record_send(ssock->session, data + total_written, ++ size); ++ ++ if (nwritten > 0) { ++ /* Good, some data was encrypted and written */ ++ total_written += nwritten; ++ } else { ++ /* Normally we would have to retry record_send but our internal ++ * state has not changed, so we have to ask for more data first. ++ * We will just try again later, although this should never happen. ++ */ ++ return tls_status_from_err(ssock, nwritten); ++ } ++ } ++ ++ /* All encrypted data is written to the output circular buffer; ++ * now send it on the socket (or notify problem). */ ++ if (total_written == size) ++ status = flush_circ_buf_output(ssock, send_key, size, flags); ++ else ++ status = PJ_ENOMEM; ++ ++ return status; ++} ++ ++ ++/* Flush delayed data sending in the write pending list. */ ++static pj_status_t flush_delayed_send(pj_ssl_sock_t *ssock) ++{ ++ /* Check for another ongoing flush */ ++ if (ssock->flushing_write_pend) { ++ return PJ_EBUSY; ++ } ++ ++ pj_lock_acquire(ssock->circ_buf_output_mutex); ++ ++ /* Again, check for another ongoing flush */ ++ if (ssock->flushing_write_pend) { ++ pj_lock_release(ssock->circ_buf_output_mutex); ++ return PJ_EBUSY; ++ } ++ ++ /* Set ongoing flush flag */ ++ ssock->flushing_write_pend = PJ_TRUE; ++ ++ while (!pj_list_empty(&ssock->write_pending)) { ++ write_data_t *wp; ++ pj_status_t status; ++ ++ wp = ssock->write_pending.next; ++ ++ /* Ticket #1573: Don't hold mutex while calling socket send. */ ++ pj_lock_release(ssock->circ_buf_output_mutex); ++ ++ status = tls_write(ssock, &wp->key, wp->data.ptr, ++ wp->plain_data_len, wp->flags); ++ if (status != PJ_SUCCESS) { ++ /* Reset ongoing flush flag first. */ ++ ssock->flushing_write_pend = PJ_FALSE; ++ return status; ++ } ++ ++ pj_lock_acquire(ssock->circ_buf_output_mutex); ++ pj_list_erase(wp); ++ pj_list_push_back(&ssock->write_pending_empty, wp); ++ } ++ ++ /* Reset ongoing flush flag */ ++ ssock->flushing_write_pend = PJ_FALSE; ++ ++ pj_lock_release(ssock->circ_buf_output_mutex); ++ ++ return PJ_SUCCESS; ++} ++ ++ ++/* Sending is delayed, push back the sending data into pending list. */ ++static pj_status_t delay_send(pj_ssl_sock_t *ssock, ++ pj_ioqueue_op_key_t *send_key, ++ const void *data, pj_ssize_t size, ++ unsigned flags) ++{ ++ write_data_t *wp; ++ ++ pj_lock_acquire(ssock->circ_buf_output_mutex); ++ ++ /* Init write pending instance */ ++ if (!pj_list_empty(&ssock->write_pending_empty)) { ++ wp = ssock->write_pending_empty.next; ++ pj_list_erase(wp); ++ } else { ++ wp = PJ_POOL_ZALLOC_T(ssock->pool, write_data_t); ++ } ++ ++ wp->app_key = send_key; ++ wp->plain_data_len = size; ++ wp->data.ptr = data; ++ wp->flags = flags; ++ ++ pj_list_push_back(&ssock->write_pending, wp); ++ ++ pj_lock_release(ssock->circ_buf_output_mutex); ++ ++ /* Must return PJ_EPENDING */ ++ return PJ_EPENDING; ++} ++ ++ ++/** ++ * Send data using the socket. ++ */ ++PJ_DEF(pj_status_t) pj_ssl_sock_send(pj_ssl_sock_t *ssock, ++ pj_ioqueue_op_key_t *send_key, ++ const void *data, pj_ssize_t *size, ++ unsigned flags) ++{ ++ pj_status_t status; ++ ++ PJ_ASSERT_RETURN(ssock && data && size && (*size > 0), PJ_EINVAL); ++ PJ_ASSERT_RETURN(ssock->connection_state==TLS_STATE_ESTABLISHED, ++ PJ_EINVALIDOP); ++ ++ /* Flush delayed send first. Sending data might be delayed when ++ * re-negotiation is on-progress. */ ++ status = flush_delayed_send(ssock); ++ if (status == PJ_EBUSY) { ++ /* Re-negotiation or flushing is on progress, delay sending */ ++ status = delay_send(ssock, send_key, data, *size, flags); ++ goto on_return; ++ } else if (status != PJ_SUCCESS) { ++ goto on_return; ++ } ++ ++ /* Write data to SSL */ ++ status = tls_write(ssock, send_key, data, *size, flags); ++ if (status == PJ_EBUSY) { ++ /* Re-negotiation is on progress, delay sending */ ++ status = delay_send(ssock, send_key, data, *size, flags); ++ } ++ ++on_return: ++ return status; ++} ++ ++ ++/** ++ * Send datagram using the socket. ++ */ ++PJ_DEF(pj_status_t) pj_ssl_sock_sendto (pj_ssl_sock_t *ssock, ++ pj_ioqueue_op_key_t *send_key, ++ const void *data, pj_ssize_t *size, ++ unsigned flags, ++ const pj_sockaddr_t *addr, int addr_len) ++{ ++ PJ_UNUSED_ARG(ssock); ++ PJ_UNUSED_ARG(send_key); ++ PJ_UNUSED_ARG(data); ++ PJ_UNUSED_ARG(size); ++ PJ_UNUSED_ARG(flags); ++ PJ_UNUSED_ARG(addr); ++ PJ_UNUSED_ARG(addr_len); ++ ++ return PJ_ENOTSUP; ++} ++ ++ ++/** ++ * Starts asynchronous socket accept() operations on this secure socket. ++ */ ++PJ_DEF(pj_status_t) pj_ssl_sock_start_accept (pj_ssl_sock_t *ssock, ++ pj_pool_t *pool, ++ const pj_sockaddr_t *localaddr, ++ int addr_len) ++{ ++ pj_activesock_cb asock_cb; ++ pj_activesock_cfg asock_cfg; ++ pj_status_t status; ++ ++ PJ_ASSERT_RETURN(ssock && pool && localaddr && addr_len, PJ_EINVAL); ++ ++ /* Create socket */ ++ status = pj_sock_socket(ssock->param.sock_af, ssock->param.sock_type, 0, ++ &ssock->sock); ++ if (status != PJ_SUCCESS) ++ goto on_error; ++ ++ /* Apply SO_REUSEADDR */ ++ if (ssock->param.reuse_addr) { ++ int enabled = 1; ++ status = pj_sock_setsockopt(ssock->sock, pj_SOL_SOCKET(), ++ pj_SO_REUSEADDR(), ++ &enabled, sizeof(enabled)); ++ if (status != PJ_SUCCESS) { ++ PJ_PERROR(4,(ssock->pool->obj_name, status, ++ "Warning: error applying SO_REUSEADDR")); ++ } ++ } ++ ++ /* Apply QoS, if specified */ ++ status = pj_sock_apply_qos2(ssock->sock, ssock->param.qos_type, ++ &ssock->param.qos_params, 2, ++ ssock->pool->obj_name, NULL); ++ if (status != PJ_SUCCESS && !ssock->param.qos_ignore_error) ++ goto on_error; ++ ++ /* Bind socket */ ++ status = pj_sock_bind(ssock->sock, localaddr, addr_len); ++ if (status != PJ_SUCCESS) ++ goto on_error; ++ ++ /* Start listening to the address */ ++ status = pj_sock_listen(ssock->sock, PJ_SOMAXCONN); ++ if (status != PJ_SUCCESS) ++ goto on_error; ++ ++ /* Create active socket */ ++ pj_activesock_cfg_default(&asock_cfg); ++ asock_cfg.async_cnt = ssock->param.async_cnt; ++ asock_cfg.concurrency = ssock->param.concurrency; ++ asock_cfg.whole_data = PJ_TRUE; ++ ++ pj_bzero(&asock_cb, sizeof(asock_cb)); ++ asock_cb.on_accept_complete = asock_on_accept_complete; ++ ++ status = pj_activesock_create(pool, ++ ssock->sock, ++ ssock->param.sock_type, ++ &asock_cfg, ++ ssock->param.ioqueue, ++ &asock_cb, ++ ssock, ++ &ssock->asock); ++ ++ if (status != PJ_SUCCESS) ++ goto on_error; ++ ++ /* Start accepting */ ++ status = pj_activesock_start_accept(ssock->asock, pool); ++ if (status != PJ_SUCCESS) ++ goto on_error; ++ ++ /* Update local address */ ++ ssock->addr_len = addr_len; ++ status = pj_sock_getsockname(ssock->sock, &ssock->local_addr, ++ &ssock->addr_len); ++ if (status != PJ_SUCCESS) ++ pj_sockaddr_cp(&ssock->local_addr, localaddr); ++ ++ ssock->is_server = PJ_TRUE; ++ ++ return PJ_SUCCESS; ++ ++on_error: ++ tls_sock_reset(ssock); ++ return status; ++} ++ ++ ++/** ++ * Starts asynchronous socket connect() operation. ++ */ ++PJ_DECL(pj_status_t) pj_ssl_sock_start_connect(pj_ssl_sock_t *ssock, ++ pj_pool_t *pool, ++ const pj_sockaddr_t *localaddr, ++ const pj_sockaddr_t *remaddr, ++ int addr_len) ++{ ++ pj_activesock_cb asock_cb; ++ pj_activesock_cfg asock_cfg; ++ pj_status_t status; ++ ++ PJ_ASSERT_RETURN(ssock && pool && localaddr && remaddr && addr_len, ++ PJ_EINVAL); ++ ++ /* Create socket */ ++ status = pj_sock_socket(ssock->param.sock_af, ssock->param.sock_type, 0, ++ &ssock->sock); ++ if (status != PJ_SUCCESS) ++ goto on_error; ++ ++ /* Apply QoS, if specified */ ++ status = pj_sock_apply_qos2(ssock->sock, ssock->param.qos_type, ++ &ssock->param.qos_params, 2, ++ ssock->pool->obj_name, NULL); ++ if (status != PJ_SUCCESS && !ssock->param.qos_ignore_error) ++ goto on_error; ++ ++ /* Bind socket */ ++ status = pj_sock_bind(ssock->sock, localaddr, addr_len); ++ if (status != PJ_SUCCESS) ++ goto on_error; ++ ++ /* Create active socket */ ++ pj_activesock_cfg_default(&asock_cfg); ++ asock_cfg.async_cnt = ssock->param.async_cnt; ++ asock_cfg.concurrency = ssock->param.concurrency; ++ asock_cfg.whole_data = PJ_TRUE; ++ ++ pj_bzero(&asock_cb, sizeof(asock_cb)); ++ asock_cb.on_connect_complete = asock_on_connect_complete; ++ asock_cb.on_data_read = asock_on_data_read; ++ asock_cb.on_data_sent = asock_on_data_sent; ++ ++ status = pj_activesock_create(pool, ++ ssock->sock, ++ ssock->param.sock_type, ++ &asock_cfg, ++ ssock->param.ioqueue, ++ &asock_cb, ++ ssock, ++ &ssock->asock); ++ ++ if (status != PJ_SUCCESS) ++ goto on_error; ++ ++ /* Save remote address */ ++ pj_sockaddr_cp(&ssock->rem_addr, remaddr); ++ ++ /* Start timer */ ++ if (ssock->param.timer_heap && ++ (ssock->param.timeout.sec != 0 || ssock->param.timeout.msec != 0)) ++ { ++ pj_assert(ssock->timer.id == TIMER_NONE); ++ ssock->timer.id = TIMER_HANDSHAKE_TIMEOUT; ++ status = pj_timer_heap_schedule(ssock->param.timer_heap, ++ &ssock->timer, ++ &ssock->param.timeout); ++ if (status != PJ_SUCCESS) ++ ssock->timer.id = TIMER_NONE; ++ } ++ ++ status = pj_activesock_start_connect(ssock->asock, pool, remaddr, ++ addr_len); ++ ++ if (status == PJ_SUCCESS) ++ asock_on_connect_complete(ssock->asock, PJ_SUCCESS); ++ else if (status != PJ_EPENDING) ++ goto on_error; ++ ++ /* Update local address */ ++ ssock->addr_len = addr_len; ++ status = pj_sock_getsockname(ssock->sock, &ssock->local_addr, ++ &ssock->addr_len); ++ /* Note that we may not get an IP address here. This can ++ * happen for example on Windows, where getsockname() ++ * would return 0.0.0.0 if socket has just started the ++ * async connect. In this case, just leave the local ++ * address with 0.0.0.0 for now; it will be updated ++ * once the socket is established. ++ */ ++ ++ /* Update socket state */ ++ ssock->is_server = PJ_FALSE; ++ ++ return PJ_EPENDING; ++ ++on_error: ++ tls_sock_reset(ssock); ++ return status; ++} ++ ++ ++PJ_DEF(pj_status_t) pj_ssl_sock_renegotiate(pj_ssl_sock_t *ssock) ++{ ++ int status; ++ ++ /* Nothing established yet */ ++ PJ_ASSERT_RETURN(ssock->connection_state == TLS_STATE_ESTABLISHED, ++ PJ_EINVALIDOP); ++ ++ /* Cannot renegotiate; we're a client */ ++ /* FIXME: in fact maybe that's not true */ ++ PJ_ASSERT_RETURN(!ssock->is_server, PJ_EINVALIDOP); ++ ++ /* First call gnutls_rehandshake() to see if this is even possible */ ++ status = gnutls_rehandshake(ssock->session); ++ ++ if (status == GNUTLS_E_SUCCESS) { ++ /* Rehandshake is possible, so try a GnuTLS handshake now. The eventual ++ * gnutls_record_recv() calls could return a few specific values during ++ * this state: ++ * ++ * - GNUTLS_E_REHANDSHAKE: rehandshake message processing ++ * - GNUTLS_E_WARNING_ALERT_RECEIVED: client does not wish to ++ * renegotiate ++ */ ++ ssock->connection_state = TLS_STATE_HANDSHAKING; ++ status = tls_try_handshake(ssock); ++ ++ return status; ++ } else { ++ return tls_status_from_err(ssock, status); ++ } ++} ++ ++#endif /* PJ_HAS_SSL_SOCK */ +diff --git a/pjlib/src/pj/ssl_sock_ossl.c b/pjlib/src/pj/ssl_sock_ossl.c +index ba4ae0b..98bbfee 100644 +--- a/pjlib/src/pj/ssl_sock_ossl.c ++++ b/pjlib/src/pj/ssl_sock_ossl.c +@@ -31,8 +31,10 @@ + #include <pj/timer.h> + + +-/* Only build when PJ_HAS_SSL_SOCK is enabled */ +-#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK!=0 ++/* Only build when PJ_HAS_SSL_SOCK is enabled and when PJ_HAS_TLS_SOCK is ++ * disabled (meaning GnuTLS is off) */ ++#if defined(PJ_HAS_SSL_SOCK) && PJ_HAS_SSL_SOCK != 0 && \ ++ defined(PJ_HAS_TLS_SOCK) && PJ_HAS_TLS_SOCK == 0 + + #define THIS_FILE "ssl_sock_ossl.c" + +-- +1.9.3 + diff --git a/contrib/src/pjproject/ice_config.patch b/contrib/src/pjproject/ice_config.patch new file mode 100644 index 0000000000..e958c0d2eb --- /dev/null +++ b/contrib/src/pjproject/ice_config.patch @@ -0,0 +1,23 @@ +--- a/pjnath/include/pjnath/config.h ++++ b/pjnath/include/pjnath/config.h +@@ -231,5 +231,5 @@ + * Default: 16 + */ + #ifndef PJ_ICE_MAX_CAND +-# define PJ_ICE_MAX_CAND 16 ++# define PJ_ICE_MAX_CAND 32 + #endif +@@ -250,5 +250,5 @@ + * the maximum number of components (PJ_ICE_MAX_COMP) value. + */ + #ifndef PJ_ICE_COMP_BITS +-# define PJ_ICE_COMP_BITS 1 ++# define PJ_ICE_COMP_BITS 2 + #endif +@@ -301,5 +301,5 @@ + * Default: 32 + */ + #ifndef PJ_ICE_MAX_CHECKS +-# define PJ_ICE_MAX_CHECKS 32 ++# define PJ_ICE_MAX_CHECKS 150 + #endif diff --git a/contrib/src/pjproject/intptr_t.patch b/contrib/src/pjproject/intptr_t.patch new file mode 100644 index 0000000000..8994a991c0 --- /dev/null +++ b/contrib/src/pjproject/intptr_t.patch @@ -0,0 +1,11 @@ +--- pjproject/pjsip/src/pjsua2/endpoint.cpp.orig 2014-09-05 16:39:29.708512865 -0400 ++++ pjproject/pjsip/src/pjsua2/endpoint.cpp 2014-09-05 16:39:00.084513427 -0400 +@@ -489,7 +489,7 @@ + LogEntry entry; + entry.level = level; + entry.msg = string(data, len); +- entry.threadId = (long)pj_thread_this(); ++ entry.threadId = (intptr_t)pj_thread_this(); + entry.threadName = string(pj_thread_get_name(pj_thread_this())); + + ep.utilLogWrite(entry); diff --git a/contrib/src/pjproject/ipv6.patch b/contrib/src/pjproject/ipv6.patch new file mode 100644 index 0000000000..8b42fbbda1 --- /dev/null +++ b/contrib/src/pjproject/ipv6.patch @@ -0,0 +1,11 @@ +--- a/pjlib/include/pj/config.h ++++ b/pjlib/include/pj/config.h +@@ -549,7 +549,7 @@ + * Default: 0 (disabled, for now) + */ + #ifndef PJ_HAS_IPV6 +-# define PJ_HAS_IPV6 0 ++# define PJ_HAS_IPV6 1 + #endif + + /** diff --git a/contrib/src/pjproject/multiple_listeners.patch b/contrib/src/pjproject/multiple_listeners.patch new file mode 100644 index 0000000000..9bd58ee0e2 --- /dev/null +++ b/contrib/src/pjproject/multiple_listeners.patch @@ -0,0 +1,66 @@ +diff --git a/pjproject/pjsip/src/pjsip/sip_transport.c b/pjproject_new/pjsip/src/pjsip/sip_transport.c +index 4b2cc1a..22e3603 100644 +--- a/pjsip/src/pjsip/sip_transport.c ++++ b/pjsip/src/pjsip/sip_transport.c +@@ -1179,22 +1179,22 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_register_tpfactory( pjsip_tpmgr *mgr, + + pj_lock_acquire(mgr->lock); + +- /* Check that no factory with the same type has been registered. */ ++ /* Check that no factory with the same type and bound address has been registered. */ + status = PJ_SUCCESS; + for (p=mgr->factory_list.next; p!=&mgr->factory_list; p=p->next) { +- if (p->type == tpf->type) { +- status = PJSIP_ETYPEEXISTS; +- break; +- } +- if (p == tpf) { +- status = PJ_EEXISTS; +- break; +- } ++ if (p->type == tpf->type && !pj_sockaddr_cmp(&tpf->local_addr, &p->local_addr)) { ++ status = PJSIP_ETYPEEXISTS; ++ break; ++ } ++ if (p == tpf) { ++ status = PJ_EEXISTS; ++ break; ++ } + } + + if (status != PJ_SUCCESS) { +- pj_lock_release(mgr->lock); +- return status; ++ pj_lock_release(mgr->lock); ++ return status; + } + + pj_list_insert_before(&mgr->factory_list, tpf); +@@ -1909,13 +1909,11 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport2(pjsip_tpmgr *mgr, + pj_memcpy(&key.rem_addr, remote, addr_len); + + transport = (pjsip_transport*) +- pj_hash_get(mgr->table, &key, key_len, NULL); +- ++ pj_hash_get(mgr->table, &key, key_len, NULL); ++ unsigned flag = pjsip_transport_get_flag_from_type(type); + if (transport == NULL) { +- unsigned flag = pjsip_transport_get_flag_from_type(type); + const pj_sockaddr *remote_addr = (const pj_sockaddr*)remote; + +- + /* Ignore address for loop transports. */ + if (type == PJSIP_TRANSPORT_LOOP || + type == PJSIP_TRANSPORT_LOOP_DGRAM) +@@ -1954,6 +1952,11 @@ PJ_DEF(pj_status_t) pjsip_tpmgr_acquire_transport2(pjsip_tpmgr *mgr, + return PJ_SUCCESS; + } + ++ if (flag & PJSIP_TRANSPORT_SECURE) { ++ TRACE_((THIS_FILE, "Can't create new TLS transport with no provided suitable TLS listener.")); ++ return PJSIP_ETPNOTSUITABLE; ++ } ++ + /* + * Transport not found! + * Find factory that can create such transport. diff --git a/contrib/src/pjproject/notestsapps.patch b/contrib/src/pjproject/notestsapps.patch new file mode 100644 index 0000000000..662f1dbf16 --- /dev/null +++ b/contrib/src/pjproject/notestsapps.patch @@ -0,0 +1,97 @@ +diff --git a/Makefile b/Makefile +index 33a4e6b..a486eb7 100644 +--- a/Makefile ++++ b/Makefile +@@ -4,7 +4,7 @@ include build/host-$(HOST_NAME).mak + include version.mak + + LIB_DIRS = pjlib/build pjlib-util/build pjnath/build third_party/build pjmedia/build pjsip/build +-DIRS = $(LIB_DIRS) pjsip-apps/build $(EXTRA_DIRS) ++DIRS = $(LIB_DIRS) $(EXTRA_DIRS) + + ifdef MINSIZE + MAKE_FLAGS := MINSIZE=1 +diff --git a/pjlib-util/build/Makefile b/pjlib-util/build/Makefile +index cb601cb..862a78a 100644 +--- a/pjlib-util/build/Makefile ++++ b/pjlib-util/build/Makefile +@@ -54,7 +54,6 @@ export UTIL_TEST_OBJS += xml.o encryption.o stun.o resolver_test.o test.o \ + export UTIL_TEST_CFLAGS += $(_CFLAGS) + export UTIL_TEST_CXXFLAGS += $(_CXXFLAGS) + export UTIL_TEST_LDFLAGS += $(PJLIB_UTIL_LDLIB) $(PJLIB_LDLIB) $(_LDFLAGS) +-export UTIL_TEST_EXE:=pjlib-util-test-$(TARGET_NAME)$(HOST_EXE) + + + export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT +diff --git a/pjlib/build/Makefile b/pjlib/build/Makefile +index 1e64950..a75fa65 100644 +--- a/pjlib/build/Makefile ++++ b/pjlib/build/Makefile +@@ -56,7 +56,6 @@ export TEST_OBJS += activesock.o atomic.o echo_clt.o errno.o exception.o \ + export TEST_CFLAGS += $(_CFLAGS) + export TEST_CXXFLAGS += $(_CXXFLAGS) + export TEST_LDFLAGS += $(PJLIB_LDLIB) $(_LDFLAGS) +-export TEST_EXE := pjlib-test-$(TARGET_NAME)$(HOST_EXE) + + + export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT +diff --git a/pjmedia/build/Makefile b/pjmedia/build/Makefile +index 8012cb7..2ca283a 100644 +--- a/pjmedia/build/Makefile ++++ b/pjmedia/build/Makefile +@@ -165,7 +165,6 @@ export PJMEDIA_TEST_LDFLAGS += $(PJMEDIA_CODEC_LDLIB) \ + $(PJLIB_UTIL_LDLIB) \ + $(PJNATH_LDLIB) \ + $(_LDFLAGS) +-export PJMEDIA_TEST_EXE:=pjmedia-test-$(TARGET_NAME)$(HOST_EXE) + + + export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT +diff --git a/pjnath/build/Makefile b/pjnath/build/Makefile +index 1bc08b5..109f79b 100644 +--- a/pjnath/build/Makefile ++++ b/pjnath/build/Makefile +@@ -54,7 +54,6 @@ export PJNATH_TEST_OBJS += ice_test.o stun.o sess_auth.o server.o concur_test.o + export PJNATH_TEST_CFLAGS += $(_CFLAGS) + export PJNATH_TEST_CXXFLAGS += $(_CXXFLAGS) + export PJNATH_TEST_LDFLAGS += $(PJNATH_LDLIB) $(PJLIB_UTIL_LDLIB) $(PJLIB_LDLIB) $(_LDFLAGS) +-export PJNATH_TEST_EXE:=pjnath-test-$(TARGET_NAME)$(HOST_EXE) + + + ############################################################################### +@@ -65,7 +64,6 @@ export PJTURN_CLIENT_OBJS += client_main.o + export PJTURN_CLIENT_CFLAGS += $(_CFLAGS) + export PJTURN_CLIENT_CXXFLAGS += $(_CXXFLAGS) + export PJTURN_CLIENT_LDFLAGS += $(PJNATH_LDLIB) $(PJLIB_UTIL_LDLIB) $(PJLIB_LDLIB) $(_LDFLAGS) +-export PJTURN_CLIENT_EXE:=pjturn-client-$(TARGET_NAME)$(HOST_EXE) + + ############################################################################### + # Defines for building TURN server application +@@ -76,7 +74,6 @@ export PJTURN_SRV_OBJS += allocation.o auth.o listener_udp.o \ + export PJTURN_SRV_CFLAGS += $(_CFLAGS) + export PJTURN_SRV_CXXFLAGS += $(_CXXFLAGS) + export PJTURN_SRV_LDFLAGS += $(PJNATH_LDLIB) $(PJLIB_UTIL_LDLIB) $(PJLIB_LDLIB) $(_LDFLAGS) +-export PJTURN_SRV_EXE:=pjturn-srv-$(TARGET_NAME)$(HOST_EXE) + + + +diff --git a/pjsip/build/Makefile b/pjsip/build/Makefile +index d2a5c2a..7e2ec60 100644 +--- a/pjsip/build/Makefile ++++ b/pjsip/build/Makefile +@@ -165,7 +165,6 @@ export PJSUA2_TEST_OBJS += $(OS_OBJS) $(M_OBJS) $(CC_OBJS) $(HOST_OBJS) \ + export PJSUA2_TEST_CFLAGS += $(_CFLAGS) $(PJ_VIDEO_CFLAGS) + export PJSUA2_TEST_CXXFLAGS = $(PJSUA2_LIB_CFLAGS) + export PJSUA2_TEST_LDFLAGS += $(PJ_LDXXFLAGS) $(PJ_LDXXLIBS) $(LDFLAGS) +-export PJSUA2_TEST_EXE := pjsua2-test-$(TARGET_NAME)$(HOST_EXE) + + export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT + +@@ -195,7 +194,6 @@ export TEST_LDFLAGS += $(PJSIP_LDLIB) \ + $(PJLIB_UTIL_LDLIB) \ + $(PJNATH_LDLIB) \ + $(_LDFLAGS) +-export TEST_EXE := pjsip-test-$(TARGET_NAME)$(HOST_EXE) + + + export CC_OUT CC AR RANLIB HOST_MV HOST_RM HOST_RMDIR HOST_MKDIR OBJEXT LD LDOUT diff --git a/contrib/src/pjproject/pj_ice_sess.patch b/contrib/src/pjproject/pj_ice_sess.patch new file mode 100644 index 0000000000..bd040ffe3e --- /dev/null +++ b/contrib/src/pjproject/pj_ice_sess.patch @@ -0,0 +1,22 @@ +--- a/pjnath/include/pjnath/ice_strans.h ++++ b/pjnath/include/pjnath/ice_strans.h +@@ -845,6 +845,8 @@ PJ_DECL(pj_status_t) pj_ice_strans_sendt + int dst_addr_len); + + ++PJ_DECL(pj_ice_sess *) pj_ice_strans_get_ice_sess(pj_ice_strans *ice_st); ++ + /** + * @} + */ +--- a/pjnath/src/pjnath/ice_strans.c ++++ b/pjnath/src/pjnath/ice_strans.c +@@ -1243,6 +1243,11 @@ PJ_DEF(pj_status_t) pj_ice_strans_sendto + return PJ_EINVALIDOP; + } + ++PJ_DECL(pj_ice_sess *) pj_ice_strans_get_ice_sess( pj_ice_strans *ice_st ) ++{ ++ return ice_st->ice; ++} ++ diff --git a/contrib/src/pjproject/pj_win.patch b/contrib/src/pjproject/pj_win.patch new file mode 100644 index 0000000000..c1c4c78f19 --- /dev/null +++ b/contrib/src/pjproject/pj_win.patch @@ -0,0 +1,11 @@ +--- pjproject/pjlib/include/pj/compat/string.h.orig 2014-09-08 16:17:36.471214560 -0400 ++++ pjproject/pjlib/include/pj/compat/string.h 2014-09-08 16:09:22.095207141 -0400 +@@ -43,7 +43,7 @@ + # include <stdlib.h> + #endif + +-#if defined(_MSC_VER) ++#if defined(PJ_WIN32) + # define strcasecmp _stricmp + # define strncasecmp _strnicmp + # define snprintf _snprintf diff --git a/contrib/src/pjproject/rules.mak b/contrib/src/pjproject/rules.mak new file mode 100644 index 0000000000..e9bf51c0ca --- /dev/null +++ b/contrib/src/pjproject/rules.mak @@ -0,0 +1,69 @@ +# PJPROJECT +PJPROJECT_VERSION := 2.2.1 +PJPROJECT_URL := http://www.pjsip.org/release/$(PJPROJECT_VERSION)/pjproject-$(PJPROJECT_VERSION).tar.bz2 + +PJPROJECT_OPTIONS := --disable-oss \ + --disable-sound \ + --disable-video \ + --enable-ext-sound \ + --disable-speex-aec \ + --disable-g711-codec \ + --disable-l16-codec \ + --disable-gsm-codec \ + --disable-g722-codec \ + --disable-g7221-codec \ + --disable-speex-codec \ + --disable-ilbc-codec \ + --disable-opencore-amr \ + --disable-sdl \ + --disable-ffmpeg \ + --disable-v4l2 \ + --enable-ssl=gnutls + +ifdef HAVE_ANDROID +PJPROJECT_OPTIONS += --with-ssl=$(PREFIX) +endif + +PJPROJECT_EXTRA_CFLAGS = -DPJ_ICE_MAX_CAND=32 -DPJ_ICE_MAX_CHECKS=150 -DPJ_ICE_COMP_BITS=2 + +PKGS += pjproject +# FIXME: nominally 2.2.0 is enough, but it has to be patched for gnutls +ifeq ($(call need_pkg,'libpjproject'),) +PKGS_FOUND += pjproject +endif + +DEPS_pjproject += gnutls +ifndef HAVE_WIN32 +ifndef HAVE_MACOSX +DEPS_pjproject += uuid +endif +endif + +$(TARBALLS)/pjproject-$(PJPROJECT_VERSION).tar.bz2: + $(call download,$(PJPROJECT_URL)) + +.sum-pjproject: pjproject-$(PJPROJECT_VERSION).tar.bz2 + +pjproject: pjproject-$(PJPROJECT_VERSION).tar.bz2 .sum-pjproject + $(UNPACK) +ifdef HAVE_WIN32 + $(APPLY) $(SRC)/pjproject/intptr_t.patch + $(APPLY) $(SRC)/pjproject/pj_win.patch +endif + $(APPLY) $(SRC)/pjproject/aconfigureupdate.patch + $(APPLY) $(SRC)/pjproject/endianness.patch + $(APPLY) $(SRC)/pjproject/unknowncipher.patch + $(APPLY) $(SRC)/pjproject/tls_cert.patch + $(APPLY) $(SRC)/pjproject/gnutls.patch + $(APPLY) $(SRC)/pjproject/notestsapps.patch + $(APPLY) $(SRC)/pjproject/ipv6.patch + $(APPLY) $(SRC)/pjproject/ice_config.patch + $(APPLY) $(SRC)/pjproject/multiple_listeners.patch + $(APPLY) $(SRC)/pjproject/pj_ice_sess.patch + $(UPDATE_AUTOCONFIG) + $(MOVE) + +.pjproject: pjproject + cd $< && $(HOSTVARS) ./aconfigure $(HOSTCONF) $(PJPROJECT_OPTIONS) + cd $< && CFLAGS="$(PJPROJECT_EXTRA_CFLAGS)" $(MAKE) && $(MAKE) install + touch $@ diff --git a/contrib/src/pjproject/tls_cert.patch b/contrib/src/pjproject/tls_cert.patch new file mode 100644 index 0000000000..f030b42319 --- /dev/null +++ b/contrib/src/pjproject/tls_cert.patch @@ -0,0 +1,10 @@ +--- a/pjlib/include/pj/ssl_sock.h ++++ b/pjlib/include/pj/ssl_sock.h +@@ -181,6 +181,8 @@ typedef struct pj_ssl_cert_info { + } subj_alt_name; /**< Subject alternative + name extension */ + ++ pj_str_t cert_raw; ++ + } pj_ssl_cert_info; + diff --git a/contrib/src/pjproject/unknowncipher.patch b/contrib/src/pjproject/unknowncipher.patch new file mode 100644 index 0000000000..b9e86ec753 --- /dev/null +++ b/contrib/src/pjproject/unknowncipher.patch @@ -0,0 +1,12 @@ +--- a/pjlib/include/pj/ssl_sock.h 2013-04-26 02:01:43.000000000 -0400 ++++ b/pjlib/include/pj/ssl_sock.h 2014-06-16 18:31:58.464991714 -0400 +@@ -243,6 +243,9 @@ + */ + typedef enum pj_ssl_cipher { + ++ /* Unsupported cipher */ ++ PJ_TLS_UNKNOWN_CIPHER = -1, ++ + /* NULL */ + PJ_TLS_NULL_WITH_NULL_NULL = 0x00000000, + diff --git a/contrib/src/pkg-static.sh b/contrib/src/pkg-static.sh new file mode 100755 index 0000000000..59cbd786b5 --- /dev/null +++ b/contrib/src/pkg-static.sh @@ -0,0 +1,38 @@ +#! /bin/sh +# Copyright (C) 2012 Rémi Denis-Courmont +# This file is distributed under the same license as the vlc package. + +if test -z "$1" || test -n "$2"; then + echo "Usage: $0 <file.pc>" >&2 + echo "Merges the pkg-config {Requires/Libs}.private stanza into {Requires/Libs} stanzas." >&2 + exit 1 +fi + +exec <"$1" >"$1.tmp" || exit $? + +LIBS_PUBLIC="" +LIBS_PRIVATE="" +REQUIRES_PUBLIC="" +REQUIRES_PRIVATE="" + +while read LINE; do + lpub="${LINE#Libs:}" + lpriv="${LINE#Libs.private:}" + rpub="${LINE#Requires:}" + rpriv="${LINE#Requires.private:}" + if test "$lpub" != "$LINE"; then + LIBS_PUBLIC="$lpub" + elif test "$lpriv" != "$LINE"; then + LIBS_PRIVATE="$lpriv" + elif test "$rpub" != "$LINE"; then + REQUIRES_PUBLIC="$rpub" + elif test "$rpriv" != "$LINE"; then + REQUIRES_PRIVATE="$rpriv" + else + echo "$LINE" + fi +done +echo "Libs: $LIBS_PUBLIC $LIBS_PRIVATE" +echo "Requires: $REQUIRES_PUBLIC $REQUIRES_PRIVATE" + +mv -f -- "$1.tmp" "$1" diff --git a/contrib/src/samplerate/SHA512SUMS b/contrib/src/samplerate/SHA512SUMS new file mode 100644 index 0000000000..0d9ad4088d --- /dev/null +++ b/contrib/src/samplerate/SHA512SUMS @@ -0,0 +1 @@ +85d93df24d9d62e7803a5d0ac5d268b2085214adcb160e32fac316b12ee8a0ce36ccfb433a3c0a08f6e3ec418a5962bdb84f8a11262286a9b347436983029a7d libsamplerate-0.1.8.tar.gz diff --git a/contrib/src/samplerate/carbon.patch b/contrib/src/samplerate/carbon.patch new file mode 100644 index 0000000000..a6980af016 --- /dev/null +++ b/contrib/src/samplerate/carbon.patch @@ -0,0 +1,10 @@ +--- a/examples/audio_out.c.orig 2014-06-26 21:09:44.000000000 -0400 ++++ b/examples/audio_out.c 2014-06-26 21:09:58.000000000 -0400 +@@ -172,7 +172,6 @@ + + #if (defined (__MACH__) && defined (__APPLE__)) /* MacOSX */ + +-#include <Carbon.h> + #include <CoreAudio/AudioHardware.h> + + #define MACOSX_MAGIC MAKE_MAGIC ('M', 'a', 'c', ' ', 'O', 'S', ' ', 'X') diff --git a/contrib/src/samplerate/rules.mak b/contrib/src/samplerate/rules.mak new file mode 100644 index 0000000000..28c064660c --- /dev/null +++ b/contrib/src/samplerate/rules.mak @@ -0,0 +1,25 @@ +# SAMPLERATE +SAMPLERATE_VERSION := 0.1.8 +SAMPLERATE_URL := http://www.mega-nerd.com/SRC/libsamplerate-$(SAMPLERATE_VERSION).tar.gz + +PKGS += samplerate +ifeq ($(call need_pkg,"samplerate"),) +PKGS_FOUND += samplerate +endif + +$(TARBALLS)/libsamplerate-$(SAMPLERATE_VERSION).tar.gz: + $(call download,$(SAMPLERATE_URL)) + +.sum-samplerate: libsamplerate-$(SAMPLERATE_VERSION).tar.gz + +samplerate: libsamplerate-$(SAMPLERATE_VERSION).tar.gz .sum-samplerate + $(UNPACK) + $(APPLY) $(SRC)/samplerate/soundcard.patch + $(APPLY) $(SRC)/samplerate/carbon.patch + $(UPDATE_AUTOCONFIG) && cd $(UNPACK_DIR) && mv config.guess config.sub Cfg + $(MOVE) + +.samplerate: samplerate + cd $< && $(HOSTVARS) ./configure $(HOSTCONF) + cd $< && $(MAKE) install + touch $@ diff --git a/contrib/src/samplerate/soundcard.patch b/contrib/src/samplerate/soundcard.patch new file mode 100644 index 0000000000..af2de623aa --- /dev/null +++ b/contrib/src/samplerate/soundcard.patch @@ -0,0 +1,14 @@ +--- a/examples/audio_out.c.orig 2014-06-18 16:52:04.269479958 -0400 ++++ b/examples/audio_out.c 2014-06-18 16:52:36.789478998 -0400 +@@ -44,7 +44,11 @@ + + #include <fcntl.h> + #include <sys/ioctl.h> ++#if defined (__ANDROID__) ++#include <linux/soundcard.h> ++#else + #include <sys/soundcard.h> ++#endif + + #define LINUX_MAGIC MAKE_MAGIC ('L', 'i', 'n', 'u', 'x', 'O', 'S', 'S') + diff --git a/contrib/src/sndfile/SHA512SUMS b/contrib/src/sndfile/SHA512SUMS new file mode 100644 index 0000000000..ad45e4bd6d --- /dev/null +++ b/contrib/src/sndfile/SHA512SUMS @@ -0,0 +1 @@ +4ca9780ed0a915aca8a10ef91bf4bf48b05ecb85285c2c3fe7eef1d46d3e0747e61416b6bddbef369bd69adf4b796ff5f61380e0bc998906b170a93341ba6f78 libsndfile-1.0.25.tar.gz diff --git a/contrib/src/sndfile/autotools.patch b/contrib/src/sndfile/autotools.patch new file mode 100644 index 0000000000..ef93ccaeb4 --- /dev/null +++ b/contrib/src/sndfile/autotools.patch @@ -0,0 +1,35 @@ +From ca790066b639ea570067afb3cb2a36c9e3383ae8 Mon Sep 17 00:00:00 2001 +From: Erik de Castro Lopo <erikd@mega-nerd.com> +Date: Sat, 13 Jul 2013 17:04:45 +1000 +Subject: [PATCH] configure.ac : Fix for current versions of autotools. + +Patch from Cristian Rodriguez. +--- + configure.ac | 7 ++++--- + 1 file changed, 4 insertions(+), 3 deletions(-) + +diff --git a/configure.ac b/configure.ac +index bef0c18..3b32cb7 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -20,7 +20,8 @@ m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])]) + + AC_LANG([C]) + +-AC_PROG_CC ++AC_PROG_CC_STDC ++AC_USE_SYSTEM_EXTENSIONS + AM_PROG_CC_C_O + AC_PROG_CXX + AC_PROG_SED +@@ -331,8 +332,8 @@ if test -n "$PKG_CONFIG" ; then + HAVE_EXTERNAL_LIBS=1 + enable_external_libs=yes + +- EXTERNAL_CFLAGS="$FLAC_CFLAGS $OGG_CFLAGS $VORBISENC_CFLAGS $SPEEX_CFLAGS" +- EXTERNAL_LIBS="$FLAC_LIBS $VORBISENC_LIBS $SPEEX_LIBS" ++ EXTERNAL_CFLAGS="$FLAC_CFLAGS $OGG_CFLAGS $VORBIS_CFLAGS $VORBISENC_CFLAGS $SPEEX_CFLAGS" ++ EXTERNAL_LIBS="$FLAC_LIBS $OGG_LIBS $VORBIS_LIBS $VORBISENC_LIBS $SPEEX_LIBS " + else + echo + AC_MSG_WARN([[*** One or more of the external libraries (ie libflac, libogg and]]) diff --git a/contrib/src/sndfile/carbon.patch b/contrib/src/sndfile/carbon.patch new file mode 100644 index 0000000000..22d89bae54 --- /dev/null +++ b/contrib/src/sndfile/carbon.patch @@ -0,0 +1,10 @@ +--- a/programs/sndfile-play.c.orig 2014-06-26 18:33:49.000000000 -0400 ++++ b/programs/sndfile-play.c 2014-06-26 18:33:52.000000000 -0400 +@@ -63,7 +63,6 @@ + #include <sys/soundcard.h> + + #elif (defined (__MACH__) && defined (__APPLE__)) +- #include <Carbon.h> + #include <CoreAudio/AudioHardware.h> + + #elif defined (HAVE_SNDIO_H) diff --git a/contrib/src/sndfile/rules.mak b/contrib/src/sndfile/rules.mak new file mode 100644 index 0000000000..077cd6c840 --- /dev/null +++ b/contrib/src/sndfile/rules.mak @@ -0,0 +1,28 @@ +# SNDFILE +SNDFILE_VERSION := 1.0.25 +SNDFILE_URL := http://www.mega-nerd.com/libsndfile/files/libsndfile-$(SNDFILE_VERSION).tar.gz + +PKGS += sndfile +ifeq ($(call need_pkg,"sndfile"),) +PKGS_FOUND += sndfile +endif + +DEPS_sndfile = ogg vorbis flac + +$(TARBALLS)/libsndfile-$(SNDFILE_VERSION).tar.gz: + $(call download,$(SNDFILE_URL)) + +.sum-sndfile: libsndfile-$(SNDFILE_VERSION).tar.gz + +sndfile: libsndfile-$(SNDFILE_VERSION).tar.gz .sum-sndfile + $(UNPACK) + $(APPLY) $(SRC)/sndfile/soundcard.patch + $(APPLY) $(SRC)/sndfile/carbon.patch + $(APPLY) $(SRC)/sndfile/autotools.patch + $(UPDATE_AUTOCONFIG) && cd $(UNPACK_DIR) && mv config.guess config.sub Cfg && autoreconf -fi + $(MOVE) + +.sndfile: sndfile + cd $< && $(HOSTVARS) ./configure $(HOSTCONF) + cd $< && $(MAKE) install + touch $@ diff --git a/contrib/src/sndfile/soundcard.patch b/contrib/src/sndfile/soundcard.patch new file mode 100644 index 0000000000..298d0e12bb --- /dev/null +++ b/contrib/src/sndfile/soundcard.patch @@ -0,0 +1,16 @@ +--- a/programs/sndfile-play.c.orig 2014-06-12 16:00:39.348060215 -0400 ++++ b/programs/sndfile-play.c 2014-06-12 16:01:05.660059438 -0400 +@@ -52,7 +52,12 @@ + #include <sys/time.h> + #endif + +-#if defined (__linux__) || defined (__FreeBSD_kernel__) || defined (__FreeBSD__) ++#if defined(__ANDROID__) ++ #include <fcntl.h> ++ #include <sys/ioctl.h> ++ #include <linux/soundcard.h> ++ ++#elif defined (__linux__) || defined (__FreeBSD_kernel__) || defined (__FreeBSD__) + #include <fcntl.h> + #include <sys/ioctl.h> + #include <sys/soundcard.h> diff --git a/contrib/src/speex/rules.mak b/contrib/src/speex/rules.mak new file mode 100644 index 0000000000..0e923b6197 --- /dev/null +++ b/contrib/src/speex/rules.mak @@ -0,0 +1,40 @@ +# speex + +SPEEX_VERSION := git +SPEEX_HASH := HEAD +SPEEX_GITURL := http://git.xiph.org/?p=speex.git;a=snapshot;h=$(SPEEX_HASH);sf=tgz + +PKGS += speex +ifeq ($(call need_pkg,"speex >= 1.0.5"),) +PKGS_FOUND += speex +endif + +$(TARBALLS)/speex-git.tar.gz: + $(call download,$(SPEEX_GITURL)) + +.sum-speex: speex-$(SPEEX_VERSION).tar.gz + $(warning $@ not implemented) + touch $@ + +speex: speex-$(SPEEX_VERSION).tar.gz .sum-speex + rm -Rf $@-git $@ + mkdir -p $@-git + $(ZCAT) "$<" | (cd $@-git && tar xv --strip-components=1) + $(MOVE) + +SPEEX_CONF := --disable-binaries +ifndef HAVE_FPU +SPEEX_CONF += --enable-fixed-point +ifeq ($(ARCH),arm) +SPEEX_CONF += --enable-arm5e-asm +endif +endif +ifeq ($(ARCH),aarch64) +SPEEX_CONF += --disable-neon +endif + +.speex: speex + mkdir -p $</m4 && $(RECONF) + cd $< && $(HOSTVARS) ./configure $(HOSTCONF) $(SPEEX_CONF) + cd $< && $(MAKE) install + touch $@ diff --git a/contrib/src/speexdsp/rules.mak b/contrib/src/speexdsp/rules.mak new file mode 100644 index 0000000000..3cc2b15bc8 --- /dev/null +++ b/contrib/src/speexdsp/rules.mak @@ -0,0 +1,44 @@ +# speexdsp + +SPEEXDSP_VERSION := git +SPEEXDSP_HASH := HEAD +SPEEXDSP_GITURL := http://git.xiph.org/?p=speexdsp.git;a=snapshot;h=$(SPEEXDSP_HASH);sf=tgz + +PKGS += speexdsp +ifeq ($(call need_pkg,"speexdsp"),) +PKGS_FOUND += speexdsp +endif + +$(TARBALLS)/speexdsp-git.tar.gz: + $(call download,$(SPEEXDSP_GITURL)) + +.sum-speexdsp: speexdsp-$(SPEEXDSP_VERSION).tar.gz + $(warning $@ not implemented) + touch $@ + +speexdsp: speexdsp-$(SPEEXDSP_VERSION).tar.gz .sum-speexdsp + rm -Rf $@-git $@ + mkdir -p $@-git + $(ZCAT) "$<" | (cd $@-git && tar xv --strip-components=1) + $(MOVE) + +SPEEXDSP_CONF := --enable-resample-full-sinc-table --disable-examples +ifeq ($(ARCH),aarch64) +# old neon, not compatible with aarch64 +SPEEXDSP_CONF += --disable-neon +endif +ifndef HAVE_NEON +SPEEXDSP_CONF += --disable-neon +endif +ifndef HAVE_FPU +SPEEXDSP_CONF += --enable-fixed-point +ifeq ($(ARCH),arm) +SPEEXDSP_CONF += --enable-arm5e-asm +endif +endif + +.speexdsp: speexdsp + mkdir -p $</m4 && $(RECONF) + cd $< && $(HOSTVARS) ./configure $(HOSTCONF) $(SPEEXDSP_CONF) + cd $< && $(MAKE) install + touch $@ diff --git a/contrib/src/upnp/SHA512SUMS b/contrib/src/upnp/SHA512SUMS new file mode 100644 index 0000000000..f30c34e49b --- /dev/null +++ b/contrib/src/upnp/SHA512SUMS @@ -0,0 +1 @@ +97af62a7483cc19cfe80157cbc3383c1b4b7c9c39b848f4ed063784b74df0b9b0527f7b467e01451e0a44dbf9e8a9eab510619146a6ee1e3dce46f3e4af6e661 libupnp-1.6.19.tar.bz2 diff --git a/contrib/src/upnp/libupnp-configure.patch b/contrib/src/upnp/libupnp-configure.patch new file mode 100644 index 0000000000..dbc14cca5e --- /dev/null +++ b/contrib/src/upnp/libupnp-configure.patch @@ -0,0 +1,82 @@ +--- libupnp/configure.ac.orig 2011-02-09 00:55:44.000000000 +0100 ++++ libupnp/configure.ac 2011-02-10 23:39:44.154929678 +0100 +@@ -397,7 +397,6 @@ + AC_PROG_MAKE_SET + AC_PROG_EGREP + +-# + # Default compilation flags + # + echo "--------------------- Default compilation flags -------------------------------" +@@ -531,39 +530,46 @@ + # Checks for POSIX Threads + # + echo "--------------------------- pthread stuff -------------------------------------" +-ACX_PTHREAD( +- [], +- [AC_MSG_ERROR([POSIX threads are required to build this program])]) ++#ACX_PTHREAD( ++# [], ++# [AC_MSG_ERROR([POSIX threads are required to build this program])]) + # ++PTHREAD_LIBS=" -lpthreadGC2 -lws2_32" ++PTHREAD_CFLAGS=" -DPTW32_STATIC_LIB -DUPNP_STATIC_LIB" + # Update environment variables for pthreads + # +-CC="$PTHREAD_CC" ++#CC="$PTHREAD_CC" + CFLAGS="$PTHREAD_CFLAGS $CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" ++ ++AC_SUBST(PTHREAD_LIBS) ++AC_SUBST(PTHREAD_CFLAGS) ++AC_SUBST(PTHREAD_CC) ++ + # + # Determine if pthread_rwlock_t is available + # +-echo "----------------------- pthread_rwlock_t stuff --------------------------------" +-AC_MSG_CHECKING([if pthread_rwlock_t is available]) +-AC_LANG([C]) +-AC_COMPILE_IFELSE( +- [AC_LANG_PROGRAM( +- [#include <pthread.h>], +- [pthread_rwlock_t *x;])], +- [AC_DEFINE([UPNP_USE_RWLOCK], [1], [Use pthread_rwlock_t]) +- AC_MSG_RESULT([yes, supported without any options])], +- [AC_COMPILE_IFELSE( +- [AC_LANG_PROGRAM( +- [#define _GNU_SOURCE +- #include <pthread.h>], +- [pthread_rwlock_t *x;])], +- [AC_DEFINE([UPNP_USE_RWLOCK], [1], [Use pthread_rwlock_t]) +- CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE" +- AC_MSG_RESULT([yes, definition of _GNU_SOURCE required])], +- [AC_DEFINE([UPNP_USE_RWLOCK], [0], [Do not use pthread_rwlock_t]) +- AC_MSG_RESULT([no, needs to fallback to pthread_mutex]) +- AC_MSG_ERROR([pthread_rwlock_t not available])])]) +-echo "-------------------------------------------------------------------------------" ++#echo "----------------------- pthread_rwlock_t stuff --------------------------------" ++#AC_MSG_CHECKING([if pthread_rwlock_t is available]) ++#AC_LANG([C]) ++#AC_COMPILE_IFELSE( ++# [AC_LANG_PROGRAM( ++# [#include <pthread.h>], ++# [pthread_rwlock_t *x;])], ++# [AC_DEFINE([UPNP_USE_RWLOCK], [1], [Use pthread_rwlock_t]) ++# AC_MSG_RESULT([yes, supported without any options])], ++# [AC_COMPILE_IFELSE( ++# [AC_LANG_PROGRAM( ++# [#define _GNU_SOURCE ++# #include <pthread.h>], ++# [pthread_rwlock_t *x;])], ++# [AC_DEFINE([UPNP_USE_RWLOCK], [1], [Use pthread_rwlock_t]) ++# CPPFLAGS="$CPPFLAGS -D_GNU_SOURCE" ++# AC_MSG_RESULT([yes, definition of _GNU_SOURCE required])], ++# [AC_DEFINE([UPNP_USE_RWLOCK], [0], [Do not use pthread_rwlock_t]) ++# AC_MSG_RESULT([no, needs to fallback to pthread_mutex]) ++# AC_MSG_ERROR([pthread_rwlock_t not available])])]) ++#echo "-------------------------------------------------------------------------------" + + + AC_CONFIG_FILES([ diff --git a/contrib/src/upnp/libupnp-ipv6.patch b/contrib/src/upnp/libupnp-ipv6.patch new file mode 100644 index 0000000000..7b89bc2688 --- /dev/null +++ b/contrib/src/upnp/libupnp-ipv6.patch @@ -0,0 +1,49 @@ +From 438ace99538713fb1370411188e0f370069a1818 Mon Sep 17 00:00:00 2001 +From: Konstantin Pavlov <thresh@videolan.org> +Date: Tue, 29 May 2012 10:18:40 +0400 +Subject: [PATCH] Fix compile under mingw with IPv6 enabled. + +--- + upnp/src/genlib/miniserver/miniserver.c | 7 +++++++ + upnp/src/ssdp/ssdp_server.c | 7 +++++++ + 2 files changed, 14 insertions(+) + +diff --git a/upnp/src/genlib/miniserver/miniserver.c b/upnp/src/genlib/miniserver/miniserver.c +index af310ca..1ae422f 100644 +--- a/upnp/src/genlib/miniserver/miniserver.c ++++ b/upnp/src/genlib/miniserver/miniserver.c +@@ -68,6 +68,13 @@ + /*! . */ + #define APPLICATION_LISTENING_PORT 49152 + ++/* IPV6_V6ONLY is missing from MinGW, hack taken from ++ * http://svn.apache.org/repos/asf/apr/apr/trunk/network_io/win32/sockopt.c ++ */ ++#ifndef IPV6_V6ONLY ++#define IPV6_V6ONLY 27 ++#endif ++ + struct mserv_request_t { + /*! Connection handle. */ + SOCKET connfd; +diff --git a/upnp/src/ssdp/ssdp_server.c b/upnp/src/ssdp/ssdp_server.c +index 231c2c5..6a9c27f 100644 +--- a/upnp/src/ssdp/ssdp_server.c ++++ b/upnp/src/ssdp/ssdp_server.c +@@ -69,6 +69,13 @@ + #endif /* UPNP_ENABLE_IPV6 */ + #endif /* INCLUDE_CLIENT_APIS */ + ++/* IPV6_V6ONLY is missing from MinGW, hack taken from ++ * http://svn.apache.org/repos/asf/apr/apr/trunk/network_io/win32/sockopt.c ++ */ ++#ifndef IPV6_V6ONLY ++#define IPV6_V6ONLY 27 ++#endif ++ + void RequestHandler(); + + enum Listener { +-- +1.7.9.7 + diff --git a/contrib/src/upnp/libupnp-win32.patch b/contrib/src/upnp/libupnp-win32.patch new file mode 100644 index 0000000000..686e3bae19 --- /dev/null +++ b/contrib/src/upnp/libupnp-win32.patch @@ -0,0 +1,45 @@ +--- libupnp/configure.ac.orig 2011-02-10 23:53:25.000000000 +0100 ++++ libupnp/configure.ac 2011-02-10 23:54:23.574454501 +0100 +@@ -546,6 +546,7 @@ + AC_SUBST(PTHREAD_CFLAGS) + AC_SUBST(PTHREAD_CC) + ++AC_DEFINE([_WIN32_WINNT], 0x0501, [Define to '0x0500' for Windows 2000 APIs.]) + # + # Determine if pthread_rwlock_t is available + # +--- libupnp/libupnp.pc.in 2010-12-23 21:24:05.000000000 +0100 ++++ libupnp.new/libupnp.pc.in 2011-02-13 11:27:23.000000000 +0100 +@@ -6,6 +6,6 @@ + Name: libupnp + Description: Linux SDK for UPnP Devices + Version: @VERSION@ +-Libs: @PTHREAD_CFLAGS@ @PTHREAD_LIBS@ -L${libdir} -lupnp -lthreadutil -lixml ++Libs: @PTHREAD_CFLAGS@ -L${libdir} -lupnp -lthreadutil -lixml -liphlpapi @PTHREAD_LIBS@ + Cflags: @PTHREAD_CFLAGS@ -I${includedir}/upnp + +--- libupnp/upnp/src/inc/upnputil.h 2010-12-23 21:24:06.000000000 +0100 ++++ libupnp.new/upnp/src/inc/upnputil.h 2011-02-13 08:24:24.000000000 +0100 +@@ -125,7 +125,7 @@ + #define strncasecmp strnicmp + #define sleep(a) Sleep((a)*1000) + #define usleep(a) Sleep((a)/1000) +- #define strerror_r(a,b,c) (strerror_s((b),(c),(a))) ++ #define strerror_r(a,b,c) strncpy( b, strerror(a), c) + #else + #define max(a, b) (((a)>(b))? (a):(b)) + #define min(a, b) (((a)<(b))? (a):(b)) +--- upnp/upnp/inc/UpnpInet.h 2011-04-03 04:50:36.000000000 +0200 ++++ upnp.neww/upnp/inc/UpnpInet.h 2011-11-18 01:54:45.418529337 +0100 +@@ -15,11 +15,6 @@ + + #ifdef WIN32 + #include <stdarg.h> +- #ifndef UPNP_USE_MSVCPP +- /* Removed: not required (and cause compilation issues) */ +- #include <winbase.h> +- #include <windef.h> +- #endif + #include <winsock2.h> + #include <iphlpapi.h> + #include <ws2tcpip.h> diff --git a/contrib/src/upnp/libupnp-win64.patch b/contrib/src/upnp/libupnp-win64.patch new file mode 100644 index 0000000000..8064e6d214 --- /dev/null +++ b/contrib/src/upnp/libupnp-win64.patch @@ -0,0 +1,41 @@ +--- libupnp/threadutil/inc/ThreadPool.h 2011-01-20 07:46:57.000000000 +0100 ++++ libupnp.new/threadutil/inc/ThreadPool.h 2011-09-23 01:36:12.000000000 +0200 +@@ -45,6 +45,7 @@ + #include <errno.h> + + #ifdef WIN32 ++ #ifndef _TIMEZONE_DEFINED + #include <time.h> + struct timezone + { +@@ -52,6 +53,7 @@ + int tz_dsttime; /* type of dst correction */ + }; + int gettimeofday(struct timeval *tv, struct timezone *tz); ++ #endif + #else /* WIN32 */ + #include <sys/param.h> + #include <sys/time.h> /* for gettimeofday() */ +--- libupnp-1.6.16/upnp/inc/upnp.h.orig 2012-03-22 00:15:38.000000000 +0100 ++++ libupnp-1.6.16/upnp/inc/upnp.h 2012-03-28 18:58:55.043642000 +0200 +@@ -61,6 +61,20 @@ + /* Other systems ??? */ + #endif + ++# if defined( __MINGW32__ ) ++# if !defined( _OFF_T_ ) ++ typedef long long _off_t; ++ typedef _off_t off_t; ++# define _OFF_T_ ++# else ++# ifdef off_t ++# undef off_t ++# endif ++# define off_t long long ++# endif ++# endif ++ ++ + #define LINE_SIZE (size_t)180 + #define NAME_SIZE (size_t)256 + #define MNFT_NAME_SIZE 64 diff --git a/contrib/src/upnp/miniserver.patch b/contrib/src/upnp/miniserver.patch new file mode 100644 index 0000000000..45ff90c675 --- /dev/null +++ b/contrib/src/upnp/miniserver.patch @@ -0,0 +1,17 @@ +--- upnp/upnp/src/api/upnpapi.c.orig 2013-04-08 00:23:46.000000000 +0200 ++++ upnp/upnp/src/api/upnpapi.c 2013-04-08 00:25:49.000000000 +0200 +@@ -358,13 +358,13 @@ + return retVal; + } + ++#ifdef INTERNAL_WEB_SERVER + #ifdef INCLUDE_DEVICE_APIS + #if EXCLUDE_SOAP == 0 + SetSoapCallback(soap_device_callback); + #endif + #endif /* INCLUDE_DEVICE_APIS */ + +-#ifdef INTERNAL_WEB_SERVER + #if EXCLUDE_GENA == 0 + SetGenaCallback(genaCallback); + #endif diff --git a/contrib/src/upnp/rules.mak b/contrib/src/upnp/rules.mak new file mode 100644 index 0000000000..717fdd9b1d --- /dev/null +++ b/contrib/src/upnp/rules.mak @@ -0,0 +1,38 @@ +# UPNP +UPNP_VERSION := 1.6.19 +UPNP_URL := http://sourceforge.net/projects/pupnp/files/pupnp/libUPnP%20$(UPNP_VERSION)/libupnp-$(UPNP_VERSION).tar.bz2/download + +PKGS += upnp +ifeq ($(call need_pkg,'libupnp'),) +PKGS_FOUND += upnp +endif + +$(TARBALLS)/libupnp-$(UPNP_VERSION).tar.bz2: + $(call download,$(UPNP_URL)) + +.sum-upnp: libupnp-$(UPNP_VERSION).tar.bz2 + +ifdef HAVE_WIN32 +DEPS_upnp += pthreads $(DEPS_pthreads) +LIBUPNP_ECFLAGS = -DPTW32_STATIC_LIB +endif + +upnp: libupnp-$(UPNP_VERSION).tar.bz2 .sum-upnp + $(UNPACK) +ifdef HAVE_WIN32 + $(APPLY) $(SRC)/upnp/libupnp-configure.patch + $(APPLY) $(SRC)/upnp/libupnp-win32.patch + $(APPLY) $(SRC)/upnp/libupnp-win64.patch +endif + $(APPLY) $(SRC)/upnp/libupnp-ipv6.patch + $(APPLY) $(SRC)/upnp/miniserver.patch + $(UPDATE_AUTOCONFIG) && cd $(UNPACK_DIR) && mv config.guess config.sub build-aux/ + $(MOVE) + +.upnp: upnp +ifdef HAVE_WIN32 + $(RECONF) +endif + cd $< && $(HOSTVARS) CFLAGS="$(CFLAGS) -DUPNP_STATIC_LIB $(LIBUPNP_ECFLAGS)" ./configure --disable-samples --without-documentation --disable-blocking_tcp_connections $(HOSTCONF) + cd $< && $(MAKE) install + touch $@ diff --git a/contrib/src/uuid/SHA512SUMS b/contrib/src/uuid/SHA512SUMS new file mode 100644 index 0000000000..f0c2117dcc --- /dev/null +++ b/contrib/src/uuid/SHA512SUMS @@ -0,0 +1 @@ +6b3dea93d080d38629f31b615d040adccdece6f4a7d4ae9fd65073f83b4b0f818896cc41bae7569449a182b11e8a0e4c4dec83ea2131f6926d4473196413395d libuuid-1.0.2.tar.gz diff --git a/contrib/src/uuid/android.patch b/contrib/src/uuid/android.patch new file mode 100644 index 0000000000..01a1aa2748 --- /dev/null +++ b/contrib/src/uuid/android.patch @@ -0,0 +1,16 @@ +--- a/c.h 2013-04-30 04:30:28.000000000 -0700 ++++ b/c.h 2014-03-28 16:54:15.000000000 -0700 +@@ -244,11 +244,13 @@ static inline int dirfd(DIR *d) + */ + static inline size_t get_hostname_max(void) + { ++#ifdef _SC_HOST_NAME_MAX + long len = sysconf(_SC_HOST_NAME_MAX); + + if (0 < len) + return len; ++#endif + + #ifdef MAXHOSTNAMELEN + return MAXHOSTNAMELEN; + #elif HOST_NAME_MAX diff --git a/contrib/src/uuid/rules.mak b/contrib/src/uuid/rules.mak new file mode 100644 index 0000000000..fa620f93ed --- /dev/null +++ b/contrib/src/uuid/rules.mak @@ -0,0 +1,23 @@ +# libuuid part of util-linux + +UUID_VERSION := 1.0.2 +UUID_URL := $(SF)/libuuid/libuuid-$(UUID_VERSION).tar.gz + +ifeq ($(call need_pkg,"uuid >= 2.0.0"),) +PKGS_FOUND += uuid +endif + +$(TARBALLS)/libuuid-$(UUID_VERSION).tar.gz: + $(call download,$(UUID_URL)) + +.sum-uuid: libuuid-$(UUID_VERSION).tar.gz + +uuid: libuuid-$(UUID_VERSION).tar.gz .sum-uuid + $(UNPACK) + $(APPLY) $(SRC)/uuid/android.patch + $(MOVE) + +.uuid: uuid + cd $< && $(HOSTVARS) ./configure $(HOSTCONF) + cd $< && $(MAKE) install + touch $@ diff --git a/contrib/src/vorbis/SHA512SUMS b/contrib/src/vorbis/SHA512SUMS new file mode 100644 index 0000000000..7f4234f423 --- /dev/null +++ b/contrib/src/vorbis/SHA512SUMS @@ -0,0 +1 @@ +f705c7740bec2dc6584ab8f103491a9d462136e3fa76454bde47e2ba04466b896ef066f7f925ad0a44d4c659c962717bc9099b3cecc20f12270d0ad53369ad53 libvorbis-1.3.4.tar.xz diff --git a/contrib/src/vorbis/osx.patch b/contrib/src/vorbis/osx.patch new file mode 100644 index 0000000000..8ae481292e --- /dev/null +++ b/contrib/src/vorbis/osx.patch @@ -0,0 +1,13 @@ +--- libvorbis/configure.ac.orig 2012-09-07 00:17:47.000000000 +0200 ++++ libvorbis/configure.ac 2012-09-07 00:18:01.000000000 +0200 +@@ -199,8 +199,8 @@ + PROFILE="-pg -g -O20 -D__NO_MATH_INLINES -fsigned-char $sparc_cpu" ;; + *-*-darwin*) + DEBUG="-DDARWIN -fno-common -force_cpusubtype_ALL -Wall -g -O0 -fsigned-char" +- CFLAGS="-DDARWIN -fno-common -force_cpusubtype_ALL -Wall -g -O4 -ffast-math -fsigned-char" +- PROFILE="-DDARWIN -fno-common -force_cpusubtype_ALL -Wall -g -pg -O4 -ffast-math -fsigned-char";; ++ CFLAGS="-DDARWIN -fno-common -force_cpusubtype_ALL -Wall -g -O3 -ffast-math -fsigned-char" ++ PROFILE="-DDARWIN -fno-common -force_cpusubtype_ALL -Wall -g -pg -O3 -ffast-math -fsigned-char";; + *-*-os2*) + # Use -W instead of -Wextra because gcc on OS/2 is an old version. + DEBUG="-g -Wall -W -D_REENTRANT -D__NO_MATH_INLINES -fsigned-char" diff --git a/contrib/src/vorbis/rules.mak b/contrib/src/vorbis/rules.mak new file mode 100644 index 0000000000..12b23a3345 --- /dev/null +++ b/contrib/src/vorbis/rules.mak @@ -0,0 +1,46 @@ +# libvorbis + +VORBIS_VERSION := 1.3.4 +VORBIS_URL := http://downloads.xiph.org/releases/vorbis/libvorbis-$(VORBIS_VERSION).tar.xz +#VORBIS_URL := $(CONTRIB_VIDEOLAN)/libvorbis-$(VORBIS_VERSION).tar.gz + +ifdef HAVE_FPU +PKGS += vorbis +endif +ifeq ($(call need_pkg,"vorbis >= 1.1"),) +PKGS_FOUND += vorbis +endif +PKGS_ALL += vorbisenc +ifdef BUILD_ENCODERS +PKGS += vorbisenc +endif +ifeq ($(call need_pkg,"vorbisenc >= 1.1"),) +PKGS_FOUND += vorbisenc +endif + +$(TARBALLS)/libvorbis-$(VORBIS_VERSION).tar.xz: + $(call download,$(VORBIS_URL)) + +.sum-vorbis: libvorbis-$(VORBIS_VERSION).tar.xz + +vorbis: libvorbis-$(VORBIS_VERSION).tar.xz .sum-vorbis + $(UNPACK) + $(APPLY) $(SRC)/vorbis/osx.patch + $(UPDATE_AUTOCONFIG) + $(MOVE) + +DEPS_vorbis = ogg $(DEPS_ogg) + +.vorbis: vorbis + $(RECONF) -Im4 + cd $< && $(HOSTVARS) ./configure $(HOSTCONF) --disable-docs --disable-examples --disable-oggtest + cd $< && $(MAKE) install + touch $@ + +.sum-vorbisenc: .sum-vorbis + touch $@ + +DEPS_vorbisenc = vorbis $(DEPS_vorbis) + +.vorbisenc: + touch $@ diff --git a/contrib/src/vpx/SHA512SUMS b/contrib/src/vpx/SHA512SUMS new file mode 100644 index 0000000000..84d2a309bc --- /dev/null +++ b/contrib/src/vpx/SHA512SUMS @@ -0,0 +1 @@ +af26766a3336155c5bc7b8cce7c23228de054287b990f9cacdc35273384a7af4999c01bb623d12143f40107036308a8b3207081efe67936748503c30c985fd6b libvpx-v1.3.0.tar.bz2 diff --git a/contrib/src/vpx/rules.mak b/contrib/src/vpx/rules.mak new file mode 100644 index 0000000000..716a430bc1 --- /dev/null +++ b/contrib/src/vpx/rules.mak @@ -0,0 +1,110 @@ +# libvpx + +VPX_HASH := 4640a0c4804b49f1870d5a2d17df0c7d0a77af2f +VPX_URL := http://libvpx.webm.googlecode.com/archive/$(VPX_HASH).tar.gz +#VPX_GITURL := https://code.google.com/p/webm.libvpx + +$(TARBALLS)/libvpx-$(VPX_HASH).tar.gz: + $(call download,$(VPX_URL)) + +.sum-vpx: libvpx-$(VPX_HASH).tar.gz + $(warning $@ not implemented) + touch $@ + +libvpx: libvpx-$(VPX_HASH).tar.gz .sum-vpx + rm -Rf $@-$(VPX_HASH) + mkdir -p $@-$(VPX_HASH) + (cd $@-$(VPX_HASH) && tar xv --strip-components=1 -f ../$<) + $(MOVE) + +DEPS_vpx = + +ifdef HAVE_CROSS_COMPILE +VPX_CROSS := $(HOST)- +else +VPX_CROSS := +endif + +ifeq ($(ARCH),arm) +VPX_ARCH := armv7 +else ifeq ($(ARCH),i386) +VPX_ARCH := x86 +else ifeq ($(ARCH),mips) +VPX_ARCH := mips32 +else ifeq ($(ARCH),ppc) +VPX_ARCH := ppc32 +else ifeq ($(ARCH),ppc64) +VPX_ARCH := ppc64 +else ifeq ($(ARCH),sparc) +VPX_ARCH := sparc +else ifeq ($(ARCH),x86_64) +VPX_ARCH := x86_64 +endif + +ifdef HAVE_ANDROID +VPX_OS := android +else ifdef HAVE_LINUX +VPX_OS := linux +else ifdef HAVE_DARWIN_OS +ifeq ($(ARCH),arm) +VPX_OS := darwin +else +ifeq ($(OSX_VERSION),10.5) +VPX_OS := darwin9 +else +VPX_OS := darwin10 +endif +endif +else ifdef HAVE_SOLARIS +VPX_OS := solaris +else ifdef HAVE_WIN64 # must be before WIN32 +VPX_OS := win64 +else ifdef HAVE_WIN32 +VPX_OS := win32 +else ifdef HAVE_BSD +VPX_OS := linux +endif + +VPX_TARGET := generic-gnu +ifdef VPX_ARCH +ifdef VPX_OS +VPX_TARGET := $(VPX_ARCH)-$(VPX_OS)-gcc +endif +endif + +VPX_CONF := \ + --as=yasm \ + --disable-docs \ + --disable-examples \ + --disable-unit-tests \ + --disable-install-bins \ + --disable-install-docs \ + --enable-realtime-only \ + --enable-error-concealment \ + --disable-runtime-cpu-detect \ + --disable-webm-io + +ifndef HAVE_WIN32 +VPX_CONF += --enable-pic +endif +ifdef HAVE_MACOSX +VPX_CONF += --sdk-path=$(MACOSX_SDK) +endif +ifdef HAVE_IOS +VPX_CONF += --sdk-path=$(SDKROOT) +endif +ifdef HAVE_ANDROID +# vpx configure.sh overrides our sysroot and it looks for it itself, and +# uses that path to look for the compiler (which we already know) +VPX_CONF += --sdk-path=$(shell dirname $(shell which $(HOST)-gcc)) +# needed for cpu-features.h +VPX_CONF += --extra-cflags="-I $(ANDROID_NDK)/sources/cpufeatures/" +endif + +.vpx: libvpx + cd $< && CROSS=$(VPX_CROSS) ./configure --target=$(VPX_TARGET) \ + $(VPX_CONF) --prefix=$(PREFIX) + cd $< && $(MAKE) + cd $< && ../../../contrib/src/pkg-static.sh vpx.pc + cd $< && $(MAKE) install + touch $@ diff --git a/contrib/src/x264/remove-align.patch b/contrib/src/x264/remove-align.patch new file mode 100644 index 0000000000..64bb928a43 --- /dev/null +++ b/contrib/src/x264/remove-align.patch @@ -0,0 +1,11 @@ +--- x264/configure 2013-03-01 16:21:59.000000000 -0500 ++++ x264patched/configure 2015-01-14 14:11:27.000000000 -0500 +@@ -456,7 +456,7 @@ + ;; + darwin*) + SYS="MACOSX" +- CFLAGS="$CFLAGS -falign-loops=16" ++ CFLAGS="$CFLAGS" + libm="-lm" + if [ "$pic" = "no" ]; then + cc_check "" -mdynamic-no-pic && CFLAGS="$CFLAGS -mdynamic-no-pic" diff --git a/contrib/src/x264/rules.mak b/contrib/src/x264/rules.mak new file mode 100644 index 0000000000..52494c2ebb --- /dev/null +++ b/contrib/src/x264/rules.mak @@ -0,0 +1,47 @@ +# x264 + +X264_HASH := fa3cac516cb71b8ece09cedbfd0ce631ca8a2a4c +X264_GITURL := git://git.videolan.org/x264.git + +ifeq ($(call need_pkg,"x264 >= 0.86"),) +PKGS_FOUND += x264 +endif + + +X264CONF = --prefix="$(PREFIX)" \ + --host="$(HOST)" \ + --enable-static \ + --disable-avs \ + --disable-lavf \ + --disable-cli \ + --disable-ffms \ + --disable-opencl + +ifndef HAVE_WIN32 +X264CONF += --enable-pic +else +X264CONF += --enable-win32thread +endif +ifdef HAVE_CROSS_COMPILE +X264CONF += --cross-prefix="$(HOST)-" +endif + +$(TARBALLS)/x264-$(X264_HASH).tar.xz: + $(call download_git,$(X264_GITURL),master,$(X264_HASH)) + +.sum-x264: x264-$(X264_HASH).tar.xz + $(warning $@ not implemented) + touch $@ + +x264: x264-$(X264_HASH).tar.xz .sum-x264 + rm -Rf $@-$(X264_HASH) + mkdir -p $@-$(X264_HASH) + (cd $@-$(X264_HASH) && tar xv --strip-components=1 -f ../$<) + $(APPLY) $(SRC)/x264/remove-align.patch + $(UPDATE_AUTOCONFIG) + $(MOVE) + +.x264: x264 + cd $< && $(HOSTVARS) ./configure $(X264CONF) + cd $< && $(MAKE) install + touch $@ diff --git a/contrib/src/yaml-cpp/SHA512SUMS b/contrib/src/yaml-cpp/SHA512SUMS new file mode 100644 index 0000000000..33bd589f3f --- /dev/null +++ b/contrib/src/yaml-cpp/SHA512SUMS @@ -0,0 +1 @@ +3c6928684d603815c016d663af36be94507f2cccf167d6d8d7cd7dea3ea5f73ec88d62952a2b5d11796e40132857afcbbacd9eafd688f2dc11d0c339caf2e013 yaml-cpp-0.5.1.tar.gz diff --git a/contrib/src/yaml-cpp/cmake.patch b/contrib/src/yaml-cpp/cmake.patch new file mode 100644 index 0000000000..ec077b2e4c --- /dev/null +++ b/contrib/src/yaml-cpp/cmake.patch @@ -0,0 +1,13 @@ +--- yaml-cpp/CMakeLists.txt.orig 2014-09-05 18:09:22.644410515 -0400 ++++ yaml-cpp/CMakeLists.txt 2014-09-05 18:12:20.372407142 -0400 +@@ -269,10 +269,10 @@ + FILES_MATCHING PATTERN "*.h" + ) + +-if(UNIX) ++if(UNIX OR MINGW) + set(PC_FILE ${CMAKE_BINARY_DIR}/yaml-cpp.pc) + configure_file("yaml-cpp.pc.cmake" ${PC_FILE} @ONLY) +- install(FILES ${PC_FILE} DESTINATION ${LIB_INSTALL_DIR}/pkgconfig) ++ install(FILES ${PC_FILE} DESTINATION ${CMAKE_INSTALL_PREFIX}/lib/pkgconfig) + endif() diff --git a/contrib/src/yaml-cpp/rules.mak b/contrib/src/yaml-cpp/rules.mak new file mode 100644 index 0000000000..5854a556a7 --- /dev/null +++ b/contrib/src/yaml-cpp/rules.mak @@ -0,0 +1,32 @@ +# YAML +YAML_CPP_VERSION := 0.5.1 +YAML_CPP_URL := http://yaml-cpp.googlecode.com/files/yaml-cpp-$(YAML_CPP_VERSION).tar.gz + +PKGS += yaml-cpp + +ifeq ($(call need_pkg,'yaml-cpp'),) +PKGS_FOUND += yaml-cpp +endif + +DEPS_yaml-cpp = boost-headers $(DEPS_boost-headers) + +YAML_CPP_CMAKECONF := -DBUILD_STATIC:BOOL=ON \ + -DBUILD_SHARED:BOOL=OFF \ + -DYAML_CPP_BUILD_TOOLS:BOOL=OFF \ + -DBoost_INCLUDE_DIR=../../$(HOST)/include \ + -DBUILD_SHARED_LIBS:BOOL=OFF + +$(TARBALLS)/yaml-cpp-$(YAML_CPP_VERSION).tar.gz: + $(call download,$(YAML_CPP_URL)) + +.sum-yaml-cpp: yaml-cpp-$(YAML_CPP_VERSION).tar.gz + +yaml-cpp: yaml-cpp-$(YAML_CPP_VERSION).tar.gz .sum-yaml-cpp + $(UNPACK) + $(APPLY) $(SRC)/yaml-cpp/cmake.patch + $(MOVE) + +.yaml-cpp: yaml-cpp toolchain.cmake + cd $< && $(HOSTVARS) $(CMAKE) . $(YAML_CPP_CMAKECONF) + cd $< && $(MAKE) install + touch $@ diff --git a/contrib/src/zlib/SHA512SUMS b/contrib/src/zlib/SHA512SUMS new file mode 100644 index 0000000000..27e9bd357c --- /dev/null +++ b/contrib/src/zlib/SHA512SUMS @@ -0,0 +1 @@ +ece209d4c7ec0cb58ede791444dc754e0d10811cbbdebe3df61c0fd9f9f9867c1c3ccd5f1827f847c005e24eef34fb5bf87b5d3f894d75da04f1797538290e4a zlib-1.2.8.tar.gz diff --git a/contrib/src/zlib/rules.mak b/contrib/src/zlib/rules.mak new file mode 100644 index 0000000000..b198aee5ed --- /dev/null +++ b/contrib/src/zlib/rules.mak @@ -0,0 +1,28 @@ +# ZLIB +ZLIB_VERSION := 1.2.8 +ZLIB_URL := $(SF)/libpng/zlib-$(ZLIB_VERSION).tar.gz + +PKGS += zlib +ifeq ($(call need_pkg,"zlib"),) +PKGS_FOUND += zlib +endif + +ifeq ($(shell uname),Darwin) # zlib tries to use libtool on Darwin +ifdef HAVE_CROSS_COMPILE +ZLIB_CONFIG_VARS=CHOST=$(HOST) +endif +endif + +$(TARBALLS)/zlib-$(ZLIB_VERSION).tar.gz: + $(call download,$(ZLIB_URL)) + +.sum-zlib: zlib-$(ZLIB_VERSION).tar.gz + +zlib: zlib-$(ZLIB_VERSION).tar.gz .sum-zlib + $(UNPACK) + $(MOVE) + +.zlib: zlib + cd $< && $(HOSTVARS) $(ZLIB_CONFIG_VARS) ./configure --prefix=$(PREFIX) --static + cd $< && $(MAKE) install + touch $@ diff --git a/contrib/tarballs/.gitignore b/contrib/tarballs/.gitignore new file mode 100644 index 0000000000..7e4045957f --- /dev/null +++ b/contrib/tarballs/.gitignore @@ -0,0 +1,5 @@ +*.tar.* +*.zip +*.h +*.tgz +*-git/ diff --git a/doc/Makefile.am b/doc/Makefile.am new file mode 100644 index 0000000000..48a288e1e0 --- /dev/null +++ b/doc/Makefile.am @@ -0,0 +1,5 @@ +SUBDIRS = doxygen + +.PHONY: doc +doc: + $(MAKE) -C doxygen doc diff --git a/doc/README b/doc/README new file mode 100644 index 0000000000..0cd3c183e8 --- /dev/null +++ b/doc/README @@ -0,0 +1,2 @@ +This directory holds all the documentation files, that are interfaced on the +ring.cx website, and in other documentation areas. diff --git a/doc/dbus-api/Makefile.am b/doc/dbus-api/Makefile.am new file mode 100644 index 0000000000..b7064e5dd9 --- /dev/null +++ b/doc/dbus-api/Makefile.am @@ -0,0 +1,28 @@ +include $(top_srcdir)/globals.mak + +XSLTPROC=xsltproc --xinclude --nonet +PYTHON=python + +XMLS= $(wildcard $(top_srcdir)/src/dbus/spec/*.xml) +TEMPLATES= $(wildcard doc/templates/*) + +GENERATED_FILES = \ + doc/spec.html \ + doc/spec/index.html + +doc/spec.html: $(XMLS) tools/doc-generator.xsl + @install -d tmp/doc + $(XSLTPROC) tools/doc-generator.xsl spec/all.xml > tmp/$@ + mv tmp/$@ $@ + +doc/spec/index.html: $(XMLS) tools/doc-generator.py tools/specparser.py $(TEMPLATES) + @install -d tmp/doc + $(PYTHON) tools/doc-generator.py spec/all.xml doc/spec sflphone-spec cx.ring.Ring + +doc: $(GENERATED_FILES) + +clean-local: + rm -rf $(GENERATED_FILES) + rm -rf doc/spec + rm -rf tmp + diff --git a/doc/dbus-api/README b/doc/dbus-api/README new file mode 100644 index 0000000000..3adb69af24 --- /dev/null +++ b/doc/dbus-api/README @@ -0,0 +1,7 @@ +SFLphone D-Bus API documentation. Generated with xsltproc. + +Wiki reference: https://projects.savoirfairelinux.com/wiki/sflphone/Dbus-API + +Run Makefile to generate the HTML API documentation, from the xml interfaces in *sflphone-common/src/dbus*. + +The documentation is generated in *sflphone-common/doc/dbus-api/doc/spec* diff --git a/doc/dbus-api/doc/templates/devhelp.devhelp2 b/doc/dbus-api/doc/templates/devhelp.devhelp2 new file mode 100644 index 0000000000..af327fa6d9 --- /dev/null +++ b/doc/dbus-api/doc/templates/devhelp.devhelp2 @@ -0,0 +1,18 @@ +<?xml version="1.0"?> +<book xmlns="http://www.devhelp.net/book" title="$spec.title" name="$name" link="index.html"> + <chapters> +#for $interface in $spec.interfaces + <sub name="$interface.name" link="$interface.get_url()"/> +#end for + <sub name="Generic Types" link="generic-types.html"/> + <sub name="Errors" link="errors.html"/> + <sub name="Full Index" link="fullindex.html"/> + </chapters> + <functions> +#for $obj in $spec.everything.values() + $spec.types.values() + $spec.errors.values() + <keyword type="$obj.devhelp_name" name="$obj.get_title()" link="$obj.get_url()" #slurp +#if $obj.deprecated: deprecated="true" #slurp +/> +#end for + </functions> +</book> diff --git a/doc/dbus-api/doc/templates/errors.html b/doc/dbus-api/doc/templates/errors.html new file mode 100644 index 0000000000..907d6601c2 --- /dev/null +++ b/doc/dbus-api/doc/templates/errors.html @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" ""> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> + <head> + <title>Errors</title> + <link rel="stylesheet" href="style.css" type="text/css"/> + </head> + <body> + <div class="header"> + <h1>Errors</h1> + <a href="index.html">Interface Index</a> + (<a href="interfaces.html">Compact</a>) + | <a href="#summary">Summary</a> + | <a href="#errors">Errors</a> + </div> + <div class="main"> + <div class="summary"> + <a name="summary"></a> + <h3>Errors</h3> + <table class="summary"> + #for $error in $spec.errors.values() + #if $error.deprecated + <tr class="deprecated"> + #else + <tr> + #end if + <td><a href="$error.get_url()">$error.short_name</a></td> + <td> + #if $error.deprecated: (deprecated) + </td> + </tr> + #end for + </table> + </div> + + <div class="outset errors error"> + <a name="errors"></a> + <h1>Errors</h1> + #for $error in $spec.errors.values() + <div class="inset error"> + <a name="$error.name"></a> + <span class="permalink">(<a href="$error.get_url()">Permalink</a>)</span> + <h2> + $error.short_name + </h2> + + <div class="indent"> + <code>$error.name</code> + </div> + + $error.get_added() + $error.get_deprecated() + $error.get_docstring() + </div> + #end for + </div> + </div> + + </body> +</html> diff --git a/doc/dbus-api/doc/templates/fullindex.html b/doc/dbus-api/doc/templates/fullindex.html new file mode 100644 index 0000000000..2c465e1dd7 --- /dev/null +++ b/doc/dbus-api/doc/templates/fullindex.html @@ -0,0 +1,60 @@ +#from itertools import groupby +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" ""> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> + <head> + <title>Full Index</title> + <link rel="stylesheet" href="style.css" type="text/css"/> + </head> + +#set $star = [] +#for $item in $spec.everything.values() + $spec.errors.values() + $spec.generic_types + #echo $star.append(($item.short_name, $item)) + #slurp +#end for +#echo $star.sort(key = lambda t: t[0].title()) +#slurp +## one use iterators... +#set $groups = [ (l, list(g)) for l, g in (groupby($star, key = lambda t: t[0][0].upper())) ] +#set $letters = set(map(lambda t: t[0], groups)) + + <body> + <div class="header"> + <h1>Full Index</h1> + <a href="index.html">Interface Index</a> + (<a href="interfaces.html">Compact</a>) + #for $a in map(chr, xrange(ord('A'), ord('Z')+1)) + #if $a in $letters + | <a href="#$a">$a</a> + #else + | $a + #end if + #end for + </div> + + <div class="main"> + <table class="summary"> + #for l, g in $groups + <tr><th colspan="3"><a name="$l"></a>$l</th></tr> + #for $n in $g + #if $n[1].deprecated + <tr class="deprecated"> + #else + <tr> + #end if + <td> + <a href="$n[1].get_url()" title="$n[1].get_title()">$n[0]</a> + #if $n[1].deprecated: (deprecated) + </td> + <td>$n[1].get_type_name()</td> + <td> + #if $n[1].parent.__class__.__name__ == 'Interface': $n[1].parent.name + </td> + </tr> + #end for + #end for + <table> + </div> + + </body> +</html> diff --git a/doc/dbus-api/doc/templates/generic-types.html b/doc/dbus-api/doc/templates/generic-types.html new file mode 100644 index 0000000000..0bb209e432 --- /dev/null +++ b/doc/dbus-api/doc/templates/generic-types.html @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" ""> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> + <head> + <title>Generic Types</title> + <link rel="stylesheet" href="style.css" type="text/css"/> + </head> + <body> + <div class="header"> + <h1>Generic Types</h1> + <a href="index.html">Interface Index</a> + (<a href="interfaces.html">Compact</a>) + | <a href="#summary">Summary</a> + | <a href="#types">Types</a> + </div> + <div class="main"> + <div class="summary"> + <a name="summary"></a> + <h3>Generic Types</h3> + <table class="summary"> + #for $type in $spec.generic_types + #if $type.deprecated + <tr class="deprecated"> + #else + <tr> + #end if + <td><a href="$type.get_url()">$type.short_name</a></td> + <td>$type.get_type_name()</td> + <td>$type.dbus_type</td> + <td> + #if $type.deprecated: (deprecated) + </td> + </tr> + #end for + </table> + </div> + + <div class="outset types type"> + <a name="types"></a> + <h1>Generic Types</h1> + #for $type in $spec.generic_types + <div class="inset type"> + <a name="$type.name"></a> + <span class="permalink">$type.get_type_name() (<a href="$type.get_url()">Permalink</a>)</span> + <h2> + $type.short_name — $type.dbus_type + </h2> + + $type.get_added() + $type.get_deprecated() + $type.get_docstring() + $type.get_breakdown() + </div> + #end for + </div> + </div> + + </body> +</html> diff --git a/doc/dbus-api/doc/templates/index.html b/doc/dbus-api/doc/templates/index.html new file mode 100644 index 0000000000..efc38d4e8e --- /dev/null +++ b/doc/dbus-api/doc/templates/index.html @@ -0,0 +1,66 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" ""> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> + <head> + <title>$spec.title &mdash v$spec.version</title> + <link rel="stylesheet" href="style.css" type="text/css"/> + </head> + <body> + <div class="header"> + <h1>$spec.title</h1> + <a href="#interfaces">Interfaces</a> + (<a href="interfaces.html">Compact</a>) + | <a href="generic-types.html">Generic Types</a> + | <a href="errors.html">Errors</a> + | <a href="fullindex.html">Full Index</a> + </div> + + <div class="main"> + <h3 class="version">Version $spec.version</h3> + <p class="copyrights"> + #echo '<br/>'.join($spec.copyrights) + </p> + $spec.license + + <a name="interfaces"></a> + <h3>Interfaces</h3> + <ul> + #def output($items) + #for $item in $items + #if $item.__class__.__name__ == 'Section' + <li class="chapter">$item.short_name</li> + $item.get_docstring() + <ul> + $output($item.items) + </ul> + #else + #if $item.causes_havoc + <li class="causes-havoc"> + #elif $item.deprecated + <li class="deprecated"> + #else + <li> + #end if + <a href="$item.get_url()">$item.name</a> + #if $item.causes_havoc + (unstable) + #elif $item.deprecated + (deprecated) + #end if + </li> + #end if + #end for + #end def + $output($spec.items) + </ul> + + <a name="other"></a> + <h3>Other</h3> + <ul> + <li><a href="generic-types.html">Generic Types</a></li> + <li><a href="errors.html">Errors</a></li> + </ul> + + </div> + </body> +</html> diff --git a/doc/dbus-api/doc/templates/interface.html b/doc/dbus-api/doc/templates/interface.html new file mode 100644 index 0000000000..c159bf511c --- /dev/null +++ b/doc/dbus-api/doc/templates/interface.html @@ -0,0 +1,424 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" ""> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> + <head> + <title>$interface.name</title> + <link rel="stylesheet" href="style.css" type="text/css"/> + </head> + <body> + <div class="header"> + <h1>Interface $interface.name</h1> + <a href="index.html">Interface Index</a> + (<a href="interfaces.html">Compact</a>) + | <a href="#summary">Summary</a> + #if $interface.docstring: | <a href="#description">Description</a> + #if $interface.methods: | <a href="#methods">Methods</a> + #if $interface.signals: | <a href="#signals">Signals</a> + #if $interface.properties: | <a href="#properties">Properties</a> + #if $interface.tpproperties: | <a href="#tpproperties">Telepathy Properties</a> + #if $interface.contact_attributes: | <a href="#contact-attributes">Contact Attributes</a> + #if $interface.handler_capability_tokens: | <a href="#handler-capability-tokens">Handler Capability Tokens</a> + #if $interface.types: | <a href="#types">Types</a> + </div> + <div class="main"> + + #if $interface.methods or $interface.signals or $interface.properties or $interface.types or $interface.tpproperties + <div class="summary"> + <a name="summary"></a> + #if $interface.methods + <h3>Methods</h3> + <table class="summary"> + #for $method in $interface.methods + #if $method.deprecated + <tr class="deprecated"> + #else + <tr> + #end if + <td><a href="$method.get_url()">$method.short_name</a></td> + <td>($method.get_in_args())</td> + <td>→</td> + <td>$method.get_out_args()</td> + <td> + #if $method.deprecated: (deprecated) + </td> + </tr> + #end for + </table> + #end if + + #if $interface.signals + <h3>Signals</h3> + <table class="summary"> + #for $signal in $interface.signals + #if $signal.deprecated + <tr class="deprecated"> + #else + <tr> + #end if + <td><a href="$signal.get_url()">$signal.short_name</a></td> + <td>($signal.get_args())</td> + <td> + #if $signal.deprecated: (deprecated) + </td> + </tr> + #end for + </table> + #end if + + #if $interface.properties + <h3>Properties</h3> + <table class="summary"> + #for $property in $interface.properties + #if $property.deprecated + <tr class="deprecated"> + #else + <tr> + #end if + <td><a href="$property.get_url()">$property.short_name</a></td> + <td> + $property.dbus_type + #if $property.type: (<a href="$property.get_type_url()" title="$property.get_type_title()">$property.get_type().short_name</a>) + </td> + <td>$property.get_access()</td> + <td> + #if $property.deprecated: (deprecated) + </td> + </tr> + #end for + </table> + #end if + + #if $interface.tpproperties + <h3>Telepathy Properties</h3> + <table class="summary"> + #for $property in $interface.tpproperties + <tr class="deprecated"> + <td><a href="$property.get_url()">$property.short_name</a></td> + <td> + $property.dbus_type + #if $property.type: (<a href="$property.get_type_url()" title="$property.get_type_title()">$property.get_type().short_name</a>) + </td> + </tr> + #end for + </table> + #end if + + #if $interface.contact_attributes + <h3>Contact Attributes</h3> + <table class="summary"> + #for $token in $interface.contact_attributes + <tr class="contact-attribute"> + <td><a href="$token.get_url()">$token.name</a></td> + <td> + $token.dbus_type + #if $token.type: (<a href="$token.get_type_url()" title="$token.get_type_title()">$token.get_type().short_name</a>) + </td> + </tr> + #end for + </table> + #end if + + #if $interface.handler_capability_tokens + <h3>Handler Capability Tokens</h3> + <table class="summary"> + #for $token in $interface.handler_capability_tokens + <tr class="handler-capability-token"> + <td><a href="$token.get_url()">$token.name</a> + #if $token.is_family + (etc.) + #end if + </td> + <td> + </td> + </tr> + #end for + </table> + #end if + + #if $interface.types + <h3>Types</h3> + <table class="summary"> + #for $type in $interface.types + #if type.deprecated + <tr class="deprecated"> + #else + <tr> + #end if + <td><a href="$type.get_url()">$type.short_name</a></td> + <td>$type.get_type_name()</td> + <td>$type.dbus_type</td> + <td> + #if $type.deprecated: (deprecated) + </td> + </tr> + #end for + </table> + #end if + </div> + #end if + + #if $interface.causes_havoc + <div class="havoc"><span class="warning">WARNING:</span> + This interface is $interface.causes_havoc and is likely to cause havoc + to your API/ABI if bindings are generated. Do not include this interface + in libraries that care about compatibility. + </div> + #end if + $interface.get_added() + $interface.get_changed() + $interface.get_deprecated() + + #if $interface.requires + <div class="requires"> + Objects implementing this interface must also implement: + <ul> + #for $req in $interface.get_requires() + <li><a href="$req.get_url()" title="$req.get_title()">$req.name</a></li> + #end for + </ul> + </div> + #end if + + #if $interface.docstring + <a name="description"></a> + <h3>Description</h3> + $interface.get_docstring() + #end if + + #if $interface.methods + <div class="outset methods method"> + <a name="methods"></a> + <h1>Methods</h1> + #for $method in $interface.methods + <div class="inset method"> + <a name="$method.name"></a> + <span class="permalink">(<a href="$method.get_url()">Permalink</a>)</span> + <h2>$method.short_name ($method.get_in_args()) → $method.get_out_args()</h2> + + $method.get_added() + $method.get_changed() + $method.get_deprecated() + + #if $method.in_args + <div class="indent"> + <h3>Parameters</h3> + <ul> + #for $arg in $method.in_args + <li> + $arg.short_name — $arg.dbus_type + #if $arg.get_type(): (<a href="$arg.get_type_url()" title="$arg.get_type_title()">$arg.get_type().short_name</a>) + </li> + $arg.get_added() + $arg.get_changed() + $arg.get_deprecated() + $arg.get_docstring() + #end for + </ul> + </div> + #end if + + #if $method.out_args + <div class="indent"> + <h3>Returns</h3> + <ul> + #for $arg in $method.out_args + <li> + $arg.short_name — $arg.dbus_type + #if $arg.get_type(): (<a href="$arg.get_type_url()" title="$arg.get_type_title()">$arg.get_type().short_name</a>) + </li> + $arg.get_added() + $arg.get_changed() + $arg.get_deprecated() + $arg.get_docstring() + #end for + </ul> + </div> + #end if + + $method.get_docstring() + + #if $method.possible_errors + <hr/> + <div class="indent"> + <h3>Possible Errors</h3> + <ul> + #for $error in $method.possible_errors + <li><a href="$error.get_url()" title="$error.get_title()">$error.get_error().short_name</a></li> + $error.get_added() + $error.get_changed() + $error.get_deprecated() + $error.get_docstring() + #end for + </ul> + </div> + #end if + </div> + #end for + </div> + #end if + + #if $interface.signals + <div class="outset signals signal"> + <a name="signals"></a> + <h1>Signals</h1> + #for $signal in $interface.signals + <div class="inset signal"> + <a name="$signal.name"></a> + <span class="permalink">(<a href="$signal.get_url()">Permalink</a>)</span> + <h2>$signal.short_name ($signal.get_args())</h2> + + $signal.get_added() + $signal.get_changed() + $signal.get_deprecated() + + #if $signal.args + <div class="indent"> + <h3>Parameters</h3> + <ul> + #for $arg in $signal.args + <li> + $arg.short_name — $arg.dbus_type + #if $arg.get_type(): (<a href="$arg.get_type_url()" title="$arg.get_type_title()">$arg.get_type().short_name</a>) + </li> + $arg.get_added() + $arg.get_changed() + $arg.get_deprecated() + $arg.get_docstring() + #end for + </ul> + </div> + #end if + + $signal.get_docstring() + </div> + #end for + </div> + #end if + + #if $interface.properties + <div class="outset properties property"> + <a name="properties"></a> + <h1>Properties</h1> + <div> + Accessed using the org.freedesktop.DBus.Properties interface. + </div> + #for $property in $interface.properties + <div class="inset property"> + <a name="$property.name"></a> + <span class="permalink">(<a href="$property.get_url()">Permalink</a>)</span> + <h2> + $property.short_name — $property.dbus_type + #if $property.type: (<a href="$property.get_type_url()" title="$property.get_type_title()">$property.get_type().short_name</a>) + </h2> + <div class="access">$property.get_access()</div> + + $property.get_added() + $property.get_changed() + $property.get_deprecated() + $property.get_docstring() + </div> + #end for + </div> + #end if + + #if $interface.tpproperties + <div class="outset tpproperties tpproperty"> + <a name="tpproperties"></a> + <h1>Telepathy Properties</h1> + <div> + Accessed using the org.freedesktop.Telepathy.Properties interface. + </div> + #for $property in $interface.tpproperties + <div class="inset tpproperty"> + <a name="$property.name"></a> + <span class="permalink">(<a href="$property.get_url()">Permalink</a>)</span> + <h2> + $property.short_name — $property.dbus_type + #if $property.type: (<a href="$property.get_type_url()" title="$property.get_type_title()">$property.get_type().short_name</a>) + </h2> + $property.get_added() + $property.get_changed() + $property.get_deprecated() + $property.get_docstring() + </div> + #end for + </div> + #end if + + #if $interface.contact_attributes + <div class="outset contact-attributes"> + <a name="contact-attributes"></a> + <h1>Contact Attributes</h1> + <div> + Attributes that a contact can have, accessed with the + org.freedesktop.Telepathy.Connection.Interface.Contacts interface. + </div> + #for $token in $interface.contact_attributes + <div class="inset contact-attribute"> + <a name="$token.name"></a> + <span class="permalink">(<a href="$token.get_url()">Permalink</a>)</span> + <h2> + $token.name — $token.dbus_type + #if $token.type: (<a href="$token.get_type_url()" title="$token.get_type_title()">$token.get_type().short_name</a>) + </h2> + $token.get_added() + $token.get_changed() + $token.get_deprecated() + $token.get_docstring() + </div> + #end for + </div> + #end if + + #if $interface.handler_capability_tokens + <div class="outset handler-capability-tokens"> + <a name="handler-capability-tokens"></a> + <h1>Handler Capability Tokens</h1> + <div> + Tokens representing capabilities that a Client.Handler can have. + </div> + #for $token in $interface.handler_capability_tokens + <div class="inset handler-capability-token"> + <a name="$token.name"></a> + <span class="permalink">(<a href="$token.get_url()">Permalink</a>)</span> + <h2> + $token.name + #if $token.is_family + (etc.) + #end if + </h2> + $token.get_added() + $token.get_changed() + $token.get_deprecated() + $token.get_docstring() + </div> + #end for + </div> + #end if + + #if $interface.types + <div class="outset types type"> + <a name="types"></a> + <h1>Types</h1> + #for $type in $interface.types + <div class="inset type"> + <a name="$type.name"></a> + <span class="permalink">$type.get_type_name() (<a href="$type.get_url()">Permalink</a>)</span> + <h2> + $type.short_name — $type.dbus_type + </h2> + + $type.get_added() + $type.get_changed() + $type.get_deprecated() + $type.get_docstring() + $type.get_breakdown() + </div> + #end for + </div> + #end if + + </div> + + </body> +</html> diff --git a/doc/dbus-api/doc/templates/interfaces.html b/doc/dbus-api/doc/templates/interfaces.html new file mode 100644 index 0000000000..a93334c65d --- /dev/null +++ b/doc/dbus-api/doc/templates/interfaces.html @@ -0,0 +1,50 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE html PUBLIC "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd" ""> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> + <head> + <title>$spec.title &mdash v$spec.version</title> + <link rel="stylesheet" href="style.css" type="text/css"/> + </head> + <body> + <div class="header"> + <h1>$spec.title</h1> + <a href="index.html">Full</a> + | <a href="generic-types.html">Generic Types</a> + | <a href="errors.html">Errors</a> + | <a href="fullindex.html">Full Index</a> + </div> + + <div class="main"> + <b>Version $spec.version</b> + + <a name="interfaces"></a> + <h3>Interfaces</h3> + <ul> + #for $interface in $spec.interfaces + #if $interface.causes_havoc + <li class="causes-havoc"> + #elif $interface.deprecated + <li class="deprecated"> + #else + <li> + #end if + <a href="$interface.get_url()">$interface.name</a> + #if $interface.causes_havoc + (unstable) + #elif $interface.deprecated + (deprecated) + #end if + </li> + #end for + </ul> + + <a name="other"></a> + <h3>Other</h3> + <ul> + <li><a href="generic-types.html">Generic Types</a></li> + <li><a href="errors.html">Errors</a></li> + </ul> + + </div> + </body> +</html> diff --git a/doc/dbus-api/doc/templates/style.css b/doc/dbus-api/doc/templates/style.css new file mode 100644 index 0000000000..979ced8cae --- /dev/null +++ b/doc/dbus-api/doc/templates/style.css @@ -0,0 +1,237 @@ +html, body, +h1, h2 { + font-family: "Georgia"; + margin: 0; + padding: 0; +} + +h3 { + margin-top: 2pt; + margin-bottom: 2pt; +} + +ul { + margin: 1ex; + margin-left: 1.5em; + padding: 0; +} + +hr { + border-style: none; + color: #cccccc; + background-color: #cccccc; + height: 1px; +} + +div.header { + position: fixed; + height: 4em; + background-color: AliceBlue; + width: 100%; + margin: 0; + padding: 0.5ex; + border-bottom: 1px solid black; + top: 0; + left: 0; + z-index: 1; +} + +div.header h1 { + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +div.main { + margin-top: 5em; + margin-left: 1ex; + margin-right: 1ex; + margin-bottom: 1ex; +} + +div.main a[name] { + position: relative; + top: -4.5em; +} + +div.outset { + padding: 1ex; + margin-top: 1ex; + margin-bottom: 1ex; +} + +div.inset { + background-color: white; + margin-top: 1ex; + margin-bottom: 1ex; + padding: 0.5ex; +} + +div.indent { + margin-left: 1em; +} + +div.methods { + background-color: #fcaf3e; +} + +div.method { + border: 1px solid #f57900; +} + +div.signals { + background-color: #729fcf; +} + +div.signal { + border: 1px solid #3465a4; +} + +div.properties { + background-color: #ad7fa8; +} + +div.property { + border: 1px solid #75507b; +} + +div.tpproperties { + background-color: #999999; +} + +div.tpproperty { + border: 1px solid #333333; +} + +div.contact-attributes { + background-color: #ccccff; + border: 1px solid #9999cc; +} + +div.contact-attribute { + border: 1px solid #9999cc; +} + +div.handler-capability-tokens { + background-color: #339933; + border: 1px solid #228822; +} + +div.handler-capability-token { + border: 1px solid #228822; +} + +div.types { + background-color: #e9b96e; +} + +div.type { + border: 1px solid #c17d11; +} + +div.errors { + background-color: #ef2929; +} + +div.error { + border: 1px solid #cc0000; +} + +div.access { + font-weight: bold; + margin-left: 1ex; +} + +div.summary { + padding: 0.5ex; + background-color: #eeeeec; + border: 1px solid #d3d7cf; +} + +table.summary { + margin: 1ex; + font-size: small; +} + +table.summary td { + padding-right: 1ex; +} + +li.chapter { + margin-top: 1ex; + font-weight: bold; +} + +li.causes-havoc { + font-style: italic; +} + +li.deprecated, +li.deprecated a, +table.summary tr.deprecated td, +table.summary tr.deprecated td a { + color: gray; +} + +div.requires, +div.docstring { + margin: 1ex; +} + +div.added { + border-left: 2px solid #4e9a06; + margin: 1ex; + padding-left: 1ex; +} + +div.added span.version { + color: #4e9a06; + font-weight: bold; +} + +div.changed { + border-left: 2px solid #8f5902; + margin: 1ex; + padding-left: 1ex; +} + +div.changed span.version { + color: #8f5902; + font-weight: bold; +} + +div.deprecated, +div.havoc { + border-left: 2px solid #a40000; + margin: 1ex; + padding-left: 1ex; +} + +div.deprecated span.version, +span.warning { + color: #a40000; + font-weight: bold; +} + +div.rationale { + border-left: 2px solid gray; + margin: 1ex; + padding-left: 1ex; +} + +span.permalink { + float: right; + font-size: x-small; +} + +.license { + clear: both; + border: 1px solid #dedede; + font-style: italic; + padding: 15px; +} + +.copyrights { + clear: both; + float: right; +} diff --git a/doc/dbus-api/spec/all.xml b/doc/dbus-api/spec/all.xml new file mode 100644 index 0000000000..b87a0e1e41 --- /dev/null +++ b/doc/dbus-api/spec/all.xml @@ -0,0 +1,55 @@ +<tp:spec + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:xi="http://www.w3.org/2001/XInclude"> + +<tp:title>SFLphone D-Bus Interface Specification</tp:title> +<tp:version>0.9.8</tp:version> + +<tp:copyright>Copyright © 2005-2010 Savoir-faire Linux Inc</tp:copyright> + +<tp:license xmlns="http://www.w3.org/1999/xhtml"> +<p>This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version.</p> + +<p>This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details.</p> + +<p>You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p> +</tp:license> + +<tp:section name="Instances Manager"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p> + An Instance Manager to handle multiple clients connections. Count the number of clients actually registered to the core. When initializing your client, you need to register it against the core by using this interface. + </p> + </tp:docstring> + <xi:include href="instance-introspec.xml"/> +</tp:section> + +<tp:section name="Call Manager"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p> + A Call Manager to handle call-related features. + </p> + </tp:docstring> + <xi:include href="callmanager-introspec.xml"/> +</tp:section> + +<tp:section name="Configuration Manager"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p> + A Configuration Manager to handle account configuration, user preferences, ... + </p> + </tp:docstring> + <xi:include href="configurationmanager-introspec.xml"/> +</tp:section> + +<xi:include href="generic-types.xml"/> + +</tp:spec> diff --git a/doc/dbus-api/spec/callmanager-introspec.xml b/doc/dbus-api/spec/callmanager-introspec.xml new file mode 120000 index 0000000000..53db533cb4 --- /dev/null +++ b/doc/dbus-api/spec/callmanager-introspec.xml @@ -0,0 +1 @@ +../../../src/client/dbus/callmanager-introspec.xml \ No newline at end of file diff --git a/doc/dbus-api/spec/configurationmanager-introspec.xml b/doc/dbus-api/spec/configurationmanager-introspec.xml new file mode 120000 index 0000000000..17ad4338ba --- /dev/null +++ b/doc/dbus-api/spec/configurationmanager-introspec.xml @@ -0,0 +1 @@ +../../../src/client/dbus/configurationmanager-introspec.xml \ No newline at end of file diff --git a/doc/dbus-api/spec/errors.xml b/doc/dbus-api/spec/errors.xml new file mode 100644 index 0000000000..22a629baff --- /dev/null +++ b/doc/dbus-api/spec/errors.xml @@ -0,0 +1,417 @@ +<?xml version="1.0" ?> +<tp:errors xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" namespace="org.freedesktop.Telepathy.Error"> + <tp:error name="Network Error"> + <tp:docstring> + Raised when there is an error reading from or writing to the network. + </tp:docstring> + </tp:error> + + <tp:error name="Not Implemented"> + <tp:docstring> + Raised when the requested method, channel, etc is not available on this connection. + </tp:docstring> + </tp:error> + + <tp:error name="Invalid Argument"> + <tp:docstring> + Raised when one of the provided arguments is invalid. + </tp:docstring> + </tp:error> + + <tp:error name="Not Available"> + <tp:docstring> + Raised when the requested functionality is temporarily unavailable. + </tp:docstring> + </tp:error> + + <tp:error name="Permission Denied"> + <tp:docstring> + The user is not permitted to perform the requested operation. + </tp:docstring> + </tp:error> + + <tp:error name="Disconnected"> + <tp:docstring> + The connection is not currently connected and cannot be used. + This error may also be raised when operations are performed on a + Connection for which + <tp:dbus-ref namespace="org.freedesktop.Telepathy.Connection">StatusChanged</tp:dbus-ref> + has signalled status Disconnected for reason None. + + <tp:rationale> + The second usage corresponds to None in the + <tp:type>Connection_Status_Reason</tp:type> enum; if a better reason + is available, the corresponding error should be used instead. + </tp:rationale> + </tp:docstring> + </tp:error> + + <tp:error name="Invalid Handle"> + <tp:docstring> + The handle specified is unknown on this channel or connection. + </tp:docstring> + </tp:error> + + <tp:error name="Channel.Banned"> + <tp:docstring> + You are banned from the channel. + </tp:docstring> + </tp:error> + + <tp:error name="Channel.Full"> + <tp:docstring> + The channel is full. + </tp:docstring> + </tp:error> + + <tp:error name="Channel.Invite Only"> + <tp:docstring> + The requested channel is invite-only. + </tp:docstring> + </tp:error> + + <tp:error name="Not Yours"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + <p>The requested channel or other resource already exists, and another + user interface in this session is responsible for it.</p> + + <p>User interfaces SHOULD handle this error unobtrusively, since it + indicates that some other user interface is already processing the + channel.</p> + </tp:docstring> + </tp:error> + + <tp:error name="Cancelled"> + <tp:docstring> + Raised by an ongoing request if it is cancelled by user request before + it has completed, or when operations are performed on an object which + the user has asked to close (for instance, a Connection where the user + has called Disconnect, or a Channel where the user has called Close). + + <tp:rationale> + The second form can be used to correspond to the Requested member in + the <tp:type>Connection_Status_Reason</tp:type> enum, or to + to represent the situation where disconnecting a Connection, + closing a Channel, etc. has been requested by the user but this + request has not yet been acted on, for instance because the + service will only act on the request when it has finished processing + an event queue. + </tp:rationale> + </tp:docstring> + </tp:error> + + <tp:error name="Authentication Failed"> + <tp:docstring> + Raised when authentication with a service was unsuccessful. + <tp:rationale> + This corresponds to Authentication_Failed in the + <tp:type>Connection_Status_Reason</tp:type> enum. + </tp:rationale> + </tp:docstring> + </tp:error> + + <tp:error name="Encryption Not Available"> + <tp:docstring> + Raised if a user request insisted that encryption should be used, + but encryption was not actually available. + + <tp:rationale> + This corresponds to part of Encryption_Error in the + <tp:type>Connection_Status_Reason</tp:type> enum. It's been separated + into a distinct error here because the two concepts that were part + of EncryptionError seem to be things that could reasonably appear + differently in the UI. + </tp:rationale> + </tp:docstring> + </tp:error> + + <tp:error name="Encryption Error"> + <tp:docstring> + Raised if encryption appears to be available, but could not actually be + used (for instance if SSL/TLS negotiation fails). + <tp:rationale> + This corresponds to part of Encryption_Error in the + <tp:type>Connection_Status_Reason</tp:type> enum. + </tp:rationale> + </tp:docstring> + </tp:error> + + <tp:error name="Cert.Not Provided"> + <tp:docstring> + Raised if the server did not provide a SSL/TLS certificate. This error + MUST NOT be used to represent the absence of a client certificate + provided by the Telepathy connection manager. + <tp:rationale> + This corresponds to Cert_Not_Provided in the + <tp:type>Connection_Status_Reason</tp:type> enum. That error + explicitly applied only to server SSL certificates, so this one + is similarly limited; having the CM present a client certificate + is a possible future feature, but it should have its own error + handling. + </tp:rationale> + </tp:docstring> + </tp:error> + + <tp:error name="Cert.Untrusted"> + <tp:docstring> + Raised if the server provided a SSL/TLS certificate signed by an + untrusted certifying authority. This error SHOULD NOT be used to + represent a self-signed certificate: see the Self Signed error for that. + <tp:rationale> + This corresponds to Cert_Untrusted in the + <tp:type>Connection_Status_Reason</tp:type> enum, with a clarification + to avoid ambiguity. + </tp:rationale> + </tp:docstring> + </tp:error> + + <tp:error name="Cert.Expired"> + <tp:docstring> + Raised if the server provided an expired SSL/TLS certificate. + <tp:rationale> + This corresponds to Cert_Expired in the + <tp:type>Connection_Status_Reason</tp:type> enum. + </tp:rationale> + </tp:docstring> + </tp:error> + + <tp:error name="Cert.Not Activated"> + <tp:docstring> + Raised if the server provided an SSL/TLS certificate that will become + valid at some point in the future. + <tp:rationale> + This corresponds to Cert_Not_Activated in the + <tp:type>Connection_Status_Reason</tp:type> enum. + </tp:rationale> + </tp:docstring> + </tp:error> + + <tp:error name="Cert.Fingerprint Mismatch"> + <tp:docstring> + Raised if the server provided an SSL/TLS certificate that did not have + the expected fingerprint. + <tp:rationale> + This corresponds to Cert_Fingerprint_Mismatch in the + <tp:type>Connection_Status_Reason</tp:type> enum. + </tp:rationale> + </tp:docstring> + </tp:error> + + <tp:error name="Cert.Hostname Mismatch"> + <tp:docstring> + Raised if the server provided an SSL/TLS certificate that did not match + its hostname. + <tp:rationale> + This corresponds to Cert_Hostname_Mismatch in the + <tp:type>Connection_Status_Reason</tp:type> enum. + </tp:rationale> + </tp:docstring> + </tp:error> + + <tp:error name="Cert.Self Signed"> + <tp:docstring> + Raised if the server provided an SSL/TLS certificate that is self-signed + and untrusted. + <tp:rationale> + This corresponds to Cert_Hostname_Mismatch in the + <tp:type>Connection_Status_Reason</tp:type> enum. + </tp:rationale> + </tp:docstring> + </tp:error> + + <tp:error name="Cert.Invalid"> + <tp:docstring> + Raised if the server provided an SSL/TLS certificate that is + unacceptable in some way that does not have a more specific error. + <tp:rationale> + This corresponds to Cert_Other_Error in the + <tp:type>Connection_Status_Reason</tp:type> enum. + </tp:rationale> + </tp:docstring> + </tp:error> + + <tp:error name="Not Capable"> + <tp:docstring> + Raised when requested functionality is unavailable due to contact + not having required capabilities. + </tp:docstring> + </tp:error> + + <tp:error name="Offline"> + <tp:docstring> + Raised when requested functionality is unavailable because a contact is + offline. + + <tp:rationale> + This corresponds to Offline in the + <tp:type>Channel_Group_Change_Reason</tp:type> enum. + </tp:rationale> + </tp:docstring> + </tp:error> + + <tp:error name="Channel.Kicked"> + <tp:docstring> + Used to represent a user being ejected from a channel by another user, + for instance being kicked from a chatroom. + + <tp:rationale> + This corresponds to Kicked in the + <tp:type>Channel_Group_Change_Reason</tp:type> enum. + </tp:rationale> + </tp:docstring> + </tp:error> + + <tp:error name="Busy"> + <tp:docstring> + Used to represent a user being removed from a channel because of a + "busy" indication. This error SHOULD NOT be used to represent a server + or other infrastructure being too busy to process a request - for that, + see ServerBusy. + + <tp:rationale> + This corresponds to Busy in the + <tp:type>Channel_Group_Change_Reason</tp:type> enum. + </tp:rationale> + </tp:docstring> + </tp:error> + + <tp:error name="No Answer"> + <tp:docstring> + Used to represent a user being removed from a channel because they did + not respond, e.g. to a StreamedMedia call. + + <tp:rationale> + This corresponds to No_Answer in the + <tp:type>Channel_Group_Change_Reason</tp:type> enum. + </tp:rationale> + </tp:docstring> + </tp:error> + + <tp:error name="Does Not Exist"> + <tp:docstring> + Raised when the requested user does not, in fact, exist. + + <tp:rationale> + This corresponds to Invalid_Contact in the + <tp:type>Channel_Group_Change_Reason</tp:type> enum, but can also be + used to represent other things not existing (like chatrooms, perhaps). + </tp:rationale> + </tp:docstring> + </tp:error> + + <tp:error name="Terminated"> + <tp:docstring> + Raised when a channel is terminated for an unspecified reason. In + particular, this error SHOULD be used whenever normal termination of + a 1-1 StreamedMedia call by the remote user is represented as a D-Bus + error name. + + <tp:rationale> + This corresponds to None in the + <tp:type>Channel_Group_Change_Reason</tp:type> enum. + </tp:rationale> + </tp:docstring> + </tp:error> + + <tp:error name="Connection Refused"> + <tp:docstring> + Raised when a connection is refused. + </tp:docstring> + </tp:error> + + <tp:error name="Connection Failed"> + <tp:docstring> + Raised when a connection can't be established. + </tp:docstring> + </tp:error> + + <tp:error name="Connection Lost"> + <tp:docstring> + Raised when a connection is broken. + </tp:docstring> + </tp:error> + + <tp:error name="Already Connected"> + <tp:docstring> + Raised when the user attempts to connect to an account but they are + already connected (perhaps from another client or computer), and the + protocol or account settings do not allow this. + + <tp:rationale> + XMPP can have this behaviour if the user chooses the same resource + in both clients (it is server-dependent whether the result is + AlreadyConnected on the new connection, ConnectionReplaced on the + old connection, or two successful connections). + </tp:rationale> + </tp:docstring> + </tp:error> + + <tp:error name="Connection Replaced"> + <tp:docstring> + Raised by an existing connection to an account if it is replaced by + a new connection (perhaps from another client or computer). + + <tp:rationale> + In MSNP, when connecting twice with the same Passport, the new + connection "wins" and the old one is automatically disconnected. + XMPP can also have this behaviour if the user chooses the same + resource in two clients (it is server-dependent whether the result is + AlreadyConnected on the new connection, ConnectionReplaced on the + old connection, or two successful connections). + </tp:rationale> + </tp:docstring> + </tp:error> + + <tp:error name="Registration Exists"> + <tp:docstring> + Raised during in-band registration if the server indicates that the + requested account already exists. + </tp:docstring> + </tp:error> + + <tp:error name="Service Busy"> + <tp:docstring xmlns="http://www.w3.org/1999/xhtml"> + Raised if a server or some other piece of infrastructure cannot process + the request, e.g. due to resource limitations. Clients MAY try again + later. + + <tp:rationale> + This is not the same error as Busy, which indicates that a + <em>user</em> is busy. + </tp:rationale> + </tp:docstring> + </tp:error> + + <tp:error name="Resource Unavailable"> + <tp:docstring> + Raised if a request cannot be satisfied because a process local to the + user has insufficient resources. Clients MAY try again + later. + + <tp:rationale> + For instance, the <tp:dbus-ref + namespace="org.freedesktop.Telepathy">ChannelDispatcher</tp:dbus-ref> + might raise this error for some or all channel requests if it has + detected that there is not enough free memory. + </tp:rationale> + </tp:docstring> + </tp:error> + + <tp:copyright>Copyright © 2005-2009 Collabora Limited</tp:copyright> + <tp:copyright>Copyright © 2005-2009 Nokia Corporation</tp:copyright> + <tp:license xmlns="http://www.w3.org/1999/xhtml"> +<p>This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version.</p> + +<p>This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details.</p> + +<p>You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.</p> + </tp:license> +</tp:errors> diff --git a/doc/dbus-api/spec/generic-types.xml b/doc/dbus-api/spec/generic-types.xml new file mode 100644 index 0000000000..d4dce1552d --- /dev/null +++ b/doc/dbus-api/spec/generic-types.xml @@ -0,0 +1,168 @@ +<tp:generic-types + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + + <tp:simple-type name="Unix_Timestamp" type="u"> + <tp:docstring>An unsigned 32-bit integer representing time as the number + of seconds elapsed since the Unix epoch + (1970-01-01T00:00:00Z)</tp:docstring> + </tp:simple-type> + + <tp:simple-type name="Unix_Timestamp64" type="x"> + <tp:docstring>An signed 64-bit integer representing time as the number + of seconds elapsed since the Unix epoch + (1970-01-01T00:00:00Z); negative for times before the epoch</tp:docstring> + + <tp:rationale>The Text interface is the only user of Unix_Timestamp so + far, and we'd like to be Y2038 compatible in future + interfaces.</tp:rationale> + </tp:simple-type> + + <tp:simple-type name="DBus_Bus_Name" type="s" + array-name="DBus_Bus_Name_List"> + <tp:docstring>A string representing a D-Bus bus name - either a well-known + name like "org.freedesktop.Telepathy.MissionControl" or a unique name + like ":1.123"</tp:docstring> + </tp:simple-type> + + <tp:simple-type name="DBus_Well_Known_Name" type="s" + array-name="DBus_Well_Known_Name_List"> + <tp:docstring>A string representing a D-Bus well-known + name like "org.freedesktop.Telepathy.MissionControl".</tp:docstring> + </tp:simple-type> + + <tp:simple-type name="DBus_Unique_Name" type="s" + array-name="DBus_Unique_Name_List"> + <tp:docstring>A string representing a D-Bus unique name, such as + ":1.123"</tp:docstring> + </tp:simple-type> + + <tp:simple-type name="DBus_Interface" type="s" + array-name="DBus_Interface_List"> + <tp:docstring>An ASCII string representing a D-Bus interface - two or more + elements separated by dots, where each element is a non-empty + string of ASCII letters, digits and underscores, not starting with + a digit. The maximum total length is 255 characters. For example, + "org.freedesktop.DBus.Peer".</tp:docstring> + </tp:simple-type> + + <tp:simple-type name="DBus_Error_Name" type="s"> + <tp:docstring>An ASCII string representing a D-Bus error. This is + syntactically the same as a <tp:type>DBus_Interface</tp:type>, but the + meaning is different.</tp:docstring> + </tp:simple-type> + + <tp:simple-type name="DBus_Signature" type="s"> + <tp:docstring>A string representing a D-Bus signature + (the 'g' type isn't used because of poor interoperability, particularly + with dbus-glib)</tp:docstring> + </tp:simple-type> + + <tp:simple-type name="DBus_Member" type="s"> + <tp:docstring>An ASCII string representing a D-Bus method, signal + or property name - a non-empty string of ASCII letters, digits and + underscores, not starting with a digit, with a maximum length of 255 + characters. For example, "Ping".</tp:docstring> + </tp:simple-type> + + <tp:simple-type name="DBus_Qualified_Member" type="s" + array-name="DBus_Qualified_Member_List"> + <tp:docstring>A string representing the full name of a D-Bus method, + signal or property, consisting of a DBus_Interface, followed by + a dot, followed by a DBus_Member. For example, + "org.freedesktop.DBus.Peer.Ping".</tp:docstring> + </tp:simple-type> + + <tp:mapping name="Qualified_Property_Value_Map" + array-name="Qualified_Property_Value_Map_List"> + <tp:docstring>A mapping from strings representing D-Bus + properties (by their namespaced names) to their values.</tp:docstring> + <tp:member type="s" name="Key" tp:type="DBus_Qualified_Member"> + <tp:docstring> + A D-Bus interface name, followed by a dot and a D-Bus property name. + </tp:docstring> + </tp:member> + <tp:member type="v" name="Value"> + <tp:docstring> + The value of the property. + </tp:docstring> + </tp:member> + </tp:mapping> + + <tp:mapping name="String_Variant_Map" array-name="String_Variant_Map_List"> + <tp:docstring>A mapping from strings to variants representing extra + key-value pairs.</tp:docstring> + <tp:member type="s" name="Key"/> + <tp:member type="v" name="Value"/> + </tp:mapping> + + <tp:mapping name="String_String_Map" array-name="String_String_Map_List"> + <tp:docstring>A mapping from strings to strings representing extra + key-value pairs.</tp:docstring> + <tp:member type="s" name="Key"/> + <tp:member type="s" name="Value"/> + </tp:mapping> + + <tp:struct name="Socket_Address_IP" array-name="Socket_Address_IP_List"> + <tp:docstring>An IP address and port.</tp:docstring> + <tp:member type="s" name="Address"> + <tp:docstring>Either a dotted-quad IPv4 address literal as for + <tp:type>Socket_Address_IPv4</tp:type>, or an RFC2373 IPv6 address + as for <tp:type>Socket_Address_IPv6</tp:type>. + </tp:docstring> + </tp:member> + <tp:member type="q" name="Port"> + <tp:docstring>The TCP or UDP port number.</tp:docstring> + </tp:member> + </tp:struct> + + <tp:struct name="Socket_Address_IPv4"> + <tp:docstring>An IPv4 address and port.</tp:docstring> + <tp:member type="s" name="Address"> + <tp:docstring>A dotted-quad IPv4 address literal: four ASCII decimal + numbers, each between 0 and 255 inclusive, e.g. + "192.168.0.1".</tp:docstring> + </tp:member> + <tp:member type="q" name="Port"> + <tp:docstring>The TCP or UDP port number.</tp:docstring> + </tp:member> + </tp:struct> + + <tp:struct name="Socket_Address_IPv6"> + <tp:docstring>An IPv6 address and port.</tp:docstring> + <tp:member type="s" name="Address"> + <tp:docstring>An IPv6 address literal as specified by RFC2373 + section 2.2, e.g. "2001:DB8::8:800:200C:4171".</tp:docstring> + </tp:member> + <tp:member type="q" name="Port"> + <tp:docstring>The TCP or UDP port number.</tp:docstring> + </tp:member> + </tp:struct> + + <tp:struct name="Socket_Netmask_IPv4"> + <tp:docstring>An IPv4 network or subnet.</tp:docstring> + <tp:member type="s" name="Address"> + <tp:docstring>A dotted-quad IPv4 address literal: four ASCII decimal + numbers, each between 0 and 255 inclusive, e.g. + "192.168.0.1".</tp:docstring> + </tp:member> + <tp:member type="y" name="Prefix_Length"> + <tp:docstring>The number of leading bits of the address that must + match, for this netmask to be considered to match an + address.</tp:docstring> + </tp:member> + </tp:struct> + + <tp:struct name="Socket_Netmask_IPv6"> + <tp:docstring>An IPv6 network or subnet.</tp:docstring> + <tp:member type="s" name="Address"> + <tp:docstring>An IPv6 address literal as specified by RFC2373 + section 2.2, e.g. "2001:DB8::8:800:200C:4171".</tp:docstring> + </tp:member> + <tp:member type="y" name="Prefix_Length"> + <tp:docstring>The number of leading bits of the address that must + match, for this netmask to be considered to match an + address.</tp:docstring> + </tp:member> + </tp:struct> + +</tp:generic-types> diff --git a/doc/dbus-api/spec/instance-introspec.xml b/doc/dbus-api/spec/instance-introspec.xml new file mode 120000 index 0000000000..c551629283 --- /dev/null +++ b/doc/dbus-api/spec/instance-introspec.xml @@ -0,0 +1 @@ +../../../src/client/dbus/instance-introspec.xml \ No newline at end of file diff --git a/doc/dbus-api/spec/presencemanager-introspec.xml b/doc/dbus-api/spec/presencemanager-introspec.xml new file mode 120000 index 0000000000..19c1ff524a --- /dev/null +++ b/doc/dbus-api/spec/presencemanager-introspec.xml @@ -0,0 +1 @@ +../../../src/client/dbus/presencemanager-introspec.xml \ No newline at end of file diff --git a/doc/dbus-api/tools/devhelp.xsl b/doc/dbus-api/tools/devhelp.xsl new file mode 100644 index 0000000000..60f9e1c530 --- /dev/null +++ b/doc/dbus-api/tools/devhelp.xsl @@ -0,0 +1,91 @@ +<!-- Generate a Devhelp index from the Telepathy specification. +The master copy of this stylesheet is in the Telepathy spec repository - +please make any changes there. + +Copyright (C) 2006-2008 Collabora Limited + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +--> + +<xsl:stylesheet version="1.0" + xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0"> + <xsl:template match="/"> + <book xmlns="http://www.devhelp.net/book" title="Telepathy Specification" + name="telepathy-spec" link="spec.html"> + <xsl:text>
</xsl:text> + <chapters> + <xsl:text>
</xsl:text> + <xsl:apply-templates select="//interface" /> + </chapters> + <xsl:text>
</xsl:text> + <functions> + <xsl:text>
</xsl:text> + <xsl:apply-templates select="//method" /> + <xsl:apply-templates select="//signal" /> + <xsl:apply-templates select="//property" /> + <xsl:apply-templates select="//tp:enum" /> + <xsl:apply-templates select="//tp:simple-type" /> + <xsl:apply-templates select="//tp:mapping" /> + <xsl:apply-templates select="//tp:flags" /> + <xsl:apply-templates select="//tp:struct" /> + </functions> + <xsl:text>
</xsl:text> + </book> + </xsl:template> + + <xsl:template match="interface"> + <xsl:text> </xsl:text> + <sub xmlns="http://www.devhelp.net/book" name="{@name}" + link="{concat('spec.html#', @name)}" /> + <xsl:text>
</xsl:text> + </xsl:template> + + <xsl:template match="method"> + <xsl:text> </xsl:text> + <keyword type="function" xmlns="http://www.devhelp.net/book" name="{@name}" + link="spec.html#{../@name}.{@name}" /> + <xsl:text>
</xsl:text> + </xsl:template> + + <xsl:template match="signal | property"> + <xsl:text> </xsl:text> + <keyword type="" xmlns="http://www.devhelp.net/book" name="{@name}" + link="spec.html#{../@name}.{@name}" /> + <xsl:text>
</xsl:text> + </xsl:template> + + <xsl:template match="tp:simple-type"> + <xsl:text> </xsl:text> + <keyword type="typedef" xmlns="http://www.devhelp.net/book" name="{@name}" + link="spec.html#type-{@name}" /> + <xsl:text>
</xsl:text> + </xsl:template> + + <xsl:template match="tp:enum | tp:flags"> + <xsl:text> </xsl:text> + <keyword type="enum" xmlns="http://www.devhelp.net/book" name="{@name}" + link="spec.html#type-{@name}" /> + <xsl:text>
</xsl:text> + </xsl:template> + + <xsl:template match="tp:mapping | tp:struct"> + <xsl:text> </xsl:text> + <keyword type="struct" xmlns="http://www.devhelp.net/book" name="{@name}" + link="spec.html#type-{@name}" /> + <xsl:text>
</xsl:text> + </xsl:template> + +</xsl:stylesheet> diff --git a/doc/dbus-api/tools/doc-generator.py b/doc/dbus-api/tools/doc-generator.py new file mode 100755 index 0000000000..5fc19ce907 --- /dev/null +++ b/doc/dbus-api/tools/doc-generator.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python +# +# doc-generator.py +# +# Generates HTML documentation from the parsed spec using Cheetah templates. +# +# Copyright (C) 2009 Collabora Ltd. +# +# This library is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or (at +# your option) any later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Authors: Davyd Madeley <davyd.madeley@collabora.co.uk> +# + +import sys +import os +import os.path +import shutil + +try: + from Cheetah.Template import Template +except ImportError, e: + print >> sys.stderr, e + print >> sys.stderr, "Install `python-cheetah'?" + sys.exit(-1) + +import specparser + +program, spec_file, output_path, project, namespace = sys.argv + +template_path = os.path.join(os.path.dirname(program), '../doc/templates') + +# make the output path +try: + os.mkdir(output_path) +except OSError: + pass +# copy in the CSS +shutil.copy(os.path.join(template_path, 'style.css'), output_path) + +def load_template(filename): + try: + file = open(os.path.join(template_path, filename)) + template_def = file.read() + file.close() + except IOError, e: + print >> sys.stderr, "Could not load template file `%s'" % filename + print >> sys.stderr, e + sys.exit(-1) + + return template_def + +spec = specparser.parse(spec_file, namespace) + +# write out HTML files for each of the interfaces + +# Not using render_template here to avoid recompiling it n times. +namespace = {} +template_def = load_template('interface.html') +t = Template(template_def, namespaces = [namespace]) +for interface in spec.interfaces: + namespace['interface'] = interface + + # open the output file + out = open(os.path.join(output_path, '%s.html' % interface.name), 'w') + print >> out, unicode(t).encode('utf-8') + out.close() + +def render_template(name, namespaces, target=None): + if target is None: + target = name + + namespace = { 'spec': spec } + template_def = load_template(name) + t = Template(template_def, namespaces=namespaces) + out = open(os.path.join(output_path, target), 'w') + print >> out, unicode(t).encode('utf-8') + out.close() + +namespaces = { 'spec': spec } + +render_template('generic-types.html', namespaces) +render_template('errors.html', namespaces) +render_template('interfaces.html', namespaces) +render_template('fullindex.html', namespaces) + +dh_namespaces = { 'spec': spec, 'name': project } +render_template('devhelp.devhelp2', dh_namespaces, + target=('%s.devhelp2' % project)) + +# write out the TOC last, because this is the file used as the target in the +# Makefile. +render_template('index.html', namespaces) diff --git a/doc/dbus-api/tools/doc-generator.xsl b/doc/dbus-api/tools/doc-generator.xsl new file mode 100644 index 0000000000..fe9cd9f08d --- /dev/null +++ b/doc/dbus-api/tools/doc-generator.xsl @@ -0,0 +1,1266 @@ +<!-- Generate HTML documentation from the Telepathy specification. +The master copy of this stylesheet is in the Telepathy spec repository - +please make any changes there. + +Copyright (C) 2006-2008 Collabora Limited + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +--> + +<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" + xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0" + xmlns:html="http://www.w3.org/1999/xhtml" + exclude-result-prefixes="tp html"> + <!--Don't move the declaration of the HTML namespace up here — XMLNSs + don't work ideally in the presence of two things that want to use the + absence of a prefix, sadly. --> + + <xsl:param name="allow-undefined-interfaces" select="false()"/> + + <xsl:template match="html:* | @*" mode="html"> + <xsl:copy> + <xsl:apply-templates mode="html" select="@*|node()"/> + </xsl:copy> + </xsl:template> + + <xsl:template match="tp:type" mode="html"> + <xsl:call-template name="tp-type"> + <xsl:with-param name="tp-type" select="string(.)"/> + </xsl:call-template> + </xsl:template> + + <!-- tp:dbus-ref: reference a D-Bus interface, signal, method or property --> + <xsl:template match="tp:dbus-ref" mode="html"> + <xsl:variable name="name"> + <xsl:choose> + <xsl:when test="@namespace"> + <xsl:value-of select="@namespace"/> + <xsl:text>.</xsl:text> + </xsl:when> + </xsl:choose> + <xsl:value-of select="string(.)"/> + </xsl:variable> + + <xsl:choose> + <xsl:when test="//interface[@name=$name] + or //interface/method[concat(../@name, '.', @name)=$name] + or //interface/signal[concat(../@name, '.', @name)=$name] + or //interface/property[concat(../@name, '.', @name)=$name] + or //interface[@name=concat($name, '.DRAFT')] + or //interface/method[ + concat(../@name, '.', @name)=concat($name, '.DRAFT')] + or //interface/signal[ + concat(../@name, '.', @name)=concat($name, '.DRAFT')] + or //interface/property[ + concat(../@name, '.', @name)=concat($name, '.DRAFT')] + "> + <a xmlns="http://www.w3.org/1999/xhtml" href="#{$name}"> + <xsl:value-of select="string(.)"/> + </a> + </xsl:when> + + <xsl:when test="$allow-undefined-interfaces"> + <span xmlns="http://www.w3.org/1999/xhtml" title="defined elsewhere"> + <xsl:value-of select="string(.)"/> + </span> + </xsl:when> + + <xsl:otherwise> + <xsl:message terminate="yes"> + <xsl:text>ERR: cannot find D-Bus interface, method, </xsl:text> + <xsl:text>signal or property called '</xsl:text> + <xsl:value-of select="$name"/> + <xsl:text>' </xsl:text> + </xsl:message> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <!-- tp:member-ref: reference a property of the current interface --> + <xsl:template match="tp:member-ref" mode="html"> + <xsl:variable name="prefix" select="concat(ancestor::interface/@name, + '.')"/> + <xsl:variable name="name" select="string(.)"/> + + <xsl:if test="not(ancestor::interface)"> + <xsl:message terminate="yes"> + <xsl:text>ERR: Cannot use tp:member-ref when not in an</xsl:text> + <xsl:text> <interface> </xsl:text> + </xsl:message> + </xsl:if> + + <xsl:choose> + <xsl:when test="ancestor::interface/signal[@name=$name]"/> + <xsl:when test="ancestor::interface/method[@name=$name]"/> + <xsl:when test="ancestor::interface/property[@name=$name]"/> + <xsl:otherwise> + <xsl:message terminate="yes"> + <xsl:text>ERR: interface </xsl:text> + <xsl:value-of select="ancestor::interface/@name"/> + <xsl:text> has no signal/method/property called </xsl:text> + <xsl:value-of select="$name"/> + <xsl:text> </xsl:text> + </xsl:message> + </xsl:otherwise> + </xsl:choose> + + <a xmlns="http://www.w3.org/1999/xhtml" href="#{$prefix}{$name}"> + <xsl:value-of select="$name"/> + </a> + </xsl:template> + + <xsl:template match="*" mode="identity"> + <xsl:copy> + <xsl:apply-templates mode="identity"/> + </xsl:copy> + </xsl:template> + + <xsl:template match="tp:docstring"> + <xsl:apply-templates mode="html"/> + </xsl:template> + + <xsl:template match="tp:added"> + <p class="added" xmlns="http://www.w3.org/1999/xhtml">Added in + version <xsl:value-of select="@version"/>. + <xsl:apply-templates select="node()" mode="html"/></p> + </xsl:template> + + <xsl:template match="tp:changed"> + <xsl:choose> + <xsl:when test="node()"> + <p class="changed" xmlns="http://www.w3.org/1999/xhtml">Changed in + version <xsl:value-of select="@version"/>: + <xsl:apply-templates select="node()" mode="html"/></p> + </xsl:when> + <xsl:otherwise> + <p class="changed">Changed in version + <xsl:value-of select="@version"/></p> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <xsl:template match="tp:deprecated"> + <p class="deprecated" xmlns="http://www.w3.org/1999/xhtml">Deprecated + since version <xsl:value-of select="@version"/>. + <xsl:apply-templates select="node()" mode="html"/></p> + </xsl:template> + + <xsl:template match="tp:rationale" mode="html"> + <div xmlns="http://www.w3.org/1999/xhtml" class="rationale"> + <xsl:apply-templates select="node()" mode="html"/> + </div> + </xsl:template> + + <xsl:template match="tp:errors"> + <h1 xmlns="http://www.w3.org/1999/xhtml">Errors</h1> + <xsl:apply-templates/> + </xsl:template> + + <xsl:template match="tp:generic-types"> + <h1 xmlns="http://www.w3.org/1999/xhtml">Generic types</h1> + <xsl:call-template name="do-types"/> + </xsl:template> + + <xsl:template name="do-types"> + <xsl:if test="tp:simple-type"> + <h2 xmlns="http://www.w3.org/1999/xhtml">Simple types</h2> + <xsl:apply-templates select="tp:simple-type"/> + </xsl:if> + + <xsl:if test="tp:enum"> + <h2 xmlns="http://www.w3.org/1999/xhtml">Enumerated types:</h2> + <xsl:apply-templates select="tp:enum"/> + </xsl:if> + + <xsl:if test="tp:flags"> + <h2 xmlns="http://www.w3.org/1999/xhtml">Sets of flags:</h2> + <xsl:apply-templates select="tp:flags"/> + </xsl:if> + + <xsl:if test="tp:struct"> + <h2 xmlns="http://www.w3.org/1999/xhtml">Structure types</h2> + <xsl:apply-templates select="tp:struct"/> + </xsl:if> + + <xsl:if test="tp:mapping"> + <h2 xmlns="http://www.w3.org/1999/xhtml">Mapping types</h2> + <xsl:apply-templates select="tp:mapping"/> + </xsl:if> + + <xsl:if test="tp:external-type"> + <h2 xmlns="http://www.w3.org/1999/xhtml">Types defined elsewhere</h2> + <dl><xsl:apply-templates select="tp:external-type"/></dl> + </xsl:if> + </xsl:template> + + <xsl:template match="tp:error"> + <h2 xmlns="http://www.w3.org/1999/xhtml"><a name="{concat(../@namespace, '.', translate(@name, ' ', ''))}"></a><xsl:value-of select="concat(../@namespace, '.', translate(@name, ' ', ''))"/></h2> + <xsl:apply-templates select="tp:docstring"/> + <xsl:apply-templates select="tp:added"/> + <xsl:apply-templates select="tp:changed"/> + <xsl:apply-templates select="tp:deprecated"/> + </xsl:template> + + <xsl:template match="/tp:spec/tp:copyright"> + <div xmlns="http://www.w3.org/1999/xhtml"> + <xsl:apply-templates mode="text"/> + </div> + </xsl:template> + <xsl:template match="/tp:spec/tp:license"> + <div xmlns="http://www.w3.org/1999/xhtml" class="license"> + <xsl:apply-templates mode="html"/> + </div> + </xsl:template> + + <xsl:template match="tp:copyright"/> + <xsl:template match="tp:license"/> + + <xsl:template match="interface"> + <h1 xmlns="http://www.w3.org/1999/xhtml"><a name="{@name}"></a><xsl:value-of select="@name"/></h1> + + <xsl:if test="@tp:causes-havoc"> + <p xmlns="http://www.w3.org/1999/xhtml" class="causes-havoc"> + This interface is <xsl:value-of select="@tp:causes-havoc"/> + and is likely to cause havoc to your API/ABI if bindings are generated. + Don't include it in libraries that care about compatibility. + </p> + </xsl:if> + + <xsl:if test="tp:requires"> + <p>Implementations of this interface must also implement:</p> + <ul xmlns="http://www.w3.org/1999/xhtml"> + <xsl:for-each select="tp:requires"> + <li><code><a href="#{@interface}"><xsl:value-of select="@interface"/></a></code></li> + </xsl:for-each> + </ul> + </xsl:if> + + <xsl:apply-templates select="tp:docstring" /> + <xsl:apply-templates select="tp:added"/> + <xsl:apply-templates select="tp:changed"/> + <xsl:apply-templates select="tp:deprecated"/> + + <xsl:choose> + <xsl:when test="method"> + <h2 xmlns="http://www.w3.org/1999/xhtml">Methods:</h2> + <xsl:apply-templates select="method"/> + </xsl:when> + <xsl:otherwise> + <p xmlns="http://www.w3.org/1999/xhtml">Interface has no methods.</p> + </xsl:otherwise> + </xsl:choose> + + <xsl:choose> + <xsl:when test="signal"> + <h2 xmlns="http://www.w3.org/1999/xhtml">Signals:</h2> + <xsl:apply-templates select="signal"/> + </xsl:when> + <xsl:otherwise> + <p xmlns="http://www.w3.org/1999/xhtml">Interface has no signals.</p> + </xsl:otherwise> + </xsl:choose> + + <xsl:choose> + <xsl:when test="tp:property"> + <h2 xmlns="http://www.w3.org/1999/xhtml">Telepathy Properties:</h2> + <p xmlns="http://www.w3.org/1999/xhtml">Accessed using the + <a href="#org.freedesktop.Telepathy.Properties">Telepathy + Properties</a> interface.</p> + <dl xmlns="http://www.w3.org/1999/xhtml"> + <xsl:apply-templates select="tp:property"/> + </dl> + </xsl:when> + <xsl:otherwise> + <p xmlns="http://www.w3.org/1999/xhtml">Interface has no Telepathy + properties.</p> + </xsl:otherwise> + </xsl:choose> + + <xsl:choose> + <xsl:when test="property"> + <h2 xmlns="http://www.w3.org/1999/xhtml">D-Bus core Properties:</h2> + <p xmlns="http://www.w3.org/1999/xhtml">Accessed using the + org.freedesktop.DBus.Properties interface.</p> + <dl xmlns="http://www.w3.org/1999/xhtml"> + <xsl:apply-templates select="property"/> + </dl> + </xsl:when> + <xsl:otherwise> + <p xmlns="http://www.w3.org/1999/xhtml">Interface has no D-Bus core + properties.</p> + </xsl:otherwise> + </xsl:choose> + + <xsl:call-template name="do-types"/> + + </xsl:template> + + <xsl:template match="tp:flags"> + + <xsl:if test="not(@name) or @name = ''"> + <xsl:message terminate="yes"> + <xsl:text>ERR: missing @name on a tp:flags type </xsl:text> + </xsl:message> + </xsl:if> + + <xsl:if test="not(@type) or @type = ''"> + <xsl:message terminate="yes"> + <xsl:text>ERR: missing @type on tp:flags type</xsl:text> + <xsl:value-of select="@name"/> + <xsl:text> </xsl:text> + </xsl:message> + </xsl:if> + + <h3> + <a name="type-{@name}"> + <xsl:value-of select="@name"/> + </a> + </h3> + <xsl:apply-templates select="tp:docstring" /> + <xsl:apply-templates select="tp:added"/> + <xsl:apply-templates select="tp:changed"/> + <xsl:apply-templates select="tp:deprecated"/> + <dl xmlns="http://www.w3.org/1999/xhtml"> + <xsl:variable name="value-prefix"> + <xsl:choose> + <xsl:when test="@value-prefix"> + <xsl:value-of select="@value-prefix"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="@name"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:for-each select="tp:flag"> + <dt xmlns="http://www.w3.org/1999/xhtml"><code><xsl:value-of select="concat($value-prefix, '_', @suffix)"/> = <xsl:value-of select="@value"/></code></dt> + <xsl:choose> + <xsl:when test="tp:docstring"> + <dd xmlns="http://www.w3.org/1999/xhtml"> + <xsl:apply-templates select="tp:docstring" /> + <xsl:apply-templates select="tp:added"/> + <xsl:apply-templates select="tp:changed"/> + <xsl:apply-templates select="tp:deprecated"/> + </dd> + </xsl:when> + <xsl:otherwise> + <dd xmlns="http://www.w3.org/1999/xhtml">(Undocumented)</dd> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + </dl> + </xsl:template> + + <xsl:template match="tp:enum"> + + <xsl:if test="not(@name) or @name = ''"> + <xsl:message terminate="yes"> + <xsl:text>ERR: missing @name on a tp:enum type </xsl:text> + </xsl:message> + </xsl:if> + + <xsl:if test="not(@type) or @type = ''"> + <xsl:message terminate="yes"> + <xsl:text>ERR: missing @type on tp:enum type</xsl:text> + <xsl:value-of select="@name"/> + <xsl:text> </xsl:text> + </xsl:message> + </xsl:if> + + <h3 xmlns="http://www.w3.org/1999/xhtml"> + <a name="type-{@name}"> + <xsl:value-of select="@name"/> + </a> + </h3> + <xsl:apply-templates select="tp:docstring" /> + <xsl:apply-templates select="tp:added"/> + <xsl:apply-templates select="tp:changed"/> + <xsl:apply-templates select="tp:deprecated"/> + <dl xmlns="http://www.w3.org/1999/xhtml"> + <xsl:variable name="value-prefix"> + <xsl:choose> + <xsl:when test="@value-prefix"> + <xsl:value-of select="@value-prefix"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="@name"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + <xsl:for-each select="tp:enumvalue"> + <dt xmlns="http://www.w3.org/1999/xhtml"><code><xsl:value-of select="concat($value-prefix, '_', @suffix)"/> = <xsl:value-of select="@value"/></code></dt> + <xsl:choose> + <xsl:when test="tp:docstring"> + <dd xmlns="http://www.w3.org/1999/xhtml"> + <xsl:apply-templates select="tp:docstring" /> + <xsl:apply-templates select="tp:added"/> + <xsl:apply-templates select="tp:changed"/> + <xsl:apply-templates select="tp:deprecated"/> + </dd> + </xsl:when> + <xsl:otherwise> + <dd xmlns="http://www.w3.org/1999/xhtml">(Undocumented)</dd> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + </dl> + </xsl:template> + + <xsl:template name="binding-name-check"> + <xsl:if test="not(@tp:name-for-bindings)"> + <xsl:message terminate="yes"> + <xsl:text>ERR: Binding name missing from </xsl:text> + <xsl:value-of select="parent::interface/@name"/> + <xsl:text>.</xsl:text> + <xsl:value-of select="@name"/> + <xsl:text> </xsl:text> + </xsl:message> + </xsl:if> + + <xsl:if test="translate(@tp:name-for-bindings, '_', '') != @name"> + <xsl:message terminate="yes"> + <xsl:text>ERR: Binding name </xsl:text> + <xsl:value-of select="@tp:name-for-bindings"/> + <xsl:text> doesn't correspond to D-Bus name </xsl:text> + <xsl:value-of select="@name"/> + <xsl:text> </xsl:text> + </xsl:message> + </xsl:if> + </xsl:template> + + <xsl:template match="property"> + + <xsl:call-template name="binding-name-check"/> + + <xsl:if test="not(parent::interface)"> + <xsl:message terminate="yes"> + <xsl:text>ERR: property </xsl:text> + <xsl:value-of select="@name"/> + <xsl:text> does not have an interface as parent </xsl:text> + </xsl:message> + </xsl:if> + + <xsl:if test="not(@name) or @name = ''"> + <xsl:message terminate="yes"> + <xsl:text>ERR: missing @name on a property of </xsl:text> + <xsl:value-of select="../@name"/> + <xsl:text> </xsl:text> + </xsl:message> + </xsl:if> + + <xsl:if test="not(@type) or @type = ''"> + <xsl:message terminate="yes"> + <xsl:text>ERR: missing @type on property </xsl:text> + <xsl:value-of select="concat(../@name, '.', @name)"/> + <xsl:text>: '</xsl:text> + <xsl:value-of select="@access"/> + <xsl:text>' </xsl:text> + </xsl:message> + </xsl:if> + + <dt xmlns="http://www.w3.org/1999/xhtml"> + <a name="{concat(../@name, '.', @name)}"> + <code><xsl:value-of select="@name"/></code> + </a> + <xsl:text> − </xsl:text> + <code><xsl:value-of select="@type"/></code> + <xsl:call-template name="parenthesized-tp-type"/> + <xsl:text>, </xsl:text> + <xsl:choose> + <xsl:when test="@access = 'read'"> + <xsl:text>read-only</xsl:text> + </xsl:when> + <xsl:when test="@access = 'write'"> + <xsl:text>write-only</xsl:text> + </xsl:when> + <xsl:when test="@access = 'readwrite'"> + <xsl:text>read/write</xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:message terminate="yes"> + <xsl:text>ERR: unknown or missing value for </xsl:text> + <xsl:text>@access on property </xsl:text> + <xsl:value-of select="concat(../@name, '.', @name)"/> + <xsl:text>: '</xsl:text> + <xsl:value-of select="@access"/> + <xsl:text>' </xsl:text> + </xsl:message> + </xsl:otherwise> + </xsl:choose> + </dt> + <dd xmlns="http://www.w3.org/1999/xhtml"> + <xsl:apply-templates select="tp:docstring"/> + <xsl:apply-templates select="tp:added"/> + <xsl:apply-templates select="tp:changed"/> + <xsl:apply-templates select="tp:deprecated"/> + </dd> + </xsl:template> + + <xsl:template match="tp:property"> + <dt xmlns="http://www.w3.org/1999/xhtml"> + <xsl:if test="@name"> + <code><xsl:value-of select="@name"/></code> − + </xsl:if> + <code><xsl:value-of select="@type"/></code> + </dt> + <dd xmlns="http://www.w3.org/1999/xhtml"> + <xsl:apply-templates select="tp:docstring"/> + <xsl:apply-templates select="tp:added"/> + <xsl:apply-templates select="tp:changed"/> + <xsl:apply-templates select="tp:deprecated"/> + </dd> + </xsl:template> + + <xsl:template match="tp:mapping"> + <div xmlns="http://www.w3.org/1999/xhtml" class="struct"> + <h3> + <a name="type-{@name}"> + <xsl:value-of select="@name"/> + </a> − a{ + <xsl:for-each select="tp:member"> + <xsl:value-of select="@type"/> + <xsl:text>: </xsl:text> + <xsl:value-of select="@name"/> + <xsl:if test="position() != last()"> → </xsl:if> + </xsl:for-each> + } + </h3> + <div class="docstring"> + <xsl:apply-templates select="tp:docstring"/> + <xsl:if test="string(@array-name) != ''"> + <p>In bindings that need a separate name, arrays of + <xsl:value-of select="@name"/> should be called + <xsl:value-of select="@array-name"/>.</p> + </xsl:if> + </div> + <div> + <h4>Members</h4> + <dl> + <xsl:apply-templates select="tp:member" mode="members-in-docstring"/> + </dl> + </div> + </div> + </xsl:template> + + <xsl:template match="tp:docstring" mode="in-index"/> + + <xsl:template match="tp:simple-type | tp:enum | tp:flags | tp:external-type" + mode="in-index"> + − <xsl:value-of select="@type"/> + </xsl:template> + + <xsl:template match="tp:simple-type"> + + <xsl:if test="not(@name) or @name = ''"> + <xsl:message terminate="yes"> + <xsl:text>ERR: missing @name on a tp:simple-type </xsl:text> + </xsl:message> + </xsl:if> + + <xsl:if test="not(@type) or @type = ''"> + <xsl:message terminate="yes"> + <xsl:text>ERR: missing @type on tp:simple-type</xsl:text> + <xsl:value-of select="@name"/> + <xsl:text> </xsl:text> + </xsl:message> + </xsl:if> + + <div xmlns="http://www.w3.org/1999/xhtml" class="simple-type"> + <h3> + <a name="type-{@name}"> + <xsl:value-of select="@name"/> + </a> − <xsl:value-of select="@type"/> + </h3> + <div class="docstring"> + <xsl:apply-templates select="tp:docstring"/> + <xsl:apply-templates select="tp:added"/> + <xsl:apply-templates select="tp:changed"/> + <xsl:apply-templates select="tp:deprecated"/> + </div> + </div> + </xsl:template> + + <xsl:template match="tp:external-type"> + + <xsl:if test="not(@name) or @name = ''"> + <xsl:message terminate="yes"> + <xsl:text>ERR: missing @name on a tp:external-type </xsl:text> + </xsl:message> + </xsl:if> + + <xsl:if test="not(@type) or @type = ''"> + <xsl:message terminate="yes"> + <xsl:text>ERR: missing @type on tp:external-type</xsl:text> + <xsl:value-of select="@name"/> + <xsl:text> </xsl:text> + </xsl:message> + </xsl:if> + + <div xmlns="http://www.w3.org/1999/xhtml" class="external-type"> + <dt> + <a name="type-{@name}"> + <xsl:value-of select="@name"/> + </a> − <xsl:value-of select="@type"/> + </dt> + <dd>Defined by: <xsl:value-of select="@from"/></dd> + </div> + </xsl:template> + + <xsl:template match="tp:struct" mode="in-index"> + − ( <xsl:for-each select="tp:member"> + <xsl:value-of select="@type"/> + <xsl:if test="position() != last()">, </xsl:if> + </xsl:for-each> ) + </xsl:template> + + <xsl:template match="tp:mapping" mode="in-index"> + − a{ <xsl:for-each select="tp:member"> + <xsl:value-of select="@type"/> + <xsl:if test="position() != last()"> → </xsl:if> + </xsl:for-each> } + </xsl:template> + + <xsl:template match="tp:struct"> + <div xmlns="http://www.w3.org/1999/xhtml" class="struct"> + <h3> + <a name="type-{@name}"> + <xsl:value-of select="@name"/> + </a> − ( + <xsl:for-each select="tp:member"> + <xsl:value-of select="@type"/> + <xsl:text>: </xsl:text> + <xsl:value-of select="@name"/> + <xsl:if test="position() != last()">, </xsl:if> + </xsl:for-each> + ) + </h3> + <div class="docstring"> + <xsl:apply-templates select="tp:docstring"/> + <xsl:apply-templates select="tp:added"/> + <xsl:apply-templates select="tp:changed"/> + <xsl:apply-templates select="tp:deprecated"/> + </div> + <xsl:choose> + <xsl:when test="string(@array-name) != ''"> + <p>In bindings that need a separate name, arrays of + <xsl:value-of select="@name"/> should be called + <xsl:value-of select="@array-name"/>.</p> + </xsl:when> + <xsl:otherwise> + <p>Arrays of <xsl:value-of select="@name"/> don't generally + make sense.</p> + </xsl:otherwise> + </xsl:choose> + <div> + <h4>Members</h4> + <dl> + <xsl:apply-templates select="tp:member" mode="members-in-docstring"/> + </dl> + </div> + </div> + </xsl:template> + + <xsl:template match="method"> + + <xsl:call-template name="binding-name-check"/> + + <xsl:if test="not(parent::interface)"> + <xsl:message terminate="yes"> + <xsl:text>ERR: method </xsl:text> + <xsl:value-of select="@name"/> + <xsl:text> does not have an interface as parent </xsl:text> + </xsl:message> + </xsl:if> + + <xsl:if test="not(@name) or @name = ''"> + <xsl:message terminate="yes"> + <xsl:text>ERR: missing @name on a method of </xsl:text> + <xsl:value-of select="../@name"/> + <xsl:text> </xsl:text> + </xsl:message> + </xsl:if> + + <xsl:for-each select="arg"> + <xsl:if test="not(@type) or @type = ''"> + <xsl:message terminate="yes"> + <xsl:text>ERR: an arg of method </xsl:text> + <xsl:value-of select="concat(../../@name, '.', ../@name)"/> + <xsl:text> has no type</xsl:text> + </xsl:message> + </xsl:if> + <xsl:choose> + <xsl:when test="@direction='in'"> + <xsl:if test="not(@name) or @name = ''"> + <xsl:message terminate="yes"> + <xsl:text>ERR: an 'in' arg of method </xsl:text> + <xsl:value-of select="concat(../../@name, '.', ../@name)"/> + <xsl:text> has no name</xsl:text> + </xsl:message> + </xsl:if> + </xsl:when> + <xsl:when test="@direction='out'"> + <xsl:if test="not(@name) or @name = ''"> + <xsl:message terminate="no"> + <xsl:text>WARNING: an 'out' arg of method </xsl:text> + <xsl:value-of select="concat(../../@name, '.', ../@name)"/> + <xsl:text> has no name</xsl:text> + </xsl:message> + </xsl:if> + </xsl:when> + <xsl:otherwise> + <xsl:message terminate="yes"> + <xsl:text>ERR: an arg of method </xsl:text> + <xsl:value-of select="concat(../../@name, '.', ../@name)"/> + <xsl:text> has direction neither 'in' nor 'out'</xsl:text> + </xsl:message> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + + <div xmlns="http://www.w3.org/1999/xhtml" class="method"> + <h3 xmlns="http://www.w3.org/1999/xhtml"> + <a name="{concat(../@name, concat('.', @name))}"> + <xsl:value-of select="@name"/> + </a> ( + <xsl:for-each xmlns="" select="arg[@direction='in']"> + <xsl:value-of select="@type"/>: <xsl:value-of select="@name"/> + <xsl:if test="position() != last()">, </xsl:if> + </xsl:for-each> + ) → + <xsl:choose> + <xsl:when test="arg[@direction='out']"> + <xsl:for-each xmlns="" select="arg[@direction='out']"> + <xsl:value-of select="@type"/> + <xsl:if test="position() != last()">, </xsl:if> + </xsl:for-each> + </xsl:when> + <xsl:otherwise>nothing</xsl:otherwise> + </xsl:choose> + </h3> + <div xmlns="http://www.w3.org/1999/xhtml" class="docstring"> + <xsl:apply-templates select="tp:docstring" /> + <xsl:apply-templates select="tp:added"/> + <xsl:apply-templates select="tp:changed"/> + <xsl:apply-templates select="tp:deprecated"/> + </div> + + <xsl:if test="arg[@direction='in']"> + <div xmlns="http://www.w3.org/1999/xhtml"> + <h4>Parameters</h4> + <dl xmlns="http://www.w3.org/1999/xhtml"> + <xsl:apply-templates select="arg[@direction='in']" + mode="parameters-in-docstring"/> + </dl> + </div> + </xsl:if> + + <xsl:if test="arg[@direction='out']"> + <div xmlns="http://www.w3.org/1999/xhtml"> + <h4>Returns</h4> + <dl xmlns="http://www.w3.org/1999/xhtml"> + <xsl:apply-templates select="arg[@direction='out']" + mode="returns-in-docstring"/> + </dl> + </div> + </xsl:if> + + <xsl:if test="tp:possible-errors"> + <div xmlns="http://www.w3.org/1999/xhtml"> + <h4>Possible errors</h4> + <dl xmlns="http://www.w3.org/1999/xhtml"> + <xsl:apply-templates select="tp:possible-errors/tp:error"/> + </dl> + </div> + </xsl:if> + + </div> + </xsl:template> + + <xsl:template name="tp-type"> + <xsl:param name="tp-type"/> + <xsl:param name="type"/> + + <xsl:variable name="single-type"> + <xsl:choose> + <xsl:when test="contains($tp-type, '[]')"> + <xsl:value-of select="substring-before($tp-type, '[]')"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$tp-type"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:variable name="type-of-single-tp-type"> + <xsl:choose> + <xsl:when test="//tp:simple-type[@name=$single-type]"> + <xsl:value-of select="string(//tp:simple-type[@name=$single-type]/@type)"/> + </xsl:when> + <xsl:when test="//tp:struct[@name=$single-type]"> + <xsl:text>(</xsl:text> + <xsl:for-each select="//tp:struct[@name=$single-type]/tp:member"> + <xsl:value-of select="@type"/> + </xsl:for-each> + <xsl:text>)</xsl:text> + </xsl:when> + <xsl:when test="//tp:enum[@name=$single-type]"> + <xsl:value-of select="string(//tp:enum[@name=$single-type]/@type)"/> + </xsl:when> + <xsl:when test="//tp:flags[@name=$single-type]"> + <xsl:value-of select="string(//tp:flags[@name=$single-type]/@type)"/> + </xsl:when> + <xsl:when test="//tp:mapping[@name=$single-type]"> + <xsl:text>a{</xsl:text> + <xsl:for-each select="//tp:mapping[@name=$single-type]/tp:member"> + <xsl:value-of select="@type"/> + </xsl:for-each> + <xsl:text>}</xsl:text> + </xsl:when> + <xsl:when test="//tp:external-type[@name=$single-type]"> + <xsl:value-of select="string(//tp:external-type[@name=$single-type]/@type)"/> + </xsl:when> + <xsl:otherwise> + <xsl:message terminate="yes"> + <xsl:text>ERR: Unable to find type '</xsl:text> + <xsl:value-of select="$tp-type"/> + <xsl:text>' </xsl:text> + </xsl:message> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:variable name="type-of-tp-type"> + <xsl:if test="contains($tp-type, '[]')"> + <!-- one 'a', plus one for each [ after the [], and delete all ] --> + <xsl:value-of select="concat('a', + translate(substring-after($tp-type, '[]'), '[]', 'a'))"/> + </xsl:if> + <xsl:value-of select="$type-of-single-tp-type"/> + </xsl:variable> + + <xsl:if test="string($type) != '' and + string($type-of-tp-type) != string($type)"> + <xsl:message terminate="yes"> + <xsl:text>ERR: tp:type '</xsl:text> + <xsl:value-of select="$tp-type"/> + <xsl:text>' has D-Bus type '</xsl:text> + <xsl:value-of select="$type-of-tp-type"/> + <xsl:text>' but has been used with type='</xsl:text> + <xsl:value-of select="$type"/> + <xsl:text>' </xsl:text> + </xsl:message> + </xsl:if> + + <xsl:if test="contains($tp-type, '[]')"> + <xsl:call-template name="tp-type-array-usage-check"> + <xsl:with-param name="single-type" select="$single-type"/> + <xsl:with-param name="type-of-single-tp-type" + select="$type-of-single-tp-type"/> + </xsl:call-template> + </xsl:if> + + <a href="#type-{$single-type}"><xsl:value-of select="$tp-type"/></a> + + </xsl:template> + + <xsl:template name="tp-type-array-usage-check"> + <xsl:param name="single-type"/> + <xsl:param name="type-of-single-tp-type"/> + + <xsl:variable name="array-name"> + <xsl:choose> + <xsl:when test="//tp:struct[@name=$single-type]"> + <xsl:value-of select="//tp:struct[@name=$single-type]/@array-name"/> + </xsl:when> + <xsl:when test="//tp:mapping[@name=$single-type]"> + <xsl:value-of select="//tp:mapping[@name=$single-type]/@array-name"/> + </xsl:when> + <xsl:when test="//tp:external-type[@name=$single-type]"> + <xsl:value-of select="//tp:external-type[@name=$single-type]/@array-name"/> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="''"/> + </xsl:otherwise> + </xsl:choose> + </xsl:variable> + + <xsl:if test="not(contains('ybnqiuxtdsvog', $type-of-single-tp-type))"> + <xsl:if test="not($array-name) or $array-name=''"> + <xsl:message terminate="yes"> + <xsl:text>No array-name specified for complex type </xsl:text> + <xsl:value-of select="$single-type"/> + <xsl:text>, but array used </xsl:text> + </xsl:message> + </xsl:if> + </xsl:if> + </xsl:template> + + <xsl:template name="parenthesized-tp-type"> + <xsl:if test="@tp:type"> + <xsl:text> (</xsl:text> + <xsl:call-template name="tp-type"> + <xsl:with-param name="tp-type" select="@tp:type"/> + <xsl:with-param name="type" select="@type"/> + </xsl:call-template> + <xsl:text>)</xsl:text> + </xsl:if> + </xsl:template> + + <xsl:template match="tp:member" mode="members-in-docstring"> + <dt xmlns="http://www.w3.org/1999/xhtml"> + <code><xsl:value-of select="@name"/></code> − + <code><xsl:value-of select="@type"/></code> + <xsl:call-template name="parenthesized-tp-type"/> + </dt> + <dd xmlns="http://www.w3.org/1999/xhtml"> + <xsl:choose> + <xsl:when test="tp:docstring"> + <xsl:apply-templates select="tp:docstring" /> + </xsl:when> + <xsl:otherwise> + <em>(undocumented)</em> + </xsl:otherwise> + </xsl:choose> + </dd> + </xsl:template> + + <xsl:template match="arg" mode="parameters-in-docstring"> + <dt xmlns="http://www.w3.org/1999/xhtml"> + <code><xsl:value-of select="@name"/></code> − + <code><xsl:value-of select="@type"/></code> + <xsl:call-template name="parenthesized-tp-type"/> + </dt> + <dd xmlns="http://www.w3.org/1999/xhtml"> + <xsl:apply-templates select="tp:docstring" /> + </dd> + </xsl:template> + + <xsl:template match="arg" mode="returns-in-docstring"> + <dt xmlns="http://www.w3.org/1999/xhtml"> + <xsl:if test="@name"> + <code><xsl:value-of select="@name"/></code> − + </xsl:if> + <code><xsl:value-of select="@type"/></code> + <xsl:call-template name="parenthesized-tp-type"/> + </dt> + <dd xmlns="http://www.w3.org/1999/xhtml"> + <xsl:apply-templates select="tp:docstring"/> + </dd> + </xsl:template> + + <xsl:template match="tp:possible-errors/tp:error"> + <dt xmlns="http://www.w3.org/1999/xhtml"> + <code><xsl:value-of select="@name"/></code> + </dt> + <dd xmlns="http://www.w3.org/1999/xhtml"> + <xsl:variable name="name" select="@name"/> + <xsl:choose> + <xsl:when test="tp:docstring"> + <xsl:apply-templates select="tp:docstring"/> + </xsl:when> + <xsl:when test="//tp:errors/tp:error[concat(../@namespace, '.', translate(@name, ' ', ''))=$name]/tp:docstring"> + <xsl:apply-templates select="//tp:errors/tp:error[concat(../@namespace, '.', translate(@name, ' ', ''))=$name]/tp:docstring"/> <em xmlns="http://www.w3.org/1999/xhtml">(generic description)</em> + </xsl:when> + <xsl:otherwise> + (Undocumented.) + </xsl:otherwise> + </xsl:choose> + </dd> + </xsl:template> + + <xsl:template match="signal"> + + <xsl:call-template name="binding-name-check"/> + + <xsl:if test="not(parent::interface)"> + <xsl:message terminate="yes"> + <xsl:text>ERR: signal </xsl:text> + <xsl:value-of select="@name"/> + <xsl:text> does not have an interface as parent </xsl:text> + </xsl:message> + </xsl:if> + + <xsl:if test="not(@name) or @name = ''"> + <xsl:message terminate="yes"> + <xsl:text>ERR: missing @name on a signal of </xsl:text> + <xsl:value-of select="../@name"/> + <xsl:text> </xsl:text> + </xsl:message> + </xsl:if> + + <xsl:for-each select="arg"> + <xsl:if test="not(@type) or @type = ''"> + <xsl:message terminate="yes"> + <xsl:text>ERR: an arg of signal </xsl:text> + <xsl:value-of select="concat(../../@name, '.', ../@name)"/> + <xsl:text> has no type</xsl:text> + </xsl:message> + </xsl:if> + <xsl:if test="not(@name) or @name = ''"> + <xsl:message terminate="yes"> + <xsl:text>ERR: an arg of signal </xsl:text> + <xsl:value-of select="concat(../../@name, '.', ../@name)"/> + <xsl:text> has no name</xsl:text> + </xsl:message> + </xsl:if> + <xsl:choose> + <xsl:when test="not(@direction)"/> + <xsl:when test="@direction='in'"> + <xsl:message terminate="no"> + <xsl:text>INFO: an arg of signal </xsl:text> + <xsl:value-of select="concat(../../@name, '.', ../@name)"/> + <xsl:text> has unnecessary direction 'in'</xsl:text> + </xsl:message> + </xsl:when> + <xsl:otherwise> + <xsl:message terminate="yes"> + <xsl:text>ERR: an arg of signal </xsl:text> + <xsl:value-of select="concat(../../@name, '.', ../@name)"/> + <xsl:text> has direction other than 'in'</xsl:text> + </xsl:message> + </xsl:otherwise> + </xsl:choose> + </xsl:for-each> + + <div xmlns="http://www.w3.org/1999/xhtml" class="signal"> + <h3 xmlns="http://www.w3.org/1999/xhtml"> + <a name="{concat(../@name, concat('.', @name))}"> + <xsl:value-of select="@name"/> + </a> ( + <xsl:for-each xmlns="" select="arg"> + <xsl:value-of select="@type"/>: <xsl:value-of select="@name"/> + <xsl:if test="position() != last()">, </xsl:if> + </xsl:for-each> + )</h3> + + <div xmlns="http://www.w3.org/1999/xhtml" class="docstring"> + <xsl:apply-templates select="tp:docstring"/> + <xsl:apply-templates select="tp:added"/> + <xsl:apply-templates select="tp:changed"/> + <xsl:apply-templates select="tp:deprecated"/> + </div> + + <xsl:if test="arg"> + <div xmlns="http://www.w3.org/1999/xhtml"> + <h4>Parameters</h4> + <dl xmlns="http://www.w3.org/1999/xhtml"> + <xsl:apply-templates select="arg" mode="parameters-in-docstring"/> + </dl> + </div> + </xsl:if> + </div> + </xsl:template> + + <xsl:output method="xml" indent="no" encoding="ascii" + omit-xml-declaration="yes" + doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd" + doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN" /> + + <xsl:template match="/tp:spec"> + <html xmlns="http://www.w3.org/1999/xhtml"> + <head> + <title> + <xsl:value-of select="tp:title"/> + <xsl:if test="tp:version"> + <xsl:text> version </xsl:text> + <xsl:value-of select="tp:version"/> + </xsl:if> + </title> + <style type="text/css"> + + body { + font-family: sans-serif; + margin: 2em; + height: 100%; + font-size: 1.2em; + } + h1 { + padding-top: 5px; + padding-bottom: 5px; + font-size: 1.6em; + background: #dadae2; + } + h2 { + font-size: 1.3em; + } + h3 { + font-size: 1.2em; + } + a:link, a:visited, a:link:hover, a:visited:hover { + font-weight: bold; + } + .topbox { + padding-top: 10px; + padding-left: 10px; + border-bottom: black solid 1px; + padding-bottom: 10px; + background: #dadae2; + font-size: 2em; + font-weight: bold; + color: #5c5c5c; + } + .topnavbox { + padding-left: 10px; + padding-top: 5px; + padding-bottom: 5px; + background: #abacba; + border-bottom: black solid 1px; + font-size: 1.2em; + } + .topnavbox a{ + color: black; + font-weight: normal; + } + .sidebar { + float: left; + /* width:9em; + border-right:#abacba solid 1px; + border-left: #abacba solid 1px; + height:100%; */ + border: #abacba solid 1px; + padding-left: 10px; + margin-left: 10px; + padding-right: 10px; + margin-right: 10px; + color: #5d5d5d; + background: #dadae2; + } + .sidebar a { + text-decoration: none; + border-bottom: #e29625 dotted 1px; + color: #e29625; + font-weight: normal; + } + .sidebar h1 { + font-size: 1.2em; + color: black; + } + .sidebar ul { + padding-left: 25px; + padding-bottom: 10px; + border-bottom: #abacba solid 1px; + } + .sidebar li { + padding-top: 2px; + padding-bottom: 2px; + } + .sidebar h2 { + font-style:italic; + font-size: 0.81em; + padding-left: 5px; + padding-right: 5px; + font-weight: normal; + } + .date { + font-size: 0.6em; + float: right; + font-style: italic; + } + .method, .signal, .property { + margin-left: 1em; + margin-right: 4em; + } + .rationale { + font-style: italic; + border-left: 0.25em solid #808080; + padding-left: 0.5em; + } + + .added { + color: #006600; + background: #ffffff; + } + .deprecated { + color: #ff0000; + background: #ffffff; + } + table, tr, td, th { + border: 1px solid #666; + } + + </style> + </head> + <body> + <h1 class="topbox"> + <xsl:value-of select="tp:title" /> + </h1> + <xsl:if test="tp:version"> + <h2>Version <xsl:value-of select="string(tp:version)"/></h2> + </xsl:if> + <xsl:apply-templates select="tp:copyright"/> + <xsl:apply-templates select="tp:license"/> + <xsl:apply-templates select="tp:docstring"/> + + <h2>Interfaces</h2> + <ul> + <xsl:for-each select="//node/interface"> + <li><code><a href="#{@name}"><xsl:value-of select="@name"/></a></code></li> + </xsl:for-each> + </ul> + + <xsl:apply-templates select="//node"/> + <xsl:apply-templates select="tp:generic-types"/> + <xsl:apply-templates select="tp:errors"/> + + <h1>Index</h1> + <h2>Index of interfaces</h2> + <ul> + <xsl:for-each select="//node/interface"> + <li><code><a href="#{@name}"><xsl:value-of select="@name"/></a></code></li> + </xsl:for-each> + </ul> + <h2>Index of types</h2> + <ul> + <xsl:for-each select="//tp:simple-type | //tp:enum | //tp:flags | //tp:mapping | //tp:struct | //tp:external-type"> + <xsl:sort select="@name"/> + <li> + <code> + <a href="#type-{@name}"> + <xsl:value-of select="@name"/> + </a> + </code> + <xsl:apply-templates mode="in-index" select="."/> + </li> + </xsl:for-each> + </ul> + </body> + </html> + </xsl:template> + + <xsl:template match="node"> + <xsl:apply-templates /> + </xsl:template> + + <xsl:template match="text()"> + <xsl:if test="normalize-space(.) != ''"> + <xsl:message terminate="yes"> + <xsl:text>Stray text: {{{</xsl:text> + <xsl:value-of select="." /> + <xsl:text>}}} </xsl:text> + </xsl:message> + </xsl:if> + </xsl:template> + + <xsl:template match="*"> + <xsl:message terminate="yes"> + <xsl:text>Unrecognised element: {</xsl:text> + <xsl:value-of select="namespace-uri(.)" /> + <xsl:text>}</xsl:text> + <xsl:value-of select="local-name(.)" /> + <xsl:text> </xsl:text> + </xsl:message> + </xsl:template> +</xsl:stylesheet> + +<!-- vim:set sw=2 sts=2 et: --> diff --git a/doc/dbus-api/tools/specparser.py b/doc/dbus-api/tools/specparser.py new file mode 100644 index 0000000000..4208ad4104 --- /dev/null +++ b/doc/dbus-api/tools/specparser.py @@ -0,0 +1,866 @@ +# +# specparser.py +# +# Reads in a spec document and generates pretty data structures from it. +# +# Copyright (C) 2009 Collabora Ltd. +# +# This library is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or (at +# your option) any later version. +# +# This library is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License +# for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this library; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# Authors: Davyd Madeley <davyd.madeley@collabora.co.uk> +# + +import sys +import xml.dom.minidom + +import xincludator + +XMLNS_TP = 'http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0' + +class UnknownAccess(Exception): pass +class UnknownDirection(Exception): pass +class UnknownType(Exception): pass +class UnnamedItem(Exception): pass +class UntypedItem(Exception): pass +class UnsupportedArray(Exception): pass + +def getText(dom): + try: + if dom.childNodes[0].nodeType == dom.TEXT_NODE: + return dom.childNodes[0].data + else: + return '' + except IndexError: + return '' + +def getChildrenByName(dom, namespace, name): + return filter(lambda n: n.nodeType == n.ELEMENT_NODE and \ + n.namespaceURI == namespace and \ + n.localName == name, + dom.childNodes) + +def build_name(namespace, name): + """Returns a name by appending `name' to the namespace of this object. + """ + return '.'.join( + filter(lambda n: n is not None and n != '', + [namespace, name.replace(' ', '')]) + ) + +class Base(object): + """The base class for any type of XML node in the spec that implements the + 'name' attribute. + + Don't instantiate this class directly. + """ + devhelp_name = "" + + def __init__(self, parent, namespace, dom): + self.short_name = name = dom.getAttribute('name') + self.namespace = namespace + self.name = build_name(namespace, name) + self.parent = parent + + try: + self.docstring = getChildrenByName(dom, XMLNS_TP, 'docstring')[0] + except IndexError: + self.docstring = None + + try: + self.added = getChildrenByName(dom, XMLNS_TP, 'added')[0] + except IndexError: + self.added = None + + try: + self.deprecated = getChildrenByName(dom, XMLNS_TP, 'deprecated')[0] + except IndexError: + self.deprecated = None + + self.changed = getChildrenByName(dom, XMLNS_TP, 'changed') + + self.validate() + + def validate(self): + if self.short_name == '': + raise UnnamedItem("Node %s of %s has no name" % ( + self.__class__.__name__, self.parent)) + + def get_type_name(self): + return self.__class__.__name__ + + def get_spec(self): + return self.parent.get_spec() + + def get_root_namespace(self): + return self.get_interface().name + + def get_interface(self): + return self.parent.get_interface() + + def get_url(self): + return "%s#%s" % (self.get_interface().get_url(), self.name) + + def _get_generic_with_ver(self, nnode, htmlclass, txt): + if nnode is None: + return '' + else: + # make a copy of this node, turn it into a HTML <div> tag + node = nnode.cloneNode(True) + node.tagName = 'div' + node.baseURI = None + node.setAttribute('class', htmlclass) + + try: + node.removeAttribute('version') + + span = xml.dom.minidom.parseString( + ('<span class="version">%s\n</span>' % txt) % + nnode.getAttribute('version')).firstChild + node.insertBefore(span, node.firstChild) + except xml.dom.NotFoundErr: + print >> sys.stderr, \ + 'WARNING: %s was %s, but gives no version' % (self, htmlclass) + + self._convert_to_html(node) + + return node.toxml().encode('ascii', 'xmlcharrefreplace') + + def get_added(self): + return self._get_generic_with_ver(self.added, 'added', + "Added in %s.") + + def get_deprecated(self): + return self._get_generic_with_ver(self.deprecated, 'deprecated', + "Deprecated since %s.") + + def get_changed(self): + return '\n'.join(map(lambda n: + self._get_generic_with_ver(n, 'changed', "Changed in %s."), + self.changed)) + + def get_docstring(self): + """Get the docstring for this node, but do node substitution to + rewrite types, interfaces, etc. as links. + """ + if self.docstring is None: + return '' + else: + # make a copy of this node, turn it into a HTML <div> tag + node = self.docstring.cloneNode(True) + node.tagName = 'div' + node.baseURI = None + node.setAttribute('class', 'docstring') + + self._convert_to_html(node) + + return node.toxml().encode('ascii', 'xmlcharrefreplace') + + def _convert_to_html(self, node): + spec = self.get_spec() + namespace = self.get_root_namespace() + + # rewrite <tp:rationale> + for n in node.getElementsByTagNameNS(XMLNS_TP, 'rationale'): + n.tagName = 'div' + n.namespaceURI = None + n.setAttribute('class', 'rationale') + + # rewrite <tp:type> + for n in node.getElementsByTagNameNS(XMLNS_TP, 'type'): + t = spec.lookup_type(getText(n)) + n.tagName = 'a' + n.namespaceURI = None + n.setAttribute('href', t.get_url()) + + # rewrite <tp:member-ref> + for n in node.getElementsByTagNameNS(XMLNS_TP, 'member-ref'): + key = getText(n) + try: + o = spec.lookup(key, namespace=namespace) + except KeyError: + print >> sys.stderr, \ + "WARNING: Key '%s' not known in namespace '%s'" % ( + key, namespace) + continue + + n.tagName = 'a' + n.namespaceURI = None + n.setAttribute('href', o.get_url()) + n.setAttribute('title', o.get_title()) + + # rewrite <tp:dbus-ref> + for n in node.getElementsByTagNameNS(XMLNS_TP, 'dbus-ref'): + namespace = n.getAttribute('namespace') + key = getText(n) + try: + o = spec.lookup(key, namespace=namespace) + except KeyError: + print >> sys.stderr, \ + "WARNING: Key '%s' not known in namespace '%s'" % ( + key, namespace) + continue + + n.tagName = 'a' + n.namespaceURI = None + n.setAttribute('href', o.get_url()) + n.setAttribute('title', o.get_title()) + + def get_title(self): + return '%s %s' % (self.get_type_name(), self.name) + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, self.name) + +class PossibleError(Base): + def __init__(self, parent, namespace, dom): + super(PossibleError, self).__init__(parent, namespace, dom) + + def get_error(self): + spec = self.get_spec() + try: + return spec.errors[self.name] + except KeyError: + return External(self.name) + + def get_url(self): + return self.get_error().get_url() + + def get_title(self): + return self.get_error().get_title() + + def get_docstring(self): + d = super(PossibleError, self).get_docstring() + if d == '': + return self.get_error().get_docstring() + else: + return d + +class Method(Base): + devhelp_name = "function" + + def __init__(self, parent, namespace, dom): + super(Method, self).__init__(parent, namespace, dom) + + args = build_list(self, Arg, self.name, + dom.getElementsByTagName('arg')) + + # separate arguments as input and output arguments + self.in_args = filter(lambda a: a.direction == Arg.DIRECTION_IN, args) + self.out_args = filter(lambda a: a.direction == Arg.DIRECTION_OUT, args) + + for arg in args: + if arg.direction == Arg.DIRECTION_IN or \ + arg.direction == Arg.DIRECTION_OUT: + continue + + print >> sys.stderr, "WARNING: '%s' of method '%s' does not specify a suitable direction" % (arg, self) + + self.possible_errors = build_list(self, PossibleError, None, + dom.getElementsByTagNameNS(XMLNS_TP, 'error')) + + def get_in_args(self): + return ', '.join(map(lambda a: a.spec_name(), self.in_args)) + + def get_out_args(self): + if len(self.out_args) > 0: + return ', '.join(map(lambda a: a.spec_name(), self.out_args)) + else: + return 'nothing' + +class Typed(Base): + """The base class for all typed nodes (i.e. Arg and Property). + + Don't instantiate this class directly. + """ + + def __init__(self, parent, namespace, dom): + super(Typed, self).__init__(parent, namespace, dom) + + self.type = dom.getAttributeNS(XMLNS_TP, 'type') + self.dbus_type = dom.getAttribute('type') + + # check we have a dbus type + if self.dbus_type == '': + raise UntypedItem("Node referred to by '%s' has no type" % dom.toxml()) + def get_type(self): + return self.get_spec().lookup_type(self.type) + + def get_type_url(self): + t = self.get_type() + if t is None: return '' + else: return t.get_url() + + def get_type_title(self): + t = self.get_type() + if t is None: return '' + else: return t.get_title() + + def spec_name(self): + return '%s: %s' % (self.dbus_type, self.short_name) + + def __repr__(self): + return '%s(%s:%s)' % (self.__class__.__name__, self.name, self.dbus_type) + +class Property(Typed): + ACCESS_READ = 1 + ACCESS_WRITE = 2 + + ACCESS_READWRITE = ACCESS_READ | ACCESS_WRITE + + def __init__(self, parent, namespace, dom): + super(Property, self).__init__(parent, namespace, dom) + + access = dom.getAttribute('access') + if access == 'read': + self.access = self.ACCESS_READ + elif access == 'write': + self.access = self.ACCESS_WRITE + elif access == 'readwrite': + self.access = self.ACCESS_READWRITE + else: + raise UnknownAccess("Unknown access '%s' on %s" % (access, self)) + + def get_access(self): + if self.access & self.ACCESS_READ and self.access & self.ACCESS_WRITE: + return 'Read/Write' + elif self.access & self.ACCESS_READ: + return 'Read only' + elif self.access & self.ACCESS_WRITE: + return 'Write only' + +class AwkwardTelepathyProperty(Typed): + def get_type_name(self): + return 'Telepathy Property' + +class Arg(Typed): + DIRECTION_IN, DIRECTION_OUT, DIRECTION_UNSPECIFIED = range(3) + + def __init__(self, parent, namespace, dom): + super(Arg, self).__init__(parent, namespace, dom) + + direction = dom.getAttribute('direction') + if direction == 'in': + self.direction = self.DIRECTION_IN + elif direction == 'out': + self.direction = self.DIRECTION_OUT + elif direction == '': + self.direction = self.DIRECTION_UNSPECIFIED + else: + raise UnknownDirection("Unknown direction '%s' on %s" % ( + direction, self.parent)) + +class Signal(Base): + def __init__(self, parent, namespace, dom): + super(Signal, self).__init__(parent, namespace, dom) + + self.args = build_list(self, Arg, self.name, + dom.getElementsByTagName('arg')) + + for arg in self.args: + if arg.direction == Arg.DIRECTION_UNSPECIFIED: + continue + + print >> sys.stderr, "WARNING: '%s' of signal '%s' does not specify a suitable direction" % (arg, self) + + def get_args(self): + return ', '.join(map(lambda a: a.spec_name(), self.args)) + +class External(object): + """External objects are objects that are referred to in another spec. + + We have to attempt to look them up if at all possible. + """ + + def __init__(self, name): + self.name = self.short_name = name + + def get_url(self): + return None + + def get_title(self): + return 'External %s' % self.name + + def get_docstring(self): + return None + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, self.name) + +class Interface(Base): + def __init__(self, parent, namespace, dom, spec_namespace): + super(Interface, self).__init__(parent, namespace, dom) + + # If you're writing a spec with more than one top-level namespace, you + # probably want to replace spec_namespace with a list. + if self.name.startswith(spec_namespace + "."): + self.short_name = self.name[len(spec_namespace) + 1:] + else: + self.short_name = self.name + + # build lists of methods, etc., in this interface + self.methods = build_list(self, Method, self.name, + dom.getElementsByTagName('method')) + self.properties = build_list(self, Property, self.name, + dom.getElementsByTagName('property')) + self.signals = build_list(self, Signal, self.name, + dom.getElementsByTagName('signal')) + self.tpproperties = build_list(self, AwkwardTelepathyProperty, + self.name, dom.getElementsByTagNameNS(XMLNS_TP, 'property')) + self.handler_capability_tokens = build_list(self, + HandlerCapabilityToken, self.name, + dom.getElementsByTagNameNS(XMLNS_TP, + 'handler-capability-token')) + self.contact_attributes = build_list(self, ContactAttribute, self.name, + dom.getElementsByTagNameNS(XMLNS_TP, 'contact-attribute')) + + # build a list of types in this interface + self.types = parse_types(self, dom, self.name) + + # find out if this interface causes havoc + self.causes_havoc = dom.getAttributeNS(XMLNS_TP, 'causes-havoc') + if self.causes_havoc == '': self.causes_havoc = None + + # find out what we're required to also implement + self.requires = map(lambda n: n.getAttribute('interface'), + getChildrenByName(dom, XMLNS_TP, 'requires')) + + def get_interface(self): + return self + + def get_requires(self): + spec = self.get_spec() + + def lookup(r): + try: + return spec.lookup(r) + except KeyError: + return External(r) + + return map(lookup, self.requires) + + def get_url(self): + return '%s.html' % self.name + +class Error(Base): + def get_url(self): + return 'errors.html#%s' % self.name + + def get_root_namespace(self): + return self.namespace + +class DBusList(object): + """Stores a list of a given DBusType. Provides some basic validation to + determine whether or not the type is sane. + """ + def __init__(self, child): + self.child = child + + if isinstance(child, DBusType): + self.ultimate = child + self.depth = 1 + + if self.child.array_name == '': + raise UnsupportedArray("Type '%s' does not support being " + "used in an array" % self.child.name) + else: + self.name = build_name(self.child.namespace, + self.child.array_name) + self.short_name = self.child.array_name + + elif isinstance(child, DBusList): + self.ultimate = child.ultimate + self.depth = child.depth + 1 + self.name = self.child.name + '_List' + self.short_name = self.child.short_name + '_List' + + # check that our child can operate at this depth + maxdepth = int(self.ultimate.array_depth) + if self.depth > maxdepth: + raise TypeError("Type '%s' has exceeded its maximum depth (%i)" % (self, maxdepth)) + + else: + raise TypeError("DBusList can contain only a DBusType or DBusList not '%s'" % child) + + self.dbus_type = 'a' + self.child.dbus_type + + def get_url(self): + return self.ultimate.get_url() + + def get_title(self): + return "Array of %s" % self.child.get_title() + + def __repr__(self): + return 'Array(%s)' % self.child + +class DBusType(Base): + """The base class for all D-Bus types referred to in the spec. + + Don't instantiate this class directly. + """ + + devhelp_name = "typedef" + + def __init__(self, parent, namespace, dom): + super(DBusType, self).__init__(parent, namespace, dom) + + self.dbus_type = dom.getAttribute('type') + self.array_name = dom.getAttribute('array-name') + self.array_depth = dom.getAttribute('array-depth') + + def get_root_namespace(self): + return self.namespace + + def get_breakdown(self): + return '' + + def get_url(self): + if isinstance(self.parent, Interface): + html = self.parent.get_url() + else: + html = 'generic-types.html' + + return '%s#%s' % (html, self.name) + +class SimpleType(DBusType): + def get_type_name(self): + return 'Simple Type' + +class ExternalType(DBusType): + def __init__(self, parent, namespace, dom): + super(ExternalType, self).__init__(parent, namespace, dom) + + # FIXME: until we are able to cross reference external types to learn + # about their array names, we're just going to assume they work like + # this + self.array_name = self.short_name + '_List' + + def get_type_name(self): + return 'External Type' + +class StructLike(DBusType): + """Base class for all D-Bus types that look kind of like Structs + + Don't instantiate this class directly. + """ + + class StructMember(Typed): + def get_root_namespace(self): + return self.parent.get_root_namespace() + + def __init__(self, parent, namespace, dom): + super(StructLike, self).__init__(parent, namespace, dom) + + self.members = build_list(self, StructLike.StructMember, None, + dom.getElementsByTagNameNS(XMLNS_TP, 'member')) + + def get_breakdown(self): + str = '' + str += '<ul>\n' + for member in self.members: + # attempt to lookup the member up in the type system + t = member.get_type() + + str += '<li>%s — %s' % (member.name, member.dbus_type) + if t: str += ' (<a href="%s" title="%s">%s</a>)' % ( + t.get_url(), t.get_title(), t.short_name) + str += '</li>\n' + str += member.get_docstring() + str += '</ul>\n' + + return str + +class Mapping(StructLike): + def __init__(self, parent, namespace, dom): + super(Mapping, self).__init__(parent, namespace, dom) + + # rewrite the D-Bus type + self.dbus_type = 'a{%s}' % ''.join(map(lambda m: m.dbus_type, self.members)) + +class Struct(StructLike): + + devhelp_name = "struct" + + def __init__(self, parent, namespace, dom): + super(Struct, self).__init__(parent, namespace, dom) + + # rewrite the D-Bus type + self.dbus_type = '(%s)' % ''.join(map(lambda m: m.dbus_type, self.members)) + +class EnumLike(DBusType): + """Base class for all D-Bus types that look kind of like Enums + + Don't instantiate this class directly. + """ + class EnumValue(Base): + def __init__(self, parent, namespace, dom): + super(EnumLike.EnumValue, self).__init__(parent, namespace, dom) + + # rewrite self.name + self.short_name = dom.getAttribute('suffix') + self.name = build_name(namespace, self.short_name) + + self.value = dom.getAttribute('value') + + super(EnumLike.EnumValue, self).validate() + + def validate(self): + pass + + def get_root_namespace(self): + return self.parent.get_root_namespace() + + def get_breakdown(self): + str = '' + str += '<ul>\n' + for value in self.values: + # attempt to lookup the member.name as a type in the type system + str += '<li>%s (%s)</li>\n' % (value.short_name, value.value) + str += value.get_added() + str += value.get_changed() + str += value.get_deprecated() + str += value.get_docstring() + str += '</ul>\n' + + return str + +class Enum(EnumLike): + + devhelp_name = "enum" + + def __init__(self, parent, namespace, dom): + super(Enum, self).__init__(parent, namespace, dom) + + self.values = build_list(self, EnumLike.EnumValue, self.name, + dom.getElementsByTagNameNS(XMLNS_TP, 'enumvalue')) + +class Flags(EnumLike): + def __init__(self, parent, namespace, dom): + super(Flags, self).__init__(parent, namespace, dom) + + self.values = build_list(self, EnumLike.EnumValue, self.name, + dom.getElementsByTagNameNS(XMLNS_TP, 'flag')) + self.flags = self.values # in case you're looking for it + +class TokenBase(Base): + + devhelp_name = "macro" # it's a constant, which is near enough... + separator = '/' + + def __init__(self, parent, namespace, dom): + super(TokenBase, self).__init__(parent, namespace, dom) + self.name = namespace + '/' + self.short_name + +class ContactAttribute(TokenBase, Typed): + + def get_type_name(self): + return 'Contact Attribute' + +class HandlerCapabilityToken(TokenBase): + + def get_type_name(self): + return 'Handler Capability Token' + + def __init__(self, parent, namespace, dom): + super(HandlerCapabilityToken, self).__init__(parent, namespace, dom) + + is_family = dom.getAttribute('is-family') + assert is_family in ('yes', 'no', '') + self.is_family = (is_family == 'yes') + +class SectionBase(object): + """A SectionBase is an abstract base class for any type of node that can + contain a <tp:section>, which means the top-level Spec object, or any + Section object. + + It should not be instantiated directly. + """ + + def __init__(self, dom, spec_namespace): + + self.items = [] + + def recurse(nodes): + # iterate through the list of child nodes + for node in nodes: + if node.nodeType != node.ELEMENT_NODE: continue + + if node.tagName == 'node': + # recurse into this level for interesting items + recurse(node.childNodes) + elif node.namespaceURI == XMLNS_TP and \ + node.localName == 'section': + self.items.append(Section(self, None, node, + spec_namespace)) + elif node.tagName == 'interface': + self.items.append(Interface(self, None, node, + spec_namespace)) + + recurse(dom.childNodes) + +class Section(Base, SectionBase): + def __init__(self, parent, namespace, dom, spec_namespace): + Base.__init__(self, parent, namespace, dom) + SectionBase.__init__(self, dom, spec_namespace) + + def get_root_namespace(self): + return None + +class Spec(SectionBase): + def __init__(self, dom, spec_namespace): + # build a dictionary of errors in this spec + try: + errorsnode = dom.getElementsByTagNameNS(XMLNS_TP, 'errors')[0] + self.errors = build_dict(self, Error, + errorsnode.getAttribute('namespace'), + errorsnode.getElementsByTagNameNS(XMLNS_TP, 'error')) + except IndexError: + self.errors = {} + + # build a list of generic types + self.generic_types = reduce (lambda a, b: a + b, + map(lambda l: parse_types(self, l), + dom.getElementsByTagNameNS(XMLNS_TP, 'generic-types')), + []) + + # create a top-level section for this Spec + SectionBase.__init__(self, dom.documentElement, spec_namespace) + + # build a list of interfaces in this spec + self.interfaces = [] + def recurse(items): + for item in items: + if isinstance(item, Section): recurse(item.items) + elif isinstance(item, Interface): self.interfaces.append(item) + recurse(self.items) + + # build a giant dictionary of everything (interfaces, methods, signals + # and properties); also build a dictionary of types + self.everything = {} + self.types = {} + + for type in self.generic_types: self.types[type.short_name] = type + + for interface in self.interfaces: + self.everything[interface.name] = interface + + for method in interface.methods: + self.everything[method.name] = method + for signal in interface.signals: + self.everything[signal.name] = signal + for property in interface.properties: + self.everything[property.name] = property + for property in interface.tpproperties: + self.everything[property.name] = property + for token in interface.contact_attributes: + self.everything[token.name] = token + for token in interface.handler_capability_tokens: + self.everything[token.name] = token + + for type in interface.types: + self.types[type.short_name] = type + + # get some extra bits for the HTML + node = dom.getElementsByTagNameNS(XMLNS_TP, 'spec')[0] + self.title = getText(getChildrenByName(node, XMLNS_TP, 'title')[0]) + + try: + self.version = getText(getChildrenByName(node, XMLNS_TP, 'version')[0]) + except IndexError: + self.version = None + + self.copyrights = map(getText, + getChildrenByName(node, XMLNS_TP, 'copyright')) + + try: + license = getChildrenByName(node, XMLNS_TP, 'license')[0] + license.tagName = 'div' + license.namespaceURI = None + license.setAttribute('class', 'license') + self.license = license.toxml() + except IndexError: + self.license = '' + + # FIXME: we need to check all args for type correctness + + def get_spec(self): + return self + + def lookup(self, name, namespace=None): + key = build_name(namespace, name) + return self.everything[key] + + def lookup_type(self, type_): + if type_.endswith('[]'): + return DBusList(self.lookup_type(type_[:-2])) + + if type_ == '': return None + elif type_ in self.types: + return self.types[type_] + + raise UnknownType("Type '%s' is unknown" % type_) + + def __repr__(self): + return '%s(%s)' % (self.__class__.__name__, self.title) + +def build_dict(parent, type_, namespace, nodes): + """Build a dictionary of D-Bus names to Python objects representing that + name using the XML node for that item in the spec. + + e.g. 'org.freedesktop.Telepathy.Channel' : Interface(Channel) + + Works for any Python object inheriting from 'Base' whose XML node + implements the 'name' attribute. + """ + + def build_tuple(node): + o = type_(parent, namespace, node) + return(o.name, o) + + return dict(build_tuple(n) for n in nodes) + +def build_list(parent, type_, namespace, nodes): + return map(lambda node: type_(parent, namespace, node), nodes) + +def parse_types(parent, dom, namespace = None): + """Parse all of the types of type nodes mentioned in 't' from the node + 'dom' and insert them into the dictionary 'd'. + """ + t = [ + (SimpleType, 'simple-type'), + (Enum, 'enum'), + (Flags, 'flags'), + (Mapping, 'mapping'), + (Struct, 'struct'), + (ExternalType, 'external-type'), + ] + + types = [] + + for (type_, tagname) in t: + types += build_list(parent, type_, namespace, + dom.getElementsByTagNameNS(XMLNS_TP, tagname)) + + return types + +def parse(filename, spec_namespace): + dom = xml.dom.minidom.parse(filename) + xincludator.xincludate(dom, filename) + + spec = Spec(dom, spec_namespace) + + return spec + +if __name__ == '__main__': + parse(sys.argv[1]) diff --git a/doc/dbus-api/tools/xincludator.py b/doc/dbus-api/tools/xincludator.py new file mode 100644 index 0000000000..63e106ace1 --- /dev/null +++ b/doc/dbus-api/tools/xincludator.py @@ -0,0 +1,39 @@ +#!/usr/bin/python + +from sys import argv, stdout, stderr +import codecs, locale +import os +import xml.dom.minidom + +stdout = codecs.getwriter('utf-8')(stdout) + +NS_XI = 'http://www.w3.org/2001/XInclude' + +def xincludate(dom, base, dropns = []): + remove_attrs = [] + for i in xrange(dom.documentElement.attributes.length): + attr = dom.documentElement.attributes.item(i) + if attr.prefix == 'xmlns': + if attr.localName in dropns: + remove_attrs.append(attr) + else: + dropns.append(attr.localName) + for attr in remove_attrs: + dom.documentElement.removeAttributeNode(attr) + for include in dom.getElementsByTagNameNS(NS_XI, 'include'): + href = include.getAttribute('href') + # FIXME: assumes Unixy paths + filename = os.path.join(os.path.dirname(base), href) + subdom = xml.dom.minidom.parse(filename) + xincludate(subdom, filename, dropns) + if './' in href: + subdom.documentElement.setAttribute('xml:base', href) + include.parentNode.replaceChild(subdom.documentElement, include) + +if __name__ == '__main__': + argv = argv[1:] + dom = xml.dom.minidom.parse(argv[0]) + xincludate(dom, argv[0]) + xml = dom.toxml() + stdout.write(xml) + stdout.write('\n') diff --git a/doc/doxygen/Makefile.am b/doc/doxygen/Makefile.am new file mode 100644 index 0000000000..c6abbec8cd --- /dev/null +++ b/doc/doxygen/Makefile.am @@ -0,0 +1,56 @@ +if ENABLE_DOXYGEN + +html_parent_dir = . +HTML_HEADER = +HTML_FOOTER = + +all: doxygen-trac + +.PHONY: doxygen-trac + +doxygen-trac: clean core-doc-trac gtk-gui-doc-trac + +%-doc-trac : %-doc + ln -sf ${html_parent_dir}/$< ${html_parent_dir}/$</html + +doc: clean core-doc gtk-gui-doc + +%-doc : %-doc.cfg + rm -rf $@/ refman.pdf + $(DOXYGEN) $< +# $(MAKE) -C latex/ +# mv latex/refman.pdf ./refman.pdf + + +clean-local: + echo "clean-local: " && pwd + rm -rf latex/ + rm -rf ${html_parent_dir}/*-doc/ + rm -f *~ + rm -f doxygen.log + rm -f doxygen.cfg + +maintainer-clean-local: clean-local + echo "maintainer-clean-local: " && pwd + rm -rf html refman.pdf + +## We borrow guile's convention and use @-...-@ as the substitution +## brackets here, instead of the usual @...@. This prevents autoconf +## from substituting the values directly into the left-hand sides of +## the sed substitutions. +%.cfg : %.cfg.in + rm -f $@.tmp + sed < $< > $@.tmp \ + -e 's:@-top_srcdir-@:${top_srcdir}:g' + sed < $@.tmp > $@.tmp2 \ + -e 's:@-html_dir-@:${html_parent_dir}/$*:g' + rm $@.tmp + sed < $@.tmp2 > $@.tmp3 \ + -e 's:@-html_header-@:${HTML_HEADER}:g' + rm $@.tmp2 + sed < $@.tmp3 > $@.tmp4 \ + -e 's:@-html_footer-@:${HTML_FOOTER}:g' + rm $@.tmp3 + mv $@.tmp4 $@ + +endif # ENABLE_DOXYGEN diff --git a/doc/doxygen/blank.html b/doc/doxygen/blank.html new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/doc/doxygen/blank.html @@ -0,0 +1 @@ + diff --git a/doc/doxygen/core-doc.cfg.in b/doc/doxygen/core-doc.cfg.in new file mode 100644 index 0000000000..764a06e924 --- /dev/null +++ b/doc/doxygen/core-doc.cfg.in @@ -0,0 +1,1716 @@ +# Doxyfile 1.7.4 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" "). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded +# by quotes) that should identify the project. + +PROJECT_NAME = "Ring Daemon" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = 2.0.1 + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = YES + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 8 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this +# tag. The format is ext=language, where ext is a file extension, and language +# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, +# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions +# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = YES + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols + +SYMBOL_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = YES + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = YES + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command <command> <input-file>, where <command> is the value of +# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. The create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = NO + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = ../../src + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = *.cpp *.h + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command <filter> <input-file>, where <filter> +# is the value of the INPUT_FILTER tag, and <input-file> is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = YES + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = YES + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = NO + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = @-html_dir-@ + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is adviced to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the stylesheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = YES + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = doc + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# <a href="http://doc.trolltech.com/qthelpproject.html#custom-filters"> +# Qt Help Project / Custom Filters</a>. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# <a href="http://doc.trolltech.com/qthelpproject.html#filter-attributes"> +# Qt Help Project / Filter Attributes</a>. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. + +GENERATE_TREEVIEW = YES + +# By enabling USE_INLINE_TREES, doxygen will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list. + +USE_INLINE_TREES = NO + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the +# mathjax.org site, so you can quickly see the result without installing +# MathJax, but it is strongly recommended to install a local copy of MathJax +# before deployment. + +MATHJAX_RELPATH = http://www.mathjax.org/mathjax + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = NO + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = NO + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = letter + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load stylesheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = NO + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = NO + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. +# Optionally an initial location of the external documentation +# can be added for each tagfile. The format of a tag file without +# this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths or +# URLs. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# Note that each tag file must have a unique name +# (where the name does NOT include the path) +# If a tag file is not located in the directory in which doxygen +# is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = NO + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will write a font called Helvetica to the output +# directory and reference it in all dot files that doxygen generates. +# When you want a differently looking font you can specify the font name +# using DOT_FONTNAME. You need to make sure dot is able to find the font, +# which can be done by putting it in a standard location or by setting the +# DOTFONTPATH environment variable or by setting DOT_FONTPATH to the directory +# containing the font. + +DOT_FONTNAME = Helvetica + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# the CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = YES + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = YES + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/doc/doxygen/gtk-gui-doc.cfg.in b/doc/doxygen/gtk-gui-doc.cfg.in new file mode 100644 index 0000000000..b887ecc989 --- /dev/null +++ b/doc/doxygen/gtk-gui-doc.cfg.in @@ -0,0 +1,231 @@ +# Doxyfile 1.5.3 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +DOXYFILE_ENCODING = UTF-8 +PROJECT_NAME = Ring GTK GUI +PROJECT_NUMBER = +OUTPUT_DIRECTORY = +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = +ALWAYS_DETAILED_SEC = YES +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = NO +STRIP_FROM_PATH = +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +DETAILS_AT_TOP = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 8 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = YES +OPTIMIZE_OUTPUT_JAVA = NO +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +DISTRIBUTE_GROUP_DOC = YES +SUBGROUPING = YES +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = YES +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = YES +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = YES +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = NO +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = YES +SORT_BY_SCOPE_NAME = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_DIRECTORIES = NO +FILE_VERSION_FILTER = +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = YES +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_FORMAT = +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = @-top_srcdir-@/sflphone-gtk/src +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = +RECURSIVE = NO +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXCLUDE_SYMBOLS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = YES +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = YES +REFERENCES_RELATION = YES +REFERENCES_LINK_SOURCE = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = NO +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = @-html_dir-@ +HTML_FILE_EXTENSION = +HTML_HEADER = @-html_header-@ +HTML_FOOTER = @-html_footer-@ +HTML_STYLESHEET = +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +HTML_DYNAMIC_SECTIONS = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = YES +TREEVIEW_WIDTH = 250 +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = NO +LATEX_OUTPUT = +LATEX_CMD_NAME = +MAKEINDEX_CMD_NAME = +COMPACT_LATEX = NO +PAPER_TYPE = letter +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = NO +RTF_OUTPUT = +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = +MAN_EXTENSION = +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +MSCGEN_PATH = +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = NO +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +TEMPLATE_RELATIONS = YES +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +DOT_PATH = +DOTFILE_DIRS = +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = NO diff --git a/doc/general_component_diagram.png b/doc/general_component_diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..fd5b11d7fdb3dd8aee972afdcc19a8bcc2998c1f GIT binary patch literal 67769 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 D?(02t literal 0 HcmV?d00001 diff --git a/extras/tools/.gitignore b/extras/tools/.gitignore new file mode 100644 index 0000000000..6c25fda402 --- /dev/null +++ b/extras/tools/.gitignore @@ -0,0 +1,6 @@ +Makefile +build/ +.* +*/ +*.tar.gz +*.tar.bz2 diff --git a/extras/tools/bootstrap b/extras/tools/bootstrap new file mode 100755 index 0000000000..08c6fa83bc --- /dev/null +++ b/extras/tools/bootstrap @@ -0,0 +1,118 @@ +#!/bin/sh +# Copyright © 2011 Rafaël Carré +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. + +export LC_ALL= +NEEDED= + +if [ ! -f tools.mak ] +then + echo "You must run me in ./extras/tools !" + exit 1 +fi + +check_tar() { +if ! tar PcJ /dev/null >/dev/null 2>&1 +then + echo "tar doesn't support xz (J option)" + NEEDED="$NEEDED .tar .xz" +fi +} + +check_sed() { +tmp="`pwd`/check_sed" +trap "rm $tmp{,-e} 2>/dev/null" EXIT +echo "test file for GNU sed" > $tmp +if ! sed -i -e 's/sed//' $tmp >/dev/null 2>&1 +then + echo "sed doesn't do in-place editing (-i option)" + NEEDED="$NEEDED .sed" +fi +} + +check() { +if ! $1 --version >/dev/null 2>&1 +then + echo "$1 not found" + NEEDED="$NEEDED .$1" +else + # found, need to check version ? + [ -z "$2" ] && return # no + gotver=`$1 --version | head -1 | sed s/'.* '//` + gotmajor=`echo $gotver|cut -d. -f1` + gotminor=`echo $gotver|cut -d. -f2` + gotmicro=`echo $gotver|cut -d. -f3` + [ -z "$gotmicro" ] && gotmicro=0 + needmajor=`echo $2|cut -d. -f1` + needminor=`echo $2|cut -d. -f2` + needmicro=`echo $2|cut -d. -f3` + [ -z "$needmicro" ] && needmicro=0 + if [ "$needmajor" -gt "$gotmajor" \ + -o "$needmajor" -eq "$gotmajor" -a "$needminor" -gt "$gotminor" \ + -o "$needmajor" -eq "$gotmajor" -a "$needminor" -eq "$gotminor" -a "$needmicro" -gt "$gotmicro" ] + then + echo "$1 too old" + NEEDED="$NEEDED .$1" + fi +fi +} + +check autoconf 2.69 +check automake 1.14 +check m4 1.4.16 +check libtool 2.4 +check pkg-config +check cmake 2.8.8 +check yasm +check_tar +check_sed +check ant +check gettext + +[ -n "$NEEDED" ] && mkdir -p build/ && echo "To-be-built packages: `echo $NEEDED | sed 's/\.//g'`" + +CPUS= +CC= +CXX= +case `uname` in + Linux) + CPUS=`grep -c ^processor /proc/cpuinfo` + ;; + Darwin) + CPUS=`sysctl hw.ncpu|cut -d: -f2` + gcc-4.2 --version >/dev/null 2>&1 && CC=CC=gcc-4.2 + g++-4.2 --version >/dev/null 2>&1 && CXX=CXX=g++-4.2 + ;; + SunOS) + CPUS=`/usr/bin/kstat -p :::state | grep 'on-line$' | wc -l | sed 's/ //g'` + ;; + *) + CPUS=1 # default + ;; +esac + + +cat > Makefile << EOF +MAKEFLAGS += -j$CPUS +$CC +$CXX +PREFIX=\$(abspath ./build) + +all: $NEEDED + @echo "You are ready to build SFLPhone and its contribs" + +include tools.mak +EOF diff --git a/extras/tools/packages.mak b/extras/tools/packages.mak new file mode 100644 index 0000000000..73089b12fd --- /dev/null +++ b/extras/tools/packages.mak @@ -0,0 +1,44 @@ +GNU=http://ftp.gnu.org/gnu +APACHE=http://mir2.ovh.net/ftp.apache.org/dist +SF= http://downloads.sourceforge.net/project + +YASM_VERSION=1.2.0 +#YASM_URL=$(CONTRIB_VIDEOLAN)/yasm-$(YASM_VERSION).tar.gz +YASM_URL=http://www.tortall.net/projects/yasm/releases/yasm-$(YASM_VERSION).tar.gz + +CMAKE_VERSION=2.8.12.2 +CMAKE_URL=http://www.cmake.org/files/v2.8/cmake-$(CMAKE_VERSION).tar.gz + +LIBTOOL_VERSION=2.4.2 +LIBTOOL_URL=$(GNU)/libtool/libtool-$(LIBTOOL_VERSION).tar.gz + +AUTOCONF_VERSION=2.69 +AUTOCONF_URL=$(GNU)/autoconf/autoconf-$(AUTOCONF_VERSION).tar.gz + +AUTOMAKE_VERSION=1.14 +AUTOMAKE_URL=$(GNU)/automake/automake-$(AUTOMAKE_VERSION).tar.gz + +M4_VERSION=1.4.16 +M4_URL=$(GNU)/m4/m4-$(M4_VERSION).tar.gz + +PKGCFG_VERSION=0.28-1 +#PKGCFG_URL=http://downloads.videolan.org/pub/videolan/testing/contrib/pkg-config-$(PKGCFG_VERSION).tar.gz +PKGCFG_URL=$(SF)/pkgconfiglite/$(PKGCFG_VERSION)/pkg-config-lite-$(PKGCFG_VERSION).tar.gz + +TAR_VERSION=1.26 +TAR_URL=$(GNU)/tar/tar-$(TAR_VERSION).tar.bz2 + +XZ_VERSION=5.0.3 +XZ_URL=http://tukaani.org/xz/xz-$(XZ_VERSION).tar.bz2 + +GAS_VERSION=72887b9 +GAS_URL=http://git.libav.org/?p=gas-preprocessor.git;a=snapshot;h=$(GAS_VERSION);sf=tgz + +SED_VERSION=4.2.2 +SED_URL=$(GNU)/sed/sed-$(SED_VERSION).tar.bz2 + +ANT_VERSION=1.9.4 +ANT_URL=$(APACHE)/ant/binaries/apache-ant-$(ANT_VERSION)-bin.tar.bz2 + +GETTEXT_VERSION=0.19.4 +GETTEXT_URL=$(GNU)/gettext/gettext-$(GETTEXT_VERSION).tar.gz diff --git a/extras/tools/tools.mak b/extras/tools/tools.mak new file mode 100644 index 0000000000..7b81585f8d --- /dev/null +++ b/extras/tools/tools.mak @@ -0,0 +1,279 @@ +# Copyright (C) 2003-2011 the VideoLAN team +# +# This file is under the same license as the vlc package. + +include packages.mak + +# +# common rules +# + +AUTOCONF=$(PREFIX)/bin/autoconf +export AUTOCONF + +ifeq ($(shell curl --version >/dev/null 2>&1 || echo FAIL),) +download = curl -f -L -- "$(1)" > "$@" +else ifeq ($(shell wget --version >/dev/null 2>&1 || echo FAIL),) +download = rm -f $@.tmp && \ + wget --passive -c -p -O $@.tmp "$(1)" && \ + touch $@.tmp && \ + mv $@.tmp $@ +else ifeq ($(which fetch >/dev/null 2>&1 || echo FAIL),) +download = rm -f $@.tmp && \ + fetch -p -o $@.tmp "$(1)" && \ + touch $@.tmp && \ + mv $@.tmp $@ +else +download = $(error Neither curl nor wget found!) +endif + +UNPACK = $(RM) -R $@ \ + $(foreach f,$(filter %.tar.gz %.tgz,$^), && tar xvzf $(f)) \ + $(foreach f,$(filter %.tar.bz2,$^), && tar xvjf $(f)) \ + $(foreach f,$(filter %.tar.xz,$^), && tar xvJf $(f)) \ + $(foreach f,$(filter %.zip,$^), && unzip $(f)) + +UNPACK_DIR = $(basename $(basename $(notdir $<))) +APPLY = (cd $(UNPACK_DIR) && patch -p1) < +MOVE = mv $(UNPACK_DIR) $@ && touch $@ + +# +# package rules +# + +# yasm + +yasm-$(YASM_VERSION).tar.gz: + $(call download,$(YASM_URL)) + +yasm: yasm-$(YASM_VERSION).tar.gz + $(UNPACK) + $(MOVE) + +.yasm: yasm + (cd $<; ./configure --prefix=$(PREFIX) && $(MAKE) && $(MAKE) install) + touch $@ + +CLEAN_FILE += .yasm +CLEAN_PKG += yasm +DISTCLEAN_PKG += yasm-$(YASM_VERSION).tar.gz + +# cmake + +cmake-$(CMAKE_VERSION).tar.gz: + $(call download,$(CMAKE_URL)) + +cmake: cmake-$(CMAKE_VERSION).tar.gz + $(UNPACK) + $(MOVE) + +.cmake: cmake + (cd $<; ./configure --prefix=$(PREFIX) && $(MAKE) && $(MAKE) install) + touch $@ + +CLEAN_FILE += .cmake +CLEAN_PKG += cmake +DISTCLEAN_PKG += cmake-$(CMAKE_VERSION).tar.gz + +# libtool + +libtool-$(LIBTOOL_VERSION).tar.gz: + $(call download,$(LIBTOOL_URL)) + +libtool: libtool-$(LIBTOOL_VERSION).tar.gz + $(UNPACK) + $(MOVE) + +.libtool: libtool .automake + (cd $<; ./configure --prefix=$(PREFIX) && $(MAKE) && $(MAKE) install) + ln -sf libtool $(PREFIX)/bin/glibtool + ln -sf libtoolize $(PREFIX)/bin/glibtoolize + touch $@ + +CLEAN_PKG += libtool +DISTCLEAN_PKG += libtool-$(LIBTOOL_VERSION).tar.gz +CLEAN_FILE += .libtool + +# GNU tar (with xz support) + +tar-$(TAR_VERSION).tar.bz2: + $(call download,$(TAR_URL)) + +tar: tar-$(TAR_VERSION).tar.bz2 + $(UNPACK) + $(MOVE) + +.tar: tar + (cd $<; ./configure --prefix=$(PREFIX) && $(MAKE) && $(MAKE) install) + touch $@ + +CLEAN_PKG += tar +DISTCLEAN_PKG += tar-$(TAR_VERSION).tar.bz2 +CLEAN_FILE += .tar + +# xz + +xz-$(XZ_VERSION).tar.bz2: + $(call download,$(XZ_URL)) + +xz: xz-$(XZ_VERSION).tar.bz2 + $(UNPACK) + $(MOVE) + +.xz: xz + (cd $<; ./configure --prefix=$(PREFIX) && $(MAKE) && $(MAKE) install) + touch $@ + +CLEAN_PKG += xz +DISTCLEAN_PKG += xz-$(XZ_VERSION).tar.bz2 +CLEAN_FILE += .xz + +# autoconf + +autoconf-$(AUTOCONF_VERSION).tar.gz: + $(call download,$(AUTOCONF_URL)) + +autoconf: autoconf-$(AUTOCONF_VERSION).tar.gz + $(UNPACK) + $(MOVE) + +.autoconf: autoconf .pkg-config + (cd $<; ./configure --prefix=$(PREFIX) && $(MAKE) && $(MAKE) install) + touch $@ + +CLEAN_FILE += .autoconf +CLEAN_PKG += autoconf +DISTCLEAN_PKG += autoconf-$(AUTOCONF_VERSION).tar.gz + +# automake + +automake-$(AUTOMAKE_VERSION).tar.gz: + $(call download,$(AUTOMAKE_URL)) + +automake: automake-$(AUTOMAKE_VERSION).tar.gz + $(UNPACK) + $(MOVE) + +.automake: automake .autoconf + (cd $<; ./configure --prefix=$(PREFIX) && $(MAKE) && $(MAKE) install) + touch $@ + +CLEAN_FILE += .automake +CLEAN_PKG += automake +DISTCLEAN_PKG += automake-$(AUTOMAKE_VERSION).tar.gz + +# m4 + +m4-$(M4_VERSION).tar.gz: + $(call download,$(M4_URL)) + +m4: m4-$(M4_VERSION).tar.gz + $(UNPACK) + $(MOVE) + +.m4: m4 + (cd $<; ./configure --prefix=$(PREFIX) && $(MAKE) && $(MAKE) install) + touch $@ + +CLEAN_FILE += .m4 +CLEAN_PKG += m4 +DISTCLEAN_PKG += m4-$(M4_VERSION).tar.gz + +# pkg-config + +pkg-config-$(PKGCFG_VERSION).tar.gz: + $(call download,$(PKGCFG_URL)) + +pkgconfig: pkg-config-$(PKGCFG_VERSION).tar.gz + $(UNPACK) + mv pkg-config-lite-$(PKGCFG_VERSION) pkg-config-$(PKGCFG_VERSION) + $(MOVE) + +.pkg-config: pkgconfig + (cd pkgconfig; ./configure --prefix=$(PREFIX) --disable-shared --enable-static && $(MAKE) && $(MAKE) install) + touch $@ + +CLEAN_FILE += .pkg-config +CLEAN_PKG += pkgconfig +DISTCLEAN_PKG += pkg-config-$(PKGCFG_VERSION).tar.gz + +# gas-preprocessor +gas-preprocessor-$(GAS_VERSION).tar.gz: + $(call download,$(GAS_URL)) + +gas: gas-preprocessor-$(GAS_VERSION).tar.gz + $(UNPACK) + $(MOVE) + +.gas: gas + mkdir -p $(PREFIX)/bin + cp gas/gas-preprocessor.pl $(PREFIX)/build/bin/ + touch $@ + +CLEAN_FILE += .gas +CLEAN_PKG += gas +DISTCLEAN_PKG += yuvi-gas-preprocessor-$(GAS_VERSION).tar.gz + +# GNU sed + +sed-$(SED_VERSION).tar.bz2: + $(call download,$(SED_URL)) + +sed: sed-$(SED_VERSION).tar.bz2 + $(UNPACK) + $(MOVE) + +.sed: sed + (cd $<; ./configure --prefix=$(PREFIX) && $(MAKE) && $(MAKE) install) + touch $@ + +CLEAN_PKG += sed +DISTCLEAN_PKG += sed-$(SED_VERSION).tar.bz2 +CLEAN_FILE += .sed + +# Apache ANT + +apache-ant-$(ANT_VERSION).tar.bz2: + $(call download,$(ANT_URL)) + +ant: apache-ant-$(ANT_VERSION).tar.bz2 + $(UNPACK) + $(MOVE) + +.ant: ant + (mkdir -p $(PREFIX)/bin && cp $</bin/* $(PREFIX)/bin/) + (mkdir -p $(PREFIX)/lib && cp $</lib/* $(PREFIX)/lib/) + touch $@ + +CLEAN_PKG += ant +DISTCLEAN_PKG += apache-ant-$(ANT_VERSION).tar.bz2 +CLEAN_FILE += .ant + +# GNU gettext + +gettext-$(GETTEXT_VERSION).tar.gz: + $(call download,$(GETTEXT_URL)) + +gettext: gettext-$(GETTEXT_VERSION).tar.gz + $(UNPACK) + $(MOVE) + +.gettext: gettext + (cd $<; ./configure --prefix=$(PREFIX) && $(MAKE) && $(MAKE) install) + touch $@ + +CLEAN_FILE += .gettext +CLEAN_PKG += gettext +DISTCLEAN_PKG += gettext-$(CMAKE_VERSION).tar.gz + +# +# +# + +clean: + rm -fr $(CLEAN_FILE) $(CLEAN_PKG) build/ + +distclean: clean + rm -fr $(DISTCLEAN_PKG) + +.PHONY: all clean distclean diff --git a/globals.mak b/globals.mak new file mode 100644 index 0000000000..2f1b5bf855 --- /dev/null +++ b/globals.mak @@ -0,0 +1,41 @@ +# Global variables + +src=$(abs_top_srcdir) +ringlibdir=$(DESTDIR)$(libdir)/ring + +ASTYLERC="$(top_srcdir)/../astylerc" +indent="/usr/bin/astyle" + +if BUILD_SPEEX +SPEEXCODEC=-DHAVE_SPEEX_CODEC +endif + +if BUILD_OPUS +OPUSCODEC=-DHAVE_OPUS +endif + +if BUILD_GSM +GSMCODEC=-DHAVE_GSM_CODEC +endif + +# Preprocessor flags +AM_CPPFLAGS = \ + -I$(src)/src \ + -I$(src)/src/config \ + -I$(src)/src/media \ + -I$(src)/test \ + -I$(src)/src/dring \ + $(SIP_CFLAGS) \ + -DPREFIX=\"$(prefix)\" \ + -DPROGSHAREDIR=\"${datadir}/ring\" \ + -DENABLE_TRACE \ + $(SPEEXCODEC) \ + $(GSMCODEC) \ + $(OPUSCODEC) + + +indent: + @echo "Indenting code:" + if [ -f $(ASTYLERC) ] ; then \ + find $(top_srcdir)/src/ -name \*.cpp -o -name \*.h | xargs $(indent) --options=$(ASTYLERC) ; \ + fi diff --git a/m4/.gitignore b/m4/.gitignore new file mode 100644 index 0000000000..464ba5caa6 --- /dev/null +++ b/m4/.gitignore @@ -0,0 +1,5 @@ +libtool.m4 +lt~obsolete.m4 +ltoptions.m4 +ltsugar.m4 +ltversion.m4 diff --git a/m4/as-ac-expand.m4 b/m4/as-ac-expand.m4 new file mode 100644 index 0000000000..d6c9e33060 --- /dev/null +++ b/m4/as-ac-expand.m4 @@ -0,0 +1,43 @@ +dnl as-ac-expand.m4 0.2.0 +dnl autostars m4 macro for expanding directories using configure's prefix +dnl thomas@apestaart.org + +dnl AS_AC_EXPAND(VAR, CONFIGURE_VAR) +dnl example +dnl AS_AC_EXPAND(SYSCONFDIR, $sysconfdir) +dnl will set SYSCONFDIR to /usr/local/etc if prefix=/usr/local + +AC_DEFUN([AS_AC_EXPAND], +[ + EXP_VAR=[$1] + FROM_VAR=[$2] + + dnl first expand prefix and exec_prefix if necessary + prefix_save=$prefix + exec_prefix_save=$exec_prefix + + dnl if no prefix given, then use /usr/local, the default prefix + if test "x$prefix" = "xNONE"; then + prefix="$ac_default_prefix" + fi + dnl if no exec_prefix given, then use prefix + if test "x$exec_prefix" = "xNONE"; then + exec_prefix=$prefix + fi + + full_var="$FROM_VAR" + dnl loop until it doesn't change anymore + while true; do + new_full_var="`eval echo $full_var`" + if test "x$new_full_var" = "x$full_var"; then break; fi + full_var=$new_full_var + done + + dnl clean up + full_var=$new_full_var + AC_SUBST([$1], "$full_var") + + dnl restore prefix and exec_prefix + prefix=$prefix_save + exec_prefix=$exec_prefix_save +]) diff --git a/m4/ax_cxx_compile_stdcxx_11.m4 b/m4/ax_cxx_compile_stdcxx_11.m4 new file mode 100644 index 0000000000..5c10a76449 --- /dev/null +++ b/m4/ax_cxx_compile_stdcxx_11.m4 @@ -0,0 +1,135 @@ +# ============================================================================ +# http://www.gnu.org/software/autoconf-archive/ax_cxx_compile_stdcxx_11.html +# ============================================================================ +# +# SYNOPSIS +# +# AX_CXX_COMPILE_STDCXX_11([ext|noext],[mandatory|optional]) +# +# DESCRIPTION +# +# Check for baseline language coverage in the compiler for the C++11 +# standard; if necessary, add switches to CXXFLAGS to enable support. +# +# The first argument, if specified, indicates whether you insist on an +# extended mode (e.g. -std=gnu++11) or a strict conformance mode (e.g. +# -std=c++11). If neither is specified, you get whatever works, with +# preference for an extended mode. +# +# The second argument, if specified 'mandatory' or if left unspecified, +# indicates that baseline C++11 support is required and that the macro +# should error out if no mode with that support is found. If specified +# 'optional', then configuration proceeds regardless, after defining +# HAVE_CXX11 if and only if a supporting mode is found. +# +# LICENSE +# +# Copyright (c) 2008 Benjamin Kosnik <bkoz@redhat.com> +# Copyright (c) 2012 Zack Weinberg <zackw@panix.com> +# Copyright (c) 2013 Roy Stogner <roystgnr@ices.utexas.edu> +# Copyright (c) 2014 Alexey Sokolov <sokolov@google.com> +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 4 + +m4_define([_AX_CXX_COMPILE_STDCXX_11_testbody], [[ + template <typename T> + struct check + { + static_assert(sizeof(int) <= sizeof(T), "not big enough"); + }; + + typedef check<check<bool>> right_angle_brackets; + + int a; + decltype(a) b; + + typedef check<int> check_type; + check_type c; + check_type&& cr = static_cast<check_type&&>(c); + + auto d = a; + auto l = [](){}; +]]) + +AC_DEFUN([AX_CXX_COMPILE_STDCXX_11], [dnl + m4_if([$1], [], [], + [$1], [ext], [], + [$1], [noext], [], + [m4_fatal([invalid argument `$1' to AX_CXX_COMPILE_STDCXX_11])])dnl + m4_if([$2], [], [ax_cxx_compile_cxx11_required=true], + [$2], [mandatory], [ax_cxx_compile_cxx11_required=true], + [$2], [optional], [ax_cxx_compile_cxx11_required=false], + [m4_fatal([invalid second argument `$2' to AX_CXX_COMPILE_STDCXX_11])]) + AC_LANG_PUSH([C++])dnl + ac_success=no + AC_CACHE_CHECK(whether $CXX supports C++11 features by default, + ax_cv_cxx_compile_cxx11, + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [ax_cv_cxx_compile_cxx11=yes], + [ax_cv_cxx_compile_cxx11=no])]) + if test x$ax_cv_cxx_compile_cxx11 = xyes; then + ac_success=yes + fi + + m4_if([$1], [noext], [], [dnl + if test x$ac_success = xno; then + for switch in -std=gnu++11 -std=gnu++0x; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, + $cachevar, + [ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXXFLAGS="$ac_save_CXXFLAGS"]) + if eval test x\$$cachevar = xyes; then + CXXFLAGS="$CXXFLAGS $switch" + ac_success=yes + break + fi + done + fi]) + + m4_if([$1], [ext], [], [dnl + if test x$ac_success = xno; then + for switch in -std=c++11 -std=c++0x; do + cachevar=AS_TR_SH([ax_cv_cxx_compile_cxx11_$switch]) + AC_CACHE_CHECK(whether $CXX supports C++11 features with $switch, + $cachevar, + [ac_save_CXXFLAGS="$CXXFLAGS" + CXXFLAGS="$CXXFLAGS $switch" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([_AX_CXX_COMPILE_STDCXX_11_testbody])], + [eval $cachevar=yes], + [eval $cachevar=no]) + CXXFLAGS="$ac_save_CXXFLAGS"]) + if eval test x\$$cachevar = xyes; then + CXXFLAGS="$CXXFLAGS $switch" + ac_success=yes + break + fi + done + fi]) + AC_LANG_POP([C++]) + if test x$ax_cxx_compile_cxx11_required = xtrue; then + if test x$ac_success = xno; then + AC_MSG_ERROR([*** A compiler with support for C++11 language features is required.]) + fi + else + if test x$ac_success = xno; then + HAVE_CXX11=0 + AC_MSG_NOTICE([No compiler with C++11 support was found]) + else + HAVE_CXX11=1 + AC_DEFINE(HAVE_CXX11,1, + [define if the compiler supports basic C++11 syntax]) + fi + + AC_SUBST(HAVE_CXX11) + fi +]) diff --git a/m4/ax_pthread.m4 b/m4/ax_pthread.m4 new file mode 100644 index 0000000000..e20a388ca8 --- /dev/null +++ b/m4/ax_pthread.m4 @@ -0,0 +1,309 @@ +# =========================================================================== +# http://www.gnu.org/software/autoconf-archive/ax_pthread.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PTHREAD([ACTION-IF-FOUND[, ACTION-IF-NOT-FOUND]]) +# +# DESCRIPTION +# +# This macro figures out how to build C programs using POSIX threads. It +# sets the PTHREAD_LIBS output variable to the threads library and linker +# flags, and the PTHREAD_CFLAGS output variable to any special C compiler +# flags that are needed. (The user can also force certain compiler +# flags/libs to be tested by setting these environment variables.) +# +# Also sets PTHREAD_CC to any special C compiler that is needed for +# multi-threaded programs (defaults to the value of CC otherwise). (This +# is necessary on AIX to use the special cc_r compiler alias.) +# +# NOTE: You are assumed to not only compile your program with these flags, +# but also link it with them as well. e.g. you should link with +# $PTHREAD_CC $CFLAGS $PTHREAD_CFLAGS $LDFLAGS ... $PTHREAD_LIBS $LIBS +# +# If you are only building threads programs, you may wish to use these +# variables in your default LIBS, CFLAGS, and CC: +# +# LIBS="$PTHREAD_LIBS $LIBS" +# CFLAGS="$CFLAGS $PTHREAD_CFLAGS" +# CC="$PTHREAD_CC" +# +# In addition, if the PTHREAD_CREATE_JOINABLE thread-attribute constant +# has a nonstandard name, defines PTHREAD_CREATE_JOINABLE to that name +# (e.g. PTHREAD_CREATE_UNDETACHED on AIX). +# +# Also HAVE_PTHREAD_PRIO_INHERIT is defined if pthread is found and the +# PTHREAD_PRIO_INHERIT symbol is defined when compiling with +# PTHREAD_CFLAGS. +# +# ACTION-IF-FOUND is a list of shell commands to run if a threads library +# is found, and ACTION-IF-NOT-FOUND is a list of commands to run it if it +# is not found. If ACTION-IF-FOUND is not specified, the default action +# will define HAVE_PTHREAD. +# +# Please let the authors know if this macro fails on any platform, or if +# you have any other suggestions or comments. This macro was based on work +# by SGJ on autoconf scripts for FFTW (http://www.fftw.org/) (with help +# from M. Frigo), as well as ac_pthread and hb_pthread macros posted by +# Alejandro Forero Cuervo to the autoconf macro repository. We are also +# grateful for the helpful feedback of numerous users. +# +# Updated for Autoconf 2.68 by Daniel Richard G. +# +# LICENSE +# +# Copyright (c) 2008 Steven G. Johnson <stevenj@alum.mit.edu> +# Copyright (c) 2011 Daniel Richard G. <skunk@iSKUNK.ORG> +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see <http://www.gnu.org/licenses/>. +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 17 + +AU_ALIAS([ACX_PTHREAD], [AX_PTHREAD]) +AC_DEFUN([AX_PTHREAD], [ +AC_REQUIRE([AC_CANONICAL_HOST]) +AC_LANG_PUSH([C]) +ax_pthread_ok=no + +# We used to check for pthread.h first, but this fails if pthread.h +# requires special compiler flags (e.g. on True64 or Sequent). +# It gets checked for in the link test anyway. + +# First of all, check if the user has set any of the PTHREAD_LIBS, +# etcetera environment variables, and if threads linking works using +# them: +if test x"$PTHREAD_LIBS$PTHREAD_CFLAGS" != x; then + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + AC_MSG_CHECKING([for pthread_join in LIBS=$PTHREAD_LIBS with CFLAGS=$PTHREAD_CFLAGS]) + AC_TRY_LINK_FUNC(pthread_join, ax_pthread_ok=yes) + AC_MSG_RESULT($ax_pthread_ok) + if test x"$ax_pthread_ok" = xno; then + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" + fi + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" +fi + +# We must check for the threads library under a number of different +# names; the ordering is very important because some systems +# (e.g. DEC) have both -lpthread and -lpthreads, where one of the +# libraries is broken (non-POSIX). + +# Create a list of thread flags to try. Items starting with a "-" are +# C compiler flags, and other items are library names, except for "none" +# which indicates that we try without any flags at all, and "pthread-config" +# which is a program returning the flags for the Pth emulation library. + +ax_pthread_flags="pthreads none -Kthread -kthread lthread -pthread -pthreads -mthreads pthread --thread-safe -mt pthread-config" + +# The ordering *is* (sometimes) important. Some notes on the +# individual items follow: + +# pthreads: AIX (must check this before -lpthread) +# none: in case threads are in libc; should be tried before -Kthread and +# other compiler flags to prevent continual compiler warnings +# -Kthread: Sequent (threads in libc, but -Kthread needed for pthread.h) +# -kthread: FreeBSD kernel threads (preferred to -pthread since SMP-able) +# lthread: LinuxThreads port on FreeBSD (also preferred to -pthread) +# -pthread: Linux/gcc (kernel threads), BSD/gcc (userland threads) +# -pthreads: Solaris/gcc +# -mthreads: Mingw32/gcc, Lynx/gcc +# -mt: Sun Workshop C (may only link SunOS threads [-lthread], but it +# doesn't hurt to check since this sometimes defines pthreads too; +# also defines -D_REENTRANT) +# ... -mt is also the pthreads flag for HP/aCC +# pthread: Linux, etcetera +# --thread-safe: KAI C++ +# pthread-config: use pthread-config program (for GNU Pth library) + +case "${host_cpu}-${host_os}" in + *solaris*) + + # On Solaris (at least, for some versions), libc contains stubbed + # (non-functional) versions of the pthreads routines, so link-based + # tests will erroneously succeed. (We need to link with -pthreads/-mt/ + # -lpthread.) (The stubs are missing pthread_cleanup_push, or rather + # a function called by this macro, so we could check for that, but + # who knows whether they'll stub that too in a future libc.) So, + # we'll just look for -pthreads and -lpthread first: + + ax_pthread_flags="-pthreads pthread -mt -pthread $ax_pthread_flags" + ;; + + *-darwin*) + ax_pthread_flags="-pthread $ax_pthread_flags" + ;; +esac + +if test x"$ax_pthread_ok" = xno; then +for flag in $ax_pthread_flags; do + + case $flag in + none) + AC_MSG_CHECKING([whether pthreads work without any flags]) + ;; + + -*) + AC_MSG_CHECKING([whether pthreads work with $flag]) + PTHREAD_CFLAGS="$flag" + ;; + + pthread-config) + AC_CHECK_PROG(ax_pthread_config, pthread-config, yes, no) + if test x"$ax_pthread_config" = xno; then continue; fi + PTHREAD_CFLAGS="`pthread-config --cflags`" + PTHREAD_LIBS="`pthread-config --ldflags` `pthread-config --libs`" + ;; + + *) + AC_MSG_CHECKING([for the pthreads library -l$flag]) + PTHREAD_LIBS="-l$flag" + ;; + esac + + save_LIBS="$LIBS" + save_CFLAGS="$CFLAGS" + LIBS="$PTHREAD_LIBS $LIBS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Check for various functions. We must include pthread.h, + # since some functions may be macros. (On the Sequent, we + # need a special flag -Kthread to make this header compile.) + # We check for pthread_join because it is in -lpthread on IRIX + # while pthread_create is in libc. We check for pthread_attr_init + # due to DEC craziness with -lpthreads. We check for + # pthread_cleanup_push because it is one of the few pthread + # functions on Solaris that doesn't have a non-functional libc stub. + # We try pthread_create on general principles. + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h> + static void routine(void *a) { a = 0; } + static void *start_routine(void *a) { return a; }], + [pthread_t th; pthread_attr_t attr; + pthread_create(&th, 0, start_routine, 0); + pthread_join(th, 0); + pthread_attr_init(&attr); + pthread_cleanup_push(routine, 0); + pthread_cleanup_pop(0) /* ; */])], + [ax_pthread_ok=yes], + []) + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + AC_MSG_RESULT($ax_pthread_ok) + if test "x$ax_pthread_ok" = xyes; then + break; + fi + + PTHREAD_LIBS="" + PTHREAD_CFLAGS="" +done +fi + +# Various other checks: +if test "x$ax_pthread_ok" = xyes; then + save_LIBS="$LIBS" + LIBS="$PTHREAD_LIBS $LIBS" + save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $PTHREAD_CFLAGS" + + # Detect AIX lossage: JOINABLE attribute is called UNDETACHED. + AC_MSG_CHECKING([for joinable pthread attribute]) + attr_name=unknown + for attr in PTHREAD_CREATE_JOINABLE PTHREAD_CREATE_UNDETACHED; do + AC_LINK_IFELSE([AC_LANG_PROGRAM([#include <pthread.h>], + [int attr = $attr; return attr /* ; */])], + [attr_name=$attr; break], + []) + done + AC_MSG_RESULT($attr_name) + if test "$attr_name" != PTHREAD_CREATE_JOINABLE; then + AC_DEFINE_UNQUOTED(PTHREAD_CREATE_JOINABLE, $attr_name, + [Define to necessary symbol if this constant + uses a non-standard name on your system.]) + fi + + AC_MSG_CHECKING([if more special flags are required for pthreads]) + flag=no + case "${host_cpu}-${host_os}" in + *-aix* | *-freebsd* | *-darwin*) flag="-D_THREAD_SAFE";; + *-osf* | *-hpux*) flag="-D_REENTRANT";; + *solaris*) + if test "$GCC" = "yes"; then + flag="-D_REENTRANT" + else + flag="-mt -D_REENTRANT" + fi + ;; + esac + AC_MSG_RESULT(${flag}) + if test "x$flag" != xno; then + PTHREAD_CFLAGS="$flag $PTHREAD_CFLAGS" + fi + + AC_CACHE_CHECK([for PTHREAD_PRIO_INHERIT], + ax_cv_PTHREAD_PRIO_INHERIT, [ + AC_LINK_IFELSE([ + AC_LANG_PROGRAM([[#include <pthread.h>]], [[int i = PTHREAD_PRIO_INHERIT;]])], + [ax_cv_PTHREAD_PRIO_INHERIT=yes], + [ax_cv_PTHREAD_PRIO_INHERIT=no]) + ]) + AS_IF([test "x$ax_cv_PTHREAD_PRIO_INHERIT" = "xyes"], + AC_DEFINE([HAVE_PTHREAD_PRIO_INHERIT], 1, [Have PTHREAD_PRIO_INHERIT.])) + + LIBS="$save_LIBS" + CFLAGS="$save_CFLAGS" + + # More AIX lossage: must compile with xlc_r or cc_r + if test x"$GCC" != xyes; then + AC_CHECK_PROGS(PTHREAD_CC, xlc_r cc_r, ${CC}) + else + PTHREAD_CC=$CC + fi +else + PTHREAD_CC="$CC" +fi + +AC_SUBST(PTHREAD_LIBS) +AC_SUBST(PTHREAD_CFLAGS) +AC_SUBST(PTHREAD_CC) + +# Finally, execute ACTION-IF-FOUND/ACTION-IF-NOT-FOUND: +if test x"$ax_pthread_ok" = xyes; then + ifelse([$1],,AC_DEFINE(HAVE_PTHREAD,1,[Define if you have POSIX threads libraries and header files.]),[$1]) + : +else + ax_pthread_ok=no + $2 +fi +AC_LANG_POP +])dnl AX_PTHREAD diff --git a/m4/dolt.m4 b/m4/dolt.m4 new file mode 100644 index 0000000000..326b6a8251 --- /dev/null +++ b/m4/dolt.m4 @@ -0,0 +1,181 @@ +dnl dolt, a replacement for libtool +dnl Copyright © 2007-2010 Josh Triplett <josh@joshtriplett.org> +dnl Copying and distribution of this file, with or without modification, +dnl are permitted in any medium without royalty provided the copyright +dnl notice and this notice are preserved. +dnl +dnl To use dolt, invoke the DOLT macro immediately after the libtool macros. +dnl Optionally, copy this file into acinclude.m4, to avoid the need to have it +dnl installed when running autoconf on your project. + +AC_DEFUN([DOLT], [ +AC_REQUIRE([AC_CANONICAL_HOST]) +# dolt, a replacement for libtool +# Josh Triplett <josh@freedesktop.org> +AC_PATH_PROG(DOLT_BASH, bash) +AC_MSG_CHECKING([if dolt supports this host]) +dolt_supported=yes +if test x$DOLT_BASH = x; then + dolt_supported=no +fi +if test x$GCC != xyes; then + dolt_supported=no +fi +case $host in +*-*-linux* \ +|amd64-*-freebsd*|i?86-*-freebsd*|ia64-*-freebsd*) + pic_options='-fPIC' + ;; +i?86-apple-darwin*) + pic_options='-fno-common' + ;; +*mingw*) + pic_options='' + ;; +*) + dolt_supported=no + ;; +esac +if test x$dolt_supported = xno ; then + AC_MSG_RESULT([no, falling back to libtool]) + LTCOMPILE='$(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(COMPILE)' + LTCXXCOMPILE='$(LIBTOOL) --tag=CXX $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=compile $(CXXCOMPILE)' +else + AC_MSG_RESULT([yes, replacing libtool]) + +dnl Start writing out doltcompile. + cat <<__DOLTCOMPILE__EOF__ >doltcompile +#!$DOLT_BASH +__DOLTCOMPILE__EOF__ + cat <<'__DOLTCOMPILE__EOF__' >>doltcompile +args=("$[]@") +for ((arg=0; arg<${#args@<:@@@:>@}; arg++)) ; do + if test x"${args@<:@$arg@:>@}" = x-o ; then + objarg=$((arg+1)) + break + fi +done +if test x$objarg = x ; then + echo 'Error: no -o on compiler command line' 1>&2 + exit 1 +fi +lo="${args@<:@$objarg@:>@}" +obj="${lo%.lo}" +if test x"$lo" = x"$obj" ; then + echo "Error: libtool object file name \"$lo\" does not end in .lo" 1>&2 + exit 1 +fi +objbase="${obj##*/}" +__DOLTCOMPILE__EOF__ + +dnl Write out shared compilation code. + if test x$enable_shared = xyes; then + cat <<'__DOLTCOMPILE__EOF__' >>doltcompile +libobjdir="${obj%$objbase}.libs" +if test ! -d "$libobjdir" ; then + mkdir_out="$(mkdir "$libobjdir" 2>&1)" + mkdir_ret=$? + if test "$mkdir_ret" -ne 0 && test ! -d "$libobjdir" ; then + echo "$mkdir_out" 1>&2 + exit $mkdir_ret + fi +fi +pic_object="$libobjdir/$objbase.o" +args@<:@$objarg@:>@="$pic_object" +__DOLTCOMPILE__EOF__ + cat <<__DOLTCOMPILE__EOF__ >>doltcompile +"\${args@<:@@@:>@}" $pic_options -DPIC || exit \$? +__DOLTCOMPILE__EOF__ + fi + +dnl Write out static compilation code. +dnl Avoid duplicate compiler output if also building shared objects. + if test x$enable_static = xyes; then + cat <<'__DOLTCOMPILE__EOF__' >>doltcompile +non_pic_object="$obj.o" +args@<:@$objarg@:>@="$non_pic_object" +__DOLTCOMPILE__EOF__ + if test x$enable_shared = xyes; then + cat <<'__DOLTCOMPILE__EOF__' >>doltcompile +"${args@<:@@@:>@}" >/dev/null 2>&1 || exit $? +__DOLTCOMPILE__EOF__ + else + cat <<'__DOLTCOMPILE__EOF__' >>doltcompile +"${args@<:@@@:>@}" || exit $? +__DOLTCOMPILE__EOF__ + fi + fi + +dnl Write out the code to write the .lo file. +dnl The second line of the .lo file must match "^# Generated by .*libtool" + cat <<'__DOLTCOMPILE__EOF__' >>doltcompile +{ +echo "# $lo - a libtool object file" +echo "# Generated by doltcompile, not libtool" +__DOLTCOMPILE__EOF__ + + if test x$enable_shared = xyes; then + cat <<'__DOLTCOMPILE__EOF__' >>doltcompile +echo "pic_object='.libs/${objbase}.o'" +__DOLTCOMPILE__EOF__ + else + cat <<'__DOLTCOMPILE__EOF__' >>doltcompile +echo pic_object=none +__DOLTCOMPILE__EOF__ + fi + + if test x$enable_static = xyes; then + cat <<'__DOLTCOMPILE__EOF__' >>doltcompile +echo "non_pic_object='${objbase}.o'" +__DOLTCOMPILE__EOF__ + else + cat <<'__DOLTCOMPILE__EOF__' >>doltcompile +echo non_pic_object=none +__DOLTCOMPILE__EOF__ + fi + + cat <<'__DOLTCOMPILE__EOF__' >>doltcompile +} > "$lo" +__DOLTCOMPILE__EOF__ + +dnl Done writing out doltcompile; substitute it for libtool compilation. + chmod +x doltcompile + LTCOMPILE='$(top_builddir)/doltcompile $(COMPILE)' + LTCXXCOMPILE='$(top_builddir)/doltcompile $(CXXCOMPILE)' + +dnl automake ignores LTCOMPILE and LTCXXCOMPILE when it has separate CFLAGS for +dnl a target, so write out a libtool wrapper to handle that case. +dnl Note that doltlibtool does not handle inferred tags or option arguments +dnl without '=', because automake does not use them. + cat <<__DOLTLIBTOOL__EOF__ > doltlibtool +#!$DOLT_BASH +__DOLTLIBTOOL__EOF__ + cat <<'__DOLTLIBTOOL__EOF__' >>doltlibtool +top_builddir_slash="${0%%doltlibtool}" +: ${top_builddir_slash:=./} +args=() +modeok=false +tagok=false +for arg in "$[]@"; do + case "$arg" in + --mode=compile) modeok=true ;; + --tag=CC|--tag=CXX) tagok=true ;; + --silent|--quiet) ;; + *) args@<:@${#args[@]}@:>@="$arg" ;; + esac +done +if $modeok && $tagok ; then + . ${top_builddir_slash}doltcompile "${args@<:@@@:>@}" +else + exec ${top_builddir_slash}libtool "$[]@" +fi +__DOLTLIBTOOL__EOF__ + +dnl Done writing out doltlibtool; substitute it for libtool. + chmod +x doltlibtool + LIBTOOL='$(top_builddir)/doltlibtool' +fi +AC_SUBST(LTCOMPILE) +AC_SUBST(LTCXXCOMPILE) +# end dolt +]) diff --git a/man/Makefile.am b/man/Makefile.am new file mode 100644 index 0000000000..1670fae98c --- /dev/null +++ b/man/Makefile.am @@ -0,0 +1,19 @@ +SECTION="1" + +TEMPLATES=dring.pod + +man_MANS=dring.1 + +POD2MAN=pod2man + +EXTRA_DIST=$(man_MANS) $(TEMPLATES) + +all: $(MANPAGES) + +SUFFIXES=.pod .1 + +.pod.1: + $(POD2MAN) --section=$(SECTION) --release=@VERSION@ --center "" $< > $@ + +clean-local: + rm -rf *.1 diff --git a/man/README.manpages b/man/README.manpages new file mode 100644 index 0000000000..75f6044095 --- /dev/null +++ b/man/README.manpages @@ -0,0 +1,13 @@ +CREATING MANPAGES + +Procedure: + + - Creating the manual pages under POD format. A template is available in this directory. + - Convert the pdo file in a manpage file: + pod2man --section=1 --release=$(VERSION) --center "" myapp.pod > myapp.1 + - You are done! You can read the manpage file with: + groff -man -Tascii myapp.1 + + + + Ref: https://wiki.ubuntu.com/PackagingGuide/Complete#Man%20Pages diff --git a/man/dring.pod b/man/dring.pod new file mode 100644 index 0000000000..e0bfa4bc12 --- /dev/null +++ b/man/dring.pod @@ -0,0 +1,56 @@ +=head1 NAME + +dring - SIP and IAX2 compatible voice over IP softphone core. + +=head1 SYNOPSIS + +B<dring> [OPTION]... + +=head1 DESCRIPTION + +Ring is meant to be a robust enterprise-class desktop phone. It provides functions like call transfer, call hold, multiple lines, multiple accounts support. +Ring audio layer is build upon a native ALSA interface and and a native PulseAudio interface. +B<dring> is the core of Ring; it communicates with the client side through DBus. You need to install a client to use the daemon, for instance GTK+ client, B<ring-client-gnome>. + +=head1 OPTIONS + +=over 8 + +=item B<-c, --console> + +Output the log to the console instead of syslog. + +=item B<-d, --debug> + +Debug mode (more verbose output). + +=item B<-h, --help> + +Print short list of command-line options. + +=back + +=head1 BUGS + +Please report bugs at https://projects.savoirfairelinux.com/projects/ring/issues/new. + +=head1 AUTHORS + +B<dring> is developed in Montreal by Savoir-Faire Linux Inc. The active developers are Tristan Matthews <tristan.matthews@savoirfairelinux.com> and Alexandre Savard <alexandre.savard@savoirfairelinux.com>. + +This manual page was written by Emmanuel Milou <emmanuel.milou@savoirfairelinux.com>. + +=head1 SEE ALSO + +B<ring>(1), B<ring-client-gnome>(1) + +=head1 COPYRIGHT + +This program is free software; you can redistribute it and/or modify it +under the terms of the GNU General Public License version 3 as published by the +Free Software Foundation. + +On Debian GNU/Linux systems, the complete text of the GNU General +Public License can be found in `/usr/share/common-licenses/GPL'. + +=cut diff --git a/ringtones/Makefile.am b/ringtones/Makefile.am new file mode 100644 index 0000000000..42b7ccf3ba --- /dev/null +++ b/ringtones/Makefile.am @@ -0,0 +1,5 @@ +ringringtonesdir = $(datadir)/ring/ringtones +dist_ringringtones_DATA = \ + konga.ul\ + phone.au\ + phone2.au diff --git a/ringtones/konga.ul b/ringtones/konga.ul new file mode 100644 index 0000000000..2191150e71 --- /dev/null +++ b/ringtones/konga.ul @@ -0,0 +1,11 @@ +�������������������������������������������������������������������������������������~����������~�������������������~������������������������~~�������������������������������~���}~{|~u}�w~z~z����iu�������k���i�z���r�j��������������������������������� +I�/$�����������������������% $5Ȭ�"��A/hnѡ������������PD? ;�������MH�ů�����������;1!"#.����cLG��Z<%.=ϾO0,,%'/Cཱུ���\7.$"$))+,//58<Xʺ��������������������������������������������qA84446>AA<2//4;>>=6/*'""$*,.+&"!#(++--+++*-.3>QS���nd������������������������������������������f���\D91//.)'*.26BA84.0/5;<41;>BEAOp˼�������qѾ���������Ȼ�����������������������e����XL>B]����OD;36//153/00,)).<GOU:18>N]SNNTVTID<??ELOD:;18=?Z~������J?Lu�����[EW�_Q��Ǿ��NMWx�ֻ�����\EROe���hF<:BD��YOOT_OPOJL��������\2/,087>@GKOF�bFD??=<<B]u��˼�jSj�����û���Ⱦ���������YS��^UGO�ý����ONLMEI�������xLL6/17COYfY���ECh���Ƿ��������T@CL`v�j^�����������Ƹ�����]�Z����RDCKt�kc��������|I>>FO`����nJCCENLGbmWJ>EIWWX[`���L=>:9<=Mr�����¿����̻������LK\���_SGE@GF_��ü����rW`��������Z<77AM����o�`REOkcb�����f@?FJOMKRPN^`e}����YXg�p�������NF:7>FGGDTu���ɾ������e\����������������������vh__\RSE?:9=::=U������{^gi\m]ZjVM]��������������������dNLSloYJGIUR]���������������������unk�ʼ����MJA<;=EMY\JG?<?EC?>@GFMa�������Ľ���NKFIPV^���z������������ne]t��l^dkbUKD>?IR[ZY`ov}�tv�iXU^m�����ÿ��������d\iprX]\XPK??>>===??CJSgho���u^s����y[W_����gVWY[\n��������˾�����YOq���jXUZ����^QHDHIKNdniv�b\a~��YZWX~�������_YW_dfWScz�������������rk\dq�v`]_geo{bVQOLKO_��������j����������������^WSNDCJKMJMSs���TMGHl���������������q]Xl������\WZTKGGJJKLNMPRLNVVOJJORU\���������ſ�����������p�����m]SXXZWNMOX\_e`ZXajcZSOQ_�������kWQKMQOPR_da^Z_m_XOMMRZYZk{���������in���������������xj]ds}yrjd]cdaTTKE>ACGKS[UOO[m���zc\Z_����������������������������z`YMLU[k���sVMFHGHIMPYi|�������o��������������������������jUOLNQOJKLKLR]c]VUSVTW[et�����xhi�������qy���������������������407=>LS5J��������U������( ')1�������;$& 03˟�L!#,������.6����-$N�����M.*8ì���0!����:&$'G��������D))7��������K9ϫ����L(&*+8Ӿ����H>6-3H��K:)#%0����r0$#$1��q?&$.=M���?-,+->ƫ�������Ȫ���H/.:Ͷ�������v.-:l�����I-.78I�ƹ���PE7469==61/358Ga���~?/$$+5Xϻ������������YC<=T�������ORTu�������Ͽ�����������������]@?J_Ƽ����C:55;99IohdK@FFLYlO9568;;FZ̾���QGDEI߿��þ��f�¾���������������jUM\ʽ�����YOQTR�½���wMIN����Z91..28Q�����U<-.07Q���QB9<>Eh����UJ?9=bµ����<89=�ø�����eOJD\f����h`Uj�ļ���NHJKX�¼���KC87:M����jKBFBLRT����L>?D]���vDB>>BO�Ž���J;78<I�ȿ���tnb��Ⱥ���G>;I忲���������������K<<DL�������NIMbͺ���L40/4Fa�����mD=<<J^t������yRFEN��qKDDO��������UE?F��ú���������������fJA@GV`q�SPY������mYL@61/1>CN\hg���a]_O_��[KIM^�����gIMKK\��Ƚ�������`^lml����ɽ������_HHNl����gOMDE\ndvfn���xWH?=7103EU������cMCDJWZf_bapk|f������gK?=DL�����_=4:=FOYOSQq����ټ�������˼���������ƿ�������X5--5<w�����UG<84873.,'$&(/8@BGA73-*.2?_ķ����SBGU����nH>AL�����������������������ǽ�����������������k������_D1+('(,6?KILG?;;;99:<=><;?ADPa�y^XID>=Lc�½��������ĺ������ey~����������ž����o]PQ[}������tC<78<AQc�p_^u������]J?>:<?CRYZYSOJFGKY]������jbZ]|��������������������������iYRVn�����������iWQLLQSgdz������VLIHM]^SKFFIIOOVV_b\TY^_n���ml`_UOQWp�����������~�����������������{l��������zTQXj������{{`^UMR^YONMLLS_{}kVJJJJN]j����j]NFHIOPSXUTaf~������eSRYc��������gho�����������������������������{VNZc��������pbjhZOPQPTV[[i���dYOOSY_ibh����qm\XX^]^lwx|�}y�����������������mico|��������������m`[^jonq���hYOMOUhwt|ne\^hjn�����kWV_n��ha]\Z^^\^o�������y`bn���������������nnnw���������bYZg�����u\Y^Zcl�����rs���zxmm_[Y[YNLS[^_f]NLQTg���������������p]SPRW]b|�����������������������sg_jj���������px}��ۼ������l�n�n�/0223FA34;?=;<;�÷���ƹ��������������\NNV`}XLA8679;>=<?Ol�����ȿ�������f^Od�Ⱦ��������|ROKE=?Nz����PG=;=?@AIV�����������aRPHHOd�þ������������r^RQ[m����}cYSLMMJJQ_{����ƿ�����zZMPs����������jWNHFFEJLNOMNVXTRNKFFJOZinit�����������}ssz�������������ia]_gmgeho�����������������������e][]l|�vfaYXchyr�mib^__`mehjhin��������tomnu���������oqpz�������w}nnojnwmb``is��������������{njewpp������������|gZY`hbj�����iWWURY[[cfgnwz���{svpivlp������{h`_cfp������ndmjim������yjrn^]bd���lv��������ui^^ft��������ccdgd_a]ce|��z�tu}xx{fxr�~���nyibgrqq������|��������~kilv���|r����{�����susx~�w����}niq����zdnjhcmw�������kd[\_nu��{��le^a`_^\]\^h���������{yqnro��y{����������}mnig������][aeg~~�����vu|�}�v�uqj��xj_]diq_h�����yx�zqd\\^kx�����}v|���vje_\Y[\i��������ln~�������h`j}�������|~o���ysolbicchjnqnhhmpumjw���ym_a\`^a^\fv|xy���������nkbls���������vy���oiuzy�������mi^b{�������tmkq~�������ytv���|mjhjca`dkbfcifcefix�����yxq�|qtw���zxww�������zffnn{�����������������zmorz�����������rhd^`fhijpojlqhed_^[\]\]_``br�������zk_^_f�������������{ypichz�������������{}v�������������}ufgfjkorssyswo`[[]_]]^^cfgt�������uha`agt{���������~uuonp����������������viifhflwxpwqxuwuv�������������������{wv~��yukgeeefed^a__cegu�����{��vnlo{�������xvqr������������vrrww}~wx����������}wwt�~{|���������|oihh{�����unlnty|yrvqqompz���x|oojhegmv���~yyz~�����~~tvp{������������������z}{��������zqnor������{tpphc`bckooqt�������{|yv����������{v|����zrpsrtzvnniiiimpu~������������������������~xpommntqqqkgedhly��|}vkgijpw������������������������}wumlkltv�}��~�}|~����������������������������}uvw{���|�}vnmlklkihjjkkkmv|�����}~�����~~��������������}|���������~xsw}|�~yvuvwx}�����������������}�{���zwnmpifmjlinkn��lrul���swk�l{ll�����mmp��j���������omxvoxtY�_k��jPf�����������_c>S'# +8�N� $�����R�������������������E50m&/-F�T�U(&#Ҧ�����������������������P3<�)$*\Z08:C()/+ />*,%$A�鷲��S�i�۬�������������������za/*3<=,!(318Z����������������������������U]UK@/..23))$ !&# %# "%=ͽ��nY]imϴ��������������������·����l9=H<D81-# !#+2.,-)))&"%+1BW�WE_ǯ����������������������������u?:0187?.+)+/24(*((,-,-,.FeZwf>?=Yо���������¹�������������j��LA<==IY�nIo<A9<J<Yd\YHG@AJ����[_S����z�ƻ���\��������OH߾�����_^CE6��{�IbC>UA>@>?�����[LOUU�����r��g������dOK?K����M�lKS>=D����[F;?;?c���\������ZǺ��ٿ������p��ƿ��I�OCS��wN91-@_osGX@:7;=<o���^Sk���������ܷ�����������ÿ��s�����S_�`MCV����WH?7<8.3;EKOOC@CSe���eMUv��ķ�������Ŀ��ҽ��|[NMX��Yt\@FB>:;:PFKERg]J<BcVw�����cmlOvo����aMk����LG>>���ʹ��~LM�b\\X^W|XbW`�����TNY��������iWU�`k�����LJ<?^]yKF^[�|^mTUgxf��WTBLPѼ���\��k��r�ͽ����d��������kMWXT`_u���Y_P?J?GN]REB=A?Dl�TJDLGS������������ɾ��������������������SeNFC>?768<BBIJTNOOI<<?EOg������p��������������������lWMHFgWdiYK=<=KTYg�lNLB<?Vv���iPk�Ļ����l�kZ_�ʾ������hZ��^G@EBKe�oWNNwsk�\__[�XYck���������������bUp��������������^OMLBCGORTUMMWNIGDGI[tfNFMx��û������������������miJ??ALH]aI??EMN���iMHX�����i]O]]�u�����_VKMd���PNPg�����ib�����������hO����iTIIJX]aTS\Z\e^Zbino�t`_������i]}���������nd^[f��UIORYa]x�jXSWW[UXhfp������������������x����SRKJPYaeielhd]][NSv���l��������k����`������vhx�z�vd^e^ZVf���bno\_`}db}��������������������njk����dZ_ja^beVUY[OMNOUn����������������������������cvY[Z[d_[YOICFKXyoZQS\Zt��������������������������������zUTSNNONPUSMQX]VPU^OIKMKWs����~��������������������������mz[X^UMIJHHISSLLJKKMOOQZr��������������������������{d[\f�ePKNIHLOOMOMJLOY[]d����~bs��������������������o������ma]ZXX[X\ejZUMMUPTOKMO]inmshbs}�������������������������|���f_WOOIOPOMOP~�ÿ����������������������ƻ����"#&$&&E�6%'%-7Ȫ�����������������ļ��O5'***&+&,0=?5<b�1!4:B13*%$.4;A3*%*6¹�bc����ͽ����������A,,/=�������I;49=D�ǻ���������A2+-5/*)('*'/;<;70.29M[]I?HQL<>O�;����MDB5,*$!"'0>@Ki������μ�������������������������������ŷ��������������Y>:64<CKF5/../42/,(&(*'#"$'/53.(%%',./00/.-.126C�徹��¿�������������������������������������OI=91/.,*)'%#!$%$%&'+-0357877::55=BIKSg��������������������������������������������`[MA@?CA=<<<=EJC>;:7779:==><;@@AAFo����\z������������������xfYQLKISOU_{{ddZUh�vdZNK?Nqol������dMLY\Z������zRNLHLN_ZOHFMO�fONINRVLFIMSy�h`nl_[F?ALYusenxgRik_Zlvkl�������û��Ǿ���������[\[h�������cJKTQMRRy����ucZUURKKP��q_cu_Wk\LJTn�������xUa����ƾ����������������������{�|mqo����zOMR[Rcn�[KFBFMOMLSY`^]][SJIM]op~�����~��ȿ�����_Zd����������s{[c�����������lONMWk�����WTWf�w_^YZTUT[Z|������q]\��������gZIIP^����W^]ZQNNULPY]p�p]VZi���������������������z������������{��zhypy�ma__WUNP[mrz��n^[neSKQW_�b\VNMIGIRTPPJHKPNJJO���������������������������������������_SPS[XUPQU[Z`ik�nm`VYYellf]_`eZZ]Zj��������RNMKLNTVagf^c_n���x_Za_p��������������vjdkkz������tnx�����|��b_m�|e_jeb^VKIIKLMLMU][k�����mjjd��������������zbftrb[]\XYTOORRRONMMPT\g�������������eiq}����������������m�������\S]ir{^WX]tolj`ZVWWVPQ[_\[agai|�����������������}������������{y��������tecXVQPSSV[_YW[_][]jp������w�����zomhss�����veYZTMOTWYY^k{����zj���������������������������fca\VXVSVVVWWYXRZ_YXTWZ[Z\_h���������������������������klsnkc]_binsxjnp��nh``eo�����vjb]XX\bihgoe_c`iqkhdco��������������ux�~�������}z�����{oeklm`]Z[[^bb_b^VTXZX[eb_^u��������uv���������������ykl���������yk^YTX\ky���jc^WXXWZ]]`kyxx�����r�����������������j�y��smm�x������2/L���V:;�������$ 60�����)������ + +#ɶ���! +#�����3ї���f&8�������F<+&6ʷ����0*:���/]���������$"'*:J�Ϭ���TFɬ��?E-'<�������8R�����Q4.&-�������79Ǭ���@+#"/E챞�����������B.=���B8Ȝ�����/9�����7%)<Ȫ����U/*(,=VSH><Vٷ������9>��������^���U:37/-,0���5-,-/,.8O�����>@˲���O������N:B����OEN�ú����X?6/,+3X����Ͻ��[PF���GBc�������e.+=W��D;436=X{SLFC85<JhgN>2((/:�ȱ���X<--;Fmy�������Ŵ��i9/..9Oī����{+*+2F����ϼ��o����LB@58Aį����5)!$3Mȸ���U:.-..03@�����_����hF:-.6N̽����JE?89=\�ļ�n_����������>>N�÷��kLC?GR�������d=>HQsŹ���������ν��k<47Alɹ�����MFEV�����^86=DS������C4<<iİ���O71/<W�������S:>CUƸ������]JCNڷ��J84=n������Y9..9Z�����o<9?EJ_�p���MA;<M���[F88O�º����[I41.2Do����������XL���YOKM������]MQHH^������W[���~iWJFDDHE�������W>5:Qϼ���I?<9Ht�����������jB:/.9W�Ƽ����VG=;AL������ngPc��ȼ�zC>;E�������jSLILKI`�����KHNv���bE=9;F�������A55Fb������M5,*0>�˹����OI[k��lR>;>JvϺ�����^A38=BԼ�����J:<G^�����C?EFGR����rL:>L㾾��VB426;R۽�����ND>Eo����������ſ���O=98=L]�ż����ZXS\oVQRIEFMt�¾���ZB>K^�����L768C_t������GD==??Mt����������\E>?@@D\���������PF>?FLYl^X���������Y@87?L�ž���TLFHR\�����eSMS`�����������wuqJ?:?I��ƾ����O>65?N���l��������x`D=>Bj�¼�����D@CN����TJGMe�����[XPc�������^KJIKO_�������VNMSRP\p��eVMN[{���~_^_SMSi�ļ���tIFM[��������}zml����bUURb�����mYOLOc������m^SOMZZm��zeYf������ZGBFMOZn������f\POKP[����������YSJMf���������OJHN_���������lsaQMUp���������kVPSVgo�������vZSQRVWh������jXTMKOMWo�������|oYTOQX`����������lh^[_k�x�hn����^OMVi�����{�����������SWh���������[VU_�����|VNJLTk��ona\\]i�����{_\]i��������eZZd�����re��������dYSQ\hk}���vigc[NN`�����h[Zc����������������kTOKVic�����lkcNN[cl����es�����zePV_h������oi\`kjo�i�����cP^\WYU]Xodޮ����������+/"2 >6� k�����NO����������������������'*' 0$'2<o0(Ī���˶��������8h�������?,|�.l���,-,"(/-' -̼¼���������������������������y<K^'"%( '(-:J^P������������������������������b:;?1(($! ((#++7G��^�����������������������������Y<6(#"##"$%#&+/0J�Z��������������������������������BJB=.(,+)'% #&&% (/77;A8D�ɾ�������������������e�����]mYG;32GJUJ?:F;U�����������ҿ�������Ű̸�̻���\I<BK���QG7*(32.9EE>E</..?8LX_U=HcNZVHN̻��������������ǻ�����EK@=O;_���JH99?>:8i˽�eO7C�g�Ƽ�l��¾���Ž��������QOgf7.=<>[�KE0(*+?�V??GC@N��|�Ŀ��z��ú�����μ������ڿ�����e_XbW�����?@F\YmpQh��wnA;?@C551//<Lp[@H<?Xig��ȶ���Ŀ����Ż�µ���������ɽ�Ͻ���KOQH\N{��I89@@>:CLINBK=C@;BEYh;Hf���x>98G�������ZU�Ľ�ƽ���u�e�o���II?J�whYNN�������������gcQ`���T[������������hICIMzo�[GF=HIIYiSHR^TH227O`����[\]Ou�����������v������YJEON�����YT[ozoOYQ]^��]�����ɺ��ĺ�������������f�WOLA@78473765<;>@B<?<CK<@?>B?[K�ƿ���¸���������ɻ��������������WZKBJ>EKMGCB69<FLFIB;?Dc������~��������RT^Zz�Ⱦ��RKTL\ZkUGCEVOYRVWOVVCGMW����������ü���������������������flTHF@=<;8=@<7988>ENQKCBRa�n�����¼��¾������������]YP]e�[G?@JLGj�I?:AO����y`hw�my]n����]`mݿ���`���Ż�������������Z[NB>ORaNF>78;DED?@HDIKNy�����t���»�����ľ�����þ�������p[INSNLAGDCHNZh[XaTURgwYV[k�����|b����������lYO^g{s\SS[^^XRONPac^YNVMT\dy�q[f��������������ǿ���������y�hubZ^_l_f^cpZMGLKNPWXJGNZY����ve����ykr�����r]������������_��������od����_�ho�����fr�_a[_k`�d\[NMVo��idn�����������zlj�����yw�����l_TQU[jpq_W\TYWYYW[OPX_vsp���pvqj�������������������������kp�����`bcjf`YMONKMT]TLIHSV]YUWez�������������������������~^Z^j��YY_ZY\[_YT[WYXYd\g�����b�������������������fgWg�yndmshh^^]tqm�t`ZZOTT[]PPKVXZclrkj��������������ͥ���ICL����I,&$$&$ܭ�����ZF~��ô�4$ "$'�������-&$)6=ũ�]-))2浤����L('*5������4)06ֽ�����L/ (/N괶��.#%%5è��N*+/A�������C,)):O������j<3G����E. $'7K����bGLHC㻵���JB;00<Ȭ�����YKO�����>58=Qjuí���:,()),:u�̹���EE\ɾ�X1-1;���������>/1Iί����H6<>=qؿ���TF>89BOTM@<>FMb¼����F/%&5O���������������wH;=Rپ�����a?0./9c��]PC>BJ����`��ZFG\���½�f@>K绯����d<867:DȺ������biUA;@UQIBGl�����W?619P��Ǿ���KJNX`�������bMRTz��nKFJV�ž��vJLD?ESs�������]XW��WE=>?Ga��Ǹ���M@>:<ES}������r[m��r\\JFO�Ž���Z?66?W��ƽ��cNB><>Gd����������pXMAFLX�ʿ���]`OF>CMc�������h\NIV|��LHLU�����WTU]n��������VV\WWr��������������L=8<Mg�������]JFHKMS}�]ROXmr�uXblr[Pl�Ǻ���yM;<AL�ľ�����WGAARc�������~O@AOVSHEGRcɽ�����d?<<?Z�ȿ��������j]f����_OMQ\���bSO[��������_C>:<LS_r�������kRQi��WOSy������\WTLMXi�������`XHDDBBJWlw������p\G?BIm�����pgRLZn����������q\J?;?Lz�������dNFELNVW`w����������tPLHMz�¿����ZRORY^������aT[flwwhULHFJ}������XC?HR�������\?:<?O�Ϳ��������iaXHADLa�ƾ�����`@<?BO������XJCGZ`����VOU\[[�����jLHS_�����eLDAHMn�������[[UYWXcmzt���������PD<<>Hl�������`THFKSb������������\QLLh������^IDDKTd������]OJFELW����������~ZLIIFLV��������mKGGJYl������������x[MLMVk�����|eSMO\m������of^[[\d�����������_NFIL[u�������VEAAGQVem�������jURNLOT_��������\QPTm������zmlj�����o��������mcVOIHN[�������gROKIMV������������aVVYVj�������^OHISg��������o]V[lkccfkm������ZTNOYj�������r[TQVZo���������vvf]PMQWk������i[POQUZg�������xlmmgfadt���������[TOT^o��������c`_[Zj����������kZTUV_h�������mXRPS^c�������|ohc``_i���������}ia[Z_l�������whj__^_djw��������ga[_v����������}no�����������oh^\]]]_x�����wcUOMOUan������|�utsnfj����������g^[[e���������kdbejt|������qlfb^_dmnhn������d[Z^y������������wz{jd^^m������i^_^TWcr������{s~viihf�����������tnfp��������m^da�Ƶ������������������������f�m��� !T�n.*'.�����������������������/*"+,%')/B��<&P���- *8;P������������C50)""I��������ļ�������������B2*%)F������g���6)(7iU>-'+-( &-/I�����N>+% $&)%&&)+.:ر��������������������������������������������[<-()*)-47*!#&/63*%!$!!! #'4Enǿ���ʹ�����������������������������������������_�sK=,% #""!!(-9HU_NKXWVKMZǰ�����������������������������������������ʿ�����cI::40///+(&""(/250***+-../3364303/.39743.1:=Wt~�����~HS��������������������������������������eGB@ER��QY^V^NQLWSkw_O\REFF:1102029:<=FUUID@=<>>CYXw����\MT�����Ȼ���������������Ƚ�����¹������������Ļ�Ǿ��|S:658>CDHEGMG==KPONMTx������hA95467<>Ar����������Ż��������Ǽ���TL`����Ž�������WKLQX~����nKFJKMGCKMHJACUnb\TY^�VE<;98AAPf����������ǽ������cIIi����~TNMMW��ǿ��������������h[M>=>Nx����lpVQFKYVZ����m]JGECA?DBACPYk�����\OQg}�������ZKC>CMPUYedw��ƽ������u_r��Ͻ������d^u��������aUNPKEC@>>><99?\���w_f_PSSXYbr�b_���������������������cYRWowdPNSj|���������������������r��~������XOKECAFEOSQLE>=CBDDBCLNbj������������YNKKTZbr�u�������������s``����|��f\MEDJMWd`^oyo�����q]VZci����ƿ������^XQ^kn^RRPPNLFFJIA??@??HL\a�����v�����m[_n����e]��������������������go|��sWOR^j����ngZVOSYXTRVTMMTZ\YOOMU\lw����t_TV^uf_ip�����������������oyt��v�zt��dUTONWb��������v�����������������lWXOJJNNONPYccfWJGGKp������������xuwcYY[p����wc[`VOSSU[]\acee[^_\VXXW]_ik���������������������jj����dX]efi]SPQR\e`\ZZ_^YUPS[u������r^SROQVVXX]][[\nzkf[Z\]b`r����������������������������^XZ^bkolhn{�|eYWNEDEFJQ_Z[\p}���vh``_���������������}whq���������zg^USZh�����xn_RURQUW[jv��������ln����������������������yrn]VTUSROKLMNU]hpwg\\^ZZkv�t��o~ku�uz���ϯ���U4),O@��* ���������ȱ����Z6�������+���$5������%!����� 7�����m.'2��������f!D��������"@��������8-�����O$$Qâ���^:1,(9���K)9����8 `��R( *CB�����]!*E��������˪����0!(M���������/"*=�����V&%(+E��8,$")+'" %)3L����W! 2��������¼��x��N+%*;��������K.+4O�������Ȳ����������Ⱦ�����R-,<ʩ����o(4���[@;-+,&(*))5^�����:'# 'Wǹ�����HO�ʺ��������ý�������ű�������YE8:Zǽ�����]>:8?>1%! %*7A|���5&%,38>?NYD=<Q��QAH35Uƨ�����<42B���������O>98Jժ���������VJ8/:?SŶ����:=1,&(9J�ɿ���K?80,2GWZ8/5C��^g:.244>��Ž���n?JFCMZɼ������������?3-?嶫�������x[YaU����ODLS^UK>GZVA=L]�����?%+@�ӻ���M.+),=H[\�ν���D::EKH88?Kȯ�������922=λ����������bVt˽���@=@G���D=>a�Ϳ���eN<.&$&-:?Uo�̾��zSFFUzL:8>kȿ���vB=?9=cܼ��������[MYLIO^��ҹ������?/0:I̽���WC97>x��������bOE>4,)*8BT�n�»�E,&#).4CXZIHFGK��M������λ�������������������KBBM�����H825K������7,())*7;BB:-$"6������T;;=<760+%$)*8Z�������CF�մ���������ɽ��������������������̼����I/,*+.3CM��[>/+**/57>>92.035:>A</'!"-6dȽ���qgLCJZw̿�ʿ������������¯��������Ź��������I;:78=]�����nr��T>0,('$'+18DIR|G3'"!#'-:>KI=<If������VE=;?R��ÿ����ʾ���������������������������]JNX��ĺ����`<70069:<<APUKGA<6/.+*++/2:Cr��mA4//4<SYRI?>AADOo�þ����������������ķ�������������������������zG?<958DU���`D4.,-279::;??;941/1;943568>Vt��L><;>Nzʽ���������������������������ǿ���������^NLSTo�����{YMMRSH?;6788?HHDFB<7/-04<EYZ`m[WKEKKDDLVV]y���������������������������������������������������x[IC@AGS^t�]WZPNMFE@=;;<;??DKNHC>;8:=FKMXZj����xo��������������������ǻ������������������������gUUW[cz����iWB=98=DECBC<;@EACEEDDFFMMYmt}xufZXZ\TOZy������k���¿���������������XR_����������ys������zZ]acZPOHDA?DHQ]�Ŵ�L�OsX���x�.))'')3,(*23///Qʰ���Ϸ���������������TU�}����@62211666=N��º���������U@?>B�Ƶ��������d=<;80.7E_���V>81/339:BLf^Vl���������o_f�˻�������Ǿ������jXVb�������]M>=96;>EJUk�˿�������L?@XԾ�����m_^RD><AHLJEB?>BDA@=<<<AHU^��������ʿ���������������������^SNNU]lbef�zjp����lKEHOXl������v[GCFJZ����}_SMNWSUPNIHHMU^_USRTY���������eUQh��Ŀ������s[ju������xn}���}ie_NLLOSUdbgi_gv�������}]\t�������k\_^gh`RMFJKKOXw����YJJFGKJQR\b��������������������������������lPMNPY|�����o_VNEDIJes�cgo���lUSWZUONMWjq���fTQNNPOPOY[}����m�~���������������������������ZRTp�������^TNNMJLMl��������]KKJMYgv����pibYb�~r�����doz����yzys�gUOWa�����k]TWcjkj���������������������v�t�y}s�����]QMKKOv���n\W\ab�s�����ls��ffo���o��{_UMMXaZg�����ierg_VW_g���������iiimuuuoe][Zl��������_`fl����e^NMT]ecfk�tj[XR_{�d`don����|he^[\aejr�������\WOSMU]i������ukkccpup^ZVfs���������|����tdfjh�������}gZ_l������e^_d��������p]YXZjlx���}e[XY\XY^r����ww���ylijfppq����������������g][bu�������}����������l_[Zduz������lc^YTRORY_go�m_bi_dc\[VUY`~�ohfo���������nfgv�����������������kbdmc^f��������}kfdh������������odY\aj|������h[QNQX__``^cc]`m������f\XZ^n}�����uwx�z{}���������������������ld^^as��������mafx{���������������ullhghhge\ZTPOPOOOOPT\_j������komkfi����������}~���������zvzx}����������x�������}z~y�yxqymmop|��}vc_\`fikkc^YWWZbiln����nbcjlxx��|{zvhim�����������������������������������������������~oibabr������wf`YWUTY\ejcbceifcbbgmqsmt�����|{�{�������xpmo{��������������������������������wrsyrvowz}tomhf`]YXY\binjgc^[XY^bu������pjeekw��������~�|{tzurq}��������zvm{����������������������������lfijprqorulf\[YZ[]_cmrnmdeadeghnjpw��������������������������������ynnuz����snmnty��������������������yvqmhfdaa_bghllkiljefinw�{xtztoy{����������������������|�����������yzou�y������n�Y[Tgfy���՜����� ����� ���-*��� + +o�����!5�����������������ȿ�?!0!+A!"'$(7"#Ϣ�������������A,_Ϋ���������NR��3!6U�( #7Z6@�������ſ����������������������/*1>)"$*&$&%-::gϺ��������������������������I4%$&(#$%& (.?CE::�����������������������������VCA0,2451,-#% "&%)1*-=F��T=/3߲�������������������������d��eIJBA<//$" $"))-0+)!!.>;LBFWHV�������������������������zy�i=:/3+)7:<:?1313:49FUR=9:49G�ŷ�̼�����������kw��ɭ���W;.4I?GK��MND:3EA9]5G=7ceZUXH����g�ij�����������������W^WPCoI@4(3/.342/SQL\;807BV����B������������������������Ke_EHB�?8*!#,2/$-&(*,,+6<A52/7eź�����������������������������_VZ<1,,/023.,,-/,+'('&$#"%.>�ǹ������˯���������������������sP�X7CH:;>7-+&,'*,2<?;/0??DANx}������ƻ����λ�������D���]oa==AH�����];7//-1:Fb�}RGS������ɿ��������Ƽ���n����e^�\_O@A=>CIDE>1.,11C�`\OBDO]���������yj���������Ƽ�������WLXNK^OOIM?60,--4=KLNEIJLSNU]Qz���ɾ�����ÿþ������������oeO_NW]SRE;533266<IZ��MFDEJ?KORVQjLkú�������������nV�jx����dOHfPTXZC@;8JLNVZWHK@:Ie���eKLN�Ϳ��������ǿ���I<JQ��Ÿ��UDLEKO[B>=@TW`����}cHGFZ~���������������������Tb��_q�����b]MA@CEFBBEJKFKVQ|����YEPU�������Ⱥ������ghX�������VKHHDE?;24=BK��_>:>E���������������dXSf�ɴ���UJKR�ÿ��VQ��������SXSEE[l�l[KCEFOT_MNYY�}_�������{������������������}ok^����LGJHIBHTROan������[SVi\Xc������������\m������UOOKLRQLVZ[]c|ex�e[KGIG]m��������i]���o��k�������r�cMNdnabdvfg����u|n�c`^g^an��kbo}j�����x�����aXUZSX|�������uq����i]n�������eUo���h��������c^XKILU^afiPLDCN[�_RYj����������������������������``gh���t^aZXNTLGE>?EHV\f��~VROO]�lhu|�����������������������wm�`nq_g_\KKKDEHJEBDEQ_jmc^yt�������Ͽ������������������ceo�_MMKIGMMLIKKIMOSNLQZ_m��f�������������������ʹ�����������������������������"+��-*.+Jʦ���������������������/%+,*$#*4���( ('$ (9i���-($*=ENΧ�����������EBK9, &>������|nlѿ������������e�?<J˿��cO?:7;2/*($(7I��E3+(# '<�и����V;3)" ,/38I[`[\Y�Ƿ�����������������ò��������������������������;.*+-9O���T8,+,-0/-)##!#+1.&""')(*+*,+++,/>Kݻ�����������������������������������������n?9473553*#!&*+**,-.45504:>=>Guɽ������������������������������������������ebJNTWUMA846=FKLF=3-)'$$'),*,+)''*4?FI?HSi���������������������ſ����������Ƚ�����k_OJ>8>M]�dO@@C??FRVW^B:9:?IS_�_K;69=M\_�����OUMRY��������I=9<GMRY�v[Qn��t�cYOFIS��Ⱦ����������������������o]��bY\[SKI??GPOLGD?A?:5225876:??;;8317?NX\n�����˺�������������������������������������PC?>AAJKK?=968>>?@HM[dcgYH:97:@CW�����tj_^l^������������������������»���������;�����fH@@FMLKIDB??>AEXg{fp_QGCCKPSWSW^UI=99<Lb������eNMNck������_^bhf`_�vee^g��ɾ���������������mgl�����������������[MGIOOYqbb^^dWI?ACJNMMONMME@?<:6558>??H_���������������������������������������k]fmjZMIGGDDKNOYYb[Y[VVZOIHHKLGC@@FMPk�����NJGBCDJL]nfdb]W^WLLRZ���������¾�������ln�����nw~�c~������|gWOOR__YVXQLLE?BEJNYWTPLIJMN^akemo{������Ƽ�������hjwvk}|��jRNNONKKJKOQv��������������|o�����eVQOQ_t��������������_RNSSVTNJKMOQRUVSSORQUZfaWSOLMR_}�����������������������������������������fNJHHIKLLJFFFA@BHPX����aUUcf������������������j`ooihi~�����o_Yk�����������������p�������~icaWUX^ZZ^^ZYSNLMLHDFIKRU]^s����v�������������������k`[[q�������f]\Y[f��������������ye_ZZbiimifd^_]]a^YX[_mtg_]_q��������~sm{��������������lbimgd^]\]kmc[WSKILNOYbowqv�����������������������������������{qc\WQNOW[k}�~^TMIGGIKOSX^bffrv���m��������������������������z_^ZY\YVY^\bginj_YWZYYa^ji�oin\]XP�ngd[j����������������ƒ���7..rJ�{:ŗ������A:L:=��F*�������# +%���(2�����[ +[�����=,�������"������ 쐑�� ̔����R( #������N(1����F$6ī���,'-:L������-&*&,Ǩ����:A�������, (8J�>J�����/+- ,Xߪ����ƻ���R,!.�������5+������%!/E������<%&-I[?2/>W{�������$ �����������WU��9"&>������1"ޯ��P5+:m�������r:8O�ĸ���;+,?������m$)8������S74HL/#%=庿��������6))D��������S@FY����M?N]>../3EUrB60>_����J8?DBG\��̶����KIJN���J953<Fgҭ����>>/'(3E������i��Ź�dD-,1]������=#%8���b,##-��������a�\H:$%(-C֫����b;,&&<{�������MTI>Uľ�A5=Q�����Q5/5<ϳ�����t7268<D{�;��ȹ�����;)!#,=Ư�����A;58>AI{��~B>Iz���IMm��Y�mŲ���X5'*5V��������?376@Ln�ĺ����N4,;?KAD_�θ�������2+&)>W������nv[H?@@̷��J4/4A˵��s;4>H��ɺ���`3+(1=IYʿ������SHReJ3-/Eɯ����P84//:Iں�����EA:773/6L��ʵ����dE=+)8ؾ�����U>77?Lb�˿������];/$#->n������R=0.68@Ib��������a_�VH@9<Nɲ����J:;<Brf������K?BU����TG:7:Kĵ����R/,2>q�������.'%(5K϶�����MSJ>=?40/3E߷������N,&).=b������>48O|ž��E<Geoc�����T7/6?������V5/02?rǼ����w_IE>;I`n�t�ÿ�����O1)(-6cų�����T>505Bs��{Z^e������iQ<:Gw�����m;804;Eʽ����G<7558D|������ɿ��P>85/2<�˸�����=527?I����_AAS]ɻ����F?9<?Ea������nOW������qRKMOZk������������V@457:Fh������M6/.0:?am��������aSMBC@?FN����fF;<Hd�����_IKJb�����dcj������{VD96<U�������J;977BZƿ����zc_~�lYFA@>L�ƻ����Q?79?X��������dWJQx�m_U[Yo�����G?==JS�Ĺ�����E<9?E_�������i`e���TKQWjp������ZNJKW^l����iVMO`�����TQLFRm�����rH>>FP��������\Y[SMZ�����������_KKMOUdm�˿���]GBCQ[����������hTKDBFZ��������tZFCDIf��������oOLHHNVu�������x[MEFPn��������l\TX�����������n]XW_]b_m�������M@??CJb������dituj\ON^���������SMDBLg������_^UY\bw����zlj_Z]ahnw���]Wi�����sNR_����������������rXHDIRl�����q^SCCKVf����nw���tf\ZUYiy��������ePTUVay�����q�q\SZQMRYo������md^]SNs�������nT[_N^n��ܟ����� �����짶�>[!� +m�����&������������������ܬ��?:)K�! 'BE/7F+*���ղ������^43CXױ�������S*'G�F+8BAʼ�^!'' 2иж�������������������������������~=��<3&( #,(%),2>պ�������������������������JMU����pJ>;><50+-)%+&1C}ƻ��|��Ȳ���������������������p�N�L=30401.(& #5>8?A98>ݫ�����������������������������m�QOaRn60('""%().+(#'3CFK=6:48H亭���ȿ������������g�L79=9?62'%""-76.3*/-0A;Naȿ���������������������������������lBET;;:<3-.-'!&#$.(+)'+(++++=OMQI?>JX]���������ź����]Z[IK��bK3LIDMG;8��ɺooF�\��Ŀ�ɮ���������������������Ľ�}=?@5AQC//()&##)+,6;?F;4=K�϶�������������������������ſ�SVlC85=G<:1./2/53//08:20,/;o�Ĺ�����ϼ���������������ȸ��dhOA:I?/41(,,-+,,61;9>FD:4:�fis��ź��������������������Xʽ����X?::HFQNG799083=:JLPG:69@LLOJ>AOWGF[Lr�����~fQG}Կ����������������cR�ھ������������ο��XMAf���¿���OqSOWYc�dbSMH<A7:AD9:2.02C]��RGFAJTZ��q��������������̼�������������W�g����ID><<@FO�����ZLGCMSk���_cHM��Ŀ������Zi�m�hUq`\�xO@=;:KFNGH>845<CMPO><<8:DVm���g��Ļ�����������»�������Ž��o]�z���hQECWM[QVPKMJ@D?BW~���f_ORaTunVUPOb����X]i�rbh����v~ZKORLLGIMOWMMWb�����oS��Ǿ�����ü���������������R@=<>E^L?:8>CDa�I=8>J����oMKOf|�ih��\PNZfڿ���Zam�Ƽ���g\�����������TJcm�rVUMHLVVVMNgs��t����������������������������jal�{dI>@CIELXQEEGBKIKJGGKY���n|������������ͽ�����o������������������eSZSdg���_TXYhVPY^V[ql]^�����YQWPKITYQX\�rff��^Y\cye^ee_o���yhv�������g�������}���������������������������^S����nrk`hh�fUKLNJNOV[Z\UHHC@JZ�XOY\u�������tir������������������������j`lYXNHHJNJObd|�����b[X^�����������������~����������od__TTNOPROFHJGHOWNKKFMY[\Y]����������������������������v]^n��]XSNQUZ\SQPMMLMNNSlvz�iQYTg����������������h^Y^{����ggnf_`�d����B9>[Q�S<(!#%���������������i&%%&/�������) $2:ϭ�6"*4ǭ����2'/�����}-#-2ί������9&&+:[�����@%%'>����E().9�������?$!%,A˭�����90O����M3#%-.Dϴ�������]ٳ����I95)*4������I:8<վ��\9,/7>hQȲ���P,+,,3P˺�����zeָ���<./:V��������.&(4v����Y:.066L�����]J=9;Dh��[GMV{߹�����C0$&6M�����������bT_vV9/0>V¹����M=/,.5aƼ��VBK�ñ������w|���ø���C@Wθ�����Q:.,,-9V������WJOB529IMF@G���F;7<��Ŀ����OMNXg����p�~_LHJMNSULDEW�����]HIFCJf��ɾ���������vLB?BKY��ĺ���K<:68>DLXg\m�aOFNVkWGI<:B^½���[B9:Jy��������SIA?FVƾ��������^LE<AIU������OVKA;;IZ��������mWNY���fV`������UWbn�������oUADGAACP_f�h��������eD:6>Hl̾������]TT_c����������������g��Ǿ����?866C]�������H>;<HJZ^������K@@IUXHDMZ�ƺ�����xC>=Dl����������oWSU����nLHIR���s^Wl���������SC>>HY^���¼����OQ_�kNJKe�����xVMJA@KT��þ��~XO@???BIc���������|XBAN]�����lYMGNYat��������ZK?:68Kd��������TLGOQ]h������������zXJEKV������WMIGIMN\�����VOV^inpaRNKIT�������iLEOp�������[>::=Nn�»���xrh[ONI?::ANz������jN;9=BO�������LGKV�����kbw��t�����}NJUe������MDBDJU�������aVNRON[cjll��������YC<;<I^�ƿ����[MDCEKT_f^��������b\LHHT������XGDEMXi������o\VNKNa�����������_MHFDIU���Ŀ���XJBCGL_����{��������YLKIPh������kUONWs�������]ZTUY`v����������\NFGKQl�������ZK@?FXh����������^WNJNO_{�������]KGIWl���u_]XWU[]ajhfq������{xi_QLLOo�������zXUMKS^������������[TSRO\�������XHBDM^��������~gYZtxgm���������YSMNZi�������lTNJKOXo��������lneYLKO\}�������i]XUZ\s��������olked^]jt~������nXJGIM[p������hVWROQ_����������m_VV[av��������]ROR\f�������lc_\\][cq��������wd]XZ`l��������qnbddgou���������o[UXf���������k_Y[hk��{������o^iachw�������pmhfghmx�������ukihimnv��������oifglp~�������~ukhilw���������~xtont}������ \ No newline at end of file diff --git a/ringtones/phone.au b/ringtones/phone.au new file mode 100644 index 0000000000000000000000000000000000000000..22be69bf25fc15d196c5e72ce1d6633d76580e98 GIT binary patch literal 225521 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�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 literal 0 HcmV?d00001 diff --git a/ringtones/phone2.au b/ringtones/phone2.au new file mode 100644 index 0000000000000000000000000000000000000000..42cb62bb8553af8a9a7b61335b4fea7865a6b344 GIT binary patch literal 18456 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 U7{mo*5C^0Jguw!%#DIkW04@F9>Hq)$ literal 0 HcmV?d00001 diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000000..e5d79ce70c --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,156 @@ +include ../globals.mak + +RING_VIDEO_LIBS= + +if RING_VIDEO +RING_VIDEO_LIBS+=./media/video/libvideo.la +if HAVE_LINUX +RING_VIDEO_LIBS+= \ + ./media/video/v4l2/libv4l2.la +endif +if HAVE_OSX +RING_VIDEO_LIBS+= \ + ./media/video/osxvideo/libosxvideo.la +endif +endif + +if BUILD_INSTANT_MESSAGING +INSTANT_MESSAGING_SUBDIR = im +IM_LIBA=./im/libim.la +IM_LIB=@EXPAT_LIBS@ +endif + +# Redefine the USE_IAX variable here, so that it could be used in managerimpl +if USE_IAX +IAX_SUBDIR=iax +IAX_CXXFLAG=-DUSE_IAX +IAX_LIBA=./iax/libiaxlink.la +IAX_LIB=-liax +endif + +if USE_DHT +RINGACC_SUBDIR=ringdht +RINGACC_CXXFLAG=-DUSE_DHT +RINGACC_LIBA=./ringdht/libringacc.la +endif + +if BUILD_TLS +TLS_LIB = @GNUTLS_LIBS@ +TLS_CFLAGS = @GNUTLS_CFLAGS@ +endif + +SUBDIRS = client media config hooks sip upnp $(IAX_SUBDIR) $(RINGACC_SUBDIR) $(INSTANT_MESSAGING_SUBDIR) $(RING_VIDEO_SUBDIR) + +# libring + +lib_LTLIBRARIES = libring.la + +libring_la_LIBADD = \ + ./sip/libsiplink.la \ + ./media/libmedia.la \ + ./client/libclient.la \ + ./config/libconfig.la \ + ./hooks/libhooks.la \ + ./upnp/libupnpcontrol.la \ + $(RINGACC_LIBA) \ + $(IAX_LIBA) \ + $(IM_LIBA) \ + $(RING_VIDEO_LIBS) + +libring_la_LDFLAGS = \ + @PJPROJECT_LIBS@ \ + @ALSA_LIBS@ \ + @PULSEAUDIO_LIBS@ \ + @SAMPLERATE_LIBS@ \ + @SNDFILE_LIBS@ \ + @YAMLCPP_LIBS@ \ + @SPEEXDSP_LIBS@ \ + @LIBUPNP_LIBS@ \ + $(TLS_LIB) \ + $(IAX_LIB) \ + $(IM_LIB) \ + $(PCRE_LIBS) + +if USE_DHT +libring_la_LDFLAGS += $(OPENDHT_LIBS) +endif + +if HAVE_OSX +#FIXME necessary for -lintl +libring_la_LDFLAGS += -L/usr/local/opt/gettext/lib +endif + +libring_la_CFLAGS = \ + @PJPROJECT_CFLAGS@ \ + @ALSA_CFLAGS@ \ + @PULSEAUDIO_CFLAGS@ \ + @SAMPLERATE_CFLAGS@ \ + @LIBUPNP_CFLAGS@ \ + @SPEEXDSP_CFLAGS@ \ + @LIBUPNP_CFLAGS@ \ + $(TLS_CFLAGS) + +if USE_DHT +libring_la_CFLAGS += $(OPENDHT_CFLAGS) +endif + +libring_la_SOURCES = conference.cpp \ + account_factory.cpp \ + call_factory.cpp \ + preferences.cpp \ + managerimpl.cpp \ + manager.cpp \ + call.cpp \ + account.cpp \ + logger.c \ + numbercleaner.cpp \ + fileutils.cpp \ + threadloop.cpp \ + ip_utils.h \ + ip_utils.cpp \ + utf8_utils.cpp \ + ice_transport.cpp \ + ice_transport.h \ + plugin_manager.cpp \ + plugin_loader_dl.cpp \ + ring_plugin.h \ + plugin_loader.h \ + plugin_manager.h \ + threadloop.h \ + conference.h \ + account_factory.h \ + call_factory.h \ + preferences.h \ + managerimpl.h \ + manager.h \ + account.h \ + call.h \ + logger.h \ + numbercleaner.h \ + fileutils.h \ + noncopyable.h \ + utf8_utils.h \ + ring_types.h \ + intrin.h \ + array_size.h \ + account_schema.h \ + registration_states.h \ + map_utils.h \ + string_utils.h \ + string_utils.cpp \ + rw_mutex.h \ + ring_api.cpp \ + gnutls_support.h + +nobase_include_HEADERS= dring/dring.h \ + dring/security_const.h \ + dring/callmanager_interface.h \ + dring/configurationmanager_interface.h \ + dring/presencemanager_interface.h \ + dring/account_const.h \ + dring/call_const.h + +if RING_VIDEO +nobase_include_HEADERS+= \ + dring/videomanager_interface.h +endif diff --git a/src/account.cpp b/src/account.cpp new file mode 100644 index 0000000000..2d713a86ba --- /dev/null +++ b/src/account.cpp @@ -0,0 +1,539 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Alexandre Bourget <alexandre.bourget@savoirfairelinux.com> + * Author : Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "account.h" + +#include <algorithm> +#include <iterator> +#include <mutex> + +#ifdef RING_VIDEO +#include "libav_utils.h" +#endif + +#include "logger.h" +#include "manager.h" + +#include "client/signal.h" +#include "account_schema.h" +#include "string_utils.h" +#include "config/yamlparser.h" +#include "system_codec_container.h" + +#include <yaml-cpp/yaml.h> + +#include "upnp/upnp_control.h" +#include "ip_utils.h" +#include "intrin.h" +#include "dring/account_const.h" + +namespace ring { + +const char * const Account::ALL_CODECS_KEY = "allCodecs"; +const char * const Account::VIDEO_CODEC_ENABLED = "enabled"; +const char * const Account::VIDEO_CODEC_NAME = "name"; +const char * const Account::VIDEO_CODEC_PARAMETERS = "parameters"; +const char * const Account::VIDEO_CODEC_BITRATE = "bitrate"; +const char * const Account::RINGTONE_PATH_KEY = "ringtonePath"; +const char * const Account::RINGTONE_ENABLED_KEY = "ringtoneEnabled"; +const char * const Account::VIDEO_ENABLED_KEY = "videoEnabled"; +const char * const Account::DISPLAY_NAME_KEY = "displayName"; +const char * const Account::ALIAS_KEY = "alias"; +const char * const Account::TYPE_KEY = "type"; +const char * const Account::ID_KEY = "id"; +const char * const Account::USERNAME_KEY = "username"; +const char * const Account::AUTHENTICATION_USERNAME_KEY = "authenticationUsername"; +const char * const Account::PASSWORD_KEY = "password"; +const char * const Account::HOSTNAME_KEY = "hostname"; +const char * const Account::ACCOUNT_ENABLE_KEY = "enable"; +const char * const Account::ACCOUNT_AUTOANSWER_KEY = "autoAnswer"; +const char * const Account::MAILBOX_KEY = "mailbox"; +const char * const Account::DEFAULT_USER_AGENT = PACKAGE_NAME "/" PACKAGE_VERSION; +const char * const Account::USER_AGENT_KEY = "useragent"; +const char * const Account::HAS_CUSTOM_USER_AGENT_KEY = "hasCustomUserAgent"; +const char * const Account::PRESENCE_MODULE_ENABLED_KEY = "presenceModuleEnabled"; +const char * const Account::UPNP_ENABLED_KEY = "upnpEnabled"; + +Account::Account(const std::string &accountID) + : accountID_(accountID) + , username_() + , hostname_() + , alias_() + , enabled_(true) + , autoAnswerEnabled_(false) + , registrationState_(RegistrationState::UNREGISTERED) + , systemCodecContainer_(getSystemCodecContainer()) + , accountCodecInfoList_() + , allCodecStr_() + , ringtonePath_("") + , ringtoneEnabled_(true) + , displayName_("") + , userAgent_(DEFAULT_USER_AGENT) + , hasCustomUserAgent_(false) + , mailBox_() + , upnp_(new upnp::Controller()) +{ + std::random_device rdev; + std::seed_seq seed {rdev(), rdev()}; + rand_.seed(seed); + + // Initialize the codec order, used when creating a new account + loadDefaultCodecs(); + #ifdef __ANDROID__ + ringtonePath_ = "/data/data/cx.ring/files/ringtones/konga.ul"; + #else + ringtonePath_ = "/usr/share/ring/ringtones/konga.ul"; + #endif +} + +Account::~Account() +{} + +void +Account::attachCall(const std::string& id) +{ + callIDSet_.insert(id); +} + +void +Account::detachCall(const std::string& id) +{ + callIDSet_.erase(id); +} + +void +Account::freeAccount() +{ + for (const auto& id : callIDSet_) + Manager::instance().hangupCall(id); + doUnregister(); +} + +void +Account::setRegistrationState(RegistrationState state, unsigned detail_code, const std::string& detail_str) +{ + if (state != registrationState_) { + registrationState_ = state; + // Notify the client + emitSignal<DRing::ConfigurationSignal::RegistrationStateChanged>( + accountID_, + mapStateNumberToString(registrationState_), + detail_code, + detail_str); + + emitSignal<DRing::ConfigurationSignal::VolatileDetailsChanged>( + accountID_, + getVolatileAccountDetails()); + } +} + +void +Account::loadDefaultCodecs() +{ + // default codec are system codecs + auto systemCodecList = systemCodecContainer_->getSystemCodecInfoList(); + + for (const auto& systemCodec: systemCodecList) { + // As defined in SDP RFC, only select a codec if he can encode and decode + if ((systemCodec->codecType & CODEC_ENCODER_DECODER) != CODEC_ENCODER_DECODER) + continue; + + if (systemCodec->mediaType & MEDIA_AUDIO) { + // we are sure of our downcast type : use static_pointer_cast + auto audioCodec = std::static_pointer_cast<SystemAudioCodecInfo>(systemCodec); + // instantiate AccountAudioCodecInfo initialized with our system codec + auto codec = std::make_shared <AccountAudioCodecInfo>(*audioCodec); + accountCodecInfoList_.push_back(codec); + RING_DBG("[%s] loading audio codec = %s", accountID_.c_str(), + codec->systemCodecInfo.name.c_str()); + } + + if (systemCodec->mediaType & MEDIA_VIDEO) { + // we are sure of our downcast type : use static_pointer_cast + auto videoCodec = std::static_pointer_cast<SystemVideoCodecInfo>(systemCodec); + // instantiate AccountVideoCodecInfo initialized with our system codec + auto codec = std::make_shared<AccountVideoCodecInfo>(*videoCodec); + accountCodecInfoList_.push_back(codec); + RING_DBG("[%s] loading video codec = %s", accountID_.c_str(), + codec->systemCodecInfo.name.c_str()); + } + } +} + +void +Account::serialize(YAML::Emitter &out) +{ + out << YAML::Key << ID_KEY << YAML::Value << accountID_; + out << YAML::Key << ALIAS_KEY << YAML::Value << alias_; + out << YAML::Key << ACCOUNT_ENABLE_KEY << YAML::Value << enabled_; + out << YAML::Key << TYPE_KEY << YAML::Value << getAccountType(); + out << YAML::Key << ALL_CODECS_KEY << YAML::Value << allCodecStr_; + out << YAML::Key << MAILBOX_KEY << YAML::Value << mailBox_; + out << YAML::Key << ACCOUNT_AUTOANSWER_KEY << YAML::Value << autoAnswerEnabled_; + out << YAML::Key << RINGTONE_ENABLED_KEY << YAML::Value << ringtoneEnabled_; + out << YAML::Key << RINGTONE_PATH_KEY << YAML::Value << ringtonePath_; + out << YAML::Key << HAS_CUSTOM_USER_AGENT_KEY << YAML::Value << hasCustomUserAgent_; + out << YAML::Key << USER_AGENT_KEY << YAML::Value << userAgent_; + out << YAML::Key << USERNAME_KEY << YAML::Value << username_; + out << YAML::Key << DISPLAY_NAME_KEY << YAML::Value << displayName_; + out << YAML::Key << HOSTNAME_KEY << YAML::Value << hostname_; + out << YAML::Key << UPNP_ENABLED_KEY << YAML::Value << upnpEnabled_; +} + +void +Account::unserialize(const YAML::Node &node) +{ + using yaml_utils::parseValue; + + parseValue(node, ALIAS_KEY, alias_); + parseValue(node, ACCOUNT_ENABLE_KEY, enabled_); + parseValue(node, USERNAME_KEY, username_); + parseValue(node, ACCOUNT_AUTOANSWER_KEY, autoAnswerEnabled_); + //parseValue(node, PASSWORD_KEY, password_); + + parseValue(node, MAILBOX_KEY, mailBox_); + parseValue(node, ALL_CODECS_KEY, allCodecStr_); + + // Update codec list which one is used for SDP offer + setActiveCodecs(split_string_to_unsigned(allCodecStr_, '/')); + parseValue(node, DISPLAY_NAME_KEY, displayName_); + parseValue(node, HOSTNAME_KEY, hostname_); + + parseValue(node, HAS_CUSTOM_USER_AGENT_KEY, hasCustomUserAgent_); + parseValue(node, USER_AGENT_KEY, userAgent_); + parseValue(node, RINGTONE_PATH_KEY, ringtonePath_); + parseValue(node, RINGTONE_ENABLED_KEY, ringtoneEnabled_); + + bool enabled; + parseValue(node, UPNP_ENABLED_KEY, enabled); + upnpEnabled_.store(enabled); +} + +void +Account::setAccountDetails(const std::map<std::string, std::string> &details) +{ + // Account setting common to SIP and IAX + parseString(details, Conf::CONFIG_ACCOUNT_ALIAS, alias_); + parseBool(details, Conf::CONFIG_ACCOUNT_ENABLE, enabled_); + parseString(details, Conf::CONFIG_ACCOUNT_USERNAME, username_); + parseString(details, Conf::CONFIG_ACCOUNT_HOSTNAME, hostname_); + parseString(details, Conf::CONFIG_ACCOUNT_MAILBOX, mailBox_); + parseString(details, Conf::CONFIG_ACCOUNT_USERAGENT, userAgent_); + parseBool(details, Conf::CONFIG_ACCOUNT_AUTOANSWER, autoAnswerEnabled_); + parseBool(details, Conf::CONFIG_RINGTONE_ENABLED, ringtoneEnabled_); + parseString(details, Conf::CONFIG_RINGTONE_PATH, ringtonePath_); + parseBool(details, Conf::CONFIG_ACCOUNT_HAS_CUSTOM_USERAGENT, hasCustomUserAgent_); + if (hasCustomUserAgent_) + parseString(details, Conf::CONFIG_ACCOUNT_USERAGENT, userAgent_); + else + userAgent_ = DEFAULT_USER_AGENT; + bool enabled; + parseBool(details, Conf::CONFIG_UPNP_ENABLED, enabled); + upnpEnabled_.store(enabled); +} + +std::map<std::string, std::string> +Account::getAccountDetails() const +{ + return { + {Conf::CONFIG_ACCOUNT_ALIAS, alias_}, + {Conf::CONFIG_ACCOUNT_ENABLE, enabled_ ? TRUE_STR : FALSE_STR}, + {Conf::CONFIG_ACCOUNT_TYPE, getAccountType()}, + {Conf::CONFIG_ACCOUNT_HOSTNAME, hostname_}, + {Conf::CONFIG_ACCOUNT_USERNAME, username_}, + {Conf::CONFIG_ACCOUNT_MAILBOX, mailBox_}, + {Conf::CONFIG_ACCOUNT_USERAGENT, hasCustomUserAgent_ ? userAgent_ : DEFAULT_USER_AGENT}, + {Conf::CONFIG_ACCOUNT_HAS_CUSTOM_USERAGENT, hasCustomUserAgent_ ? userAgent_ : DEFAULT_USER_AGENT}, + {Conf::CONFIG_ACCOUNT_AUTOANSWER, autoAnswerEnabled_ ? TRUE_STR : FALSE_STR}, + {Conf::CONFIG_RINGTONE_ENABLED, ringtoneEnabled_ ? TRUE_STR : FALSE_STR}, + {Conf::CONFIG_RINGTONE_PATH, ringtonePath_}, + {Conf::CONFIG_UPNP_ENABLED, upnpEnabled_ ? TRUE_STR : FALSE_STR}, + }; +} + +std::map<std::string, std::string> +Account::getVolatileAccountDetails() const +{ + return { + {Conf::CONFIG_ACCOUNT_REGISTRATION_STATUS, mapStateNumberToString(registrationState_)} + }; +} + +// Convert a list of payloads in a special format, readable by the server. +// Required format: payloads separated by slashes. +// @return std::string The serializable string +static std::string +join_string(const std::vector<unsigned> &v) +{ + std::ostringstream os; + std::copy(v.begin(), v.end(), std::ostream_iterator<unsigned>(os, "/")); + return os.str(); +} + +std::vector<unsigned> +Account::getActiveCodecs() const +{ + return getActiveAccountCodecInfoIdList(MEDIA_ALL); +} + +void +Account::setActiveCodecs(const std::vector<unsigned>& list) +{ + // first clear the previously stored codecs + // TODO: mutex to protect isActive + desactivateAllMedia(MEDIA_ALL); + + // list contains the ordered payload of active codecs picked by the user for this account + // we used the codec vector to save the order. + uint16_t order = 1; + for (const auto& item : list) { + if (auto accCodec = searchCodecById(item, MEDIA_ALL)) { + accCodec->isActive = true; + accCodec->order = order; + ++order; + } + } + + std::sort(std::begin(accountCodecInfoList_), + std::end (accountCodecInfoList_), + [](std::shared_ptr<AccountCodecInfo> a, + std::shared_ptr<AccountCodecInfo> b) { + return a->order < b->order; + }); + + allCodecStr_ = join_string(getActiveAccountCodecInfoIdList(MEDIA_ALL)); +} + +std::string +Account::mapStateNumberToString(RegistrationState state) +{ +#define CASE_STATE(X) case RegistrationState::X: \ + return #X + + switch (state) { + CASE_STATE(UNREGISTERED); + CASE_STATE(TRYING); + CASE_STATE(REGISTERED); + CASE_STATE(ERROR_GENERIC); + CASE_STATE(ERROR_AUTH); + CASE_STATE(ERROR_NETWORK); + CASE_STATE(ERROR_HOST); + CASE_STATE(ERROR_SERVICE_UNAVAILABLE); + CASE_STATE(ERROR_EXIST_STUN); + CASE_STATE(ERROR_NOT_ACCEPTABLE); + default: + return DRing::Account::States::ERROR_GENERIC; + } + +#undef CASE_STATE +} + +std::vector<unsigned> +Account::getDefaultCodecsId() +{ + return getSystemCodecContainer()->getSystemCodecInfoIdList(MEDIA_ALL); +} + +std::map<std::string, std::string> +Account::getDefaultCodecDetails(const unsigned& codecId) +{ + auto codec = ring::getSystemCodecContainer()->searchCodecById(codecId, ring::MEDIA_ALL); + if (codec) { + if (codec->mediaType & ring::MEDIA_AUDIO) { + auto audioCodec = std::static_pointer_cast<ring::SystemAudioCodecInfo>(codec); + return audioCodec->getCodecSpecifications(); + } + if (codec->mediaType & ring::MEDIA_VIDEO) { + auto videoCodec = std::static_pointer_cast<ring::SystemVideoCodecInfo>(codec); + return videoCodec->getCodecSpecifications(); + } + } + return {{}}; +} + +#define find_iter() \ + const auto& iter = details.find(key); \ + if (iter == details.end()) { \ + RING_ERR("Couldn't find key \"%s\"", key); \ + return; \ + } + +void +Account::parseString(const std::map<std::string, std::string>& details, + const char* key, std::string& s) +{ + find_iter(); + s = iter->second; +} + +void +Account::parseBool(const std::map<std::string, std::string>& details, + const char* key, bool &b) +{ + find_iter(); + b = iter->second == TRUE_STR; +} + +#undef find_iter + +/** + * Get the UPnP IP (external router) address. + * If use UPnP is set to false, the address will be empty. + */ +IpAddr +Account::getUPnPIpAddress() const +{ + std::lock_guard<std::mutex> lk(upnp_mtx); + if (upnpEnabled_) + return upnp_->getExternalIP(); + return {}; +} + +/** + * returns whether or not UPnP is enabled and active_ + * ie: if it is able to make port mappings + */ +bool +Account::getUPnPActive(std::chrono::seconds timeout) const +{ + std::lock_guard<std::mutex> lk(upnp_mtx); + if (upnpEnabled_) + return upnp_->hasValidIGD(timeout); + return false; +} + +/* + * private account codec searching functions + * + * */ +std::shared_ptr<AccountCodecInfo> +Account::searchCodecById(unsigned codecId, MediaType mediaType) +{ + if (mediaType != MEDIA_NONE) { + for (auto& codecIt: accountCodecInfoList_) { + if ((codecIt->systemCodecInfo.id == codecId) && + (codecIt->systemCodecInfo.mediaType & mediaType )) + return codecIt; + } + } + return {}; +} + +std::shared_ptr<AccountCodecInfo> +Account::searchCodecByName(std::string name, MediaType mediaType) +{ + if (mediaType != MEDIA_NONE) { + for (auto& codecIt: accountCodecInfoList_) { + if ((codecIt->systemCodecInfo.name.compare(name) == 0) && + (codecIt->systemCodecInfo.mediaType & mediaType )) + return codecIt; + } + } + return {}; +} + +std::shared_ptr<AccountCodecInfo> +Account::searchCodecByPayload(unsigned payload, MediaType mediaType) +{ + if (mediaType != MEDIA_NONE) { + for (auto& codecIt: accountCodecInfoList_) { + if ((codecIt->payloadType == payload ) && + (codecIt->systemCodecInfo.mediaType & mediaType )) + return codecIt; + } + } + return {}; +} + +std::vector<unsigned> +Account::getActiveAccountCodecInfoIdList(MediaType mediaType) const +{ + if (mediaType == MEDIA_NONE) + return {}; + + std::vector<unsigned> idList; + for (auto& codecIt: accountCodecInfoList_) { + if ((codecIt->systemCodecInfo.mediaType & mediaType) && + (codecIt->isActive)) + idList.push_back(codecIt->systemCodecInfo.id); + } + return idList; +} + +std::vector<unsigned> +Account::getAccountCodecInfoIdList(MediaType mediaType) const +{ + if (mediaType == MEDIA_NONE) + return {}; + + std::vector<unsigned> idList; + for (auto& codecIt: accountCodecInfoList_) { + if (codecIt->systemCodecInfo.mediaType & mediaType) + idList.push_back(codecIt->systemCodecInfo.id); + } + + return idList; +} + +void +Account::desactivateAllMedia(MediaType mediaType) +{ + if (mediaType == MEDIA_NONE) + return; + + for (auto& codecIt: accountCodecInfoList_) { + if (codecIt->systemCodecInfo.mediaType & mediaType) + codecIt->isActive = false; + } +} + +std::vector<std::shared_ptr<AccountCodecInfo>> +Account::getActiveAccountCodecInfoList(MediaType mediaType) const +{ + if (mediaType == MEDIA_NONE) + return {}; + + std::vector<std::shared_ptr<AccountCodecInfo>> accountCodecList; + for (auto& codecIt: accountCodecInfoList_) { + if ((codecIt->systemCodecInfo.mediaType & mediaType) && + (codecIt->isActive)) + accountCodecList.push_back(codecIt); + } + + return accountCodecList; +} + +} // namespace ring diff --git a/src/account.h b/src/account.h new file mode 100644 index 0000000000..6f82021472 --- /dev/null +++ b/src/account.h @@ -0,0 +1,425 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * Author : Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef ACCOUNT_H +#define ACCOUNT_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "noncopyable.h" +#include "config/serializable.h" +#include "registration_states.h" +#include "ip_utils.h" +#include "media_codec.h" + +#include <functional> +#include <string> +#include <vector> +#include <memory> +#include <map> +#include <set> +#include <random> +#include <stdexcept> +#include <atomic> +#include <mutex> +#include <chrono> + +namespace ring { namespace upnp { +class Controller; +}} // namespace ring::upnp + +namespace YAML { +class Emitter; +class Node; +} // namespace YAML + +namespace ring { + +class Call; +class SystemCodecContainer; + +class VoipLinkException : public std::runtime_error +{ + public: + VoipLinkException(const std::string &str = "") : + std::runtime_error("VoipLinkException occured: " + str) {} +}; + +/** + * @file account.h + * @brief Interface to protocol account (SIPAccount, IAXAccount) + * It can be enable on loading or activate after. + * It contains account, configuration, VoIP Link and Calls (inside the VoIPLink) + */ + +class Account : public Serializable, public std::enable_shared_from_this<Account> +{ + public: + Account(const std::string& accountID); + + /** + * Virtual destructor + */ + virtual ~Account(); + + /** + * Free all ressources related to this account. + * ***Current calls using this account are HUNG-UP*** + */ + void freeAccount(); + + virtual void setAccountDetails(const std::map<std::string, std::string> &details); + + virtual std::map<std::string, std::string> getAccountDetails() const; + + virtual std::map<std::string, std::string> getVolatileAccountDetails() const; + + /** + * Load the settings for this account. + */ + virtual void loadConfig() = 0; + + virtual void serialize(YAML::Emitter &out); + virtual void unserialize(const YAML::Node &node); + + /** + * Get the account ID + * @return constant account id + */ + const std::string& getAccountID() const { + return accountID_; + } + + virtual const char* getAccountType() const = 0; + + /** + * Returns true if this is the IP2IP account + */ + virtual bool isIP2IP() const { return false; } + + /** + * Register the account. + * This should update the getRegistrationState() return value. + */ + virtual void doRegister() = 0; + + /** + * Unregister the account. + * This should update the getRegistrationState() return value. + */ + virtual void doUnregister(std::function<void(bool)> cb = std::function<void(bool)>()) = 0; + + /** + * Create a new outgoing call. + * + * @param toUrl The address to call + * @return std::shared_ptr<Call> A pointer on the created call + */ + virtual std::shared_ptr<Call> newOutgoingCall(const std::string& toUrl) = 0; + + /* Note: we forbid incoming call creation from an instance of Account. + * This is why no newIncomingCall() method exist here. + */ + + std::vector<std::shared_ptr<Call>> getCalls(); + + /** + * Tell if the account is enable or not. + * @return true if enabled + * false otherwise + */ + bool isEnabled() const { + return enabled_; + } + + bool isVideoEnabled() const { + return videoEnabled_; + } + + void setEnabled(bool enable) { + enabled_ = enable; + } + + /** + * Set the registration state of the specified link + * @param state The registration state of underlying VoIPLink + */ + virtual void setRegistrationState(RegistrationState state, unsigned detail_code=0, const std::string& detail_str={}); + + /* They should be treated like macro definitions by the C++ compiler */ + std::string getUsername() const { + return username_; + } + + std::string getHostname() const { + return hostname_; + } + void setHostname(const std::string &hostname) { + hostname_ = hostname; + } + + std::string getAlias() const { + return alias_; + } + + void setAlias(const std::string &alias) { + alias_ = alias; + } + + static std::vector<unsigned> getDefaultCodecsId(); + static std::map<std::string, std::string> getDefaultCodecDetails(const unsigned& codecId); + + /* Accessor to data structures + * @return The list that reflects the user's choice + */ + std::vector<unsigned> getActiveCodecs() const; + + /** + * Update both the codec order structure and the codec string used for + * SDP offer and configuration respectively + */ + void setActiveCodecs(const std::vector<unsigned>& list); + std::shared_ptr<AccountCodecInfo> searchCodecById(unsigned codecId, MediaType mediaType); + std::vector<unsigned> getActiveAccountCodecInfoIdList(MediaType mediaType) const; + std::vector<std::shared_ptr<AccountCodecInfo>> getActiveAccountCodecInfoList(MediaType mediaType) const; + std::shared_ptr<AccountCodecInfo> searchCodecByPayload(unsigned payload, MediaType mediaType); + + + std::string getRingtonePath() const { + return ringtonePath_; + } + + void setRingtonePath(const std::string &path) { + ringtonePath_ = path; + } + + bool getRingtoneEnabled() const { + return ringtoneEnabled_; + } + void setRingtoneEnabled(bool enable) { + ringtoneEnabled_ = enable; + } + + std::string getDisplayName() const { + return displayName_; + } + void setDisplayName(const std::string &name) { + displayName_ = name; + } + + std::string getMailBox() const { + return mailBox_; + } + + void setMailBox(const std::string &mb) { + mailBox_ = mb; + } + + void attachCall(const std::string& id); + void detachCall(const std::string& id); + + static const char * const VIDEO_CODEC_ENABLED; + static const char * const VIDEO_CODEC_NAME; + static const char * const VIDEO_CODEC_PARAMETERS; + static const char * const VIDEO_CODEC_BITRATE; + + /** + * returns whether or not UPnP is enabled and active + * ie: if it is able to make port mappings + */ + bool getUPnPActive(std::chrono::seconds timeout = {}) const; + + /** + * Get the UPnP IP (external router) address. + * If use UPnP is set to false, the address will be empty. + */ + IpAddr getUPnPIpAddress() const; + + private: + NON_COPYABLE(Account); + + /** + * Helper function used to load the default codec order from the codec factory + */ + void loadDefaultCodecs(); + + /** + * Set of call's ID attached to the account. + */ + std::set<std::string> callIDSet_; + + protected: + static void parseString(const std::map<std::string, std::string> &details, const char *key, std::string &s); + static void parseBool(const std::map<std::string, std::string> &details, const char *key, bool &b); + + friend class ConfigurationTest; + + // General configuration keys for accounts + static const char * const ALL_CODECS_KEY; + static const char * const RINGTONE_PATH_KEY; + static const char * const RINGTONE_ENABLED_KEY; + static const char * const VIDEO_ENABLED_KEY; + static const char * const DISPLAY_NAME_KEY; + static const char * const ALIAS_KEY; + static const char * const TYPE_KEY; + static const char * const ID_KEY; + static const char * const USERNAME_KEY; + static const char * const AUTHENTICATION_USERNAME_KEY; + static const char * const PASSWORD_KEY; + static const char * const HOSTNAME_KEY; + static const char * const ACCOUNT_ENABLE_KEY; + static const char * const ACCOUNT_AUTOANSWER_KEY; + static const char * const MAILBOX_KEY; + static const char * const USER_AGENT_KEY; + static const char * const HAS_CUSTOM_USER_AGENT_KEY; + static const char * const DEFAULT_USER_AGENT; + static const char * const PRESENCE_MODULE_ENABLED_KEY; + static const char * const UPNP_ENABLED_KEY; + + static std::string mapStateNumberToString(RegistrationState state); + + /** + * Account ID are assign in constructor and shall not changed + */ + const std::string accountID_; + + /** + * Account login information: username + */ + std::string username_; + + /** + * Account login information: hostname + */ + std::string hostname_; + + /** + * Account login information: Alias + */ + std::string alias_; + + /** + * Tells if the link is enabled, active. + * This implies the link will be initialized on startup. + * Modified by the configuration (key: ENABLED) + */ + bool enabled_; + + /* If true, automatically answer calls to this account */ + bool autoAnswerEnabled_; + + /* + * The general, protocol neutral registration + * state of the account + */ + RegistrationState registrationState_; + + /** + * Vector containing all system codecs (with default parameters) + */ + std::shared_ptr<SystemCodecContainer> systemCodecContainer_; + /** + * Vector containing all account codecs (set of system codecs with custom parameters) + */ + std::vector<std::shared_ptr<AccountCodecInfo>> accountCodecInfoList_; + + /** + * List of audio and video codecs obtained when parsing configuration and used + * to generate codec order list + */ + std::string allCodecStr_; + + /** + * Ringtone .au file used for this account + */ + std::string ringtonePath_; + + /** + * Allows user to temporarily disable video calling + */ + + bool videoEnabled_ = true; + + /** + * Play ringtone when receiving a call + */ + bool ringtoneEnabled_; + + /** + * Display name when calling + */ + std::string displayName_; + + /** + * Useragent used for registration + */ + std::string userAgent_; + + // true if user has overridden default + bool hasCustomUserAgent_; + + /** + * Account mail box + */ + std::string mailBox_; + + /** + * Random generator engine + * Logical account state shall never rely on the state of the random generator. + */ + mutable std::mt19937_64 rand_; + + /** + * UPnP IGD controller and the mutex to access it + */ + std::unique_ptr<ring::upnp::Controller> upnp_; + mutable std::mutex upnp_mtx {}; + + /** + * flag which determines if this account is set to use UPnP. + */ + std::atomic_bool upnpEnabled_ {false}; + + /** + * private account codec searching functions + */ + std::shared_ptr<AccountCodecInfo> searchCodecByName(std::string name, MediaType mediaType); + std::vector<unsigned> getAccountCodecInfoIdList(MediaType mediaType) const; + void desactivateAllMedia(MediaType mediaType); + +}; + +} // namespace ring + +#endif diff --git a/src/account_factory.cpp b/src/account_factory.cpp new file mode 100644 index 0000000000..1d55ef44fa --- /dev/null +++ b/src/account_factory.cpp @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "account_factory.h" + +#include "sip/sipaccount.h" +#if HAVE_IAX +#include "iax/iaxaccount.h" +#endif +#if HAVE_DHT +#include "ringdht/ringaccount.h" +#endif + +#include "sip/sipvoiplink.h" // for SIPVoIPLink::loadIP2IPSettings + +#include <stdexcept> + +namespace ring { + +const char* const AccountFactory::DEFAULT_ACCOUNT_TYPE = SIPAccount::ACCOUNT_TYPE; + +AccountFactory::AccountFactory() +{ + auto sipfunc = [](const std::string& id){ return std::make_shared<SIPAccount>(id, true); }; + generators_.insert(std::make_pair(SIPAccount::ACCOUNT_TYPE, sipfunc)); + RING_DBG("registered %s account", SIPAccount::ACCOUNT_TYPE); +#if HAVE_IAX + auto iaxfunc = [](const std::string& id){ return std::make_shared<IAXAccount>(id); }; + generators_.insert(std::make_pair(IAXAccount::ACCOUNT_TYPE, iaxfunc)); + RING_DBG("registered %s account", IAXAccount::ACCOUNT_TYPE); +#endif +#if HAVE_DHT + auto dhtfunc = [](const std::string& id){ return std::make_shared<RingAccount>(id, false); }; + generators_.insert(std::make_pair(RingAccount::ACCOUNT_TYPE, dhtfunc)); + RING_DBG("registered %s account", RingAccount::ACCOUNT_TYPE); +#endif +} + +std::shared_ptr<Account> +AccountFactory::createAccount(const char* const accountType, + const std::string& id) +{ + if (hasAccount(id)) { + RING_ERR("Existing account %s", id.c_str()); + return nullptr; + } + + std::shared_ptr<Account> account; + { + const auto& it = generators_.find(accountType); + if (it != generators_.cend()) + account = it->second(id); + } + + { + std::lock_guard<std::recursive_mutex> lock(mutex_); + accountMaps_[accountType].insert(std::make_pair(id, account)); + } + + return account; + } + +bool +AccountFactory::isSupportedType(const char* const name) const +{ + return generators_.find(name) != generators_.cend(); +} + +void +AccountFactory::removeAccount(Account& account) +{ + const auto account_type = account.getAccountType(); + + std::lock_guard<std::recursive_mutex> lock(mutex_); + const auto& id = account.getAccountID(); + RING_DBG("Removing account %s", id.c_str()); + auto& map = accountMaps_.at(account.getAccountType()); + map.erase(id); + RING_DBG("Remaining %u %s account(s)", map.size(), account_type); +} + +void +AccountFactory::removeAccount(const std::string& id) +{ + std::lock_guard<std::recursive_mutex> lock(mutex_); + + if (auto account = getAccount(id)) { + removeAccount(*account); + } else + RING_ERR("No account with ID %s", id.c_str()); +} + +template <> bool +AccountFactory::hasAccount(const std::string& id) const +{ + std::lock_guard<std::recursive_mutex> lk(mutex_); + + for (const auto& item : accountMaps_) { + const auto& map = item.second; + if (map.find(id) != map.cend()) + return true; + } + + return false; +} + +template <> void +AccountFactory::clear() +{ + std::lock_guard<std::recursive_mutex> lk(mutex_); + accountMaps_.clear(); +} + +template <> +std::vector<std::shared_ptr<Account> > +AccountFactory::getAllAccounts() const +{ + std::lock_guard<std::recursive_mutex> lock(mutex_); + std::vector<std::shared_ptr<Account> > v; + + for (const auto& itemmap : accountMaps_) { + const auto& map = itemmap.second; + for (const auto item : map) + v.push_back(item.second); + } + + v.shrink_to_fit(); + return v; +} + +template <> +std::shared_ptr<Account> +AccountFactory::getAccount(const std::string& id) const +{ + std::lock_guard<std::recursive_mutex> lock(mutex_); + + for (const auto& item : accountMaps_) { + const auto& map = item.second; + const auto& iter = map.find(id); + if (iter != map.cend()) + return iter->second; + } + + return nullptr; +} + +template <> +bool +AccountFactory::empty() const +{ + std::lock_guard<std::recursive_mutex> lock(mutex_); + + for (const auto& item : accountMaps_) { + const auto& map = item.second; + if (!map.empty()) + return false; + } + + return true; +} + +template <> +std::size_t +AccountFactory::accountCount() const +{ + std::lock_guard<std::recursive_mutex> lock(mutex_); + std::size_t count = 0; + + for (const auto& it : accountMaps_) + count += it.second.size(); + + return count; +} + +std::shared_ptr<Account> +AccountFactory::getIP2IPAccount() const +{ + return ip2ip_account_.lock(); +} + +void AccountFactory::initIP2IPAccount() +{ + // cache this often used account using a weak_ptr + ip2ip_account_ = createAccount(SIPAccount::ACCOUNT_TYPE, + SIPAccount::IP2IP_PROFILE); +} + +} // namespace ring diff --git a/src/account_factory.h b/src/account_factory.h new file mode 100644 index 0000000000..2d1d11f6f2 --- /dev/null +++ b/src/account_factory.h @@ -0,0 +1,176 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef ACCOUNT_FACTORY_H +#define ACCOUNT_FACTORY_H + +#include <string> +#include <map> +#include <vector> +#include <memory> +#include <mutex> +#include <utility> + +namespace ring { + +class Account; +class AccountGeneratorBase; + +template <class T> using AccountMap = std::map<std::string, std::shared_ptr<T> >; + +class AccountFactory { + public: + static const char* const DEFAULT_ACCOUNT_TYPE; + + AccountFactory(); + + bool isSupportedType(const char* const accountType) const; + + std::shared_ptr<Account> createAccount(const char* const accountType, + const std::string& id); + + void removeAccount(Account& account); + + void removeAccount(const std::string& id); + + template <class T=Account> + bool hasAccount(const std::string& id) const { + std::lock_guard<std::recursive_mutex> lk(mutex_); + + const auto map = getMap_<T>(); + return map and map->find(id) != map->cend(); + } + + template <class T=Account> + void clear() { + std::lock_guard<std::recursive_mutex> lk(mutex_); + + auto map = getMap_<T>(); + if (!map) return; + + map->clear(); + } + + template <class T=Account> + bool empty() const { + std::lock_guard<std::recursive_mutex> lock(mutex_); + + const auto map = getMap_<T>(); + return map and map->empty(); + } + + template <class T=Account> + std::size_t accountCount() const { + std::lock_guard<std::recursive_mutex> lock(mutex_); + + const auto map = getMap_<T>(); + if (!map) return 0; + + return map->size(); + } + + template <class T=Account> + std::shared_ptr<T> + getAccount(const std::string& id) const { + std::lock_guard<std::recursive_mutex> lock(mutex_); + + const auto map = getMap_<T>(); + if (!map) return nullptr; + + const auto& it = map->find(id); + if (it == map->cend()) + return nullptr; + + return std::static_pointer_cast<T>(it->second); + } + + template <class T=Account> + std::vector<std::shared_ptr<T> > getAllAccounts() const { + std::lock_guard<std::recursive_mutex> lock(mutex_); + std::vector<std::shared_ptr<T> > v; + + const auto map = getMap_<T>(); + if (map) { + for (const auto& it : *map) + v.push_back(std::static_pointer_cast<T>(it.second)); + } + + v.shrink_to_fit(); + return v; + } + + std::shared_ptr<Account> getIP2IPAccount() const; + + void initIP2IPAccount(); + + private: + mutable std::recursive_mutex mutex_ = {}; + std::map<std::string, std::function<std::shared_ptr<Account>(const std::string&)> > generators_ = {{}}; + std::map<std::string, AccountMap<Account> > accountMaps_ = {{}}; + std::weak_ptr<Account> ip2ip_account_ = {}; //! cached pointer on IP2IP account + + template <class T> + const AccountMap<Account>* getMap_() const { + const auto& itermap = accountMaps_.find(T::ACCOUNT_TYPE); + + if (itermap != accountMaps_.cend()) + return &itermap->second; + + return nullptr; + } +}; + +template <> +bool +AccountFactory::hasAccount(const std::string& id) const; + +template <> +void +AccountFactory::clear(); + +template <> +std::vector<std::shared_ptr<Account> > +AccountFactory::getAllAccounts() const; + +template <> +std::shared_ptr<Account> +AccountFactory::getAccount(const std::string& accountId) const; + +template <> +bool +AccountFactory::empty() const; + +template <> +std::size_t +AccountFactory::accountCount() const; + +} // namespace ring + +#endif // ACCOUNT_FACTORY_H diff --git a/src/account_schema.h b/src/account_schema.h new file mode 100644 index 0000000000..495b617073 --- /dev/null +++ b/src/account_schema.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef ACCOUNT_SCHEMA_H_ +#define ACCOUNT_SCHEMA_H_ + +/** + * @file account_schema.h + * @brief Account specfic keys/constants that must be shared in daemon and clients. + */ + +namespace ring { namespace Conf { + +// Common account parameters +static const char *const CONFIG_ACCOUNT_TYPE = "Account.type"; +static const char *const CONFIG_ACCOUNT_ALIAS = "Account.alias"; +static const char *const CONFIG_ACCOUNT_MAILBOX = "Account.mailbox"; +static const char *const CONFIG_ACCOUNT_ENABLE = "Account.enable"; +static const char *const CONFIG_ACCOUNT_AUTOANSWER = "Account.autoAnswer"; +static const char *const CONFIG_ACCOUNT_REGISTRATION_EXPIRE = "Account.registrationExpire"; +static const char *const CONFIG_ACCOUNT_DTMF_TYPE = "Account.dtmfType"; +static const char *const CONFIG_RINGTONE_PATH = "Account.ringtonePath"; +static const char *const CONFIG_RINGTONE_ENABLED = "Account.ringtoneEnabled"; +static const char *const CONFIG_VIDEO_ENABLED = "Account.videoEnabled"; +static const char *const CONFIG_KEEP_ALIVE_ENABLED = "Account.keepAliveEnabled"; +static const char *const CONFIG_PRESENCE_ENABLED = "Account.presenceEnabled"; +static const char *const CONFIG_PRESENCE_PUBLISH_SUPPORTED = "Account.presencePublishSupported"; +static const char *const CONFIG_PRESENCE_SUBSCRIBE_SUPPORTED = "Account.presenceSubscribeSupported"; +static const char *const CONFIG_PRESENCE_STATUS = "Account.presenceStatus"; +static const char *const CONFIG_PRESENCE_NOTE = "Account.presenceNote"; + +static const char *const CONFIG_ACCOUNT_HOSTNAME = "Account.hostname"; +static const char *const CONFIG_ACCOUNT_USERNAME = "Account.username"; +static const char *const CONFIG_ACCOUNT_ROUTESET = "Account.routeset"; +static const char *const CONFIG_ACCOUNT_PASSWORD = "Account.password"; +static const char *const CONFIG_ACCOUNT_REALM = "Account.realm"; +static const char *const CONFIG_ACCOUNT_USERAGENT = "Account.useragent"; +static const char *const CONFIG_ACCOUNT_HAS_CUSTOM_USERAGENT = "Account.hasCustomUserAgent"; +static const char *const CONFIG_ACCOUNT_AUDIO_PORT_MIN = "Account.audioPortMin"; +static const char *const CONFIG_ACCOUNT_AUDIO_PORT_MAX = "Account.audioPortMax"; +static const char *const CONFIG_ACCOUNT_VIDEO_PORT_MIN = "Account.videoPortMin"; +static const char *const CONFIG_ACCOUNT_VIDEO_PORT_MAX = "Account.videoPortMax"; + +static const char *const CONFIG_LOCAL_INTERFACE = "Account.localInterface"; +static const char *const CONFIG_PUBLISHED_SAMEAS_LOCAL = "Account.publishedSameAsLocal"; +static const char *const CONFIG_LOCAL_PORT = "Account.localPort"; +static const char *const CONFIG_PUBLISHED_PORT = "Account.publishedPort"; +static const char *const CONFIG_PUBLISHED_ADDRESS = "Account.publishedAddress"; +static const char *const CONFIG_UPNP_ENABLED = "Account.upnpEnabled"; + +// SIP specific parameters +static const char *const CONFIG_STUN_SERVER = "STUN.server"; +static const char *const CONFIG_STUN_ENABLE = "STUN.enable"; + +// SRTP specific parameters +static const char *const CONFIG_SRTP_ENABLE = "SRTP.enable"; +static const char *const CONFIG_SRTP_KEY_EXCHANGE = "SRTP.keyExchange"; +static const char *const CONFIG_SRTP_RTP_FALLBACK = "SRTP.rtpFallback"; +static const char *const CONFIG_ZRTP_HELLO_HASH = "ZRTP.helloHashEnable"; +static const char *const CONFIG_ZRTP_DISPLAY_SAS = "ZRTP.displaySAS"; +static const char *const CONFIG_ZRTP_NOT_SUPP_WARNING = "ZRTP.notSuppWarning"; +static const char *const CONFIG_ZRTP_DISPLAY_SAS_ONCE = "ZRTP.displaySasOnce"; + +static const char *const CONFIG_TLS_LISTENER_PORT = "TLS.listenerPort"; +static const char *const CONFIG_TLS_ENABLE = "TLS.enable"; +static const char *const CONFIG_TLS_CA_LIST_FILE = "TLS.certificateListFile"; +static const char *const CONFIG_TLS_CERTIFICATE_FILE = "TLS.certificateFile"; +static const char *const CONFIG_TLS_PRIVATE_KEY_FILE = "TLS.privateKeyFile"; +static const char *const CONFIG_TLS_PASSWORD = "TLS.password"; +static const char *const CONFIG_TLS_METHOD = "TLS.method"; +static const char *const CONFIG_TLS_CIPHERS = "TLS.ciphers"; +static const char *const CONFIG_TLS_SERVER_NAME = "TLS.serverName"; +static const char *const CONFIG_TLS_VERIFY_SERVER = "TLS.verifyServer"; +static const char *const CONFIG_TLS_VERIFY_CLIENT = "TLS.verifyClient"; +static const char *const CONFIG_TLS_REQUIRE_CLIENT_CERTIFICATE = "TLS.requireClientCertificate"; +static const char *const CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC = "TLS.negotiationTimeoutSec"; + +// DHT specific parameters +static const char *const CONFIG_DHT_PORT = "DHT.port"; +static const char *const CONFIG_DHT_PRIVKEY_PATH = "DHT.privkeyPath"; +static const char *const CONFIG_DHT_CERT_PATH = "DHT.certificatePath"; + +// Volatile parameters +static const char *const CONFIG_ACCOUNT_REGISTRATION_STATUS = "Account.registrationStatus"; +static const char *const CONFIG_ACCOUNT_REGISTRATION_STATE_CODE = "Account.registrationCode"; +static const char *const CONFIG_ACCOUNT_REGISTRATION_STATE_DESC = "Account.registrationDescription"; +static const char *const CONFIG_TRANSPORT_STATE_CODE = "Transport.statusCode"; +static const char *const CONFIG_TRANSPORT_STATE_DESC = "Transport.statusDescription"; + +}} // namespace ring::Conf + +#endif // ACCOUNT_SCHEMA_H_ diff --git a/src/array_size.h b/src/array_size.h new file mode 100644 index 0000000000..046d75240a --- /dev/null +++ b/src/array_size.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef ARRAY_SIZE_H_ +#define ARRAY_SIZE_H_ + +// Returns the number of elements in a, calculated at compile-time +#define RING_ARRAYSIZE(a) \ + ((sizeof(a) / sizeof(*(a))) / \ + static_cast<size_t>(!(sizeof(a) % sizeof(*(a))))) + +/* Only use this macro with string literals or character arrays, will not work + * as expected with char pointers */ +#define CONST_PJ_STR(X) {(char *) (X), RING_ARRAYSIZE(X) - 1} + +#endif // ARRAY_SIZE_H_ diff --git a/src/call.cpp b/src/call.cpp new file mode 100644 index 0000000000..b326657875 --- /dev/null +++ b/src/call.cpp @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * Author : Laurielle Lea <laurielle.lea@savoirfairelinux.com> + * Author : Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "call.h" +#include "account.h" +#include "manager.h" +#include "audio/ringbufferpool.h" +#include "dring/call_const.h" + +#include "sip/sip_utils.h" +#include "ip_utils.h" +#include "array_size.h" +#include "map_utils.h" +#include "call_factory.h" +#include "string_utils.h" + +namespace ring { + +Call::Call(Account& account, const std::string& id, Call::CallType type) + : id_(id) + , type_(type) + , account_(account) +{ + time(×tamp_start_); + account_.attachCall(id_); +} + +Call::~Call() +{ + account_.detachCall(id_); +} + +void +Call::removeCall() +{ + Manager::instance().callFactory.removeCall(*this); + iceTransport_.reset(); +} + +const std::string& +Call::getAccountId() const +{ + return account_.getAccountID(); +} + +void +Call::setConnectionState(ConnectionState state) +{ + std::lock_guard<std::mutex> lock(callMutex_); + connectionState_ = state; +} + +Call::ConnectionState +Call::getConnectionState() const +{ + std::lock_guard<std::mutex> lock(callMutex_); + return connectionState_; +} + +bool +Call::validTransition(CallState newState) +{ + switch (callState_) { + case INACTIVE: + switch (newState) { + case INACTIVE: + return false; + default: + return true; + } + + case ACTIVE: + switch (newState) { + case HOLD: + return true; + default: + return false; + } + + case HOLD: + switch (newState) { + case ACTIVE: + return true; + default: + return false; + } + + default: + return false; + } +} + +bool +Call::setState(CallState state) +{ + std::lock_guard<std::mutex> lock(callMutex_); + if (not validTransition(state)) { + static const char *states[] = {"INACTIVE", "ACTIVE", "HOLD", "BUSY", "ERROR"}; + assert(callState_ < RING_ARRAYSIZE(states) and state < RING_ARRAYSIZE(states)); + + RING_ERR("Invalid call state transition from %s to %s", + states[callState_], states[state]); + return false; + } + + callState_ = state; + return true; +} + +Call::CallState +Call::getState() const +{ + std::lock_guard<std::mutex> lock(callMutex_); + return callState_; +} + +std::string +Call::getStateStr() const +{ + switch (getState()) { + case ACTIVE: + switch (getConnectionState()) { + case RINGING: + return isIncoming() ? "INCOMING" : "RINGING"; + case CONNECTED: + default: + return "CURRENT"; + } + + case HOLD: + return "HOLD"; + case BUSY: + return "BUSY"; + case INACTIVE: + + switch (getConnectionState()) { + case RINGING: + return isIncoming() ? "INCOMING" : "RINGING"; + case CONNECTED: + return "CURRENT"; + default: + return "INACTIVE"; + } + + case MERROR: + default: + return "FAILURE"; + } +} + +IpAddr +Call::getLocalIp() const +{ + std::lock_guard<std::mutex> lock(callMutex_); + return localAddr_; +} + +unsigned int +Call::getLocalAudioPort() const +{ + std::lock_guard<std::mutex> lock(callMutex_); + return localAudioPort_; +} + +unsigned int +Call::getLocalVideoPort() const +{ + std::lock_guard<std::mutex> lock(callMutex_); + return localVideoPort_; +} + +bool +Call::toggleRecording() +{ + const bool startRecording = Recordable::toggleRecording(); + RingBufferPool &rbPool = Manager::instance().getRingBufferPool(); + std::string process_id = Recordable::recorder_.getRecorderID(); + + if (startRecording) { + rbPool.bindHalfDuplexOut(process_id, id_); + rbPool.bindHalfDuplexOut(process_id, RingBufferPool::DEFAULT_ID); + + Recordable::recorder_.start(); + } else { + rbPool.unBindHalfDuplexOut(process_id, id_); + rbPool.unBindHalfDuplexOut(process_id, RingBufferPool::DEFAULT_ID); + } + + return startRecording; +} + +void Call::time_stop() +{ + time(×tamp_stop_); +} + +std::string Call::getTypeStr() const +{ + switch (type_) { + case INCOMING: + return "incoming"; + case OUTGOING: + return "outgoing"; + case MISSED: + return "missed"; + default: + return ""; + } +} + +std::map<std::string, std::string> +Call::getDetails() const +{ + return { + {DRing::Call::Details::CALL_TYPE, ring::to_string(type_)}, + {DRing::Call::Details::PEER_NUMBER, peerNumber_}, + {DRing::Call::Details::DISPLAY_NAME, displayName_}, + {DRing::Call::Details::CALL_STATE, getStateStr()}, + {DRing::Call::Details::CONF_ID, confID_}, + {DRing::Call::Details::TIMESTAMP_START, ring::to_string(timestamp_start_)}, + {DRing::Call::Details::ACCOUNTID, getAccountId()}, + }; +} + +std::map<std::string, std::string> +Call::getNullDetails() +{ + return { + {DRing::Call::Details::CALL_TYPE, "0"}, + {DRing::Call::Details::PEER_NUMBER, ""}, + {DRing::Call::Details::DISPLAY_NAME, "Unknown"}, + {DRing::Call::Details::CALL_STATE, "UNKNOWN"}, + {DRing::Call::Details::CONF_ID, ""}, + {DRing::Call::Details::TIMESTAMP_START, ""}, + {DRing::Call::Details::ACCOUNTID, ""}, + }; +} + +bool +Call::initIceTransport(bool master, unsigned channel_num) +{ + auto& iceTransportFactory = Manager::instance().getIceTransportFactory(); + iceTransport_ = iceTransportFactory.createTransport(getCallId().c_str(), channel_num, + master, account_.getUPnPActive()); + return static_cast<bool>(iceTransport_); +} + +int +Call::waitForIceInitialization(unsigned timeout) +{ + return iceTransport_->waitForInitialization(timeout); +} + +int +Call::waitForIceNegotiation(unsigned timeout) +{ + return iceTransport_->waitForNegotiation(timeout); +} + +bool +Call::isIceUsed() const +{ + return iceTransport_ and iceTransport_->isInitialized(); +} + +bool +Call::isIceRunning() const +{ + return iceTransport_ and iceTransport_->isRunning(); +} + +std::unique_ptr<IceSocket> +Call::newIceSocket(unsigned compId) +{ + return std::unique_ptr<IceSocket> {new IceSocket(iceTransport_, compId)}; +} + +} // namespace ring diff --git a/src/call.h b/src/call.h new file mode 100644 index 0000000000..d148a40e36 --- /dev/null +++ b/src/call.h @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * Author : Laurielle Lea <laurielle.lea@savoirfairelinux.com> + * Author : Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __CALL_H__ +#define __CALL_H__ + +#include "logger.h" + +#include "audio/recordable.h" +#include "ip_utils.h" +#include "ice_transport.h" + +#include <mutex> +#include <map> +#include <sstream> +#include <memory> +#include <vector> +#include <condition_variable> + +namespace ring { + +class VoIPLink; +class Account; + +template <class T> using CallMap = std::map<std::string, std::shared_ptr<T> >; + +/* + * @file call.h + * @brief A call is the base class for protocol-based calls + */ + +class Call : public Recordable, public std::enable_shared_from_this<Call> { + public: + static const char * const DEFAULT_ID; + + /** + * This determines if the call originated from the local user (OUTGOING) + * or from some remote peer (INCOMING, MISSED). + */ + enum CallType {INCOMING, OUTGOING, MISSED}; + + /** + * Tell where we're at with the call. The call gets Connected when we know + * from the other end what happened with out call. A call can be 'Connected' + * even if the call state is Busy, or Error. + * + * Audio should be transmitted when ConnectionState = Connected AND + * CallState = Active. + */ + enum ConnectionState {DISCONNECTED, TRYING, PROGRESSING, RINGING, CONNECTED}; + + /** + * The Call State. + */ + enum CallState {INACTIVE, ACTIVE, HOLD, BUSY, MERROR}; + + virtual ~Call(); + + /** + * Return a reference on the call id + * @return call id + */ + const std::string& getCallId() const { + return id_; + } + + /** + * Return a reference on the conference id + * @return call id + */ + const std::string& getConfId() const { + return confID_; + } + + void setConfId(const std::string &id) { + confID_ = id; + } + + Account& getAccount() const { return account_; } + const std::string& getAccountId() const; + + CallType getCallType() const { + return type_; + } + + /** + * Set the peer number (destination on outgoing) + * not protected by mutex (when created) + * @param number peer number + */ + void setPeerNumber(const std::string& number) { + peerNumber_ = number; + } + + /** + * Get the peer number (destination on outgoing) + * not protected by mutex (when created) + * @return std::string The peer number + */ + std::string getPeerNumber() const { + return peerNumber_; + } + + /** + * Set the display name (caller in ingoing) + * not protected by mutex (when created) + * @return std::string The peer display name + */ + void setDisplayName(const std::string& name) { + displayName_ = name; + } + + /** + * Get the peer display name (caller in ingoing) + * not protected by mutex (when created) + * @return std::string The peer name + */ + const std::string& getDisplayName() const { + return displayName_; + } + + /** + * Tell if the call is incoming + * @return true if yes false otherwise + */ + bool isIncoming() const { + return type_ == INCOMING; + } + + /** + * Set the connection state of the call (protected by mutex) + * @param state The connection state + */ + void setConnectionState(ConnectionState state); + + /** + * Get the connection state of the call (protected by mutex) + * @return ConnectionState The connection state + */ + ConnectionState getConnectionState() const; + + /** + * Set the state of the call (protected by mutex) + * @param state The call state + * @return true if the requested state change was valid, false otherwise + */ + bool setState(CallState state); + + /** + * Get the call state of the call (protected by mutex) + * @return CallState The call state + */ + CallState getState() const; + + std::string getStateStr() const; + + void setIPToIP(bool IPToIP) { + isIPToIP_ = IPToIP; + } + + /** + * Set my IP [not protected] + * @param ip The local IP address + */ + void setLocalIp(const IpAddr& ip) { + localAddr_ = ip; + } + + /** + * Set local audio port, as seen by me [not protected] + * @param port The local audio port + */ + void setLocalAudioPort(unsigned int port) { + localAudioPort_ = port; + } + + /** + * Set local video port, as seen by me [not protected] + * @param port The local video port + */ + void setLocalVideoPort(unsigned int port) { + localVideoPort_ = port; + } + + /** + * Return my IP [mutex protected] + * @return std::string The local IP + */ + IpAddr getLocalIp() const; + + /** + * Return port used locally (for my machine) [mutex protected] + * @return unsigned int The local audio port + */ + unsigned int getLocalAudioPort() const; + + /** + * Return port used locally (for my machine) [mutex protected] + * @return unsigned int The local video port + */ + unsigned int getLocalVideoPort() const; + + void time_stop(); + virtual std::map<std::string, std::string> getDetails() const; + static std::map<std::string, std::string> getNullDetails(); + + virtual bool toggleRecording(); + + /** + * Answer the call + */ + virtual void answer() = 0; + + /** + * Hang up the call + * @param reason + */ + virtual void hangup(int reason) = 0; + + virtual void switchInput(const std::string&) {}; + + /** + * Refuse incoming call + */ + virtual void refuse() = 0; + + /** + * Transfer a call to specified URI + * @param to The recipient of the call + */ + virtual void transfer(const std::string &to) = 0; + + /** + * Attended transfer + * @param The target call id + * @return True on success + */ + virtual bool attendedTransfer(const std::string& to) = 0; + + /** + * Put a call on hold + * @return bool True on success + */ + virtual void onhold() = 0; + + /** + * Resume a call from hold state + * @return bool True on success + */ + virtual void offhold() = 0; + + /** + * Peer Hung up a call + */ + virtual void peerHungup() = 0; + + /** + * Send DTMF + * @param code The char code + */ + virtual void carryingDTMFdigits(char code) = 0; + +#if HAVE_INSTANT_MESSAGING + /** + * Send a message to a call identified by its callid + * + * @param The actual message to be transmitted + * @param The sender of this message (could be another participant of a conference) + */ + virtual void sendTextMessage(const std::string &message, + const std::string &from) = 0; +#endif + + void removeCall(); + + bool initIceTransport(bool master, unsigned channel_num=4); + + int waitForIceInitialization(unsigned timeout); + + int waitForIceNegotiation(unsigned timeout); + + bool isIceUsed() const; + bool isIceRunning() const; + + std::unique_ptr<IceSocket> newIceSocket(unsigned compId); + + std::shared_ptr<IceTransport> getIceTransport() const { + return iceTransport_; + } + + protected: + /** + * Constructor of a call + * @param id Unique identifier of the call + * @param type set definitely this call as incoming/outgoing + */ + Call(Account& account, const std::string& id, Call::CallType type); + + std::shared_ptr<IceTransport> iceTransport_ {}; + + private: + bool validTransition(CallState newState); + + std::string getTypeStr() const; + /** Protect every attribute that can be changed by two threads */ + mutable std::mutex callMutex_ {}; + + // Informations about call socket / audio + + /** My IP address */ + IpAddr localAddr_ {}; + + /** Local audio port, as seen by me. */ + unsigned int localAudioPort_ {0}; + + /** Local video port, as seen by me. */ + unsigned int localVideoPort_ {0}; + + /** Unique ID of the call */ + std::string id_; + + /** Unique conference ID, used exclusively in case of a conferece */ + std::string confID_ {}; + + /** Type of the call */ + CallType type_; + + /** Associate account ID */ + Account& account_; + + /** Disconnected/Progressing/Trying/Ringing/Connected */ + ConnectionState connectionState_ {Call::DISCONNECTED}; + + /** Inactive/Active/Hold/Busy/Error */ + CallState callState_ {Call::INACTIVE}; + + /** Direct IP-to-IP or classic call */ + bool isIPToIP_ {false}; + + /** Number of the peer */ + std::string peerNumber_ {}; + + /** Display Name */ + std::string displayName_ {}; + + time_t timestamp_start_ {0}; + time_t timestamp_stop_ {0}; +}; + +} // namespace ring + +#endif // __CALL_H__ diff --git a/src/call_factory.cpp b/src/call_factory.cpp new file mode 100644 index 0000000000..50b02c142e --- /dev/null +++ b/src/call_factory.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author : Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "call_factory.h" + +#include <stdexcept> + +namespace ring { + +void +CallFactory::forbid() +{ + allowNewCall_ = false; +} + +void +CallFactory::removeCall(Call& call) +{ + std::lock_guard<std::recursive_mutex> lk(callMapsMutex_); + + const auto& id = call.getCallId(); + RING_DBG("Removing call %s", id.c_str()); + const auto& account = call.getAccount(); + auto& map = callMaps_.at(account.getAccountType()); + map.erase(id); + RING_DBG("Remaining %u %s call(s)", map.size(), account.getAccountType()); +} + +void +CallFactory::removeCall(const std::string& id) +{ + std::lock_guard<std::recursive_mutex> lk(callMapsMutex_); + + if (auto call = getCall(id)) { + removeCall(*call); + } else + RING_ERR("No call with ID %s", id.c_str()); +} + +//============================================================================== +// Template specializations (when T = Call) + +template <> bool +CallFactory::hasCall<Call>(const std::string& id) const +{ + std::lock_guard<std::recursive_mutex> lk(callMapsMutex_); + + for (const auto& item : callMaps_) { + const auto& map = item.second; + if (map.find(id) != map.cend()) + return true; + } + + return false; +} + +template <> void +CallFactory::clear<Call>() +{ + std::lock_guard<std::recursive_mutex> lk(callMapsMutex_); + callMaps_.clear(); +} + +template <> bool +CallFactory::empty<Call>() const +{ + std::lock_guard<std::recursive_mutex> lk(callMapsMutex_); + + for (const auto& item : callMaps_) { + const auto& map = item.second; + if (!map.empty()) + return false; + } + + return true; +} + +template <> std::shared_ptr<Call> +CallFactory::getCall<Call>(const std::string& id) +{ + std::lock_guard<std::recursive_mutex> lk(callMapsMutex_); + + for (const auto& item : callMaps_) { + const auto& map = item.second; + const auto& iter = map.find(id); + if (iter != map.cend()) + return iter->second; + } + + return nullptr; +} + +template <> std::vector<std::shared_ptr<Call> > +CallFactory::getAllCalls<Call>() +{ + std::lock_guard<std::recursive_mutex> lk(callMapsMutex_); + std::vector<std::shared_ptr<Call> > v; + + for (const auto& itemmap : callMaps_) { + const auto& map = itemmap.second; + for (const auto item : map) + v.push_back(item.second); + } + + v.shrink_to_fit(); + return v; +} + +template <> std::vector<std::string> +CallFactory::getCallIDs<Call>() const { + std::vector<std::string> v; + + for (const auto& item : callMaps_) { + const auto& map = item.second; + for (const auto& it : map) + v.push_back(it.first); + } + + v.shrink_to_fit(); + return v; +} + +template <> std::size_t +CallFactory::callCount<Call>() +{ + std::lock_guard<std::recursive_mutex> lk(callMapsMutex_); + std::size_t count = 0; + + for (const auto& itemmap : callMaps_) + count += itemmap.second.size(); + + return count; +} + +} // namespace ring diff --git a/src/call_factory.h b/src/call_factory.h new file mode 100644 index 0000000000..d4076a3b37 --- /dev/null +++ b/src/call_factory.h @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author : Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef CALL_FACTORY_H +#define CALL_FACTORY_H + +#include <call.h> +#include <account.h> + +#include <map> +#include <memory> +#include <mutex> +#include <vector> +#include <string> +#include <utility> + +namespace ring { + +class CallFactory { + public: + /** + * Forbid creation of new calls. + */ + void forbid(); + + /** + * Remove given call instance from call list. + */ + void removeCall(Call& call); + + /** + * Accessor on removeCall with callID than instance. + */ + void removeCall(const std::string& id); + + // Specializations of following template methods for T = Call + // are defined in call.cpp + + /** + * Return if given call exists. Type can optionally be specified. + */ + template <class T = Call> + bool hasCall(const std::string& id) const { + std::lock_guard<std::recursive_mutex> lk(callMapsMutex_); + + const auto map = getMap_<T>(); + return map and map->find(id) != map->cend(); + } + + /** + * Create a new call instance of Call class T, using Account class A. + * @param id Unique identifier of the call + * @param type set definitely this call as incoming/outgoing + * @param account account useed to create this call + */ + template <class T, class A> + std::shared_ptr<T> newCall(A& account, const std::string& id, Call::CallType type) { + if (!allowNewCall_) { + RING_WARN("newCall aborted : CallFactory in forbid state"); + return nullptr; + } + + // Trick: std::make_shared<T> can't build as T constructor is protected + // and not accessible from std::make_shared. + // We use a concrete class to bypass this restriction. + struct ConcreteCall : T { + ConcreteCall(A& account, const std::string& id, Call::CallType type) + : T(account, id, type) {} + }; + + if (hasCall(id)) { + RING_ERR("Call %s is already created", id.c_str()); + return nullptr; + } + + auto call = std::make_shared<ConcreteCall>(account, id, type); + + { + std::lock_guard<std::recursive_mutex> lk(callMapsMutex_); + callMaps_[account.getAccountType()].insert(std::make_pair(id, call)); + } + + return call; + } + + /** + * Return if calls exist. Type can optionally be specified. + */ + template <class T = Call> + bool empty() const { + std::lock_guard<std::recursive_mutex> lk(callMapsMutex_); + + const auto map = getMap_<T>(); + return !map or map->empty(); + } + + /** + * Erase all calls. Type can optionally be specified. + */ + template <class T = Call> + void clear() { + std::lock_guard<std::recursive_mutex> lk(callMapsMutex_); + + auto map = getMap_<T>(); + if (!map) return; + + map->clear(); + } + + /** + * Return call pointer associated to given ID. Type can optionally be specified. + */ + template <class T = Call> + std::shared_ptr<T> getCall(const std::string& id) { + std::lock_guard<std::recursive_mutex> lk(callMapsMutex_); + + const auto map = getMap_<T>(); + if (!map) return nullptr; + + const auto& it = map->find(id); + if (it == map->cend()) + return nullptr; + + return std::static_pointer_cast<T>(it->second); + } + + /** + * Return all calls. Type can optionally be specified. + */ + template <class T = Call> + std::vector<std::shared_ptr<T> > getAllCalls() { + std::lock_guard<std::recursive_mutex> lk(callMapsMutex_); + std::vector<std::shared_ptr<T> > v; + + const auto map = getMap_<T>(); + if (map) { + for (const auto& it : *map) + v.push_back(std::static_pointer_cast<T>(it.second)); + } + + v.shrink_to_fit(); + return v; + } + + /** + * Return all call's IDs. Type can optionally be specified. + */ + template <class T = Call> + std::vector<std::string> getCallIDs() const { + std::lock_guard<std::recursive_mutex> lk(callMapsMutex_); + std::vector<std::string> v; + + const auto map = getMap_<T>(); + if (map) { + for (const auto& it : *map) + v.push_back(it.first); + } + + v.shrink_to_fit(); + return v; + } + + /** + * Return number of calls. Type can optionally be specified. + */ + template <class T = Call> + std::size_t callCount() { + std::lock_guard<std::recursive_mutex> lk(callMapsMutex_); + + const auto map = getMap_<T>(); + if (!map) return 0; + + return map->size(); + } + + private: + mutable std::recursive_mutex callMapsMutex_{}; + + std::atomic_bool allowNewCall_{true}; + + std::map<std::string, CallMap<Call> > callMaps_{}; + + template <class T> + const CallMap<Call>* getMap_() const { + const auto& itermap = callMaps_.find(T::LINK_TYPE); + + if (itermap != callMaps_.cend()) + return &itermap->second; + + return nullptr; + } +}; + +// Specializations defined in call_factory.cpp + +template <> bool +CallFactory::hasCall<Call>(const std::string& id) const; + +template <> void +CallFactory::clear<Call>(); + +template <> bool +CallFactory::empty<Call>() const; + +template <> std::shared_ptr<Call> +CallFactory::getCall<Call>(const std::string& id); + +template <> std::vector<std::shared_ptr<Call> > +CallFactory::getAllCalls<Call>(); + +template <> std::vector<std::string> +CallFactory::getCallIDs<Call>() const; + +template <> std::size_t +CallFactory::callCount<Call>(); + +} // namespace ring + +#endif // CALL_FACTORY_H diff --git a/src/client/Makefile.am b/src/client/Makefile.am new file mode 100644 index 0000000000..6a6c0cbec8 --- /dev/null +++ b/src/client/Makefile.am @@ -0,0 +1,26 @@ +include $(top_srcdir)/globals.mak + +noinst_LTLIBRARIES = libclient.la + +noinst_HEADERS = \ + signal.h + +PRESENCE_SRC = presencemanager.cpp + +if RING_VIDEO +VIDEO_SRC = videomanager.cpp +noinst_HEADERS += videomanager.h +endif + +libclient_la_SOURCES = \ + signal.cpp \ + callmanager.cpp \ + configurationmanager.cpp \ + $(PRESENCE_SRC) \ + $(VIDEO_SRC) + +libclient_la_CXXFLAGS = \ + -I./ \ + -I../ \ + -DPREFIX=\"$(prefix)\" \ + -DPROGSHAREDIR=\"${datadir}/ring\" diff --git a/src/client/callmanager.cpp b/src/client/callmanager.cpp new file mode 100644 index 0000000000..4d521e2878 --- /dev/null +++ b/src/client/callmanager.cpp @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Pierre-Luc Beaudoin <pierre-luc.beaudoin@savoirfairelinux.com> + * Author: Alexandre Bourget <alexandre.bourget@savoirfairelinux.com> + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#include <vector> +#include <cstring> + +#include "callmanager_interface.h" +#include "call_factory.h" +#include "client/signal.h" + +#include "sip/sipcall.h" +#include "sip/sipvoiplink.h" +#include "audio/audiolayer.h" + +#include "logger.h" +#include "manager.h" + +namespace DRing { + +void +registerCallHandlers(const std::map<std::string, + std::shared_ptr<CallbackWrapperBase>>& handlers) +{ + auto& handlers_ = ring::getSignalHandlers(); + for (auto& item : handlers) { + auto iter = handlers_.find(item.first); + if (iter == handlers_.end()) { + RING_ERR("Signal %s not supported", item.first.c_str()); + continue; + } + + iter->second = std::move(item.second); + } +} + +std::string +placeCall(const std::string& accountID, const std::string& to) +{ + // Check if a destination number is available + if (to.empty()) { + RING_DBG("No number entered - Call stopped"); + return {}; + } else { + return ring::Manager::instance().outgoingCall(accountID, to); + } +} + +bool +refuse(const std::string& callID) +{ + return ring::Manager::instance().refuseCall(callID); +} + +bool +accept(const std::string& callID) +{ + return ring::Manager::instance().answerCall(callID); +} + +bool +hangUp(const std::string& callID) +{ + return ring::Manager::instance().hangupCall(callID); +} + +bool +hangUpConference(const std::string& confID) +{ + return ring::Manager::instance().hangupConference(confID); +} + +bool +hold(const std::string& callID) +{ + return ring::Manager::instance().onHoldCall(callID); +} + +bool +unhold(const std::string& callID) +{ + return ring::Manager::instance().offHoldCall(callID); +} + +bool +transfer(const std::string& callID, const std::string& to) +{ + return ring::Manager::instance().transferCall(callID, to); +} + +bool +attendedTransfer(const std::string& transferID, const std::string& targetID) +{ + return ring::Manager::instance().attendedTransfer(transferID, targetID); +} + +bool +joinParticipant(const std::string& sel_callID, + const std::string& drag_callID) +{ + return ring::Manager::instance().joinParticipant(sel_callID, drag_callID); +} + +void +createConfFromParticipantList(const std::vector<std::string>& participants) +{ + ring::Manager::instance().createConfFromParticipantList(participants); +} + +bool +isConferenceParticipant(const std::string& callID) +{ + return ring::Manager::instance().isConferenceParticipant(callID); +} + +void +removeConference(const std::string& conference_id) +{ + ring::Manager::instance().removeConference(conference_id); +} + +bool +addParticipant(const std::string& callID, const std::string& confID) +{ + return ring::Manager::instance().addParticipant(callID, confID); +} + +bool +addMainParticipant(const std::string& confID) +{ + return ring::Manager::instance().addMainParticipant(confID); +} + +bool +detachParticipant(const std::string& callID) +{ + return ring::Manager::instance().detachParticipant(callID); +} + +bool +joinConference(const std::string& sel_confID, const std::string& drag_confID) +{ + return ring::Manager::instance().joinConference(sel_confID, drag_confID); +} + +bool +holdConference(const std::string& confID) +{ + return ring::Manager::instance().holdConference(confID); +} + +bool +unholdConference(const std::string& confID) +{ + return ring::Manager::instance().unHoldConference(confID); +} + +std::map<std::string, std::string> +getConferenceDetails(const std::string& callID) +{ + return ring::Manager::instance().getConferenceDetails(callID); +} + +std::vector<std::string> +getConferenceList() +{ + return ring::Manager::instance().getConferenceList(); +} + +std::vector<std::string> +getParticipantList(const std::string& confID) +{ + return ring::Manager::instance().getParticipantList(confID); +} + +std::vector<std::string> +getDisplayNames(const std::string& confID) +{ + return ring::Manager::instance().getDisplayNames(confID); +} + +std::string +getConferenceId(const std::string& callID) +{ + return ring::Manager::instance().getConferenceId(callID); +} + +bool +startRecordedFilePlayback(const std::string& filepath) +{ + return ring::Manager::instance().startRecordedFilePlayback(filepath); +} + +void +stopRecordedFilePlayback(const std::string& filepath) +{ + ring::Manager::instance().stopRecordedFilePlayback(filepath); +} + +bool +toggleRecording(const std::string& callID) +{ + return ring::Manager::instance().toggleRecordingCall(callID); +} + +void +setRecording(const std::string& callID) +{ + toggleRecording(callID); +} + +void +recordPlaybackSeek(double value) +{ + ring::Manager::instance().recordingPlaybackSeek(value); +} + +bool +getIsRecording(const std::string& callID) +{ + return ring::Manager::instance().isRecording(callID); +} + +std::string +getCurrentAudioCodecName(const std::string&) +{ + RING_WARN("Deprecated"); + return ""; +} + +std::map<std::string, std::string> +getCallDetails(const std::string& callID) +{ + return ring::Manager::instance().getCallDetails(callID); +} + +std::vector<std::string> +getCallList() +{ + return ring::Manager::instance().getCallList(); +} + +void +playDTMF(const std::string& key) +{ + auto code = key.data()[0]; + ring::Manager::instance().playDtmf(code); + + if (auto current_call = ring::Manager::instance().getCurrentCall()) + current_call->carryingDTMFdigits(code); +} + +void +startTone(int32_t start, int32_t type) +{ + if (start) { + if (type == 0) + ring::Manager::instance().playTone(); + else + ring::Manager::instance().playToneWithMessage(); + } else + ring::Manager::instance().stopTone(); +} + +bool +switchInput(const std::string& callID, const std::string& resource) +{ + return ring::Manager::instance().switchInput(callID, resource); +} + +void +setSASVerified(const std::string& /*callID*/) +{ + RING_ERR("ZRTP not supported"); +} + +void +resetSASVerified(const std::string& /*callID*/) +{ + RING_ERR("ZRTP not supported"); +} + +void +setConfirmGoClear(const std::string& /*callID*/) +{ + RING_ERR("ZRTP not supported"); +} + +void +requestGoClear(const std::string& /*callID*/) +{ + RING_ERR("ZRTP not supported"); +} + +void +acceptEnrollment(const std::string& /*callID*/, bool /*accepted*/) +{ + RING_ERR("ZRTP not supported"); +} + +void +sendTextMessage(const std::string& callID, const std::string& message, const std::string& from) +{ +#if HAVE_INSTANT_MESSAGING + ring::Manager::instance().sendTextMessage(callID, message, from); +#endif +} + +void +sendTextMessage(const std::string& callID, const std::string& message) +{ +#if HAVE_INSTANT_MESSAGING + ring::Manager::instance().sendTextMessage(callID, message, "Me"); +#else + RING_ERR("Could not send \"%s\" text message to %s since Ring daemon does not support it, please recompile with instant messaging support", message.c_str(), callID.c_str()); +#endif +} + +} // namespace DRing diff --git a/src/client/configurationmanager.cpp b/src/client/configurationmanager.cpp new file mode 100644 index 0000000000..9ffef7f813 --- /dev/null +++ b/src/client/configurationmanager.cpp @@ -0,0 +1,686 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Pierre-Luc Beaudoin <pierre-luc.beaudoin@savoirfairelinux.com> + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Guillaume Carmel-Archambault <guillaume.carmel-archambault@savoirfairelinux.com> + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "configurationmanager_interface.h" +#include "account_schema.h" +#include "manager.h" +#if HAVE_TLS && HAVE_DHT +#include "sip/tlsvalidator.h" +#endif +#include "logger.h" +#include "fileutils.h" +#include "ip_utils.h" +#include "sip/sipaccount.h" +#include "ringdht/ringaccount.h" +#if HAVE_IAX +#include "iax/iaxaccount.h" +#endif +#include "security_const.h" +#include "audio/audiolayer.h" +#include "system_codec_container.h" +#include "client/signal.h" +#include "account_const.h" + +#include <dirent.h> + +#include <cerrno> +#include <cstring> +#include <sstream> + +namespace DRing { + +constexpr unsigned CODECS_NOT_LOADED = 0x1000; /** Codecs not found */ + +using ring::SIPAccount; +using ring::TlsValidator; +using ring::DeviceType; +using ring::HookPreference; + +void +registerConfHandlers(const std::map<std::string, + std::shared_ptr<CallbackWrapperBase>>& handlers) +{ + auto& handlers_ = ring::getSignalHandlers(); + for (auto& item : handlers) { + auto iter = handlers_.find(item.first); + if (iter == handlers_.end()) { + RING_ERR("Signal %s not supported", item.first.c_str()); + continue; + } + + iter->second = std::move(item.second); + } +} + +std::map<std::string, std::string> +getIp2IpDetails() +{ + auto account = ring::Manager::instance().getIP2IPAccount(); + if (auto sipaccount = static_cast<SIPAccount*>(account.get())) + return sipaccount->getIp2IpDetails(); + RING_ERR("Could not find IP2IP account"); + return std::map<std::string, std::string>(); +} + +std::map<std::string, std::string> +getAccountDetails(const std::string& accountID) +{ + return ring::Manager::instance().getAccountDetails(accountID); +} + +std::map<std::string, std::string> +getVolatileAccountDetails(const std::string& accountID) +{ + return ring::Manager::instance().getVolatileAccountDetails(accountID); +} + +std::map<std::string, std::string> +getTlsDefaultSettings() +{ + std::stringstream portstr; + portstr << ring::sip_utils::DEFAULT_SIP_TLS_PORT; + + return { + {ring::Conf::CONFIG_TLS_LISTENER_PORT, portstr.str()}, + {ring::Conf::CONFIG_TLS_CA_LIST_FILE, ""}, + {ring::Conf::CONFIG_TLS_CERTIFICATE_FILE, ""}, + {ring::Conf::CONFIG_TLS_PRIVATE_KEY_FILE, ""}, + {ring::Conf::CONFIG_TLS_PASSWORD, ""}, + {ring::Conf::CONFIG_TLS_METHOD, "TLSv1"}, + {ring::Conf::CONFIG_TLS_CIPHERS, ""}, + {ring::Conf::CONFIG_TLS_SERVER_NAME, ""}, + {ring::Conf::CONFIG_TLS_VERIFY_SERVER, "true"}, + {ring::Conf::CONFIG_TLS_VERIFY_CLIENT, "true"}, + {ring::Conf::CONFIG_TLS_REQUIRE_CLIENT_CERTIFICATE, "true"}, + {ring::Conf::CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC, "2"} + }; +} + +std::map<std::string, std::string> +getTlsSettings() +{ + auto account = ring::Manager::instance().getIP2IPAccount(); + if (auto sipaccount = static_cast<SIPAccount*>(account.get())) + return sipaccount->getTlsSettings(); + RING_ERR("Could not find IP2IP account"); + return std::map<std::string, std::string>(); +} + +void +setTlsSettings(const std::map<std::string, std::string>& details) +{ + auto account = ring::Manager::instance().getIP2IPAccount(); + if (auto sipaccount = static_cast<SIPAccount*>(account.get())) { + sipaccount->setTlsSettings(details); + ring::Manager::instance().saveConfig(); + ring::emitSignal<ConfigurationSignal::AccountsChanged>(); + } + + RING_DBG("No valid account in set TLS settings"); + return; +} + +std::map<std::string, std::string> +validateCertificate(const std::string&, + const std::string& certificate, + const std::string& privateKey) +{ +#if HAVE_TLS && HAVE_DHT + try { + return TlsValidator{certificate, privateKey}.getSerializedChecks(); + } catch(const std::runtime_error& e) { + RING_WARN("Certificate loading failed"); + return {{Certificate::ChecksNames::EXIST, Certificate::CheckValuesNames::FAILED}}; + } +#else + RING_WARN("TLS not supported"); + return {}; +#endif +} + +std::map<std::string, std::string> +validateCertificateRaw(const std::string&, + const std::vector<uint8_t>& certificate_raw) +{ +#if HAVE_TLS && HAVE_DHT + try { + return TlsValidator{certificate_raw}.getSerializedChecks(); + } catch(const std::runtime_error& e) { + RING_WARN("Certificate loading failed"); + return {{Certificate::ChecksNames::EXIST, Certificate::CheckValuesNames::FAILED}}; + } +#else + RING_WARN("TLS not supported"); + return {{}}; +#endif +} + +std::map<std::string, std::string> +getCertificateDetails(const std::string& certificate) +{ +#if HAVE_TLS && HAVE_DHT + try { + return TlsValidator{certificate,""}.getSerializedDetails(); + } catch(const std::runtime_error& e) { + RING_WARN("Certificate loading failed"); + } +#else + RING_WARN("TLS not supported"); +#endif + return {{}}; +} + +std::map<std::string, std::string> +getCertificateDetailsRaw(const std::vector<uint8_t>& certificate_raw) +{ +#if HAVE_TLS && HAVE_DHT + try { + return TlsValidator{certificate_raw}.getSerializedDetails(); + } catch(const std::runtime_error& e) { + RING_WARN("Certificate loading failed"); + } +#else + RING_WARN("TLS not supported"); +#endif + return {{}}; +} + +void +setAccountDetails(const std::string& accountID, const std::map<std::string, std::string>& details) +{ + ring::Manager::instance().setAccountDetails(accountID, details); +} + +void +sendRegister(const std::string& accountID, bool enable) +{ + ring::Manager::instance().sendRegister(accountID, enable); +} + +void +registerAllAccounts() +{ + ring::Manager::instance().registerAccounts(); +} + +///This function is used as a base for new accounts for clients that support it +std::map<std::string, std::string> +getAccountTemplate(const std::string& accountType) +{ + if (accountType == Account::ProtocolNames::RING) + return ring::RingAccount("dummy", false).getAccountDetails(); + else if (accountType == Account::ProtocolNames::SIP) + return ring::SIPAccount("dummy", false).getAccountDetails(); +#if HAVE_IAX + else if (accountType == Account::ProtocolNames::IAX) + return ring::IAXAccount("dummy").getAccountDetails(); +#endif + return {{}}; +} + +std::string +addAccount(const std::map<std::string, std::string>& details) +{ + return ring::Manager::instance().addAccount(details); +} + +void +removeAccount(const std::string& accountID) +{ + return ring::Manager::instance().removeAccount(accountID); +} + +std::vector<std::string> +getAccountList() +{ + return ring::Manager::instance().getAccountList(); +} + +/** + * Send the list of all codecs loaded to the client through DBus. + * Can stay global, as only the active codecs will be set per accounts + */ +std::vector<unsigned> +getCodecList() +{ + std::vector<unsigned> list {ring::getSystemCodecContainer()->getSystemCodecInfoIdList(ring::MEDIA_ALL)}; + if (list.empty()) + ring::emitSignal<ConfigurationSignal::Error>(CODECS_NOT_LOADED); + return list; +} + +std::vector<std::string> +getSupportedTlsMethod() +{ + return {"Default", "TLSv1", "SSLv3", "SSLv23"}; +} + +std::vector<std::string> +getSupportedCiphers(const std::string& accountID) +{ +#if HAVE_TLS + if (auto sipaccount = ring::Manager::instance().getAccount<SIPAccount>(accountID)) + return sipaccount->getSupportedCiphers(); + RING_ERR("SIP account %s doesn't exist", accountID.c_str()); +#endif + return {}; +} + + +bool +setCodecDetails(const std::string& accountID, + const unsigned& codecId, + const std::map<std::string, std::string>& details) +{ + auto acc = ring::Manager::instance().getAccount(accountID); + if (!acc) { + RING_ERR("Could not find account %s. can not set codec details" + , accountID.c_str()); + return false; + } + + auto codec = acc->searchCodecById(codecId, ring::MEDIA_ALL); + if (!codec) { + RING_ERR("can not find codec %d", codecId); + return false; + + } + if (codec->systemCodecInfo.mediaType & ring::MEDIA_AUDIO) { + if (auto foundCodec = std::static_pointer_cast<ring::AccountAudioCodecInfo>(codec)) { + foundCodec->setCodecSpecifications(details); + return true; + } + } + + if (codec->systemCodecInfo.mediaType & ring::MEDIA_VIDEO) { + if (auto foundCodec = std::static_pointer_cast<ring::AccountVideoCodecInfo>(codec)) { + foundCodec->setCodecSpecifications(details); + return true; + } + } + return false; +} + +std::map<std::string, std::string> +getCodecDetails(const std::string& accountID, const unsigned& codecId) +{ + auto acc = ring::Manager::instance().getAccount(accountID); + if (!acc) + { + RING_ERR("Could not find account %s return default codec details" + , accountID.c_str()); + return ring::Account::getDefaultCodecDetails(codecId); + } + + auto codec = acc->searchCodecById(codecId, ring::MEDIA_ALL); + if (!codec) + { + ring::emitSignal<ConfigurationSignal::Error>(CODECS_NOT_LOADED); + return {{}}; + } + + if (codec->systemCodecInfo.mediaType & ring::MEDIA_AUDIO) + if (auto foundCodec = std::static_pointer_cast<ring::AccountAudioCodecInfo>(codec)) + return foundCodec->getCodecSpecifications(); + + if (codec->systemCodecInfo.mediaType & ring::MEDIA_VIDEO) + if (auto foundCodec = std::static_pointer_cast<ring::AccountVideoCodecInfo>(codec)) + return foundCodec->getCodecSpecifications(); + + ring::emitSignal<ConfigurationSignal::Error>(CODECS_NOT_LOADED); + return {{}}; +} + +std::vector<unsigned> +getActiveCodecList(const std::string& accountID) +{ + if (auto acc = ring::Manager::instance().getAccount(accountID)) + return acc->getActiveCodecs(); + RING_ERR("Could not find account %s, returning default", accountID.c_str()); + return ring::Account::getDefaultCodecsId(); +} + +void +setActiveCodecList(const std::string& accountID + , const std::vector<unsigned>& list) +{ + if (auto acc = ring::Manager::instance().getAccount(accountID)) + { + acc->setActiveCodecs(list); + ring::Manager::instance().saveConfig(); + } else { + RING_ERR("Could not find account %s", accountID.c_str()); + } +} + +std::vector<std::string> +getAudioPluginList() +{ + return {PCM_DEFAULT, PCM_DMIX_DSNOOP}; +} + +void +setAudioPlugin(const std::string& audioPlugin) +{ + return ring::Manager::instance().setAudioPlugin(audioPlugin); +} + +std::vector<std::string> +getAudioOutputDeviceList() +{ + return ring::Manager::instance().getAudioOutputDeviceList(); +} + +std::vector<std::string> +getAudioInputDeviceList() +{ + return ring::Manager::instance().getAudioInputDeviceList(); +} + +void +setAudioOutputDevice(int32_t index) +{ + return ring::Manager::instance().setAudioDevice(index, DeviceType::PLAYBACK); +} + +void +setAudioInputDevice(int32_t index) +{ + return ring::Manager::instance().setAudioDevice(index, DeviceType::CAPTURE); +} + +void +setAudioRingtoneDevice(int32_t index) +{ + return ring::Manager::instance().setAudioDevice(index, DeviceType::RINGTONE); +} + +std::vector<std::string> +getCurrentAudioDevicesIndex() +{ + return ring::Manager::instance().getCurrentAudioDevicesIndex(); +} + +int32_t +getAudioInputDeviceIndex(const std::string& name) +{ + return ring::Manager::instance().getAudioInputDeviceIndex(name); +} + +int32_t +getAudioOutputDeviceIndex(const std::string& name) +{ + return ring::Manager::instance().getAudioOutputDeviceIndex(name); +} + +std::string +getCurrentAudioOutputPlugin() +{ + auto plugin = ring::Manager::instance().getCurrentAudioOutputPlugin(); + RING_DBG("Get audio plugin %s", plugin.c_str()); + return plugin; +} + +bool +getNoiseSuppressState() +{ + return ring::Manager::instance().getNoiseSuppressState(); +} + +void +setNoiseSuppressState(bool state) +{ + ring::Manager::instance().setNoiseSuppressState(state); +} + +bool +isAgcEnabled() +{ + return ring::Manager::instance().isAGCEnabled(); +} + +void +setAgcState(bool enabled) +{ + ring::Manager::instance().setAGCState(enabled); +} + +int32_t +isIax2Enabled() +{ + return HAVE_IAX; +} + +std::string +getRecordPath() +{ + return ring::Manager::instance().audioPreference.getRecordPath(); +} + +void +setRecordPath(const std::string& recPath) +{ + ring::Manager::instance().audioPreference.setRecordPath(recPath); +} + +bool +getIsAlwaysRecording() +{ + return ring::Manager::instance().getIsAlwaysRecording(); +} + +void +setIsAlwaysRecording(bool rec) +{ + ring::Manager::instance().setIsAlwaysRecording(rec); +} + +int32_t +getHistoryLimit() +{ + return ring::Manager::instance().getHistoryLimit(); +} + +void +setHistoryLimit(int32_t days) +{ + ring::Manager::instance().setHistoryLimit(days); +} + +bool +setAudioManager(const std::string& api) +{ + return ring::Manager::instance().setAudioManager(api); +} + +std::string +getAudioManager() +{ + return ring::Manager::instance().getAudioManager(); +} + +void +setVolume(const std::string& device, double value) +{ + if (auto audiolayer = ring::Manager::instance().getAudioDriver()) { + RING_DBG("set volume for %s: %f", device.c_str(), value); + + if (device == "speaker") + audiolayer->setPlaybackGain(value); + else if (device == "mic") + audiolayer->setCaptureGain(value); + + ring::emitSignal<ConfigurationSignal::VolumeChanged>(device, value); + } else { + RING_ERR("Audio layer not valid while updating volume"); + } +} + +double +getVolume(const std::string& device) +{ + if (auto audiolayer = ring::Manager::instance().getAudioDriver()) { + if (device == "speaker") + return audiolayer->getPlaybackGain(); + if (device == "mic") + return audiolayer->getCaptureGain(); + } + + RING_ERR("Audio layer not valid while updating volume"); + return 0.0; +} + +// FIXME: we should store "muteDtmf" instead of "playDtmf" +// in config and avoid negating like this +bool +isDtmfMuted() +{ + return not ring::Manager::instance().voipPreferences.getPlayDtmf(); +} + +void +muteDtmf(bool mute) +{ + ring::Manager::instance().voipPreferences.setPlayDtmf(not mute); +} + +bool +isCaptureMuted() +{ + if (auto audiolayer = ring::Manager::instance().getAudioDriver()) + return audiolayer->isCaptureMuted(); + + RING_ERR("Audio layer not valid"); + return false; +} + +void +muteCapture(bool mute) +{ + if (auto audiolayer = ring::Manager::instance().getAudioDriver()) + return audiolayer->muteCapture(mute); + + RING_ERR("Audio layer not valid"); + return; +} + +bool +isPlaybackMuted() +{ + if (auto audiolayer = ring::Manager::instance().getAudioDriver()) + return audiolayer->isPlaybackMuted(); + + RING_ERR("Audio layer not valid"); + return false; +} + +void +mutePlayback(bool mute) +{ + if (auto audiolayer = ring::Manager::instance().getAudioDriver()) + return audiolayer->mutePlayback(mute); + + RING_ERR("Audio layer not valid"); + return; +} + +std::map<std::string, std::string> +getHookSettings() +{ + return ring::Manager::instance().hookPreference.toMap(); +} + +void +setHookSettings(const std::map<std::string, + std::string>& settings) +{ + ring::Manager::instance().hookPreference = HookPreference(settings); +} + +void setAccountsOrder(const std::string& order) +{ + ring::Manager::instance().setAccountsOrder(order); +} + +std::string +getAddrFromInterfaceName(const std::string& interface) +{ + return ring::ip_utils::getInterfaceAddr(interface); +} + +std::vector<std::string> +getAllIpInterface() +{ + return ring::ip_utils::getAllIpInterface(); +} + +std::vector<std::string> +getAllIpInterfaceByName() +{ + return ring::ip_utils::getAllIpInterfaceByName(); +} + +std::map<std::string, std::string> +getShortcuts() +{ + return ring::Manager::instance().shortcutPreferences.getShortcuts(); +} + +void +setShortcuts(const std::map<std::string, std::string>& shortcutsMap) +{ + ring::Manager::instance().shortcutPreferences.setShortcuts(shortcutsMap); + ring::Manager::instance().saveConfig(); +} + +std::vector<std::map<std::string, std::string>> +getCredentials(const std::string& accountID) +{ + if (auto sipaccount = ring::Manager::instance().getAccount<SIPAccount>(accountID)) + return sipaccount->getCredentials(); + return {}; +} + +void +setCredentials(const std::string& accountID, + const std::vector<std::map<std::string, std::string>>& details) +{ + if (auto sipaccount = ring::Manager::instance().getAccount<SIPAccount>(accountID)) + sipaccount->setCredentials(details); +} + +} // namespace DRing diff --git a/src/client/presencemanager.cpp b/src/client/presencemanager.cpp new file mode 100644 index 0000000000..a4b1a56003 --- /dev/null +++ b/src/client/presencemanager.cpp @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2013-2015 Savoir-Faire Linux Inc. + * Author: Patrick Keroulas <patrick.keroulas@savoirfairelinux.com> + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "presencemanager_interface.h" + +#include <cerrno> +#include <sstream> +#include <cstring> + +#include "logger.h" +#include "manager.h" +#include "sip/sipaccount.h" +#include "sip/sippresence.h" +#include "sip/pres_sub_client.h" +#include "client/signal.h" + +namespace DRing { + +using ring::SIPAccount; + +constexpr static const char* STATUS_KEY = "Status"; +constexpr static const char* LINESTATUS_KEY = "LineStatus"; +constexpr static const char* ONLINE_KEY = "Online"; +constexpr static const char* OFFLINE_KEY = "Offline"; + +void +registerPresHandlers(const std::map<std::string, + std::shared_ptr<CallbackWrapperBase>>& handlers) +{ + auto& handlers_ = ring::getSignalHandlers(); + for (auto& item : handlers) { + auto iter = handlers_.find(item.first); + if (iter == handlers_.end()) { + RING_ERR("Signal %s not supported", item.first.c_str()); + continue; + } + + iter->second = std::move(item.second); + } +} + +/** + * Un/subscribe to buddySipUri for an accountID + */ +void +subscribeBuddy(const std::string& accountID, const std::string& uri, bool flag) +{ + if (auto sipaccount = ring::Manager::instance().getAccount<SIPAccount>(accountID)) { + auto pres = sipaccount->getPresence(); + if (pres and pres->isEnabled() and pres->isSupported(PRESENCE_FUNCTION_SUBSCRIBE)) { + RING_DBG("%subscribePresence (acc:%s, buddy:%s)", + flag ? "S" : "Uns", accountID.c_str(), uri.c_str()); + pres->subscribeClient(uri, flag); + } + } else + RING_ERR("Could not find account %s", accountID.c_str()); +} + +/** + * push a presence for a account + * Notify for IP2IP account and publish for PBX account + */ +void +publish(const std::string& accountID, bool status, const std::string& note) +{ + if (auto sipaccount = ring::Manager::instance().getAccount<SIPAccount>(accountID)) { + auto pres = sipaccount->getPresence(); + if (pres and pres->isEnabled() and pres->isSupported(PRESENCE_FUNCTION_PUBLISH)) { + RING_DBG("Send Presence (acc:%s, status %s).", accountID.c_str(), + status ? "online" : "offline"); + pres->sendPresence(status, note); + } + } else + RING_ERR("Could not find account %s.", accountID.c_str()); +} + +/** + * Accept or not a PresSubServer request for IP2IP account + */ +void +answerServerRequest(const std::string& uri, bool flag) +{ + auto account = ring::Manager::instance().getIP2IPAccount(); + if (auto sipaccount = static_cast<SIPAccount *>(account.get())) { + RING_DBG("Approve presence (acc:IP2IP, serv:%s, flag:%s)", uri.c_str(), + flag ? "true" : "false"); + + if (auto pres = sipaccount->getPresence()) + pres->approvePresSubServer(uri, flag); + else + RING_ERR("Presence not initialized"); + } else + RING_ERR("Could not find account IP2IP"); +} + +/** + * Get all active subscriptions for "accountID" + */ +std::vector<std::map<std::string, std::string> > +getSubscriptions(const std::string& accountID) +{ + std::vector<std::map<std::string, std::string>> ret; + + if (auto sipaccount = ring::Manager::instance().getAccount<SIPAccount>(accountID)) { + if (auto pres = sipaccount->getPresence()) { + for (const auto& s : pres->getClientSubscriptions()) { + ret.push_back({ + {STATUS_KEY, s->isPresent() ? ONLINE_KEY : OFFLINE_KEY}, + {LINESTATUS_KEY, s->getLineStatus()} + }); + } + } else + RING_ERR("Presence not initialized"); + } else + RING_ERR("Could not find account %s.", accountID.c_str()); + + return ret; +} + +/** + * Batch subscribing of URIs + */ +void +setSubscriptions(const std::string& accountID, const std::vector<std::string>& uris) +{ + if (auto sipaccount = ring::Manager::instance().getAccount<SIPAccount>(accountID)) { + if (auto pres = sipaccount->getPresence()) { + for (const auto &u : uris) + pres->subscribeClient(u, true); + } else + RING_ERR("Presence not initialized"); + } else + RING_ERR("Could not find account %s.", accountID.c_str()); +} + +} // namespace DRing diff --git a/src/client/signal.cpp b/src/client/signal.cpp new file mode 100644 index 0000000000..ae5e99a988 --- /dev/null +++ b/src/client/signal.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "signal.h" + +namespace ring { + +SignalHandlerMap& +getSignalHandlers() +{ + static SignalHandlerMap handlers = { + /* Call */ + exported_callback<DRing::CallSignal::StateChange>(), + exported_callback<DRing::CallSignal::TransferFailed>(), + exported_callback<DRing::CallSignal::TransferSucceeded>(), + exported_callback<DRing::CallSignal::RecordPlaybackStopped>(), + exported_callback<DRing::CallSignal::VoiceMailNotify>(), + exported_callback<DRing::CallSignal::IncomingMessage>(), + exported_callback<DRing::CallSignal::IncomingCall>(), + exported_callback<DRing::CallSignal::RecordPlaybackFilepath>(), + exported_callback<DRing::CallSignal::ConferenceCreated>(), + exported_callback<DRing::CallSignal::ConferenceChanged>(), + exported_callback<DRing::CallSignal::UpdatePlaybackScale>(), + exported_callback<DRing::CallSignal::ConferenceRemoved>(), + exported_callback<DRing::CallSignal::NewCallCreated>(), + exported_callback<DRing::CallSignal::SipCallStateChanged>(), + exported_callback<DRing::CallSignal::RecordingStateChanged>(), + exported_callback<DRing::CallSignal::SecureSdesOn>(), + exported_callback<DRing::CallSignal::SecureSdesOff>(), + exported_callback<DRing::CallSignal::SecureZrtpOn>(), + exported_callback<DRing::CallSignal::SecureZrtpOff>(), + exported_callback<DRing::CallSignal::ShowSAS>(), + exported_callback<DRing::CallSignal::ZrtpNotSuppOther>(), + exported_callback<DRing::CallSignal::ZrtpNegotiationFailed>(), + exported_callback<DRing::CallSignal::RtcpReportReceived>(), + exported_callback<DRing::CallSignal::PeerHold>(), + + /* Configuration */ + exported_callback<DRing::ConfigurationSignal::VolumeChanged>(), + exported_callback<DRing::ConfigurationSignal::AccountsChanged>(), + exported_callback<DRing::ConfigurationSignal::StunStatusFailed>(), + exported_callback<DRing::ConfigurationSignal::RegistrationStateChanged>(), + exported_callback<DRing::ConfigurationSignal::VolatileDetailsChanged>(), + exported_callback<DRing::ConfigurationSignal::Error>(), + + /* Presence */ + exported_callback<DRing::PresenceSignal::NewServerSubscriptionRequest>(), + exported_callback<DRing::PresenceSignal::ServerError>(), + exported_callback<DRing::PresenceSignal::NewBuddyNotification>(), + exported_callback<DRing::PresenceSignal::SubscriptionStateChanged>(), + +#ifdef RING_VIDEO + /* Video */ + exported_callback<DRing::VideoSignal::DeviceEvent>(), + exported_callback<DRing::VideoSignal::DecodingStarted>(), + exported_callback<DRing::VideoSignal::DecodingStopped>(), +#endif + }; + + return handlers; +} + +}; // namespace ring diff --git a/src/client/signal.h b/src/client/signal.h new file mode 100644 index 0000000000..afdb584bf1 --- /dev/null +++ b/src/client/signal.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "callmanager_interface.h" +#include "configurationmanager_interface.h" +#include "presencemanager_interface.h" + +#ifdef RING_VIDEO +#include "videomanager_interface.h" +#endif + +#include "dring.h" +#include "logger.h" + +#include <exception> +#include <memory> +#include <map> +#include <utility> +#include <string> + +namespace ring { + +using SignalHandlerMap = std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>>; +extern SignalHandlerMap& getSignalHandlers(); + +/* + * Find related user given callback and call it with given + * arguments. + */ +template <typename Ts, typename ...Args> +static void emitSignal(Args...args) { + const auto& handlers = getSignalHandlers(); + if (auto cb = *DRing::CallbackWrapper<typename Ts::cb_type>(handlers.at(Ts::name))) { + try { + cb(args...); + } catch (std::exception& e) { + RING_ERR("Exception during emit signal %s:\n%s", Ts::name, e.what()); + } + } +} + +template <typename Ts> +std::pair<std::string, std::shared_ptr<DRing::CallbackWrapper<typename Ts::cb_type>>> +exported_callback() { + return std::make_pair((const std::string&)Ts::name, std::make_shared<DRing::CallbackWrapper<typename Ts::cb_type>>()); +} + +} // namespace ring diff --git a/src/client/videomanager.cpp b/src/client/videomanager.cpp new file mode 100644 index 0000000000..a6ec5f022a --- /dev/null +++ b/src/client/videomanager.cpp @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Pierre-Luc Beaudoin <pierre-luc.beaudoin@savoirfairelinux.com> + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Guillaume Carmel-Archambault <guillaume.carmel-archambault@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "videomanager_interface.h" +#include "videomanager.h" +#include "libav_utils.h" +#include "video/video_input.h" +#include "video/video_device_monitor.h" +#include "account.h" +#include "logger.h" +#include "manager.h" +#include "system_codec_container.h" +#include "client/signal.h" +#include "video/sinkclient.h" + +#include <functional> +#include <memory> +#include <string> +#include <vector> + +namespace DRing { + +using ring::videoManager; + +void +registerVideoHandlers(const std::map<std::string, + std::shared_ptr<CallbackWrapperBase>>& handlers) +{ + auto& handlers_ = ring::getSignalHandlers(); + for (auto& item : handlers) { + auto iter = handlers_.find(item.first); + if (iter == handlers_.end()) { + RING_ERR("Signal %s not supported", item.first.c_str()); + continue; + } + + iter->second = std::move(item.second); + } +} + + +std::vector<std::string> +getDeviceList() +{ + return videoManager.videoDeviceMonitor.getDeviceList(); +} + +VideoCapabilities +getCapabilities(const std::string& name) +{ + return videoManager.videoDeviceMonitor.getCapabilities(name); +} + +std::string +getDefaultDevice() +{ + return videoManager.videoDeviceMonitor.getDefaultDevice(); +} + +void +setDefaultDevice(const std::string& name) +{ + RING_DBG("Setting default device to %s", name.c_str()); + videoManager.videoDeviceMonitor.setDefaultDevice(name); +} + +std::map<std::string, std::string> +getSettings(const std::string& name) +{ + return videoManager.videoDeviceMonitor.getSettings(name).to_map(); +} + +void +applySettings(const std::string& name, + const std::map<std::string, std::string>& settings) +{ + videoManager.videoDeviceMonitor.applySettings(name, settings); +} + +void +startCamera() +{ + videoManager.videoPreview = ring::getVideoCamera(); + videoManager.started = switchToCamera(); +} + +void +stopCamera() +{ + if (switchInput("")) + videoManager.started = false; + videoManager.videoPreview.reset(); +} + +bool +switchInput(const std::string& resource) +{ + if (auto call = ring::Manager::instance().getCurrentCall()) { + // TODO remove this part when clients are updated to use CallManager::switchInput + call->switchInput(resource); + return true; + } else { + if (auto input = videoManager.videoInput.lock()) + return input->switchInput(resource).valid(); + RING_WARN("Video input not initialized"); + } + return false; +} + +bool +switchToCamera() +{ + return switchInput(videoManager.videoDeviceMonitor.getMRLForDefaultDevice()); +} + +bool +hasCameraStarted() +{ + return videoManager.started; +} + +template <class T> +static void +registerSinkTarget_(const std::string& sinkId, T&& cb) +{ + if (auto sink = ring::Manager::instance().getSinkClient(sinkId)) + sink->registerTarget(std::forward<T>(cb)); + else + RING_WARN("No sink found for id '%s'", sinkId.c_str()); +} + +void +registerSinkTarget(const std::string& sinkId, + const std::function<void(unsigned char*)>& cb) +{ + registerSinkTarget_(sinkId, cb); +} + +void +registerSinkTarget(const std::string& sinkId, + std::function<void(unsigned char*)>&& cb) +{ + registerSinkTarget_(sinkId, cb); +} + +} // namespace DRing + +namespace ring { + +VideoManager videoManager; + +std::shared_ptr<video::VideoFrameActiveWriter> +getVideoCamera() +{ + if (auto input = videoManager.videoInput.lock()) + return input; + + videoManager.started = false; + auto input = std::make_shared<video::VideoInput>(); + videoManager.videoInput = input; + return input; +} + +video::VideoDeviceMonitor& +getVideoDeviceMonitor() +{ + return videoManager.videoDeviceMonitor; +} + +} // namespace ring diff --git a/src/client/videomanager.h b/src/client/videomanager.h new file mode 100644 index 0000000000..6e3eed64b6 --- /dev/null +++ b/src/client/videomanager.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2012-2015 Savoir-Faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef VIDEOMANAGER_H_ +#define VIDEOMANAGER_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <memory> // for weak/shared_ptr +#include <vector> +#include <map> +#include <string> + +#include "video/video_device_monitor.h" +#include "video/video_base.h" +#include "video/video_input.h" + +namespace ring { + +struct VideoManager +{ + public: + /* VideoManager acts as a cache of the active VideoInput. + * When this input is needed, you must use getVideoCamera + * to create the instance if not done yet and obtain a shared pointer + * for your own usage. + * VideoManager instance doesn't increment the reference count of + * this video input instance: this instance is destroyed when the last + * external user has released its shared pointer. + */ + std::weak_ptr<video::VideoInput> videoInput; + std::shared_ptr<video::VideoFrameActiveWriter> videoPreview; + video::VideoDeviceMonitor videoDeviceMonitor; + std::atomic_bool started; +}; + +extern VideoManager videoManager; + +std::shared_ptr<video::VideoFrameActiveWriter> getVideoCamera(); +video::VideoDeviceMonitor& getVideoDeviceMonitor(); + +} // namespace ring + +#endif // VIDEOMANAGER_H_ diff --git a/src/conference.cpp b/src/conference.cpp new file mode 100644 index 0000000000..744a5ad856 --- /dev/null +++ b/src/conference.cpp @@ -0,0 +1,192 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author : Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author : Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <sstream> + +#include "conference.h" +#include "manager.h" +#include "audio/audiolayer.h" +#include "audio/ringbufferpool.h" + +#ifdef RING_VIDEO +#include "sip/sipcall.h" +#include "client/videomanager.h" +#include "video/video_input.h" +#endif + +#include "call_factory.h" + +#include "logger.h" + +namespace ring { + +Conference::Conference() + : id_(Manager::instance().getNewCallID()) + , confState_(ACTIVE_ATTACHED) + , participants_() +#ifdef RING_VIDEO + , videoMixer_(nullptr) +#endif +{ + Recordable::initRecFilename(id_); +} + +Conference::~Conference() +{ +#ifdef RING_VIDEO + for (const auto &participant_id : participants_) { + if (auto call = Manager::instance().callFactory.getCall<SIPCall>(participant_id)) + call->getVideoRtp().exitConference(); + } +#endif // RING_VIDEO +} + +Conference::ConferenceState Conference::getState() const +{ + return confState_; +} + +void Conference::setState(ConferenceState state) +{ + confState_ = state; +} + +void Conference::add(const std::string &participant_id) +{ + if (participants_.insert(participant_id).second) { +#ifdef RING_VIDEO + if (auto call = Manager::instance().callFactory.getCall<SIPCall>(participant_id)) + call->getVideoRtp().enterConference(this); +#endif // RING_VIDEO + } +} + +void Conference::remove(const std::string &participant_id) +{ + if (participants_.erase(participant_id)) { +#ifdef RING_VIDEO + if (auto call = Manager::instance().callFactory.getCall<SIPCall>(participant_id)) + call->getVideoRtp().exitConference(); +#endif // RING_VIDEO + } +} + +void Conference::bindParticipant(const std::string &participant_id) +{ + auto &rbPool = Manager::instance().getRingBufferPool(); + + for (const auto &item : participants_) { + if (participant_id != item) + rbPool.bindCallID(participant_id, item); + rbPool.flush(item); + } + + rbPool.bindCallID(participant_id, RingBufferPool::DEFAULT_ID); + rbPool.flush(RingBufferPool::DEFAULT_ID); +} + +std::string Conference::getStateStr() const +{ + switch (confState_) { + case ACTIVE_ATTACHED: + return "ACTIVE_ATTACHED"; + case ACTIVE_DETACHED: + return "ACTIVE_DETACHED"; + case ACTIVE_ATTACHED_REC: + return "ACTIVE_ATTACHED_REC"; + case ACTIVE_DETACHED_REC: + return "ACTIVE_DETACHED_REC"; + case HOLD: + return "HOLD"; + case HOLD_REC: + return "HOLD_REC"; + default: + return ""; + } +} + +ParticipantSet Conference::getParticipantList() const +{ + return participants_; +} + +std::vector<std::string> +Conference::getDisplayNames() const +{ + std::vector<std::string> result; + + for (const auto &p : participants_) { + auto details = Manager::instance().getCallDetails(p); + const auto tmp = details["DISPLAY_NAME"]; + result.push_back(tmp.empty() ? details["PEER_NUMBER"] : tmp); + } + return result; +} + +bool Conference::toggleRecording() +{ + const bool startRecording = Recordable::toggleRecording(); + auto& rbPool = Manager::instance().getRingBufferPool(); + + std::string process_id(Recordable::recorder_.getRecorderID()); + + // start recording + if (startRecording) { + for (const auto &item : participants_) + rbPool.bindHalfDuplexOut(process_id, item); + + rbPool.bindHalfDuplexOut(process_id, RingBufferPool::DEFAULT_ID); + + Recordable::recorder_.start(); + } else { + for (const auto &item : participants_) + rbPool.unBindHalfDuplexOut(process_id, item); + + rbPool.unBindHalfDuplexOut(process_id, RingBufferPool::DEFAULT_ID); + } + + return startRecording; +} + +std::string Conference::getConfID() const { + return id_; +} + +#ifdef RING_VIDEO +std::shared_ptr<video::VideoMixer> Conference::getVideoMixer() +{ + if (!videoMixer_) + videoMixer_.reset(new video::VideoMixer(id_)); + return videoMixer_; +} +#endif + +} // namespace ring diff --git a/src/conference.h b/src/conference.h new file mode 100644 index 0000000000..24e8519f82 --- /dev/null +++ b/src/conference.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#ifndef CONFERENCE_H +#define CONFERENCE_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <set> +#include <string> +#include <memory> + +#include "audio/recordable.h" + +#ifdef RING_VIDEO +#include "video/video_mixer.h" +#endif + +namespace ring { + +typedef std::set<std::string> ParticipantSet; + +class Conference : public Recordable { + public: + enum ConferenceState {ACTIVE_ATTACHED, ACTIVE_DETACHED, ACTIVE_ATTACHED_REC, ACTIVE_DETACHED_REC, HOLD, HOLD_REC}; + + /** + * Constructor for this class, increment static counter + */ + Conference(); + + /** + * Destructor for this class, decrement static counter + */ + ~Conference(); + + /** + * Return the conference id + */ + std::string getConfID() const; + + /** + * Return the current conference state + */ + ConferenceState getState() const; + + /** + * Set conference state + */ + void setState(ConferenceState state); + + /** + * Return a string description of the conference state + */ + std::string getStateStr() const; + + /** + * Add a new participant to the conference + */ + void add(const std::string &participant_id); + + /** + * Remove a participant from the conference + */ + void remove(const std::string &participant_id); + + /** + * Bind a participant to the conference + */ + void bindParticipant(const std::string &participant_id); + + /** + * Get the participant list for this conference + */ + ParticipantSet getParticipantList() const; + + /** + * Get the display names or peer numbers for this conference + */ + std::vector<std::string> + getDisplayNames() const; + + /** + * Start/stop recording toggle + */ + virtual bool toggleRecording(); + +#ifdef RING_VIDEO + std::shared_ptr<video::VideoMixer> getVideoMixer(); +#endif + + private: + std::string id_; + ConferenceState confState_; + ParticipantSet participants_; + +#ifdef RING_VIDEO + std::shared_ptr<video::VideoMixer> videoMixer_; +#endif +}; + +} // namespace ring + +#endif diff --git a/src/config/Makefile.am b/src/config/Makefile.am new file mode 100644 index 0000000000..a9af69eb4f --- /dev/null +++ b/src/config/Makefile.am @@ -0,0 +1,5 @@ +noinst_LTLIBRARIES = libconfig.la + +libconfig_la_SOURCES = serializable.h yamlparser.h yamlparser.cpp + +libconfig_la_CXXFLAGS = -I $(top_srcdir)/src diff --git a/src/config/serializable.h b/src/config/serializable.h new file mode 100644 index 0000000000..d5a83ab52e --- /dev/null +++ b/src/config/serializable.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef SERIALIZABLE_H__ +#define SERIALIZABLE_H__ + +namespace YAML { + class Emitter; + class Node; +} + +namespace ring { + +class Serializable { + + public: + virtual ~Serializable() {}; + virtual void serialize(YAML::Emitter &out) = 0; + virtual void unserialize(const YAML::Node &node) = 0; +}; + +} // namespace ring + +#endif diff --git a/src/config/yamlparser.cpp b/src/config/yamlparser.cpp new file mode 100644 index 0000000000..b410c0d042 --- /dev/null +++ b/src/config/yamlparser.cpp @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "yamlparser.h" + +namespace ring { + +// FIXME: Maybe think of something more clever, this is due to yaml-cpp's poor +// handling of empty values for nested collections. +std::vector<std::map<std::string, std::string>> +yaml_utils::parseVectorMap(const YAML::Node &node, const std::initializer_list<std::string> &keys) +{ + std::vector<std::map<std::string, std::string>> result; + for (const auto &n : node) { + std::map<std::string, std::string> t; + for (const auto &k : keys) { + t[k] = n[k].as<std::string>(""); + } + result.push_back(t); + } + return result; +} + +} // namespace ring diff --git a/src/config/yamlparser.h b/src/config/yamlparser.h new file mode 100644 index 0000000000..d947228c9f --- /dev/null +++ b/src/config/yamlparser.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __YAMLPARSER_H__ +#define __YAMLPARSER_H__ + +#include <yaml-cpp/yaml.h> + +namespace ring { namespace yaml_utils { + +// set T to the value stored at key, or leaves T unchanged +// if no value is stored. +template <typename T> +void parseValue(const YAML::Node &node, const char *key, T &value) +{ + value = node[key].as<T>(value); +} + +std::vector<std::map<std::string, std::string>> +parseVectorMap(const YAML::Node &node, const std::initializer_list<std::string> &keys); + +}} // namespace ring::yaml_utils + +#endif diff --git a/src/dring/account_const.h b/src/dring/account_const.h new file mode 100644 index 0000000000..7b6543c844 --- /dev/null +++ b/src/dring/account_const.h @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * + * This program is free software you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#ifndef DRING_ACCOUNT_H +#define DRING_ACCOUNT_H + +namespace DRing { + +namespace Account { + +namespace ProtocolNames { + +constexpr static const char SIP [] = "SIP"; +constexpr static const char IAX [] = "IAX"; +constexpr static const char IP2IP [] = "IP2IP"; +constexpr static const char RING [] = "RING"; + +} //namespace DRing::ProtocolNames + +namespace States { + +constexpr static const char REGISTERED [] = "REGISTERED"; +constexpr static const char READY [] = "READY"; +constexpr static const char UNREGISTERED [] = "UNREGISTERED"; +constexpr static const char TRYING [] = "TRYING"; +constexpr static const char ERROR [] = "ERROR"; +constexpr static const char ERROR_GENERIC [] = "ERROR_GENERIC"; +constexpr static const char ERROR_AUTH [] = "ERRORAUTH"; +constexpr static const char ERROR_NETWORK [] = "ERRORNETWORK"; +constexpr static const char ERROR_HOST [] = "ERRORHOST"; +constexpr static const char ERROR_CONF_STUN [] = "ERROR_CONF_STUN"; +constexpr static const char ERROR_EXIST_STUN [] = "ERROREXISTSTUN"; +constexpr static const char ERROR_SERVICE_UNAVAILABLE [] = "ERRORSERVICEUNAVAILABLE"; +constexpr static const char ERROR_NOT_ACCEPTABLE [] = "ERRORNOTACCEPTABLE"; +constexpr static const char REQUEST_TIMEOUT [] = "Request Timeout"; + +} //namespace DRing::Account + +namespace VolatileProperties { + +// Volatile parameters +namespace Registration { + +constexpr static const char STATUS [] = "Account.registrationStatus"; +constexpr static const char STATE_CODE [] = "Account.registrationCode"; +constexpr static const char STATE_DESC [] = "Account.registrationDescription"; + +} //namespace DRing::VolatileProperties::Registration + +namespace Transport { + +constexpr static const char STATE_CODE [] = "Transport.statusCode"; +constexpr static const char STATE_DESC [] = "Transport.statusDescription"; + +} //namespace DRing::VolatileProperties::Transport + +} //namespace DRing::Account::VolatileProperties + +namespace ConfProperties { + +constexpr static const char ID [] = "Account.id"; +constexpr static const char TYPE [] = "Account.type"; +constexpr static const char ALIAS [] = "Account.alias"; +constexpr static const char ENABLED [] = "Account.enable"; +constexpr static const char MAILBOX [] = "Account.mailbox"; +constexpr static const char DTMF_TYPE [] = "Account.dtmfType"; +constexpr static const char AUTOANSWER [] = "Account.autoAnswer"; +constexpr static const char HOSTNAME [] = "Account.hostname"; +constexpr static const char USERNAME [] = "Account.username"; +constexpr static const char ROUTE [] = "Account.routeset"; +constexpr static const char PASSWORD [] = "Account.password"; +constexpr static const char REALM [] = "Account.realm"; +constexpr static const char LOCAL_INTERFACE [] = "Account.localInterface"; +constexpr static const char PUBLISHED_SAMEAS_LOCAL[] = "Account.publishedSameAsLocal"; +constexpr static const char LOCAL_PORT [] = "Account.localPort"; +constexpr static const char PUBLISHED_PORT [] = "Account.publishedPort"; +constexpr static const char PUBLISHED_ADDRESS [] = "Account.publishedAddress"; +constexpr static const char USER_AGENT [] = "Account.useragent"; +constexpr static const char UPNP_ENABLED [] = "Account.upnpEnabled"; +constexpr static const char HAS_CUSTOM_USER_AGENT [] = "Account.hasCustomUserAgent"; + + +namespace Audio { + +constexpr static const char PORT_MAX [] = "Account.audioPortMax"; +constexpr static const char PORT_MIN [] = "Account.audioPortMin"; + +} //namespace DRing::Account::ConfProperties::Audio + +namespace Video { + +constexpr static const char ENABLED [] = "Account.videoEnabled"; +constexpr static const char PORT_MAX [] = "Account.videoPortMax"; +constexpr static const char PORT_MIN [] = "Account.videoPortMin"; + +} //namespace DRing::Account::ConfProperties::Video + +namespace STUN { + +constexpr static const char SERVER [] = "STUN.server"; +constexpr static const char ENABLED [] = "STUN.enable"; + +} //namespace DRing::Account::ConfProperties::STUN + +namespace Presence { + +constexpr static const char SUPPORT_PUBLISH [] = "Account.presencePublishSupported"; +constexpr static const char SUPPORT_SUBSCRIBE [] = "Account.presenceSubscribeSupported"; +constexpr static const char ENABLED [] = "Account.presenceEnabled"; + +} //namespace DRing::Account::ConfProperties::Presence + +namespace Registration { + +constexpr static const char EXPIRE [] = "Account.registrationExpire"; +constexpr static const char STATUS [] = "Account.registrationStatus"; + +} //namespace DRing::Account::ConfProperties::Registration + +namespace Ringtone { + +constexpr static const char PATH [] = "Account.ringtonePath"; +constexpr static const char ENABLED [] = "Account.ringtoneEnabled"; + +} //namespace DRing::Account::ConfProperties::Ringtone + +namespace SRTP { + +constexpr static const char KEY_EXCHANGE [] = "SRTP.keyExchange"; +constexpr static const char ENABLED [] = "SRTP.enable"; +constexpr static const char RTP_FALLBACK [] = "SRTP.rtpFallback"; + +} //namespace DRing::Account::ConfProperties::SRTP + + +namespace ZRTP { + +constexpr static const char DISPLAY_SAS [] = "ZRTP.displaySAS"; +constexpr static const char NOT_SUPP_WARNING [] = "ZRTP.notSuppWarning"; +constexpr static const char HELLO_HASH [] = "ZRTP.helloHashEnable"; +constexpr static const char DISPLAY_SAS_ONCE [] = "ZRTP.displaySasOnce"; + +} //namespace DRing::Account::ConfProperties::ZRTP + +namespace TLS { + +constexpr static const char LISTENER_PORT [] = "TLS.listenerPort"; +constexpr static const char ENABLED [] = "TLS.enable"; +constexpr static const char PORT [] = "TLS.port"; +constexpr static const char CA_LIST_FILE [] = "TLS.certificateListFile"; +constexpr static const char CERTIFICATE_FILE [] = "TLS.certificateFile"; +constexpr static const char PRIVATE_KEY_FILE [] = "TLS.privateKeyFile"; +constexpr static const char PASSWORD [] = "TLS.password"; +constexpr static const char METHOD [] = "TLS.method"; +constexpr static const char CIPHERS [] = "TLS.ciphers"; +constexpr static const char SERVER_NAME [] = "TLS.serverName"; +constexpr static const char VERIFY_SERVER [] = "TLS.verifyServer"; +constexpr static const char VERIFY_CLIENT [] = "TLS.verifyClient"; +constexpr static const char REQUIRE_CLIENT_CERTIFICATE [] = "TLS.requireClientCertificate"; +constexpr static const char NEGOTIATION_TIMEOUT_SEC [] = "TLS.negotiationTimeoutSec"; + +} //namespace DRing::Account::ConfProperties::TLS + +namespace DHT { + +constexpr static const char PORT [] = "DHT.port"; + +} //namespace DRing::Account::DHT + +namespace CodecInfo { + +constexpr static const char NAME [] = "CodecInfo.name"; +constexpr static const char TYPE [] = "CodecInfo.type"; +constexpr static const char SAMPLE_RATE [] = "CodecInfo.sampleRate"; +constexpr static const char FRAME_RATE [] = "CodecInfo.frameRate"; +constexpr static const char BITRATE [] = "CodecInfo.bitrate"; +constexpr static const char CHANNEL_NUMBER [] = "CodecInfo.channelNumber"; + +} //namespace DRing::Account::ConfProperties::CodecInfo + +} //namespace DRing::Account::ConfProperties + +} //namespace DRing::Account + +} //namespace DRing + +#endif diff --git a/src/dring/call_const.h b/src/dring/call_const.h new file mode 100644 index 0000000000..cf26a8b4e9 --- /dev/null +++ b/src/dring/call_const.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2015 Savoir-Faire Linux Inc. + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#ifndef DRING_CALL_H +#define DRING_CALL_H + +namespace DRing { + +namespace Call { + +namespace Details { + +constexpr static char CALL_TYPE [] = "CALL_TYPE" ; +constexpr static char PEER_NUMBER [] = "PEER_NUMBER" ; +constexpr static char DISPLAY_NAME [] = "DISPLAY_NAME" ; +constexpr static char CALL_STATE [] = "CALL_STATE" ; +constexpr static char CONF_ID [] = "CONF_ID" ; +constexpr static char TIMESTAMP_START [] = "TIMESTAMP_START" ; +constexpr static char ACCOUNTID [] = "ACCOUNTID" ; +constexpr static char PEER_HOLDING [] = "PEER_HOLDING" ; +constexpr static char TLS_PEER_CERT [] = "TLS_PEER_CERT" ; +constexpr static char TLS_CIPHER [] = "TLS_CIPHER" ; + +} + + +} //namespace DRing::Call + +} //namespace DRing + +#endif diff --git a/src/dring/callmanager_interface.h b/src/dring/callmanager_interface.h new file mode 100644 index 0000000000..cc05fae0ca --- /dev/null +++ b/src/dring/callmanager_interface.h @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Pierre-Luc Beaudoin <pierre-luc.beaudoin@savoirfairelinux.com> + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef DRING_CALLMANAGERI_H +#define DRING_CALLMANAGERI_H + +#include <stdexcept> +#include <map> +#include <memory> +#include <vector> +#include <string> +#include <cstdint> + +#include "dring.h" + +namespace DRing { + +void registerCallHandlers(const std::map<std::string, std::shared_ptr<CallbackWrapperBase>>&); + +/* Call related methods */ +std::string placeCall(const std::string& accountID, const std::string& to); + +bool refuse(const std::string& callID); +bool accept(const std::string& callID); +bool hangUp(const std::string& callID); +bool hold(const std::string& callID); +bool unhold(const std::string& callID); +bool transfer(const std::string& callID, const std::string& to); +bool attendedTransfer(const std::string& transferID, const std::string& targetID); +std::map<std::string, std::string> getCallDetails(const std::string& callID); +std::vector<std::string> getCallList(); + +/* Conference related methods */ +void removeConference(const std::string& conference_id); +bool joinParticipant(const std::string& sel_callID, const std::string& drag_callID); +void createConfFromParticipantList(const std::vector<std::string>& participants); +bool isConferenceParticipant(const std::string& call_id); +bool addParticipant(const std::string& callID, const std::string& confID); +bool addMainParticipant(const std::string& confID); +bool detachParticipant(const std::string& callID); +bool joinConference(const std::string& sel_confID, const std::string& drag_confID); +bool hangUpConference(const std::string& confID); +bool holdConference(const std::string& confID); +bool unholdConference(const std::string& confID); +std::vector<std::string> getConferenceList(); +std::vector<std::string> getParticipantList(const std::string& confID); +std::vector<std::string> getDisplayNames(const std::string& confID); +std::string getConferenceId(const std::string& callID); +std::map<std::string, std::string> getConferenceDetails(const std::string& callID); + +/* File Playback methods */ +bool startRecordedFilePlayback(const std::string& filepath); +void stopRecordedFilePlayback(const std::string& filepath); + +/* General audio methods */ +bool toggleRecording(const std::string& callID); +/* DEPRECATED */ +void setRecording(const std::string& callID); + +void recordPlaybackSeek(double value); +bool getIsRecording(const std::string& callID); +std::string getCurrentAudioCodecName(const std::string& callID); +void playDTMF(const std::string& key); +void startTone(int32_t start, int32_t type); + +bool switchInput(const std::string& callID, const std::string& resource); + +/* Security related methods */ +void setSASVerified(const std::string& callID); +void resetSASVerified(const std::string& callID); +void setConfirmGoClear(const std::string& callID); +void requestGoClear(const std::string& callID); +void acceptEnrollment(const std::string& callID, bool accepted); + +/* Instant messaging */ +void sendTextMessage(const std::string& callID, const std::string& message); +void sendTextMessage(const std::string& callID, const std::string& message, const std::string& from); + +// Call signal type definitions +struct CallSignal { + struct StateChange { + constexpr static const char* name = "StateChange"; + using cb_type = void(const std::string&, const std::string&, int); + }; + struct TransferFailed { + constexpr static const char* name = "TransferFailed"; + using cb_type = void(void); + }; + struct TransferSucceeded { + constexpr static const char* name = "TransferSucceeded"; + using cb_type = void(void); + }; + struct RecordPlaybackStopped { + constexpr static const char* name = "RecordPlaybackStopped"; + using cb_type = void(const std::string&); + }; + struct VoiceMailNotify { + constexpr static const char* name = "VoiceMailNotify"; + using cb_type = void(const std::string&, int32_t); + }; + struct IncomingMessage { + constexpr static const char* name = "IncomingMessage"; + using cb_type = void(const std::string&, const std::string&, const std::string&); + }; + struct IncomingCall { + constexpr static const char* name = "IncomingCall"; + using cb_type = void(const std::string&, const std::string&, const std::string&); + }; + struct RecordPlaybackFilepath { + constexpr static const char* name = "RecordPlaybackFilepath"; + using cb_type = void(const std::string&, const std::string&); + }; + struct ConferenceCreated { + constexpr static const char* name = "ConferenceCreated"; + using cb_type = void(const std::string&); + }; + struct ConferenceChanged { + constexpr static const char* name = "ConferenceChanged"; + using cb_type = void(const std::string&, const std::string&); + }; + struct UpdatePlaybackScale { + constexpr static const char* name = "UpdatePlaybackScale"; + using cb_type = void(const std::string&, unsigned, unsigned); + }; + struct ConferenceRemoved { + constexpr static const char* name = "ConferenceRemoved"; + using cb_type = void(const std::string&); + }; + struct NewCallCreated { + constexpr static const char* name = "NewCallCreated"; + using cb_type = void(const std::string&, const std::string&, const std::string&); + }; + struct SipCallStateChanged { + constexpr static const char* name = "SipCallStateChanged"; + using cb_type = void(const std::string&, const std::string&, int); + }; + struct RecordingStateChanged { + constexpr static const char* name = "RecordingStateChanged"; + using cb_type = void(const std::string&, int); + }; + struct SecureSdesOn { + constexpr static const char* name = "SecureSdesOn"; + using cb_type = void(const std::string&); + }; + struct SecureSdesOff { + constexpr static const char* name = "SecureSdesOff"; + using cb_type = void(const std::string&); + }; + struct SecureZrtpOn { + constexpr static const char* name = "SecureZrtpOn"; + using cb_type = void(const std::string&, const std::string&); + }; + struct SecureZrtpOff { + constexpr static const char* name = "SecureZrtpOff"; + using cb_type = void(const std::string&); + }; + struct ShowSAS { + constexpr static const char* name = "ShowSAS"; + using cb_type = void(const std::string&, const std::string&, int); + }; + struct ZrtpNotSuppOther { + constexpr static const char* name = "ZrtpNotSuppOther"; + using cb_type = void(const std::string&); + }; + struct ZrtpNegotiationFailed { + constexpr static const char* name = "ZrtpNegotiationFailed"; + using cb_type = void(const std::string&, const std::string&, const std::string&); + }; + struct RtcpReportReceived { + constexpr static const char* name = "RtcpReportReceived"; + using cb_type = void(const std::string&, const std::map<std::string, int>&); + }; + struct PeerHold { + constexpr static const char* name = "PeerHold"; + using cb_type = void(const std::string&, bool); + }; +}; + +} // namespace DRing + +#endif // DRING_CALLMANAGERI_H diff --git a/src/dring/configurationmanager_interface.h b/src/dring/configurationmanager_interface.h new file mode 100644 index 0000000000..5bf2c7d49c --- /dev/null +++ b/src/dring/configurationmanager_interface.h @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Pierre-Luc Beaudoin <pierre-luc.beaudoin@savoirfairelinux.com> + * Author: Alexandre Bourget <alexandre.bourget@savoirfairelinux.com> + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Guillaume Carmel-Archambault <guillaume.carmel-archambault@savoirfairelinux.com> + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef DRING_CONFIGURATIONMANAGERI_H +#define DRING_CONFIGURATIONMANAGERI_H + +#include <vector> +#include <map> +#include <memory> +#include <string> +#include <cstdint> + +#include "dring.h" + +namespace DRing { + +void registerConfHandlers(const std::map<std::string, std::shared_ptr<CallbackWrapperBase>>&); + +std::map<std::string, std::string> getAccountDetails(const std::string& accountID); +std::map<std::string, std::string> getVolatileAccountDetails(const std::string& accountID); +void setAccountDetails(const std::string& accountID, const std::map<std::string, std::string>& details); +std::map<std::string, std::string> getAccountTemplate(const std::string& accountType); +std::string addAccount(const std::map<std::string, std::string>& details); +void removeAccount(const std::string& accoundID); +std::vector<std::string> getAccountList(); +void sendRegister(const std::string& accoundID, bool enable); +void registerAllAccounts(void); + +std::map<std::string, std::string> getTlsDefaultSettings(); + +std::vector<unsigned> getCodecList(); +std::vector<std::string> getSupportedTlsMethod(); +std::vector<std::string> getSupportedCiphers(const std::string& accountID); +std::map<std::string, std::string> getCodecDetails(const std::string& accountID, const unsigned& codecId); +bool setCodecDetails(const std::string& accountID, const unsigned& codecId, const std::map<std::string, std::string>& details); +std::vector<unsigned> getActiveCodecList(const std::string& accountID); + +void setActiveCodecList(const std::string& accountID, const std::vector<unsigned>& list); + +std::vector<std::string> getAudioPluginList(); +void setAudioPlugin(const std::string& audioPlugin); +std::vector<std::string> getAudioOutputDeviceList(); +void setAudioOutputDevice(int32_t index); +void setAudioInputDevice(int32_t index); +void setAudioRingtoneDevice(int32_t index); +std::vector<std::string> getAudioInputDeviceList(); +std::vector<std::string> getCurrentAudioDevicesIndex(); +int32_t getAudioInputDeviceIndex(const std::string& name); +int32_t getAudioOutputDeviceIndex(const std::string& name); +std::string getCurrentAudioOutputPlugin(); +bool getNoiseSuppressState(); +void setNoiseSuppressState(bool state); + +bool isAgcEnabled(); +void setAgcState(bool enabled); + +void muteDtmf(bool mute); +bool isDtmfMuted(); + +bool isCaptureMuted(); +void muteCapture(bool mute); +bool isPlaybackMuted(); +void mutePlayback(bool mute); + +std::string getAudioManager(); +bool setAudioManager(const std::string& api); + +int32_t isIax2Enabled(); +std::string getRecordPath(); +void setRecordPath(const std::string& recPath); +bool getIsAlwaysRecording(); +void setIsAlwaysRecording(bool rec); + +void setHistoryLimit(int32_t days); +int32_t getHistoryLimit(); + +void setAccountsOrder(const std::string& order); + +std::map<std::string, std::string> getHookSettings(); +void setHookSettings(const std::map<std::string, std::string>& settings); + +std::map<std::string, std::string> getTlsSettings(); +void setTlsSettings(const std::map<std::string, std::string>& details); +std::map<std::string, std::string> getIp2IpDetails(); + +std::vector<std::map<std::string, std::string>> getCredentials(const std::string& accountID); +void setCredentials(const std::string& accountID, const std::vector<std::map<std::string, std::string>>& details); + +std::string getAddrFromInterfaceName(const std::string& interface); + +std::vector<std::string> getAllIpInterface(); +std::vector<std::string> getAllIpInterfaceByName(); + +std::map<std::string, std::string> getShortcuts(); +void setShortcuts(const std::map<std::string, std::string> &shortcutsMap); + +void setVolume(const std::string& device, double value); +double getVolume(const std::string& device); + +/* + * Security + */ +std::map<std::string, std::string> validateCertificate(const std::string& accountId, + const std::string& certificate, const std::string& privateKey); +std::map<std::string, std::string> validateCertificateRaw(const std::string& accountId, + const std::vector<uint8_t>& certificate); +std::map<std::string, std::string> getCertificateDetails(const std::string& certificate); +std::map<std::string, std::string> getCertificateDetailsRaw(const std::vector<uint8_t>& certificate); + +// Configuration signal type definitions +struct ConfigurationSignal { + struct VolumeChanged { + constexpr static const char* name = "VolumeChanged"; + using cb_type = void(const std::string& /*device*/, double /*value*/); + }; + struct AccountsChanged { + constexpr static const char* name = "AccountsChanged"; + using cb_type = void(void); + }; + struct StunStatusFailed { + constexpr static const char* name = "StunStatusFailed"; + using cb_type = void(const std::string& /*account_id*/); + }; + struct RegistrationStateChanged { + constexpr static const char* name = "RegistrationStateChanged"; + using cb_type = void(const std::string& /*account_id*/, const std::string& /*state*/, int /*detailsCode*/, const std::string& /*detailsStr*/); + }; + struct VolatileDetailsChanged { + constexpr static const char* name = "VolatileDetailsChanged"; + using cb_type = void(const std::string& /*account_id*/, const std::map<std::string, std::string>& /* details */); + }; + struct Error { + constexpr static const char* name = "Error"; + using cb_type = void(int /*alert*/); + }; +}; + +} // namespace DRing + +#endif // DRING_CONFIGURATIONMANAGERI_H diff --git a/src/dring/dring.h b/src/dring/dring.h new file mode 100644 index 0000000000..fe9b421392 --- /dev/null +++ b/src/dring/dring.h @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2014-2015 Savoir-Faire Linux Inc. + * Author: Philippe Proulx <philippe.proulx@savoirfairelinux.com> + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#ifndef DRING_H +#define DRING_H + +#include <vector> +#include <functional> +#include <string> +#include <map> +#include <memory> +#include <type_traits> + +namespace DRing { + +/* flags for initialization */ +enum InitFlag { + DRING_FLAG_DEBUG=1, + DRING_FLAG_CONSOLE_LOG=2, +}; + +/** + * Return the library version as string. + */ +const char* version() noexcept; + +/** + * Initialize globals, create underlaying daemon. + * + * @param flags Flags to customize this initialization + * @returns true if initialization succeed else false. + */ +bool init(enum InitFlag flags) noexcept; + +/** + * Start asynchronously daemon created by init(). + * @returns true if daemon started successfuly + */ +bool start(const std::string& config_file={}) noexcept; + +/** + * Stop and freeing any resource allocated by daemon + */ +void fini() noexcept; + +/** + * Poll daemon events. + * This function has to be called by user at a fixed frequency + * to let daemon checks its internal ressources and io and + * manages events reported by them. + */ +void pollEvents() noexcept; + +/* External Callback Dynamic Utilities + * + * The library provides to users a way to be acknowledged + * when daemon's objects have a state change. + * The user is aware of this changement wen the deamon calls + * a user-given callback. + * Daemon handles many of these callbacks, one per event type. + * The user registers his callbacks using registerXXXXHandlers() functions. + * As each callback has its own function signature, + * to keep compatibility over releases we don't let user directly provides + * his callbacks as it or through a structure. + * This way brings ABI violation if we need to change the order + * and/or the existance of any callback type. + * Thus the user have to pass them using following template classes + * and functions, that wraps user-callback in a generic and ABI-compatible way. + */ + +/* Generic class to transit user callbacks to daemon library. + * Used conjointly with std::shared_ptr to hide the concrete class. + * See CallbackWrapper template for details. + */ +class CallbackWrapperBase {}; + +/* Concrete class of CallbackWrapperBase. + * This class wraps callbacks of a specific signature. + * Also used to obtain the user callback from a CallbackWrapperBase shared ptr. + * + * This class is CopyConstructible, CopyAssignable, MoveConstructible + * and MoveAssignable. + */ +template <typename TProto> +class CallbackWrapper : public CallbackWrapperBase { + private: + using TFunc = std::function<TProto>; + TFunc cb_; // The user-callback + + public: + // Empty wrapper: no callback associated. + // Used to initialize internal callback arrays. + CallbackWrapper() noexcept {} + + // Create and initialize a wrapper to given callback. + CallbackWrapper(TFunc&& func) noexcept { + cb_ = std::forward<TFunc>(func); + } + + // Create and initialize a wrapper from a generic CallbackWrapperBase + // shared pointer. + // Note: the given callback is copied into internal storage. + CallbackWrapper(std::shared_ptr<CallbackWrapperBase> p) noexcept { + if (p) + cb_ = ((CallbackWrapper<TProto>*)p.get())->cb_; + } + + // Return user-callback reference. + // The returned std::function can be null-initialized if no callback + // has been set. + constexpr const TFunc& operator *() const noexcept { + return cb_; + } + + // Return boolean true value if a non-null callback has been set + constexpr explicit operator bool() const noexcept { + return static_cast<bool>(cb_); + } +}; + +/** + * Return an exportable callback object. + * This object is a std::pair of a string and a CallbackWrapperBase shared_ptr. + * This last wraps given callback in a ABI-compatible way. + * Note: this version accepts callbacks as rvalue only. + */ +template <typename Ts> +std::pair<std::string, std::shared_ptr<CallbackWrapperBase>> +exportable_callback(std::function<typename Ts::cb_type>&& func) { + return std::make_pair((const std::string&)Ts::name, + std::make_shared<CallbackWrapper<typename Ts::cb_type>> + (std::forward<std::function<typename Ts::cb_type>>(func))); +} + +} // namespace DRing + +#endif /* DRING_H */ diff --git a/src/dring/presencemanager_interface.h b/src/dring/presencemanager_interface.h new file mode 100644 index 0000000000..0790cc793b --- /dev/null +++ b/src/dring/presencemanager_interface.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2013-2015 Savoir-Faire Linux Inc. + * Author: Patrick Keroulas <patrick.keroulas@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef DRING_PRESENCEMANAGERI_H +#define DRING_PRESENCEMANAGERI_H + +#include <vector> +#include <map> +#include <string> +#include <memory> + +#include "dring.h" + +namespace DRing { + +void registerPresHandlers(const std::map<std::string, std::shared_ptr<CallbackWrapperBase>>&); + +/* Presence subscription/Notification. */ +void publish(const std::string& accountID, bool status, const std::string& note); +void answerServerRequest(const std::string& uri, bool flag); +void subscribeBuddy(const std::string& accountID, const std::string& uri, bool flag); +std::vector<std::map<std::string, std::string>> getSubscriptions(const std::string& accountID); +void setSubscriptions(const std::string& accountID, const std::vector<std::string>& uris); + +// Presence signal type definitions +struct PresenceSignal { + struct NewServerSubscriptionRequest { + constexpr static const char* name = "NewServerSubscriptionRequest"; + using cb_type = void(const std::string& /*remote*/); + }; + struct ServerError { + constexpr static const char* name = "ServerError"; + using cb_type = void(const std::string& /*account_id*/, const std::string& /*error*/, const std::string& /*msg*/); + }; + struct NewBuddyNotification { + constexpr static const char* name = "NewBuddyNotification"; + using cb_type = void(const std::string& /*account_id*/, const std::string& /*buddy_uri*/, int /*status*/, const std::string& /*line_status*/); + }; + struct SubscriptionStateChanged { + constexpr static const char* name = "SubscriptionStateChanged"; + using cb_type = void(const std::string& /*account_id*/, const std::string& /*buddy_uri*/, int /*state*/); + }; +}; + +} // namespace DRing + +#endif //PRESENCEMANAGERI_H diff --git a/src/dring/security_const.h b/src/dring/security_const.h new file mode 100644 index 0000000000..276dab2928 --- /dev/null +++ b/src/dring/security_const.h @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2015 Savoir-Faire Linux Inc. + * Author: Philippe Proulx <philippe.proulx@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#ifndef DRING_SECURITY_H +#define DRING_SECURITY_H + +namespace DRing { + +namespace Certificate { + +/** +* Those constantes are used by the ConfigurationManager.validateCertificate method +*/ +namespace ChecksNames { + constexpr static char HAS_PRIVATE_KEY [] = "HAS_PRIVATE_KEY" ; + constexpr static char EXPIRED [] = "EXPIRED" ; + constexpr static char STRONG_SIGNING [] = "STRONG_SIGNING" ; + constexpr static char NOT_SELF_SIGNED [] = "NOT_SELF_SIGNED" ; + constexpr static char KEY_MATCH [] = "KEY_MATCH" ; + constexpr static char PRIVATE_KEY_STORAGE_PERMISSION [] = "PRIVATE_KEY_STORAGE_PERMISSION" ; + constexpr static char PUBLIC_KEY_STORAGE_PERMISSION [] = "PUBLIC_KEY_STORAGE_PERMISSION" ; + constexpr static char PRIVATE_KEY_DIRECTORY_PERMISSIONS[] = "PRIVATEKEY_DIRECTORY_PERMISSIONS"; + constexpr static char PUBLIC_KEY_DIRECTORY_PERMISSIONS [] = "PUBLICKEY_DIRECTORY_PERMISSIONS" ; + constexpr static char PRIVATE_KEY_STORAGE_LOCATION [] = "PRIVATE_KEY_STORAGE_LOCATION" ; + constexpr static char PUBLIC_KEY_STORAGE_LOCATION [] = "PUBLIC_KEY_STORAGE_LOCATION" ; + constexpr static char PRIVATE_KEY_SELINUX_ATTRIBUTES [] = "PRIVATE_KEY_SELINUX_ATTRIBUTES" ; + constexpr static char PUBLIC_KEY_SELINUX_ATTRIBUTES [] = "PUBLIC_KEY_SELINUX_ATTRIBUTES" ; + constexpr static char EXIST [] = "EXIST" ; + constexpr static char VALID [] = "VALID" ; + constexpr static char VALID_AUTHORITY [] = "VALID_AUTHORITY" ; + constexpr static char KNOWN_AUTHORITY [] = "KNOWN_AUTHORITY" ; + constexpr static char NOT_REVOKED [] = "NOT_REVOKED" ; + constexpr static char AUTHORITY_MISMATCH [] = "AUTHORITY_MISMATCH" ; + constexpr static char UNEXPECTED_OWNER [] = "UNEXPECTED_OWNER" ; + constexpr static char NOT_ACTIVATED [] = "NOT_ACTIVATED" ; +} //namespace DRing::Certificate::CheckValuesNames + +/** +* Those constants are used by the ConfigurationManager.getCertificateDetails method +*/ +namespace DetailsNames { + constexpr static char EXPIRATION_DATE [] = "EXPIRATION_DATE" ; + constexpr static char ACTIVATION_DATE [] = "ACTIVATION_DATE" ; + constexpr static char REQUIRE_PRIVATE_KEY_PASSWORD[] = "REQUIRE_PRIVATE_KEY_PASSWORD" ; + constexpr static char PUBLIC_SIGNATURE [] = "PUBLIC_SIGNATURE" ; + constexpr static char VERSION_NUMBER [] = "VERSION_NUMBER" ; + constexpr static char SERIAL_NUMBER [] = "SERIAL_NUMBER" ; + constexpr static char ISSUER [] = "ISSUER" ; + constexpr static char SUBJECT_KEY_ALGORITHM [] = "SUBJECT_KEY_ALGORITHM" ; + constexpr static char CN [] = "CN" ; + constexpr static char N [] = "N" ; + constexpr static char O [] = "O" ; + constexpr static char SIGNATURE_ALGORITHM [] = "SIGNATURE_ALGORITHM" ; + constexpr static char MD5_FINGERPRINT [] = "MD5_FINGERPRINT" ; + constexpr static char SHA1_FINGERPRINT [] = "SHA1_FINGERPRINT" ; + constexpr static char PUBLIC_KEY_ID [] = "PUBLIC_KEY_ID" ; + constexpr static char ISSUER_DN [] = "ISSUER_DN" ; + constexpr static char NEXT_EXPECTED_UPDATE_DATE [] = "NEXT_EXPECTED_UPDATE_DATE" ; + constexpr static char OUTGOING_SERVER [] = "OUTGOING_SERVER" ; +} //namespace DRing::Certificate::CheckValuesNames + +/** +* Those constants are used by the ConfigurationManager.getCertificateDetails and +* ConfigurationManager.validateCertificate methods +*/ +namespace ChecksValuesTypesNames { + constexpr static char BOOLEAN [] = "BOOLEAN" ; + constexpr static char ISO_DATE[] = "ISO_DATE" ; + constexpr static char CUSTOM [] = "CUSTOM" ; + constexpr static char NUMBER [] = "NUMBER" ; +} //namespace DRing::Certificate::CheckValuesNames + +/** +* Those constantes are used by the ConfigurationManager.validateCertificate method +*/ +namespace CheckValuesNames { + constexpr static char PASSED [] = "PASSED" ; + constexpr static char FAILED [] = "FAILED" ; + constexpr static char UNSUPPORTED[] = "UNSUPPORTED"; + constexpr static char ISO_DATE [] = "ISO_DATE" ; + constexpr static char CUSTOM [] = "CUSTOM" ; + constexpr static char DATE [] = "DATE" ; +} //namespace DRing::Certificate::CheckValuesNames + +} //namespace DRing::Certificate + +} //namespace DRing + +#endif diff --git a/src/dring/videomanager_interface.h b/src/dring/videomanager_interface.h new file mode 100644 index 0000000000..1f90cce2f0 --- /dev/null +++ b/src/dring/videomanager_interface.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2012-2015 Savoir-Faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef DRING_VIDEOMANAGERI_H +#define DRING_VIDEOMANAGERI_H + +#include <memory> +#include <vector> +#include <map> +#include <string> +#include <functional> + +#include "dring.h" + +namespace DRing { + +using VideoCapabilities = std::map<std::string, std::map<std::string, std::vector<std::string>>>; + +void registerVideoHandlers(const std::map<std::string, std::shared_ptr<CallbackWrapperBase>>&); + +std::vector<unsigned> getVideoCodecList(const std::string& accountID); +std::vector<std::string> getVideoCodecDetails(unsigned codecId); +void setVideoCodecList(const std::string& accountID, const std::vector<unsigned>& list); +std::vector<std::string> getDeviceList(); +VideoCapabilities getCapabilities(const std::string& name); +std::map<std::string, std::string> getSettings(const std::string& name); +void applySettings(const std::string& name, const std::map<std::string, std::string>& settings); +void setDefaultDevice(const std::string& name); +std::string getDefaultDevice(); +std::string getCurrentCodecName(const std::string& callID); +void startCamera(); +void stopCamera(); +bool hasCameraStarted(); +bool switchInput(const std::string& resource); +bool switchToCamera(); +void registerSinkTarget(const std::string& sinkId, const std::function<void(unsigned char*)>& cb); +void registerSinkTarget(const std::string& sinkId, std::function<void(unsigned char*)>&& cb); + +// Video signal type definitions +struct VideoSignal { + struct DeviceEvent { + constexpr static const char* name = "DeviceEvent"; + using cb_type = void(void); + }; + struct DecodingStarted { + constexpr static const char* name = "DecodingStarted"; + using cb_type = void(const std::string& /*id*/, const std::string& /*shm_path*/, int /*w*/, int /*h*/, bool /*is_mixer*/id); + }; + struct DecodingStopped { + constexpr static const char* name = "DecodingStopped"; + using cb_type = void(const std::string& /*id*/, const std::string& /*shm_path*/, bool /*is_mixer*/); + }; +}; + +} // namespace DRing + +#endif // DRING_VIDEOMANAGERI_H diff --git a/src/enumclass_utils.h b/src/enumclass_utils.h new file mode 100644 index 0000000000..283a11cc31 --- /dev/null +++ b/src/enumclass_utils.h @@ -0,0 +1,296 @@ +/* + * Copyright (C) 2014-2015 Savoir-Faire Linux Inc. + * + * Author: Emmanuel Lepage <emmanuel.lepage@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + */ +#ifndef ENUM_CLASS_UTILS_H +#define ENUM_CLASS_UTILS_H + +#include <map> +#include "logger.h" +#include <type_traits> +#include <vector> +#include <cassert> + +namespace ring { + +/** + * This function adds a safe way to get an enum class size + * @note it cannot be unsigned to avoid some compiler warnings + */ +template<typename A> constexpr inline int enum_class_size() { + return size_t(A::COUNT__); +} + +/** + * This generic class represents a multidimensional enum class array. + * It safely converts them to integers. Each enum class needs a "COUNT__" item + * at the end." + * + * This struct enforces: + * * That the rows are indexed using enum_classes + * * That the size of the matrix matches the enum_class size + * * That the operators are within the matrix boundary + */ +template<class Row, typename Value, typename A = Value> +struct Matrix1D +{ + + Matrix1D(std::initializer_list< std::initializer_list<Value> > s); + + // Row is a built-in type ("int" by default) + Value operator[](Row v); + + const Value operator[](Row v) const; + + /** + * An Iterator for enum classes + */ + class EnumClassIter + { + public: + EnumClassIter (const Matrix1D<Row, Value, A>* p_vec, int pos) + : pos_( pos ), p_vec_( p_vec ) {} + + bool operator!= (const EnumClassIter& other) const; + Row operator* () const; + const EnumClassIter& operator++ (); + + private: + int pos_; + const Matrix1D<Row, Value, A> *p_vec_; + }; + + //Iterators + EnumClassIter begin(); + EnumClassIter end(); + + // Only use for single reverse mappable arrays, will ASSERT otherwise + Row fromValue(const Value& value) const; + + static void setReverseMapping(Matrix1D<Row,const char *> names); + +private: + const std::vector<Value> data_; + static std::map<A, Row> reverseMapping_; +}; + + +/** + * A matrix with no value + * + * This is useful to use enum class in C++11 foreach loops + * + * @usage + * for (const MyEnum& value : Matrix0D<MyEnum>()) { + * std::cout << "Name: " << MyEnumNames[value] << std::endl; + * } + */ +template<class EnumClass> +struct Matrix0D +{ + + /** + * An Iterator for enum classes + */ + class EnumClassIter + { + public: + EnumClassIter (const Matrix0D<EnumClass>* p_vec, int pos) + : pos_( pos ), p_vec_( p_vec ) {} + + bool operator!= (const EnumClassIter& other) const; + EnumClass operator* () const; + const EnumClassIter& operator++ (); + + private: + int pos_; + const Matrix0D<EnumClass> *p_vec_; + }; + + Matrix0D(); + + //Iterators + EnumClassIter begin(); + EnumClassIter end(); +}; + +/** + * A helper to type to match serializable string to enum elements + */ +template<class Row> +using EnumClassNames = Matrix1D<Row,const char*>; + +/** + * Create a matrix type with 2 enum class dimensions M[I,J] = V + * ^ ^ ^ + * | | | + * Rows <--- | | + * Columns <----- | + * Value <---------- + */ +template<class Row, class Column, typename Value> +using Matrix2D = Matrix1D<Row, Matrix1D<Column, Value>>; + +/** + * Create an array of callbacks. + * + * This type hides all the C++ syntax requirements + */ +template<class Row, class Class, typename Result = void,typename... Args> +using CallbackMatrix1D = Matrix1D<Row,Result(Class::*)(Args... args)>; + +/** + * Create a method callback matrix. + * + * This type hides all the C++ syntax requirements + */ +template<class Row, class Column, class Class, typename Result = void,typename... Args> +using CallbackMatrix2D = Matrix2D<Row,Column,void(Class::*)(Args... args)>; + + + + + + +/* + * IMPLEMENTATION + * + */ + +template<class Row, typename Value, typename Accessor> +Matrix1D<Row,Value,Accessor>::Matrix1D(std::initializer_list< std::initializer_list<Value>> s) +: data_(*std::begin(s)) { + static_assert(std::is_enum<Row>(),"Row has to be an enum class"); + static_assert((int)Row::COUNT__ > 0,"Row need a COUNT__ element"); + + // FIXME C++14, use static_assert and make the ctor constexpr + assert(std::begin(s)->size() == enum_class_size<Row>());//,"Matrix row have to match the enum class size"); +} + +template<class Row, typename Value, typename Accessor> +Value Matrix1D<Row,Value,Accessor>::operator[](Row v) { + //ASSERT(size_t(v) >= size_t(Row::COUNT__),"State Machine Out of Bounds\n"); + if (size_t(v) >= enum_class_size<Row>() || static_cast<int>(v) < 0) { + RING_ERR("State Machine Out of Bounds %d\n", size_t(v)); + assert(false); + throw v; + } + return data_[size_t(v)]; +} + +template<class Row, typename Value, typename Accessor> +const Value Matrix1D<Row,Value,Accessor>::operator[](Row v) const { + assert(size_t(v) <= enum_class_size<Row>()+1 && size_t(v)>=0); //COUNT__ is also valid + if (size_t(v) >= enum_class_size<Row>()) { + RING_ERR("State Machine Out of Bounds %d\n", size_t(v)); + assert(false); + throw v; + } + return data_[size_t(v)]; +} + +template <class E, class T, class A> std::map<A,E> Matrix1D<E,T,A>::reverseMapping_; + +template<class Row, typename Value, typename Accessor> +void Matrix1D<Row,Value,Accessor>::setReverseMapping(Matrix1D<Row,const char*> names) +{ + for ( const Row row : Matrix0D<Row>() ) + reverseMapping_[names[row]] = row; +} + +template<class Row, typename Value, typename Accessor> +Row Matrix1D<Row,Value,Accessor>::fromValue(const Value& value) const { + if (!reverseMapping_.empty()) { + for (int i = 0; i < enum_class_size<Row>();i++) { + const_cast<Matrix1D*>(this)->reverseMapping_[(*const_cast<Matrix1D*>(this))[(Row)i]] + = static_cast<Row>(i); + } + assert(reverseMapping_.empty() == enum_class_size<Row>()); + } + if (reverseMapping_.count(value) == 0) { + throw value; + } + return reverseMapping_[value]; +} + +template<class EnumClass > +Matrix0D<EnumClass>::Matrix0D() { + static_assert(std::is_enum<EnumClass>(),"The first template parameter has to be an enum class\n"); +} + +template<class EnumClass > +EnumClass Matrix0D<EnumClass>::EnumClassIter::operator* () const +{ + assert(pos_ < enum_class_size<EnumClass>()); + return static_cast<EnumClass>(pos_); +} + +template<class EnumClass > +const typename Matrix0D<EnumClass>::EnumClassIter& Matrix0D<EnumClass>::EnumClassIter::operator++ () +{ + ++pos_; + return *this; +} + +template<class EnumClass > +bool Matrix0D<EnumClass>::EnumClassIter::operator!= (const EnumClassIter& other) const +{ + return pos_ != other.pos_; +} + +template< class EnumClass > +typename Matrix0D<EnumClass>::EnumClassIter Matrix0D<EnumClass>::begin() +{ + return Matrix0D<EnumClass>::EnumClassIter( this, 0 ); +} + +template<class EnumClass > +typename Matrix0D<EnumClass>::EnumClassIter Matrix0D<EnumClass>::end() +{ + return Matrix0D<EnumClass>::EnumClassIter( this, enum_class_size<EnumClass>() ); +} + +template<class Row, typename Value, typename Accessor> +const typename Matrix1D<Row,Value,Accessor>::EnumClassIter& Matrix1D<Row,Value,Accessor>::EnumClassIter::operator++ () +{ + ++pos_; + return *this; +} + +template<class Row, typename Value, typename Accessor> +bool Matrix1D<Row,Value,Accessor>::EnumClassIter::operator!= (const EnumClassIter& other) const +{ + return pos_ != other.pos_; +} + +template<class Row, typename Value, typename Accessor> +typename Matrix1D<Row,Value,Accessor>::EnumClassIter Matrix1D<Row,Value,Accessor>::begin() +{ + return Matrix1D<Row,Value,Accessor>::EnumClassIter( this, 0 ); +} + +template<class Row, typename Value, typename Accessor> +typename Matrix1D<Row,Value,Accessor>::EnumClassIter Matrix1D<Row,Value,Accessor>::end() +{ + return Matrix1D<Row,Value,Accessor>::EnumClassIter( this, enum_class_size<Row>() ); +} + +} // namespace ring + +#endif //ENUM_CLASS_UTILS_H diff --git a/src/fileutils.cpp b/src/fileutils.cpp new file mode 100644 index 0000000000..b96bb745d4 --- /dev/null +++ b/src/fileutils.cpp @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2011-2015 Savoir-Faire Linux Inc. + * Copyright (C) 2010 Michael Kerrisk + * Copyright (C) 2007-2009 Rémi Denis-Courmont + * + * Author: Rafaël Carré <rafael.carre@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "fileutils.h" +#include "logger.h" +#include "intrin.h" + +#include <sys/types.h> +#include <sys/stat.h> + +#include <libgen.h> +#include <dirent.h> +#include <signal.h> +#include <unistd.h> +#include <fcntl.h> +#include <pwd.h> +#ifndef __ANDROID__ +# include <wordexp.h> +#endif + +#include <sstream> +#include <fstream> +#include <iostream> +#include <stdexcept> +#include <limits> + +#include <cstdlib> +#include <cstring> +#include <cerrno> +#include <cstddef> + +namespace ring { namespace fileutils { + +// returns true if directory exists +bool check_dir(const char *path) +{ + DIR *dir = opendir(path); + + if (!dir) { // doesn't exist + if (mkdir(path, 0755) != 0) { // couldn't create the dir + perror(path); + return false; + } + } else + closedir(dir); + + return true; +} + +#ifdef __ANDROID__ +static char *program_dir = "/data/data/cx.ring"; +#else +static char *program_dir = NULL; +#endif + +void set_program_dir(char *program_path) +{ + program_dir = dirname(program_path); +} + +const char *get_program_dir() +{ + return program_dir; +} + +/* Lock a file region */ +static int +lockReg(int fd, int cmd, int type, int whence, int start, off_t len) +{ + struct flock fl; + + fl.l_type = type; + fl.l_whence = whence; + fl.l_start = start; + fl.l_len = len; + + return fcntl(fd, cmd, &fl); +} + +static int /* Lock a file region using nonblocking F_SETLK */ +lockRegion(int fd, int type, int whence, int start, int len) +{ + return lockReg(fd, F_SETLK, type, whence, start, len); +} + +FileHandle +create_pidfile() +{ + const std::string name(get_home_dir() + DIR_SEPARATOR_STR PIDFILE); + FileHandle f(name); + char buf[100]; + f.fd = open(f.name.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + if (f.fd == -1) { + RING_ERR("Could not open PID file %s", f.name.c_str()); + return f; + } + + if (lockRegion(f.fd, F_WRLCK, SEEK_SET, 0, 0) == -1) { + if (errno == EAGAIN or errno == EACCES) + RING_ERR("PID file '%s' is locked; probably " + "'%s' is already running", f.name.c_str(), PACKAGE_NAME); + else + RING_ERR("Unable to lock PID file '%s'", f.name.c_str()); + close(f.fd); + f.fd = -1; + return f; + } + + if (ftruncate(f.fd, 0) == -1) { + RING_ERR("Could not truncate PID file '%s'", f.name.c_str()); + close(f.fd); + f.fd = -1; + return f; + } + + snprintf(buf, sizeof(buf), "%ld\n", (long) getpid()); + + const int buf_strlen = strlen(buf); + if (write(f.fd, buf, buf_strlen) != buf_strlen) { + RING_ERR("Problem writing to PID file '%s'", f.name.c_str()); + close(f.fd); + f.fd = -1; + return f; + } + + return f; +} + +std::string +expand_path(const std::string &path) +{ +#ifdef __ANDROID__ + RING_ERR("Path expansion not implemented, returning original"); + return path; +#else + + std::string result; + + wordexp_t p; + int ret = wordexp(path.c_str(), &p, 0); + + switch (ret) { + case WRDE_BADCHAR: + RING_ERR("Illegal occurrence of newline or one of |, &, ;, <, >, " + "(, ), {, }."); + return result; + case WRDE_BADVAL: + RING_ERR("An undefined shell variable was referenced"); + return result; + case WRDE_CMDSUB: + RING_ERR("Command substitution occurred"); + return result; + case WRDE_SYNTAX: + RING_ERR("Shell syntax error"); + return result; + case WRDE_NOSPACE: + RING_ERR("Out of memory."); + // This is the only error where we must call wordfree + break; + default: + if (p.we_wordc > 0) + result = std::string(p.we_wordv[0]); + break; + } + + wordfree(&p); + + return result; +#endif +} + +bool isDirectoryWritable(const std::string &directory) +{ + return access(directory.c_str(), W_OK) == 0; +} + +std::vector<uint8_t> +loadFile(const std::string& path) +{ + std::vector<uint8_t> buffer; + std::ifstream file(path, std::ios::binary); + if (!file) + throw std::runtime_error("Can't read file: "+path); + file.seekg(0, std::ios::end); + std::streamsize size = file.tellg(); + if (size > std::numeric_limits<unsigned>::max()) + throw std::runtime_error("File is too big: "+path); + buffer.resize(size); + file.seekg(0, std::ios::beg); + if (!file.read((char*)buffer.data(), size)) + throw std::runtime_error("Can't load file: "+path); + return buffer; +} + +void +saveFile(const std::string& path, const std::vector<uint8_t>& data) +{ + std::ofstream file(path, std::ios::trunc | std::ios::binary); + if (!file.is_open()) { + RING_ERR("Could not write data to %s", path.c_str()); + return; + } + file.write((char*)data.data(), data.size()); +} + +static size_t +dirent_buf_size(UNUSED DIR* dirp) +{ + long name_max; +#if defined(HAVE_FPATHCONF) && defined(HAVE_DIRFD) && defined(_PC_NAME_MAX) + name_max = fpathconf(dirfd(dirp), _PC_NAME_MAX); + if (name_max == -1) +#if defined(NAME_MAX) + name_max = (NAME_MAX > 255) ? NAME_MAX : 255; +#else + return (size_t)(-1); +#endif +#else +#if defined(NAME_MAX) + name_max = (NAME_MAX > 255) ? NAME_MAX : 255; +#else +#error "buffer size for readdir_r cannot be determined" +#endif +#endif + size_t name_end = (size_t) offsetof(struct dirent, d_name) + name_max + 1; + return name_end > sizeof(struct dirent) ? name_end : sizeof(struct dirent); +} + +std::vector<std::string> +readDirectory(const std::string& dir) +{ + DIR *dp = opendir(dir.c_str()); + if (!dp) { + RING_ERR("Could not open %s", dir.c_str()); + return {}; + } + + size_t size = dirent_buf_size(dp); + if (size == (size_t)(-1)) + return {}; + std::vector<uint8_t> buf(size); + dirent* entry; + + std::vector<std::string> files; + while (!readdir_r(dp, reinterpret_cast<dirent*>(buf.data()), &entry) && entry) { + const std::string fname {entry->d_name}; + if (fname == "." || fname == "..") + continue; + files.push_back(std::move(fname)); + } + closedir(dp); + return files; +} + +FileHandle::FileHandle(const std::string &n) : fd(-1), name(n) +{} + +FileHandle::~FileHandle() +{ + // we will only delete the file if it was created by this process + if (fd != -1) { + close(fd); + if (unlink(name.c_str()) == -1) + RING_ERR("%s", strerror(errno)); + } +} + +std::string +get_cache_dir() +{ + const std::string cache_home(XDG_CACHE_HOME); + + if (not cache_home.empty()) { + return cache_home; + } else { +#ifdef __ANDROID__ + return get_home_dir() + DIR_SEPARATOR_STR + PACKAGE; +#elif __APPLE__ + return get_home_dir() + DIR_SEPARATOR_STR + + "Library" + DIR_SEPARATOR_STR + "Caches" + + DIR_SEPARATOR_STR + PACKAGE; +#else + return get_home_dir() + DIR_SEPARATOR_STR + + ".cache" + DIR_SEPARATOR_STR + PACKAGE; +#endif + } +} + +std::string +get_home_dir() +{ +#ifdef __ANDROID__ + return get_program_dir(); +#else + + // 1) try getting user's home directory from the environment + const std::string home(PROTECTED_GETENV("HOME")); + if (not home.empty()) + return home; + + // 2) try getting it from getpwuid_r (i.e. /etc/passwd) + const long max = sysconf(_SC_GETPW_R_SIZE_MAX); + if (max != -1) { + char buf[max]; + struct passwd pwbuf, *pw; + if (getpwuid_r(getuid(), &pwbuf, buf, sizeof(buf), &pw) == 0 and pw != NULL) + return pw->pw_dir; + } + + return ""; +#endif +} + +std::string +get_data_dir() +{ +#ifdef __ANDROID__ + return get_program_dir(); +#elif __APPLE__ + return get_home_dir() + DIR_SEPARATOR_STR + + "Library" + DIR_SEPARATOR_STR + "Application Support" + + DIR_SEPARATOR_STR + PACKAGE; +#else + const std::string data_home(XDG_DATA_HOME); + if (not data_home.empty()) + return data_home + DIR_SEPARATOR_STR + PACKAGE; + // "If $XDG_DATA_HOME is either not set or empty, a default equal to + // $HOME/.local/share should be used." + return get_home_dir() + DIR_SEPARATOR_STR ".local" DIR_SEPARATOR_STR + "share" DIR_SEPARATOR_STR + PACKAGE; +#endif +} + +}} // namespace ring::fileutils diff --git a/src/fileutils.h b/src/fileutils.h new file mode 100644 index 0000000000..b1558e6f21 --- /dev/null +++ b/src/fileutils.h @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2011-2015 Savoir-Faire Linux Inc. + * Author: Rafaël Carré <rafael.carre@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef FILEUTILS_H_ +#define FILEUTILS_H_ + +#include <string> +#include <vector> + +#define PROTECTED_GETENV(str) ({char *envvar_ = getenv((str)); \ + envvar_ ? envvar_ : "";}) + +#define XDG_DATA_HOME (PROTECTED_GETENV("XDG_DATA_HOME")) +#define XDG_CONFIG_HOME (PROTECTED_GETENV("XDG_CONFIG_HOME")) +#define XDG_CACHE_HOME (PROTECTED_GETENV("XDG_CACHE_HOME")) + +#define PIDFILE ".ring.pid" + + +#define DIR_SEPARATOR_STR "/" // Directory separator char +#define DIR_SEPARATOR_CH '/' // Directory separator string + +namespace ring { namespace fileutils { + + std::string get_data_dir(); + std::string get_home_dir(); + std::string get_cache_dir(); + bool check_dir(const char *path); + void set_program_dir(char *program_path); + const char *get_program_dir(); + std::string expand_path(const std::string &path); + bool isDirectoryWritable(const std::string &directory); + + /** + * Read content of the directory. + * The result is a list of full paths of files in the directory, + * without "." and "..". + */ + std::vector<std::string> readDirectory(const std::string &dir); + + std::vector<uint8_t> loadFile(const std::string& path); + void saveFile(const std::string& path, const std::vector<uint8_t>& data); + + struct FileHandle { + int fd; + const std::string name; + FileHandle(const std::string &name); + ~FileHandle(); + }; + FileHandle create_pidfile(); + +}} // namespace ring::fileutils + +#endif // FILEUTILS_H_ diff --git a/src/gnutls_support.h b/src/gnutls_support.h new file mode 100644 index 0000000000..eb15d08780 --- /dev/null +++ b/src/gnutls_support.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#pragma once + +#include <string> +#include <stdexcept> +#include <memory> + +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> + +namespace ring { namespace tls { + +/** + * This class provides a C++ access to (de)initialization of GNU TLS library. + * Typically used with a std::unique_ptr to implement RAII behavior. + */ + +class GnuTlsGlobalInit +{ + public: + static std::unique_ptr<GnuTlsGlobalInit> make_guard() { + return std::unique_ptr<GnuTlsGlobalInit> {new GnuTlsGlobalInit}; + } + + ~GnuTlsGlobalInit() { + gnutls_global_deinit(); + } + + private: + GnuTlsGlobalInit() { + const auto ret = gnutls_global_init(); + if (ret < 0) + throw std::runtime_error("Can't initialise GNUTLS : " + + std::string(gnutls_strerror(ret))); + } +}; + +}} // namespace ring::tls diff --git a/src/hooks/Makefile.am b/src/hooks/Makefile.am new file mode 100644 index 0000000000..33a0882f81 --- /dev/null +++ b/src/hooks/Makefile.am @@ -0,0 +1,4 @@ +noinst_LTLIBRARIES = libhooks.la + +libhooks_la_SOURCES = \ + urlhook.cpp urlhook.h diff --git a/src/hooks/urlhook.cpp b/src/hooks/urlhook.cpp new file mode 100644 index 0000000000..373fa2bc0c --- /dev/null +++ b/src/hooks/urlhook.cpp @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "urlhook.h" +#include <cstdlib> + +namespace ring { + +int UrlHook::runAction(const std::string &command, const std::string &args) +{ + //FIXME : use fork and execve, so no need to escape shell arguments + const std::string cmd = command + (args.empty() ? "" : " ") + + "\"" + args + "\" &"; + return system(cmd.c_str()); +} + +} // namespace ring diff --git a/src/hooks/urlhook.h b/src/hooks/urlhook.h new file mode 100644 index 0000000000..2c51325181 --- /dev/null +++ b/src/hooks/urlhook.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef URL_HOOK_H +#define URL_HOOK_H + +#include <string> + +namespace ring { namespace UrlHook { + +int runAction(const std::string &command, const std::string &arg); + +}} // namespace ring::UrlHook + +#endif // URL_HOOK_H diff --git a/src/iax/Makefile.am b/src/iax/Makefile.am new file mode 100644 index 0000000000..be31d770c3 --- /dev/null +++ b/src/iax/Makefile.am @@ -0,0 +1,21 @@ +include $(top_srcdir)/globals.mak + +if USE_IAX + +noinst_LTLIBRARIES = libiaxlink.la + +libiaxlink_la_SOURCES = \ + iaxaccount.cpp \ + iaxcall.cpp \ + iaxvoiplink.cpp + +libiaxlink_la_CXXFLAGS = \ + -DUSE_IAX + +noinst_HEADERS = \ + iaxaccount.h \ + iaxcall.h \ + iaxvoiplink.h + +endif + diff --git a/src/iax/iaxaccount.cpp b/src/iax/iaxaccount.cpp new file mode 100644 index 0000000000..8ddf2fce07 --- /dev/null +++ b/src/iax/iaxaccount.cpp @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Alexandre Bourget <alexandre.bourget@savoirfairelinux.com> + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "iaxaccount.h" +#include "account_schema.h" +#include "iaxvoiplink.h" +#include "iaxcall.h" +#include "logger.h" +#include "manager.h" +#include "call_factory.h" +#include "intrin.h" + +#include "config/yamlparser.h" +#include <yaml-cpp/yaml.h> + +namespace ring { + +constexpr const char * const IAXAccount::ACCOUNT_TYPE; + +IAXAccount::IAXAccount(const std::string& accountID) + : Account(accountID), link_(new IAXVoIPLink(*this)) +{} + +void IAXAccount::serialize(YAML::Emitter &out) +{ + out << YAML::BeginMap; + Account::serialize(out); + out << YAML::Key << PASSWORD_KEY << YAML::Value << password_; + out << YAML::EndMap; +} + +void IAXAccount::unserialize(const YAML::Node &node) +{ + Account::unserialize(node); + yaml_utils::parseValue(node, PASSWORD_KEY, password_); +} + +void IAXAccount::setAccountDetails(const std::map<std::string, std::string> &details) +{ + // Account setting common to SIP and IAX + Account::setAccountDetails(details); + parseString(details, Conf::CONFIG_ACCOUNT_PASSWORD, password_); +} + +std::map<std::string, std::string> IAXAccount::getAccountDetails() const +{ + std::map<std::string, std::string> a = Account::getAccountDetails(); + a[Conf::CONFIG_ACCOUNT_PASSWORD] = password_; + return a; +} + +std::map<std::string, std::string> IAXAccount::getVolatileAccountDetails() const +{ + std::map<std::string, std::string> a = Account::getVolatileAccountDetails(); + + return a; +} + +void IAXAccount::doRegister() +{ + try { + link_->init(rand_); + sendRegister(); + } catch (const VoipLinkException &e) { + RING_ERR("IAXAccount: %s", e.what()); + } +} + +void +IAXAccount::doUnregister(std::function<void(bool)> cb) +{ + try { + sendUnregister(); + link_->terminate(); + } catch (const VoipLinkException &e) { + RING_ERR("IAXAccount: %s", e.what()); + } + if (cb) + cb(true); +} + +void +IAXAccount::loadConfig() +{ + // If IAX is not supported, do not register this account +#if !HAVE_IAX + enabled_ = false; +#endif +} + +template <> +std::shared_ptr<IAXCall> +IAXAccount::newIncomingCall(const std::string& from UNUSED) +{ + auto& manager = Manager::instance(); + return manager.callFactory.newCall<IAXCall, IAXAccount>(*this, manager.getNewCallID(), + Call::INCOMING); +} + +template <> +std::shared_ptr<IAXCall> +IAXAccount::newOutgoingCall(const std::string& toUrl) +{ + auto& manager = Manager::instance(); + auto call = manager.callFactory.newCall<IAXCall, IAXAccount>(*this, manager.getNewCallID(), + Call::OUTGOING); + + call->setPeerNumber(toUrl); + call->initRecFilename(toUrl); + + iaxOutgoingInvite(call.get()); + + call->setConnectionState(Call::PROGRESSING); + call->setState(Call::ACTIVE); + + return call; +} + +std::shared_ptr<Call> +IAXAccount::newOutgoingCall(const std::string& toUrl) +{ + return newOutgoingCall<IAXCall>(toUrl); +} + +void +IAXAccount::iaxOutgoingInvite(IAXCall* call) +{ + std::lock_guard<std::mutex> lock(IAXVoIPLink::mutexIAX); + + call->session = iax_session_new(); + + std::string username(getUsername()); + std::string strNum(username + ":" + getPassword() + "@" + getHostname() + "/" + call->getPeerNumber()); + + /** @todo Make preference dynamic, and configurable */ + const auto accountID = getAccountID(); + int audio_format_preferred = call->getFirstMatchingFormat(call->getSupportedFormat(accountID), accountID); + int audio_format_capability = call->getSupportedFormat(accountID); + + iax_call(call->session, username.c_str(), username.c_str(), strNum.c_str(), + NULL, 0, audio_format_preferred, audio_format_capability); +} + +void +IAXAccount::sendRegister() +{ + if (not isEnabled()) { + RING_WARN("Account must be enabled to register, ignoring"); + return; + } + + if (getHostname().empty()) + throw VoipLinkException("Account hostname is empty"); + + if (getUsername().empty()) + throw VoipLinkException("Account username is empty"); + + { + std::lock_guard<std::mutex> lock(IAXVoIPLink::mutexIAX); + regSession_.reset(iax_session_new()); + } + + if (regSession_) { + { + std::lock_guard<std::mutex> lock(IAXVoIPLink::mutexIAX); + RING_DBG("register IAXAccount %s", getHostname().c_str()); + iax_register(regSession_.get(), getHostname().data(), + getUsername().data(), getPassword().data(), 120); + } + + nextRefreshStamp_ = time(NULL) + 10; + setRegistrationState(RegistrationState::TRYING); + } +} + +void +IAXAccount::sendUnregister(std::function<void(bool)> cb) +{ + RING_DBG("unregister IAXAccount %s", getHostname().c_str()); + destroyRegSession(); + + nextRefreshStamp_ = 0; + setRegistrationState(RegistrationState::UNREGISTERED); + + if (cb) + cb(true); +} + +void +IAXAccount::destroyRegSession() +{ + std::lock_guard<std::mutex> lock(IAXVoIPLink::mutexIAX); + regSession_.reset(); +} + +void +IAXAccount::checkRegister() +{ + if (nextRefreshStamp_ and nextRefreshStamp_ < time(NULL)) + sendRegister(); +} + +bool +IAXAccount::matchRegSession(const iax_session* session) const +{ + return regSession_.get() == session; +} + +} // namespace ring diff --git a/src/iax/iaxaccount.h b/src/iax/iaxaccount.h new file mode 100644 index 0000000000..7586cae944 --- /dev/null +++ b/src/iax/iaxaccount.h @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Bourget <alexandre.bourget@savoirfairelinux.com> + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef IAXACCOUNT_H +#define IAXACCOUNT_H + +#include "account.h" +#include "iaxvoiplink.h" +#include "ring_types.h" // enable_if_base_of + +namespace YAML { + class Emitter; + class Node; +} + +namespace ring { + +class IAXCall; + +/** + * @file: iaxaccount.h + * @brief An IAX Account specify IAX specific functions and objects (IAXCall/IAXVoIPLink) + */ +class IAXAccount : public Account { + public: + constexpr static const char * const ACCOUNT_TYPE = "IAX"; + + IAXAccount(const std::string& accountID); + + virtual void serialize(YAML::Emitter &out); + virtual void unserialize(const YAML::Node &node); + + const char* getAccountType() const { + return ACCOUNT_TYPE; + } + + std::map<std::string, std::string> getAccountDetails() const; + + virtual std::map<std::string, std::string> getVolatileAccountDetails() const; + + void setNextRefreshStamp(int value) { + nextRefreshStamp_ = value; + } + + // Actually useless, since config loading is done in init() + void loadConfig(); + + // Register an account + void doRegister(); + + // Unregister an account + void doUnregister(std::function<void(bool)> cb = std::function<void(bool)>()); + + /** + * Send out registration + */ + void sendRegister(); + + /** + * Destroy registration session + * @todo Send an IAX_COMMAND_REGREL to force unregistration upstream. + * Urgency: low + */ + void sendUnregister(std::function<void(bool)> cb = std::function<void(bool)>()); + + std::string getPassword() const { + return password_; + } + + bool matchRegSession(const iax_session* session) const; + + void destroyRegSession(); + + void checkRegister(); + + /** + * Implementation of Account::newOutgoingCall() + * Note: keep declaration before newOutgoingCall template. + */ + std::shared_ptr<Call> newOutgoingCall(const std::string& toUrl); + + /** + * Create outgoing IAXCall. + * @param[in] toUrl The address to call + * @return std::shared_ptr<T> A shared pointer on the created call. + * The type of this instance is given in template argument. + * This type can be any base class of IAXCall class (included). + */ + template <class T=IAXCall> + std::shared_ptr<enable_if_base_of<T, IAXCall> > + newOutgoingCall(const std::string& toUrl); + + /** + * Create incoming IAXCall. + * @param[in] from The origin uri of the call + * @return std::shared_ptr<T> A shared pointer on the created call. + * The type of this instance is given in template argument. + * This type can be any base class of IAXCall class (included). + */ + template <class T=IAXCall> + std::shared_ptr<enable_if_base_of<T, IAXCall> > + newIncomingCall(const std::string& from); + + /** + * Set whether or not to use UPnP + */ + void setUseUPnP(bool) { + /* do nothing for now as UPnP isn't implemented for IAX */ + } + + private: + + void setAccountDetails(const std::map<std::string, std::string> &details); + + /** + * Send an outgoing call invite to iax + * @param call An IAXCall pointer + */ + void iaxOutgoingInvite(IAXCall* call); + + /** registration session : nullptr if not register */ + struct RegSessionDeleter { + void operator()(iax_session* session) { iax_destroy(session); } + }; + std::unique_ptr<iax_session, RegSessionDeleter> regSession_ = nullptr; + + // Account login information: password + std::string password_{}; + std::unique_ptr<IAXVoIPLink> link_; + + /** Timestamp of when we should refresh the registration up with + * the registrar. Values can be: EPOCH timestamp, 0 if we want no registration, 1 + * to force a registration. */ + int nextRefreshStamp_ = 0; +}; + +} // namespace ring + +#endif diff --git a/src/iax/iaxcall.cpp b/src/iax/iaxcall.cpp new file mode 100644 index 0000000000..1512350871 --- /dev/null +++ b/src/iax/iaxcall.cpp @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Bourget <alexandre.bourget@savoirfairelinux.com> + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * Author : Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <cstring> +#include <sys/socket.h> +#include <iax/iax-client.h> +#include <iax/frame.h> + +#include "intrin.h" +#include "iaxcall.h" +#include "logger.h" +#include "manager.h" +#include "iaxaccount.h" +#include "iaxvoiplink.h" +#include "audio/ringbufferpool.h" +#include "audio/ringbuffer.h" + +#if HAVE_INSTANT_MESSAGING +#include "im/instant_messaging.h" +#endif + +namespace ring { + +const char* const IAXCall::LINK_TYPE = IAXAccount::ACCOUNT_TYPE; + +static int +codecToASTFormat(int c) +{ + switch (c) { + case PAYLOAD_CODEC_ULAW: + return AST_FORMAT_ULAW; + case PAYLOAD_CODEC_GSM: + return AST_FORMAT_GSM; + case PAYLOAD_CODEC_ALAW: + return AST_FORMAT_ALAW; + case PAYLOAD_CODEC_ILBC_20: + return AST_FORMAT_ILBC; + case PAYLOAD_CODEC_SPEEX_8000: + return AST_FORMAT_SPEEX; + + default: + RING_ERR("Codec %d not supported!", c); + return 0; + } +} + +IAXCall::IAXCall(IAXAccount& account, const std::string& id, Call::CallType type) + : Call(account, id, type), + format(0), + session(NULL) +{ + ringbuffer_ = Manager::instance().getRingBufferPool().createRingBuffer(getCallId()); +} + +int +IAXCall::getSupportedFormat(const std::string &accountID) const +{ + const auto account = Manager::instance().getAccount(accountID); + + int format_mask = 0; + + if (account) { + std::vector<unsigned> codecs{account->getActiveAccountCodecInfoIdList(MEDIA_AUDIO)}; + + for (const auto &i : codecs) + format_mask |= codecToASTFormat(i); + } else + RING_ERR("No IAx account could be found"); + + return format_mask; +} + +int +IAXCall::getFirstMatchingFormat(int needles, const std::string &accountID) const +{ + const auto account = Manager::instance().getAccount(accountID); + + if (account != NULL) { + std::vector<unsigned> codecs{account->getActiveAccountCodecInfoIdList(MEDIA_AUDIO)}; + + for (const auto &i : codecs) { + int format_mask = codecToASTFormat(i); + + // Return the first that matches + if (format_mask & needles) + return format_mask; + } + } else + RING_ERR("No IAx account could be found"); + + return 0; +} + +int +IAXCall::getAudioCodecPayload() const +{ + switch (format) { + case AST_FORMAT_ULAW: + return PAYLOAD_CODEC_ULAW; + case AST_FORMAT_GSM: + return PAYLOAD_CODEC_GSM; + case AST_FORMAT_ALAW: + return PAYLOAD_CODEC_ALAW; + case AST_FORMAT_ILBC: + return PAYLOAD_CODEC_ILBC_20; + case AST_FORMAT_SPEEX: + return PAYLOAD_CODEC_SPEEX_8000; + default: + RING_ERR("IAX: Format %d not supported!", format); + return -1; + } +} + +void +IAXCall::answer() +{ + Manager::instance().addStream(*this); + + { + std::lock_guard<std::mutex> lock(IAXVoIPLink::mutexIAX); + iax_answer(session); + } + + setState(Call::ACTIVE); + setConnectionState(Call::CONNECTED); + + Manager::instance().getRingBufferPool().flushAllBuffers(); +} + +void +IAXCall::hangup(int reason UNUSED) +{ + Manager::instance().getRingBufferPool().unBindAll(getCallId()); + + std::lock_guard<std::mutex> lock(IAXVoIPLink::mutexIAX); + iax_hangup(session, (char*) "Dumped Call"); + session = nullptr; + + removeCall(); +} + +void +IAXCall::refuse() +{ + { + std::lock_guard<std::mutex> lock(IAXVoIPLink::mutexIAX); + iax_reject(session, (char*) "Call rejected manually."); + } + + removeCall(); +} + +void +IAXCall::transfer(const std::string& to) +{ + std::lock_guard<std::mutex> lock(IAXVoIPLink::mutexIAX); + char callto[to.length() + 1]; + strcpy(callto, to.c_str()); + iax_transfer(session, callto); +} + +bool +IAXCall::attendedTransfer(const std::string& /*targetID*/) +{ + return false; // TODO +} + +void +IAXCall::onhold() +{ + Manager::instance().getRingBufferPool().unBindAll(getCallId()); + + { + std::lock_guard<std::mutex> lock(IAXVoIPLink::mutexIAX); + iax_quelch_moh(session, true); + } + + setState(Call::HOLD); +} + +void +IAXCall::offhold() +{ + Manager::instance().addStream(*this); + + { + std::lock_guard<std::mutex> lock(IAXVoIPLink::mutexIAX); + iax_unquelch(session); + } + + setState(Call::ACTIVE); + + Manager::instance().startAudioDriverStream(); +} + +void +IAXCall::peerHungup() +{ + Manager::instance().getRingBufferPool().unBindAll(getCallId()); + + session = nullptr; +} + +void +IAXCall::carryingDTMFdigits(char code) +{ + std::lock_guard<std::mutex> lock(IAXVoIPLink::mutexIAX); + iax_send_dtmf(session, code); +} + +#if HAVE_INSTANT_MESSAGING +void +IAXCall::sendTextMessage(const std::string& message, const std::string& /*from*/) +{ + std::lock_guard<std::mutex> lock(IAXVoIPLink::mutexIAX); + InstantMessaging::send_iax_message(session, getCallId(), message.c_str()); +} +#endif + +void +IAXCall::putAudioData(AudioBuffer& buf) +{ + ringbuffer_->put(buf); +} + +} // namespace ring diff --git a/src/iax/iaxcall.h b/src/iax/iaxcall.h new file mode 100644 index 0000000000..6472330b2b --- /dev/null +++ b/src/iax/iaxcall.h @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Bourget <alexandre.bourget@savoirfairelinux.com> + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * Author : Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#ifndef IAXCALL_H +#define IAXCALL_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "call.h" +#include "noncopyable.h" + +class iax_session; + +namespace ring { + +class IAXAccount; +class RingBuffer; +class AudioBuffer; + +/** Enumeration that contains known audio payloads */ +enum { + // http://www.iana.org/assignments/rtp-parameters + // http://www.gnu.org/software/ccrtp/doc/refman/html/formats_8h.html#a0 + // 0 PCMU A 8000 1 [RFC3551] + PAYLOAD_CODEC_ULAW = 0, + // 3 GSM A 8000 1 [RFC3551] + PAYLOAD_CODEC_GSM = 3, + // 8 PCMA A 8000 1 [RFC3551] + PAYLOAD_CODEC_ALAW = 8, + // 9 G722 A 8000 1 [RFC3551] + PAYLOAD_CODEC_G722 = 9, + // http://www.ietf.org/rfc/rfc3952.txt + // 97 iLBC/8000 + PAYLOAD_CODEC_ILBC_20 = 97, + PAYLOAD_CODEC_ILBC_30 = 98, + // http://www.speex.org/drafts/draft-herlein-speex-rtp-profile-00.txt + // 97 speex/8000 + // http://support.xten.com/viewtopic.php?p=8684&sid=3367a83d01fdcad16c7459a79859b08e + // 100 speex/16000 + PAYLOAD_CODEC_SPEEX_8000 = 110, + PAYLOAD_CODEC_SPEEX_16000 = 111, + PAYLOAD_CODEC_SPEEX_32000 = 112 +}; + + +/** + * @file: iaxcall.h + * @brief IAXCall are IAX implementation of a normal Call + */ + +class IAXCall : public Call +{ + public: + static const char* const LINK_TYPE; + + protected: + /** + * Constructor + * @param id The unique ID of the call + * @param type The type of the call + */ + IAXCall(IAXAccount& account, const std::string& id, Call::CallType type); + + public: + /** + * @return int The bitwise list of supported formats + */ + int getSupportedFormat(const std::string &accountID) const; + + /** + * Return a format (int) with the first matching codec selected. + * + * This considers the order of the appearance in the CodecMap, + * thus, the order of preference. + * + * NOTE: Everything returned is bound to the content of the local + * CodecMap, so it won't return format values that aren't valid + * in this call context. + * + * @param needles The format(s) (bitwise) you are looking for to match + * @return int The matching format, thus 0 if none matches + */ + int getFirstMatchingFormat(int needles, const std::string &accountID) const; + + int getAudioCodecPayload() const; + + int format; + iax_session* session; + + void answer(); + + void hangup(int reason); + + void refuse(); + + void transfer(const std::string& to); + + bool attendedTransfer(const std::string& to); + + void onhold(); + + void offhold(); + + void peerHungup(); + + void carryingDTMFdigits(char code); + +#if HAVE_INSTANT_MESSAGING + void sendTextMessage(const std::string& message, + const std::string& from); +#endif + + void putAudioData(AudioBuffer& buf); + + private: + NON_COPYABLE(IAXCall); + + // Incoming audio ring buffer + std::shared_ptr<RingBuffer> ringbuffer_{}; +}; + +} // namespace ring + +#endif diff --git a/src/iax/iaxvoiplink.cpp b/src/iax/iaxvoiplink.cpp new file mode 100644 index 0000000000..025a0f1f0b --- /dev/null +++ b/src/iax/iaxvoiplink.cpp @@ -0,0 +1,463 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Bourget <alexandre.bourget@savoirfairelinux.com> + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "iaxvoiplink.h" +#include <unistd.h> +#include <cmath> +#include <algorithm> + +#include "manager.h" +#include "iaxcall.h" +#include "iaxaccount.h" +#include "logger.h" +#include "hooks/urlhook.h" +#include "audio/audiolayer.h" +#include "audio/resampler.h" +#include "audio/ringbufferpool.h" +#include "array_size.h" +#include "map_utils.h" +#include "call_factory.h" +#include "ring_types.h" +#include "system_codec_container.h" +#include "intrin.h" // for UNUSED + +namespace ring { + +std::mutex IAXVoIPLink::mutexIAX = {}; + +IAXVoIPLink::IAXVoIPLink(IAXAccount& account) : account_(account), resampler_(new Resampler{44100}) +{} + +IAXVoIPLink::~IAXVoIPLink() +{ + terminate(); +} + +void +IAXVoIPLink::init(std::mt19937_64& rand_generator) +{ + if (initDone_) + return; + + std::uniform_int_distribution<int> port_dist(1024, 65024); + std::lock_guard<std::mutex> lock(mutexIAX); + for (int port = IAX_DEFAULT_PORTNO, nbTry = 0; nbTry < 3 ; port = port_dist(rand_generator), nbTry++) { + if (iax_init(port) >= 0) { + Manager::instance().registerEventHandler((uintptr_t)this, std::bind(&IAXVoIPLink::handleEvents, this)); + initDone_ = true; + break; + } + } +} + +void +IAXVoIPLink::terminate() +{ + if (!initDone_) + return; + + Manager::instance().unregisterEventHandler((uintptr_t)this); + + for (const auto& call : Manager::instance().callFactory.getAllCalls<IAXCall>()) { + std::lock_guard<std::mutex> lock(mutexIAX); + iax_hangup(call->session, const_cast<char*>("Dumped Call")); + call->removeCall(); + } + + initDone_ = false; +} + +static std::shared_ptr<IAXCall> +iaxGetCallFromSession(iax_session* session) +{ + for (auto call : Manager::instance().callFactory.getAllCalls<IAXCall>()) { + if (call->session == session) + return call; + } + return nullptr; +} + +void +IAXVoIPLink::handleEvents() +{ + iax_event *event = NULL; + + { + std::lock_guard<std::mutex> lock(mutexIAX); + event = iax_get_event(0); + } + + while (event != NULL) { + + // If we received an 'ACK', libiax2 tells apps to ignore them. + if (event->etype == IAX_EVENT_NULL) { + std::lock_guard<std::mutex> lock(mutexIAX); + iax_event_free(event); + event = iax_get_event(0); + continue; + } + + if (auto raw_call_ptr = iaxGetCallFromSession(event->session)) { + iaxHandleCallEvent(event, *raw_call_ptr); + } else if (event->session && account_.matchRegSession(event->session)) { + // This is a registration session, deal with it + iaxHandleRegReply(event); + } else { + // We've got an event before it's associated with any call + iaxHandlePrecallEvent(event); + } + + { + std::lock_guard<std::mutex> lock(mutexIAX); + iax_event_free(event); + event = iax_get_event(0); + } + } + + account_.checkRegister(); + + sendAudioFromMic(); +} + +void +IAXVoIPLink::sendAudioFromMic() +{ + for (const auto currentCall : Manager::instance().callFactory.getAllCalls<IAXCall>()) { + if (currentCall->getState() != Call::ACTIVE) + continue; + + int codecType = currentCall->getAudioCodecPayload(); + + auto codec = account_.searchCodecByPayload(codecType, MEDIA_AUDIO); + auto accountAudioCodec = std::static_pointer_cast<AccountAudioCodecInfo>(codec); + if (!accountAudioCodec) + continue; + + Manager::instance().getRingBufferPool().setInternalSamplingRate(accountAudioCodec->audioformat.sample_rate); + + unsigned int mainBufferSampleRate = Manager::instance().getRingBufferPool().getInternalSamplingRate(); + + // we have to get 20ms of data from the mic *20/1000 = /50 + // rate/50 shall be lower than IAX__20S_48KHZ_MAX + size_t samples = mainBufferSampleRate * 20 / 1000; + + if (Manager::instance().getRingBufferPool().availableForGet(currentCall->getCallId()) < samples) + continue; + + // Get bytes from micRingBuffer to data_from_mic + rawBuffer_.resize(samples); + samples = Manager::instance().getRingBufferPool().getData(rawBuffer_, currentCall->getCallId()); + + int compSize = 0; + unsigned int audioRate = accountAudioCodec->audioformat.sample_rate; + int outSamples; + UNUSED AudioBuffer *in; + + if (audioRate != mainBufferSampleRate) { + rawBuffer_.setSampleRate(audioRate); + resampledData_.setSampleRate(mainBufferSampleRate); + resampler_->resample(rawBuffer_, resampledData_); + in = &resampledData_; + outSamples = 0; + } else { + outSamples = samples; + in = &rawBuffer_; + } + + /* + * TODO ebail : * + * IAX use old codec API (based on audiocodec wrapper) + * It does not use libav API + * We disable it for the moment + */ +#if 0 + compSize = audioCodec->encode(in->getData(), encodedData_, RAW_BUFFER_SIZE); +#endif + + if (currentCall->session and samples > 0) { + std::lock_guard<std::mutex> lock(mutexIAX); + + if (iax_send_voice(currentCall->session, currentCall->format, + encodedData_, compSize, outSamples) == -1) + RING_ERR("IAX: Error sending voice data."); + } + } +} + +void +IAXVoIPLink::handleReject(IAXCall& call) +{ + call.setConnectionState(Call::CONNECTED); + call.setState(Call::MERROR); + Manager::instance().callFailure(call); + call.removeCall(); +} + +void +IAXVoIPLink::handleAccept(iax_event* event, IAXCall& call) +{ + if (event->ies.format) + call.format = event->ies.format; +} + +void +IAXVoIPLink::handleAnswerTransfer(iax_event* event, IAXCall& call) +{ + if (call.getConnectionState() == Call::CONNECTED) + return; + + call.setConnectionState(Call::CONNECTED); + call.setState(Call::ACTIVE); + + if (event->ies.format) + call.format = event->ies.format; + + Manager::instance().addStream(call); + Manager::instance().peerAnsweredCall(call); + Manager::instance().startAudioDriverStream(); + Manager::instance().getRingBufferPool().flushAllBuffers(); +} + +void +IAXVoIPLink::handleBusy(IAXCall& call) +{ + call.setConnectionState(Call::CONNECTED); + call.setState(Call::BUSY); + + Manager::instance().callBusy(call); + call.removeCall(); +} + +#if HAVE_INSTANT_MESSAGING +void +IAXVoIPLink::handleMessage(iax_event* event, IAXCall& call) +{ + Manager::instance().incomingMessage(call.getCallId(), call.getPeerNumber(), + std::string((const char*) event->data)); +} +#endif + +void +IAXVoIPLink::handleRinging(IAXCall& call) +{ + call.setConnectionState(Call::RINGING); + Manager::instance().peerRingingCall(call); +} + +void +IAXVoIPLink::handleHangup(IAXCall& call) +{ + Manager::instance().peerHungupCall(call); + call.removeCall(); +} + +void +IAXVoIPLink::iaxHandleCallEvent(iax_event* event, IAXCall& call) +{ + switch (event->etype) { + case IAX_EVENT_HANGUP: + handleHangup(call); + break; + + case IAX_EVENT_REJECT: + handleReject(call); + break; + + case IAX_EVENT_ACCEPT: + handleAccept(event, call); + break; + + case IAX_EVENT_ANSWER: + case IAX_EVENT_TRANSFER: + handleAnswerTransfer(event, call); + break; + + case IAX_EVENT_BUSY: + handleBusy(call); + break; + + case IAX_EVENT_VOICE: + iaxHandleVoiceEvent(event, call); + break; + + case IAX_EVENT_TEXT: +#if HAVE_INSTANT_MESSAGING + handleMessage(event, call); +#endif + break; + + case IAX_EVENT_RINGA: + handleRinging(call); + break; + + case IAX_IE_MSGCOUNT: + case IAX_EVENT_TIMEOUT: + case IAX_EVENT_PONG: + default: + break; + + case IAX_EVENT_URL: + + if (Manager::instance().hookPreference.getIax2Enabled()) + UrlHook::runAction(Manager::instance().hookPreference.getUrlCommand(), (char*) event->data); + + break; + } +} + +/* Handle audio event, VOICE packet received */ +void +IAXVoIPLink::iaxHandleVoiceEvent(iax_event* event, IAXCall& call) +{ + // Skip this empty packet. + if (!event->datalen) + return; + + auto codec = account_.searchCodecByPayload(call.getAudioCodecPayload(), MEDIA_AUDIO); + auto accountAudioCodec = std::static_pointer_cast<AccountAudioCodecInfo>(codec); + if (!accountAudioCodec) + return; + + Manager::instance().getRingBufferPool().setInternalSamplingRate(accountAudioCodec->audioformat.sample_rate); + unsigned int mainBufferSampleRate = Manager::instance().getRingBufferPool().getInternalSamplingRate(); + + if (event->subclass) + call.format = event->subclass; + + unsigned int size = event->datalen; + unsigned int max = accountAudioCodec->audioformat.sample_rate * 20 / 1000; + + if (size > max) + size = max; + + /* + * TODO ebail : * + * IAX use old codec API (based on audiocodec wrapper) + * It does not use libav API + * We disable it for the moment + */ +#if 0 + unsigned char *data = (unsigned char*) event->data; + audioCodec->decode(rawBuffer_.getData(), data , size); +#endif + AudioBuffer *out = &rawBuffer_; + unsigned int audioRate = accountAudioCodec->audioformat.sample_rate; + + if (audioRate != mainBufferSampleRate) { + rawBuffer_.setSampleRate(mainBufferSampleRate); + resampledData_.setSampleRate(audioRate); + resampler_->resample(rawBuffer_, resampledData_); + out = &resampledData_; + } + + call.putAudioData(*out); +} + +/** + * Handle the registration process + */ +void +IAXVoIPLink::iaxHandleRegReply(iax_event* event) +{ + if (event->etype != IAX_EVENT_REGREJ && event->etype != IAX_EVENT_REGACK) + return; + + account_.destroyRegSession(); + account_.setRegistrationState((event->etype == IAX_EVENT_REGREJ) ? RegistrationState::ERROR_AUTH : RegistrationState::REGISTERED); + + if (event->etype == IAX_EVENT_REGACK) + account_.setNextRefreshStamp(time(NULL) + (event->ies.refresh ? event->ies.refresh : 60)); +} + +void IAXVoIPLink::iaxHandlePrecallEvent(iax_event* event) +{ + const auto accountID = account_.getAccountID(); + std::shared_ptr<IAXCall> call; + + switch (event->etype) { + case IAX_EVENT_CONNECT: + call = account_.newIncomingCall<IAXCall>(""); + if (!call) { + RING_ERR("failed to create an incoming IAXCall from account %s", + accountID.c_str()); + return; + } + + call->session = event->session; + call->setConnectionState(Call::PROGRESSING); + + if (event->ies.calling_number) + call->setPeerNumber(event->ies.calling_number); + + if (event->ies.calling_name) + call->setDisplayName(std::string(event->ies.calling_name)); + + // if peerNumber exist append it to the name string + if (event->ies.calling_number) + call->initRecFilename(std::string(event->ies.calling_number)); + + Manager::instance().incomingCall(*call, accountID); + + call->format = call->getFirstMatchingFormat(event->ies.format, accountID); + if (!call->format) + call->format = call->getFirstMatchingFormat(event->ies.capability, accountID); + + { + std::lock_guard<std::mutex> lock(mutexIAX); + iax_accept(event->session, call->format); + iax_ring_announce(event->session); + } + + break; + + case IAX_EVENT_HANGUP: + if (auto raw_call_ptr = iaxGetCallFromSession(event->session)) { + Manager::instance().peerHungupCall(*raw_call_ptr); + raw_call_ptr->removeCall(); + } + + break; + + case IAX_EVENT_TIMEOUT: // timeout for an unknown session + case IAX_IE_MSGCOUNT: + case IAX_EVENT_REGACK: + case IAX_EVENT_REGREJ: + case IAX_EVENT_REGREQ: + + // Received when someone wants to register to us!?! + // Asterisk receives and answers to that, not us, we're a phone. + default: + break; + } +} + +} // namespace ring diff --git a/src/iax/iaxvoiplink.h b/src/iax/iaxvoiplink.h new file mode 100644 index 0000000000..09078ed76f --- /dev/null +++ b/src/iax/iaxvoiplink.h @@ -0,0 +1,173 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Alexandre Bourget <alexandre.bourget@savoirfairelinux.com> + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * Author : Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#ifndef IAXVOIPLINK_H +#define IAXVOIPLINK_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "audio/audiobuffer.h" +#include "ring_types.h" + +#include <iax/iax-client.h> + +#include <mutex> +#include <memory> +#include <random> + +#define RAW_BUFFER_SIZE (120 * 48000 / 1000) + + +namespace ring { + +class IAXAccount; +class IAXCall; +class AudioCodec; +class AudioLayer; +class Resampler; + +/** + * @file iaxvoiplink.h + * @brief VoIPLink contains a thread that listen to external events + * and contains IAX Call related functions + */ + +class IAXVoIPLink { + public: + IAXVoIPLink(IAXAccount& account); + ~IAXVoIPLink(); + + /** + * Listen to events sent by the call manager ( asterisk, etc .. ) + */ + void handleEvents(); + + /** + * Init the voip link + */ + void init(std::mt19937_64& rand); + + /** + * Terminate a voip link by clearing the call list + */ + void terminate(); + + /** + * Cancel a call + * @param id The ID of the call + */ + void cancel(const std::string& /*id*/) {} + + /** Mutex for iax_ calls, since we're the only one dealing with the incorporated + * iax_stuff inside this class. */ + static std::mutex mutexIAX; + + private: + void handleAccept(iax_event* event, IAXCall& call); + void handleReject(IAXCall& call); + void handleRinging(IAXCall& call); + void handleAnswerTransfer(iax_event* event, IAXCall& call); + void handleBusy(IAXCall& call); +#if HAVE_INSTANT_MESSAGING + void handleMessage(iax_event* event, IAXCall& call); +#endif + void handleHangup(IAXCall& call); + + /* + * Decode the message count IAX send. + * Returns only the new messages number + * + * @param msgcount The value sent by IAX in the REGACK message + * @return int The number of new messages waiting for the current registered user + */ + int processIAXMsgCount(int msgcount); + + + /** + * Find a iaxcall by iax session number + * @param session an iax_session valid pointer + * @return iaxcall or 0 if not found + */ + std::string iaxFindCallIDBySession(struct iax_session* session); + + /** + * Handle IAX Event for a call + * @param event An iax_event pointer + * @param call An IAXCall pointer + */ + void iaxHandleCallEvent(iax_event* event, IAXCall& call); + + /** + * Handle the VOICE events specifically + * @param event The iax_event containing the IAX_EVENT_VOICE + * @param call The associated IAXCall + */ + void iaxHandleVoiceEvent(iax_event* event, IAXCall& call); + + /** + * Handle IAX Registration Reply event + * @param event An iax_event pointer + */ + void iaxHandleRegReply(iax_event* event); + + /** + * Handle IAX pre-call setup-related events + * @param event An iax_event pointer + */ + void iaxHandlePrecallEvent(iax_event* event); + + /** + * Work out the audio data from Microphone to IAX2 channel + */ + void sendAudioFromMic(); + + IAXAccount& account_; + + /** encoder/decoder/resampler buffers */ + AudioBuffer rawBuffer_{RAW_BUFFER_SIZE, AudioFormat::MONO()}; + AudioBuffer resampledData_{RAW_BUFFER_SIZE * 4, AudioFormat::MONO()}; + unsigned char encodedData_[RAW_BUFFER_SIZE] = {}; + + std::unique_ptr<Resampler> resampler_; + + /** Whether init() was called already or not + * This should be used in init() and terminate(), to + * indicate that init() was called, or reset by terminate(). + */ + bool initDone_{false}; +}; + +} // namespace ring + +#endif diff --git a/src/ice_socket.h b/src/ice_socket.h new file mode 100644 index 0000000000..bd47c2d28b --- /dev/null +++ b/src/ice_socket.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#ifndef ICE_SOCKET_H +#define ICE_SOCKET_H + +#include <memory> + +namespace ring { + +class IceTransport; + +class IceSocket +{ + private: + std::shared_ptr<IceTransport> ice_transport_ {}; + int compId_ = -1; + + public: + IceSocket(std::shared_ptr<IceTransport> iceTransport, int compId) + : ice_transport_(iceTransport), compId_(compId) {} + + void close(); + ssize_t recv(unsigned char* buf, size_t len); + ssize_t send(const unsigned char* buf, size_t len); + ssize_t getNextPacketSize() const; + ssize_t waitForData(unsigned int timeout); +}; + +}; + +#endif /* ICE_SOCKET_H */ diff --git a/src/ice_transport.cpp b/src/ice_transport.cpp new file mode 100644 index 0000000000..d0e9afbd7f --- /dev/null +++ b/src/ice_transport.cpp @@ -0,0 +1,887 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "ice_transport.h" +#include "ice_socket.h" +#include "logger.h" +#include "sip/sip_utils.h" +#include "manager.h" +#include "upnp/upnp_control.h" + +#include <pjlib.h> + +#include <utility> +#include <algorithm> +#include <sstream> +#include <chrono> +#include <thread> + +#define TRY(ret) do { \ + if ((ret) != PJ_SUCCESS) \ + throw std::runtime_error(#ret " failed"); \ + } while (0) + +namespace ring { + +static void +register_thread() +{ + // We have to register the external thread so it could access the pjsip frameworks + if (!pj_thread_is_registered()) { +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) + static thread_local pj_thread_desc desc; + static thread_local pj_thread_t *this_thread; +#else + static __thread pj_thread_desc desc; + static __thread pj_thread_t *this_thread; +#endif + pj_thread_register(NULL, desc, &this_thread); + RING_DBG("Registered thread %p (0x%X)", this_thread, pj_getpid()); + } +} + +IceTransport::Packet::Packet(void *pkt, pj_size_t size) + : data(new char[size]), datalen(size) +{ + std::copy_n(reinterpret_cast<char*>(pkt), size, data.get()); +} + +void +IceTransport::cb_on_rx_data(pj_ice_strans* ice_st, + unsigned comp_id, + void *pkt, pj_size_t size, + const pj_sockaddr_t* /*src_addr*/, + unsigned /*src_addr_len*/) +{ + if (auto tr = static_cast<IceTransport*>(pj_ice_strans_get_user_data(ice_st))) + tr->onReceiveData(comp_id, pkt, size); + else + RING_WARN("null IceTransport"); +} + +void +IceTransport::cb_on_ice_complete(pj_ice_strans* ice_st, + pj_ice_strans_op op, + pj_status_t status) +{ + if (auto tr = static_cast<IceTransport*>(pj_ice_strans_get_user_data(ice_st))) + tr->onComplete(ice_st, op, status); + else + RING_WARN("null IceTransport"); +} + +IceTransport::IceTransport(const char* name, int component_count, + bool master, bool upnp_enabled, + IceTransportCompleteCb on_initdone_cb, + IceTransportCompleteCb on_negodone_cb) + : pool_(nullptr, pj_pool_release) + , on_initdone_cb_(on_initdone_cb) + , on_negodone_cb_(on_negodone_cb) + , component_count_(component_count) + , compIO_(component_count) + , initiator_session_(master) +{ + if (upnp_enabled) + upnp_.reset(new upnp::Controller()); + + auto& iceTransportFactory = Manager::instance().getIceTransportFactory(); + + pool_.reset(pj_pool_create(iceTransportFactory.getPoolFactory(), + "IceTransport.pool", 512, 512, NULL)); + if (not pool_) + throw std::runtime_error("pj_pool_create() failed"); + + pj_ice_strans_cb icecb; + pj_bzero(&icecb, sizeof(icecb)); + icecb.on_rx_data = cb_on_rx_data; + icecb.on_ice_complete = cb_on_ice_complete; + + pj_ice_strans* icest = nullptr; + pj_status_t status = pj_ice_strans_create(name, + iceTransportFactory.getIceCfg(), + component_count, this, &icecb, + &icest); + if (status != PJ_SUCCESS || icest == nullptr) + throw std::runtime_error("pj_ice_strans_create() failed"); +} + +IceTransport::~IceTransport() +{ + icest_.reset(); // must be done first to invalid callbacks +} + +void +IceTransport::onComplete(pj_ice_strans* ice_st, pj_ice_strans_op op, + pj_status_t status) +{ + const char *opname = + op == PJ_ICE_STRANS_OP_INIT? "initialization" : + op == PJ_ICE_STRANS_OP_NEGOTIATION ? "negotiation" : "unknown_op"; + + if (!icest_.get()) + icest_.reset(ice_st); + + const bool done = status == PJ_SUCCESS; + RING_DBG("ICE %s with %s", opname, done?"success":"error"); + + if (!done) { + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(status, errmsg, sizeof(errmsg)); + RING_ERR("ICE %s failed: %s", opname, errmsg); + } + + { + std::lock_guard<std::mutex> lk(iceMutex_); + if (op == PJ_ICE_STRANS_OP_INIT) { + iceTransportInitDone_ = done; + if (iceTransportInitDone_) { + if (initiator_session_) + setInitiatorSession(); + else + setSlaveSession(); + selectUPnPIceCandidates(); + } + } else if (op == PJ_ICE_STRANS_OP_NEGOTIATION) { + iceTransportNegoDone_ = done; + } + } + + if (op == PJ_ICE_STRANS_OP_INIT and on_initdone_cb_) + on_initdone_cb_(*this, done); + else if (op == PJ_ICE_STRANS_OP_NEGOTIATION and on_negodone_cb_) + on_negodone_cb_(*this, done); + + iceCV_.notify_all(); +} + +void +IceTransport::getUFragPwd() +{ + pj_str_t local_ufrag, local_pwd; + pj_ice_strans_get_ufrag_pwd(icest_.get(), &local_ufrag, &local_pwd, NULL, NULL); + local_ufrag_.assign(local_ufrag.ptr, local_ufrag.slen); + local_pwd_.assign(local_pwd.ptr, local_pwd.slen); +} + +void +IceTransport::getDefaultCanditates() +{ + for (unsigned i=0; i < component_count_; ++i) + pj_ice_strans_get_def_cand(icest_.get(), i+1, &cand_[i]); +} + +bool +IceTransport::createIceSession(pj_ice_sess_role role) +{ + if (pj_ice_strans_init_ice(icest_.get(), role, NULL, NULL) != PJ_SUCCESS) { + RING_ERR("pj_ice_strans_init_ice() failed"); + return false; + } + + // Fetch some information on local configuration + getUFragPwd(); + getDefaultCanditates(); + RING_DBG("ICE [local] ufrag=%s, pwd=%s", local_ufrag_.c_str(), local_pwd_.c_str()); + return true; +} + +bool +IceTransport::setInitiatorSession() +{ + RING_DBG("ICE as master"); + initiator_session_ = true; + if (isInitialized()) { + auto status = pj_ice_strans_change_role(icest_.get(), PJ_ICE_SESS_ROLE_CONTROLLING); + if (status != PJ_SUCCESS) { + RING_ERR("ICE role change failed"); + sip_utils::sip_strerror(status); + return false; + } + return true; + } + return createIceSession(PJ_ICE_SESS_ROLE_CONTROLLING); +} + +bool +IceTransport::setSlaveSession() +{ + RING_DBG("ICE as slave"); + initiator_session_ = false; + if (isInitialized()) { + auto status = pj_ice_strans_change_role(icest_.get(), PJ_ICE_SESS_ROLE_CONTROLLED); + if (status != PJ_SUCCESS) { + RING_ERR("ICE role change failed"); + sip_utils::sip_strerror(status); + return false; + } + return true; + } + return createIceSession(PJ_ICE_SESS_ROLE_CONTROLLED); +} + +bool +IceTransport::isInitiator() const +{ + if (isInitialized()) + return pj_ice_strans_get_role(icest_.get()) == PJ_ICE_SESS_ROLE_CONTROLLING; + return initiator_session_; +} + +bool +IceTransport::start(const Attribute& rem_attrs, + const std::vector<IceCandidate>& rem_candidates) +{ + // pj_ice_strans_start_ice crashes if remote candidates array is empty + if (rem_candidates.empty()) { + RING_ERR("ICE start failed: no remote candidates"); + return false; + } + + pj_str_t ufrag, pwd; + RING_DBG("ICE negotiation starting (%u remote candidates)", rem_candidates.size()); + auto status = pj_ice_strans_start_ice(icest_.get(), + pj_cstr(&ufrag, rem_attrs.ufrag.c_str()), + pj_cstr(&pwd, rem_attrs.pwd.c_str()), + rem_candidates.size(), + rem_candidates.data()); + if (status != PJ_SUCCESS) { + RING_ERR("ICE start failed"); + sip_utils::sip_strerror(status); + return false; + } + return true; +} + +std::string +IceTransport::unpackLine(std::vector<uint8_t>::const_iterator& begin, + std::vector<uint8_t>::const_iterator& end) +{ + if (std::distance(begin, end) <= 0) + return {}; + + // Search for EOL + std::vector<uint8_t>::const_iterator line_end(begin); + while (line_end != end && *line_end != NEW_LINE && *line_end) + ++line_end; + + if (std::distance(begin, line_end) <= 0) + return {}; + + std::string str(begin, line_end); + + // Consume the new line character + if (std::distance(line_end, end) > 0) + ++line_end; + + begin = line_end; + return str; +} + +bool +IceTransport::start(const std::vector<uint8_t>& rem_data) +{ + auto begin = rem_data.cbegin(); + auto end = rem_data.cend(); + auto rem_ufrag = unpackLine(begin, end); + auto rem_pwd = unpackLine(begin, end); + if (rem_pwd.empty() or rem_pwd.empty()) { + RING_ERR("ICE remote attributes parsing error"); + return false; + } + std::vector<IceCandidate> rem_candidates; + try { + while (true) { + IceCandidate candidate; + const auto line = unpackLine(begin, end); + if (line.empty()) + break; + if (getCandidateFromSDP(line, candidate)) + rem_candidates.push_back(candidate); + } + } catch (std::exception& e) { + RING_ERR("ICE remote candidates parsing error"); + return false; + } + return start({rem_ufrag, rem_pwd}, rem_candidates); +} + +bool +IceTransport::stop() +{ + if (not pj_ice_strans_has_sess(icest_.get())) { + RING_ERR("Session not created yet"); + return false; + } + + auto status = pj_ice_strans_stop_ice(icest_.get()); + if (status != PJ_SUCCESS) { + RING_ERR("ICE start failed"); + sip_utils::sip_strerror(status); + return false; + } + + return true; +} + +bool +IceTransport::isInitialized() const +{ + return pj_ice_strans_has_sess(icest_.get()); +} + +bool +IceTransport::isStarted() const +{ + return pj_ice_strans_sess_is_running(icest_.get()); +} + +bool +IceTransport::isCompleted() const +{ + return pj_ice_strans_sess_is_complete(icest_.get()); +} + +bool +IceTransport::isRunning() const +{ + return pj_ice_strans_get_state(icest_.get()) == PJ_ICE_STRANS_STATE_RUNNING; +} + +bool +IceTransport::isFailed() const +{ + return pj_ice_strans_get_state(icest_.get()) == PJ_ICE_STRANS_STATE_FAILED; +} + +IpAddr +IceTransport::getLocalAddress(unsigned comp_id) const +{ + if (isInitialized()) + return cand_[comp_id].addr; + return {}; +} + +IpAddr +IceTransport::getRemoteAddress(unsigned comp_id) const +{ + if (auto sess = pj_ice_strans_get_valid_pair(icest_.get(), comp_id+1)) + return sess->rcand->addr; + return {}; +} + +const IceTransport::Attribute +IceTransport::getLocalAttributes() const +{ + return {local_ufrag_, local_pwd_}; +} + +std::vector<std::string> +IceTransport::getLocalCandidates(unsigned comp_id) const +{ + std::vector<std::string> res; + pj_ice_sess_cand cand[PJ_ARRAY_SIZE(cand_)]; + unsigned cand_cnt = PJ_ARRAY_SIZE(cand); + + if (pj_ice_strans_enum_cands(icest_.get(), comp_id+1, &cand_cnt, cand) != PJ_SUCCESS) { + RING_ERR("pj_ice_strans_enum_cands() failed"); + return res; + } + + for (unsigned i=0; i<cand_cnt; ++i) { + std::ostringstream val; + char ipaddr[PJ_INET6_ADDRSTRLEN]; + + val << std::string(cand[i].foundation.ptr, cand[i].foundation.slen); + val << " " << (unsigned)cand[i].comp_id << " UDP " << cand[i].prio; + val << " " << pj_sockaddr_print(&cand[i].addr, ipaddr, sizeof(ipaddr), 0); + val << " " << (unsigned)pj_sockaddr_get_port(&cand[i].addr); + val << " typ " << pj_ice_get_cand_type_name(cand[i].type); + + res.push_back(val.str()); + } + + return res; +} + +std::vector<IpAddr> +IceTransport::getLocalCandidatesAddr(unsigned comp_id) const +{ + std::vector<IpAddr> cand_addrs; + pj_ice_sess_cand cand[PJ_ARRAY_SIZE(cand_)]; + unsigned cand_cnt = PJ_ARRAY_SIZE(cand); + + if (pj_ice_strans_enum_cands(icest_.get(), comp_id, &cand_cnt, cand) != PJ_SUCCESS) { + RING_ERR("pj_ice_strans_enum_cands() failed"); + return cand_addrs; + } + + for (unsigned i=0; i<cand_cnt; ++i) + cand_addrs.push_back(cand[i].addr); + + return cand_addrs; +} + +void +IceTransport::addCandidate(int comp_id, const IpAddr& localAddr, + const IpAddr& publicAddr) +{ + pj_ice_sess_cand cand; + + cand.type = PJ_ICE_CAND_TYPE_SRFLX; + cand.status = PJ_EPENDING; /* not used */ + cand.comp_id = comp_id; + cand.transport_id = 1; /* 1 = STUN */ + cand.local_pref = 65535; /* host */ + /* cand.foundation = ? */ + /* cand.prio = calculated by ice session */ + /* make base and addr the same since we're not going through a server */ + pj_sockaddr_cp(&cand.base_addr, localAddr.pjPtr()); + pj_sockaddr_cp(&cand.addr, publicAddr.pjPtr()); + pj_sockaddr_cp(&cand.rel_addr, &cand.base_addr); + pj_ice_calc_foundation(pool_.get(), &cand.foundation, cand.type, &cand.base_addr); + + auto ret = pj_ice_sess_add_cand(pj_ice_strans_get_ice_sess(icest_.get()), + cand.comp_id, + cand.transport_id, + cand.type, + cand.local_pref, + &cand.foundation, + &cand.addr, + &cand.base_addr, + &cand.rel_addr, + pj_sockaddr_get_len(&cand.addr), + NULL); + + if (ret != PJ_SUCCESS) { + RING_ERR("fail to add candidate for comp_id=%d : %s : %s", comp_id + , localAddr.toString().c_str() + , publicAddr.toString().c_str()); + sip_utils::sip_strerror(ret); + } else { + RING_DBG("success to add candidate for comp_id=%d : %s : %s", comp_id + , localAddr.toString().c_str() + , publicAddr.toString().c_str()); + } +} + +void +IceTransport::selectUPnPIceCandidates() +{ + /* use upnp to open ports and add the proper candidates */ + if (upnp_) { + /* for every component, get the candidate(s) + * create a port mapping either with that port, or with an available port + * add candidate with that port and public IP + */ + if (auto publicIP = upnp_->getExternalIP()) { + /* comp_id start at 1 */ + for (unsigned comp_id = 1; comp_id <= component_count_; ++comp_id) { + RING_DBG("UPnP: Opening port(s) for ICE comp %d and adding candidate with public IP", + comp_id); + auto candidates = getLocalCandidatesAddr(comp_id); + for (IpAddr addr : candidates) { + auto localIP = upnp_->getLocalIP(); + localIP.setPort(addr.getPort()); + if (addr != localIP) + continue; + uint16_t port = addr.getPort(); + uint16_t port_used; + if (upnp_->addAnyMapping(port, upnp::PortType::UDP, true, &port_used)) { + publicIP.setPort(port_used); + addCandidate(comp_id, addr, publicIP); + } else + RING_WARN("UPnP: Could not create a port mapping for the ICE candide"); + } + } + } else { + RING_WARN("UPnP: Could not determine public IP for ICE candidates"); + } + } +} + +std::vector<uint8_t> +IceTransport::getLocalAttributesAndCandidates() const +{ + std::stringstream ss; + ss << local_ufrag_ << NEW_LINE; + ss << local_pwd_ << NEW_LINE; + for (unsigned i=0; i<component_count_; i++) { + const auto& candidates = getLocalCandidates(i); + for (const auto& c : candidates) + ss << c << NEW_LINE; + } + auto str(ss.str()); + std::vector<uint8_t> ret(str.begin(), str.end()); + return ret; +} + +void +IceTransport::onReceiveData(unsigned comp_id, void *pkt, pj_size_t size) +{ + if (!comp_id or comp_id > component_count_) { + RING_ERR("rx: invalid comp_id (%u)", comp_id); + return; + } + if (!size) + return; + auto& io = compIO_[comp_id-1]; + std::unique_lock<std::mutex> lk(io.mutex); + if (io.cb) { + io.cb((uint8_t*)pkt, size); + } else { + io.queue.emplace_back(pkt, size); + io.cv.notify_one(); + } +} + +bool +IceTransport::getCandidateFromSDP(const std::string& line, IceCandidate& cand) +{ + char foundation[33], transport[13], ipaddr[81], type[33]; + pj_str_t tmpaddr; + int af, comp_id, prio, port; + int cnt = sscanf(line.c_str(), "%32s %d %12s %d %80s %d typ %32s", + foundation, + &comp_id, + transport, + &prio, + ipaddr, + &port, + type); + + if (cnt != 7) { + RING_WARN("ICE: invalid remote candidate line"); + return false; + } + + pj_bzero(&cand, sizeof(IceCandidate)); + + if (strcmp(type, "host")==0) + cand.type = PJ_ICE_CAND_TYPE_HOST; + else if (strcmp(type, "srflx")==0) + cand.type = PJ_ICE_CAND_TYPE_SRFLX; + else if (strcmp(type, "relay")==0) + cand.type = PJ_ICE_CAND_TYPE_RELAYED; + else { + RING_WARN("ICE: invalid remote candidate type '%s'", type); + return false; + } + + cand.comp_id = (pj_uint8_t)comp_id; + cand.prio = prio; + + if (strchr(ipaddr, ':')) + af = pj_AF_INET6(); + else + af = pj_AF_INET(); + + tmpaddr = pj_str(ipaddr); + pj_sockaddr_init(af, &cand.addr, NULL, 0); + auto status = pj_sockaddr_set_str_addr(af, &cand.addr, &tmpaddr); + if (status != PJ_SUCCESS) { + RING_ERR("ICE: invalid remote IP address '%s'", ipaddr); + return false; + } + + pj_sockaddr_set_port(&cand.addr, (pj_uint16_t)port); + pj_strdup2(pool_.get(), &cand.foundation, foundation); + + return true; +} + +ssize_t +IceTransport::recv(int comp_id, unsigned char* buf, size_t len) +{ + register_thread(); + auto& io = compIO_[comp_id]; + std::unique_lock<std::mutex> lk(io.mutex); + + if (io.queue.empty()) + return 0; + + auto& packet = io.queue.front(); + const auto count = std::min(len, packet.datalen); + std::copy_n(packet.data.get(), count, buf); + io.queue.pop_front(); + + return count; +} + +void +IceTransport::setOnRecv(unsigned comp_id, IceRecvCb cb) +{ + auto& io = compIO_[comp_id]; + std::unique_lock<std::mutex> lk(io.mutex); + io.cb = cb; + + if (cb) { + // Flush existing queue using the callback + for (const auto& packet : io.queue) + io.cb((uint8_t*)packet.data.get(), packet.datalen); + io.queue.clear(); + } +} + +ssize_t +IceTransport::send(int comp_id, const unsigned char* buf, size_t len) +{ + register_thread(); + auto remote = getRemoteAddress(comp_id); + if (!remote) { + RING_ERR("Can't find remote address for component %d", comp_id); + return -1; + } + auto status = pj_ice_strans_sendto(icest_.get(), comp_id+1, buf, len, remote.pjPtr(), remote.getLength()); + if (status != PJ_SUCCESS) { + sip_utils::sip_strerror(status); + RING_ERR("send failed"); + return -1; + } + return len; +} + +ssize_t +IceTransport::getNextPacketSize(int comp_id) +{ + auto& io = compIO_[comp_id]; + std::unique_lock<std::mutex> lk(io.mutex); + if (io.queue.empty()) { + return 0; + } + return io.queue.front().datalen; +} + +int +IceTransport::waitForInitialization(unsigned timeout) +{ + std::unique_lock<std::mutex> lk(iceMutex_); + if (!iceCV_.wait_for(lk, std::chrono::seconds(timeout), + [this]{ return iceTransportInitDone_; })) { + RING_WARN("waitForInitialization: timeout"); + return -1; + } + RING_DBG("waitForInitialization: %u", iceTransportInitDone_); + return iceTransportInitDone_; +} + +int +IceTransport::waitForNegotiation(unsigned timeout) +{ + std::unique_lock<std::mutex> lk(iceMutex_); + if (!iceCV_.wait_for(lk, std::chrono::seconds(timeout), + [this]{ return iceTransportNegoDone_; })) { + RING_WARN("waitForIceNegotiation: timeout"); + return -1; + } + RING_DBG("waitForNegotiation: %u", iceTransportNegoDone_); + return iceTransportNegoDone_; +} + +ssize_t +IceTransport::waitForData(int comp_id, unsigned int timeout) +{ + auto& io = compIO_[comp_id]; + std::unique_lock<std::mutex> lk(io.mutex); + if (!io.cv.wait_for(lk, std::chrono::milliseconds(timeout), + [&io]{ return !io.queue.empty(); })) { + return 0; + } + return io.queue.front().datalen; +} + +// TODO: C++14 ? remove me and use std::min +template< class T > +static constexpr const T& min( const T& a, const T& b ) { + return (b < a) ? b : a; +} + +IceTransportFactory::IceTransportFactory() + : cp_() + , pool_(nullptr, pj_pool_release) + , thread_() + , ice_cfg_() +{ + pj_caching_pool_init(&cp_, NULL, 0); + pool_.reset(pj_pool_create(&cp_.factory, "IceTransportFactory.pool", + 512, 512, NULL)); + if (not pool_) + throw std::runtime_error("pj_pool_create() failed"); + + pj_ice_strans_cfg_default(&ice_cfg_); + ice_cfg_.stun_cfg.pf = &cp_.factory; + + static constexpr auto IOQUEUE_MAX_HANDLES = min(PJ_IOQUEUE_MAX_HANDLES, 64); + TRY( pj_timer_heap_create(pool_.get(), 100, &ice_cfg_.stun_cfg.timer_heap) ); + TRY( pj_ioqueue_create(pool_.get(), IOQUEUE_MAX_HANDLES, &ice_cfg_.stun_cfg.ioqueue) ); + + thread_ = std::thread(std::bind(&IceTransportFactory::processThread, this)); + + ice_cfg_.af = pj_AF_INET(); + + ice_cfg_.stun.cfg.max_pkt_size = 8192; + ice_cfg_.turn.cfg.max_pkt_size = 8192; + //ice_cfg_.stun.max_host_cands = icedemo.opt.max_host; + ice_cfg_.opt.aggressive = PJ_FALSE; + + // TODO: STUN server candidate + + // TODO: TURN server candidate +} + +IceTransportFactory::~IceTransportFactory() +{ + thread_quit_flag_ = PJ_TRUE; + if (thread_.joinable()) + thread_.join(); + + if (ice_cfg_.stun_cfg.ioqueue) + pj_ioqueue_destroy(ice_cfg_.stun_cfg.ioqueue); + + if (ice_cfg_.stun_cfg.timer_heap) + pj_timer_heap_destroy(ice_cfg_.stun_cfg.timer_heap); + + pool_.reset(); + pj_caching_pool_destroy(&cp_); +} + +int +IceTransportFactory::processThread() +{ + register_thread(); + while (!thread_quit_flag_) { + handleEvents(500, NULL); + } + + return 0; +} + +int +IceTransportFactory::handleEvents(unsigned max_msec, unsigned *p_count) +{ + enum { MAX_NET_EVENTS = 1 }; + pj_time_val max_timeout = {0, 0}; + pj_time_val timeout = {0, 0}; + unsigned count = 0, net_event_count = 0; + int c; + + max_timeout.msec = max_msec; + + timeout.sec = timeout.msec = 0; + c = pj_timer_heap_poll(ice_cfg_.stun_cfg.timer_heap, &timeout); + if (c > 0) + count += c; + + pj_assert(timeout.sec >= 0 && timeout.msec >= 0); + if (timeout.msec >= 1000) timeout.msec = 999; + + if (PJ_TIME_VAL_GT(timeout, max_timeout)) + timeout = max_timeout; + + do { + c = pj_ioqueue_poll(ice_cfg_.stun_cfg.ioqueue, &timeout); + if (c < 0) { + pj_status_t err = pj_get_netos_error(); + std::this_thread::sleep_for(std::chrono::milliseconds(PJ_TIME_VAL_MSEC(timeout))); + if (p_count) + *p_count = count; + return err; + } else if (c == 0) { + break; + } else { + net_event_count += c; + timeout.sec = timeout.msec = 0; + } + } while (c > 0 && net_event_count < MAX_NET_EVENTS); + + count += net_event_count; + if (p_count) + *p_count = count; + + return PJ_SUCCESS; +} + + +std::shared_ptr<IceTransport> +IceTransportFactory::createTransport(const char* name, + int component_count, + bool master, + bool upnp_enabled, + IceTransportCompleteCb&& on_initdone_cb, + IceTransportCompleteCb&& on_negodone_cb) +{ + try { + return std::make_shared<IceTransport>(name, component_count, master, upnp_enabled, + std::forward<IceTransportCompleteCb>(on_initdone_cb), + std::forward<IceTransportCompleteCb>(on_negodone_cb)); + } catch(const std::exception& e) { + RING_ERR("%s",e.what()); + return nullptr; + } +} + +void +IceSocket::close() +{ + ice_transport_.reset(); +} + +ssize_t +IceSocket::recv(unsigned char* buf, size_t len) +{ + if (!ice_transport_.get()) + return -1; + return ice_transport_->recv(compId_, buf, len); +} + +ssize_t +IceSocket::send(const unsigned char* buf, size_t len) +{ + if (!ice_transport_.get()) + return -1; + return ice_transport_->send(compId_, buf, len); +} + +ssize_t +IceSocket::getNextPacketSize() const +{ + if (!ice_transport_.get()) + return -1; + return ice_transport_->getNextPacketSize(compId_); +} + +ssize_t +IceSocket::waitForData(unsigned int timeout) +{ + if (!ice_transport_.get()) + return -1; + + return ice_transport_->waitForData(compId_, timeout); +} + +} // namespace ring diff --git a/src/ice_transport.h b/src/ice_transport.h new file mode 100644 index 0000000000..04ab579456 --- /dev/null +++ b/src/ice_transport.h @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef ICE_TRANSPORT_H +#define ICE_TRANSPORT_H + +#include "ice_socket.h" +#include "ip_utils.h" + +#include <pjnath.h> +#include <pjlib.h> +#include <pjlib-util.h> + +#include <map> +#include <functional> +#include <memory> +#include <atomic> +#include <vector> +#include <queue> +#include <mutex> +#include <condition_variable> +#include <thread> + +namespace ring { + +namespace upnp { +class Controller; +} + +class IceTransport; + +using IceTransportCompleteCb = std::function<void(IceTransport&, bool)>; +using IceRecvCb = std::function<ssize_t(unsigned char* buf, size_t len)>; +using IceCandidate = pj_ice_sess_cand; + +class IceTransport { + public: + using Attribute = struct { + std::string ufrag; + std::string pwd; + }; + + /** + * Constructor + */ + IceTransport(const char* name, int component_count, + bool master, + bool upnp_enabled = false, + IceTransportCompleteCb on_initdone_cb={}, + IceTransportCompleteCb on_negodone_cb={}); + + /** + * Destructor + */ + ~IceTransport(); + + /** + * Set/change transport role as initiator. + * Should be called before start method. + */ + bool setInitiatorSession(); + + /** + * Set/change transport role as slave. + * Should be called before start method. + */ + bool setSlaveSession(); + + /** + * Get current state + */ + bool isInitiator() const; + + /** + * Start tranport negociation between local candidates and given remote + * to find the right candidate pair. + * This function doesn't block, the callback on_negodone_cb will be called + * with the negotiation result when operation is really done. + * Return false if negotiation cannot be started else true. + */ + bool start(const Attribute& rem_attrs, + const std::vector<IceCandidate>& rem_candidates); + bool start(const std::vector<uint8_t>& attrs_candidates); + + /** + * Stop a started or completed transport. + */ + bool stop(); + + bool isInitialized() const; + + bool isStarted() const; + + bool isCompleted() const; + + bool isRunning() const; + bool isFailed() const; + + IpAddr getLocalAddress(unsigned comp_id) const; + + IpAddr getRemoteAddress(unsigned comp_id) const; + + IpAddr getDefaultLocalAddress() const { + return getLocalAddress(0); + } + + /** + * Return ICE session attributes + */ + const Attribute getLocalAttributes() const; + + /** + * Return ICE session attributes + */ + std::vector<std::string> getLocalCandidates(unsigned comp_id) const; + + /** + * Returns serialized ICE attributes and candidates. + */ + std::vector<uint8_t> getLocalAttributesAndCandidates() const; + + bool getCandidateFromSDP(const std::string& line, IceCandidate& cand); + + // I/O methods + + void setOnRecv(unsigned comp_id, IceRecvCb cb); + + ssize_t recv(int comp_id, unsigned char* buf, size_t len); + + ssize_t send(int comp_id, const unsigned char* buf, size_t len); + + ssize_t getNextPacketSize(int comp_id); + + int waitForInitialization(unsigned timeout); + + int waitForNegotiation(unsigned timeout); + + ssize_t waitForData(int comp_id, unsigned int timeout); + + unsigned getComponentCount() const {return component_count_;}; + + private: + static constexpr int MAX_CANDIDATES {32}; + + // New line character used for (de)serialisation + static constexpr char NEW_LINE = '\n'; + + static void cb_on_rx_data(pj_ice_strans *ice_st, + unsigned comp_id, + void *pkt, pj_size_t size, + const pj_sockaddr_t *src_addr, + unsigned src_addr_len); + + static void cb_on_ice_complete(pj_ice_strans *ice_st, + pj_ice_strans_op op, + pj_status_t status); + + static std::string unpackLine(std::vector<uint8_t>::const_iterator& begin, + std::vector<uint8_t>::const_iterator& end); + + struct IceSTransDeleter { + void operator ()(pj_ice_strans* ptr) { + pj_ice_strans_stop_ice(ptr); + pj_ice_strans_destroy(ptr); + } + }; + + void onComplete(pj_ice_strans* ice_st, pj_ice_strans_op op, + pj_status_t status); + + void onReceiveData(unsigned comp_id, void *pkt, pj_size_t size); + + bool createIceSession(pj_ice_sess_role role); + + void getUFragPwd(); + + void getDefaultCanditates(); + + std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&> pool_; + IceTransportCompleteCb on_initdone_cb_; + IceTransportCompleteCb on_negodone_cb_; + bool iceTransportInitDone_ {false}; + bool iceTransportNegoDone_ {false}; + std::unique_ptr<pj_ice_strans, IceSTransDeleter> icest_; + unsigned component_count_; + pj_ice_sess_cand cand_[MAX_CANDIDATES] {}; + std::string local_ufrag_; + std::string local_pwd_; + pj_sockaddr remoteAddr_; + std::condition_variable iceCV_ {}; + mutable std::mutex iceMutex_ {}; + + struct Packet { + Packet(void *pkt, pj_size_t size); + std::unique_ptr<char> data; + size_t datalen; + }; + struct ComponentIO { + std::mutex mutex; + std::condition_variable cv; + std::deque<Packet> queue; + IceRecvCb cb; + }; + std::vector<ComponentIO> compIO_; + + bool initiator_session_ {true}; + + /** + * Returns the IP of each candidate for a given component in the ICE session + */ + std::vector<IpAddr> getLocalCandidatesAddr(unsigned comp_id) const; + + /** + * Adds candidate to ICE session + */ + void addCandidate(int comp_id, const IpAddr& localAddr, + const IpAddr& publicAddr); + + /** + * Creates UPnP port mappings and adds ICE candidates based on those mappings + */ + void selectUPnPIceCandidates(); + + std::unique_ptr<upnp::Controller> upnp_; +}; + +class IceTransportFactory { + public: + IceTransportFactory(); + ~IceTransportFactory(); + + std::shared_ptr<IceTransport> createTransport(const char* name, + int component_count, + bool master, + bool upnp_enabled = false, + IceTransportCompleteCb&& on_initdone_cb={}, + IceTransportCompleteCb&& on_negodone_cb={}); + + int processThread(); + + /** + * PJSIP specifics + */ + const pj_ice_strans_cfg* getIceCfg() const { return &ice_cfg_; } + pj_pool_factory* getPoolFactory() { return &cp_.factory; } + + private: + int handleEvents(unsigned max_msec, unsigned *p_count); + + pj_caching_pool cp_; + std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&> pool_; + std::thread thread_; + pj_ice_strans_cfg ice_cfg_; + pj_bool_t thread_quit_flag_ {PJ_FALSE}; +}; + +}; + +#endif /* ICE_TRANSPORT_H */ diff --git a/src/im/Makefile.am b/src/im/Makefile.am new file mode 100644 index 0000000000..d3315a25f7 --- /dev/null +++ b/src/im/Makefile.am @@ -0,0 +1,5 @@ +include $(top_srcdir)/globals.mak + +noinst_LTLIBRARIES = libim.la + +libim_la_SOURCES = instant_messaging.cpp instant_messaging.h diff --git a/src/im/instant_messaging.cpp b/src/im/instant_messaging.cpp new file mode 100644 index 0000000000..bda8768170 --- /dev/null +++ b/src/im/instant_messaging.cpp @@ -0,0 +1,215 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "instant_messaging.h" +#include "logger.h" +#include <expat.h> + +namespace ring { + +static void XMLCALL +startElementCallback(void *userData, const char *name, const char **atts) +{ + if (strcmp(name, "entry")) + return; + + InstantMessaging::UriEntry entry = InstantMessaging::UriEntry(); + + for (const char **att = atts; *att; att += 2) + entry.insert(std::pair<std::string, std::string> (*att, *(att+1))); + + static_cast<InstantMessaging::UriList *>(userData)->push_back(entry); +} + +static void XMLCALL +endElementCallback(void * /*userData*/, const char * /*name*/) +{} + +bool InstantMessaging::saveMessage(const std::string &message, const std::string &author, const std::string &id, int mode) +{ + std::ofstream File; + std::string filename = "im:" + id; + File.open(filename.c_str(), static_cast<std::ios_base::openmode>(mode)); + + if (!File.good() || !File.is_open()) + return false; + + File << "[" << author << "] " << message << '\n'; + File.close(); + + return true; +} + +void InstantMessaging::sip_send(pjsip_inv_session *session, const std::string& id, const std::string& text) +{ + pjsip_tx_data *tdata; + + pjsip_dialog* dialog = session->dlg; + + pjsip_dlg_inc_lock(dialog); + + pjsip_method msg_method = { PJSIP_OTHER_METHOD, pj_str((char*)"MESSAGE") }; + + if (pjsip_dlg_create_request(dialog, &msg_method, -1, &tdata) != PJ_SUCCESS) { + pjsip_dlg_dec_lock(dialog); + return; + } + + const pj_str_t type = pj_str((char*) "text"); + const pj_str_t subtype = pj_str((char*) "plain"); + + pj_str_t message = pj_str((char*) text.c_str()); + + tdata->msg->body = pjsip_msg_body_create(tdata->pool, &type, &subtype, &message); + + pjsip_dlg_send_request(dialog, tdata, -1, NULL); + pjsip_dlg_dec_lock(dialog); + + saveMessage(text, "Me", id); +} + +void InstantMessaging::send_sip_message(pjsip_inv_session *session, const std::string &id, const std::string &message) +{ + std::vector<std::string> msgs(split_message(message)); + for (const auto &item : msgs) + sip_send(session, id, item); +} + +#if HAVE_IAX +void InstantMessaging::send_iax_message(iax_session *session, const std::string &/* id */, const std::string &message) +{ + std::vector<std::string> msgs(split_message(message)); + + for (const auto &item : msgs) + iax_send_text(session, item.c_str()); +} +#endif + + +std::vector<std::string> InstantMessaging::split_message(std::string text) +{ + std::vector<std::string> messages; + size_t len = MAXIMUM_MESSAGE_LENGTH; + + while (text.length() > len - 2) { + messages.push_back(text.substr(len - 2) + "\n\n"); + text = text.substr(len - 2); + } + + messages.push_back(text); + + return messages; +} + +std::string InstantMessaging::generateXmlUriList(UriList &list) +{ + std::string xmlbuffer = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" + "<resource-lists xmlns=\"urn:ietf:params:xml:ns:resource-lists\" xmlns:cp=\"urn:ietf:params:xml:ns:copycontrol\">" + "<list>"; + + for (auto &item : list) + xmlbuffer += "<entry uri=" + item[IM_XML_URI] + " cp:copyControl=\"to\" />"; + + return xmlbuffer + "</list></resource-lists>"; +} + + +InstantMessaging::UriList +InstantMessaging::parseXmlUriList(const std::string &urilist) +{ + InstantMessaging::UriList list; + + XML_Parser parser = XML_ParserCreate(NULL); + XML_SetUserData(parser, &list); + XML_SetElementHandler(parser, startElementCallback, endElementCallback); + + if (XML_Parse(parser, urilist.c_str(), urilist.size(), 1) == XML_STATUS_ERROR) { + RING_ERR("%s at line %lu\n", XML_ErrorString(XML_GetErrorCode(parser)), + XML_GetCurrentLineNumber(parser)); + throw InstantMessageException("Error while parsing uri-list xml content"); + } + + return list; +} + +std::string InstantMessaging::appendUriList(const std::string &text, UriList& list) +{ + return "--boundary Content-Type: text/plain" + text + + "--boundary Content-Type: application/resource-lists+xml" + + "Content-Disposition: recipient-list" + generateXmlUriList(list) + + "--boundary--"; +} + +std::string InstantMessaging::findTextUriList(const std::string &text) +{ + const std::string ctype("Content-Type: application/resource-lists+xml"); + const std::string cdispo("Content-Disposition: recipient-list"); + const std::string boundary("--boundary--"); + + // init position pointer + size_t pos = 0; + + // find the content type + if ((pos = text.find(ctype)) == std::string::npos) + throw InstantMessageException("Could not find Content-Type tag while parsing sip message for recipient-list"); + + // find the content disposition + if ((pos = text.find(cdispo, pos)) == std::string::npos) + throw InstantMessageException("Could not find Content-Disposition tag while parsing sip message for recipient-list"); + + // xml content start after content disposition tag (plus \n\n) + const size_t begin = pos + cdispo.size(); + + // find final boundary + size_t end; + if ((end = text.find(boundary, begin)) == std::string::npos) + throw InstantMessageException("Could not find final \"boundary\" while parsing sip message for recipient-list"); + + return text.substr(begin, end - begin); +} + +std::string InstantMessaging::findTextMessage(const std::string &text) +{ + std::string ctype = "Content-Type: text/plain"; + const size_t pos = text.find(ctype); + if (pos == std::string::npos) + throw InstantMessageException("Could not find Content-Type tag while parsing sip message for text"); + + const size_t begin = pos + ctype.size(); + + const size_t end = text.find("--boundary", begin); + if (end == std::string::npos) + throw InstantMessageException("Could not find end of text \"boundary\" while parsing sip message for text"); + + return text.substr(begin, end - begin); +} + +} // namespace ring diff --git a/src/im/instant_messaging.h b/src/im/instant_messaging.h new file mode 100644 index 0000000000..415857c528 --- /dev/null +++ b/src/im/instant_messaging.h @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __INSTANT_MESSAGING_H__ +#define __INSTANT_MESSAGING_H__ + +#include <string> +#include <iostream> +#include <vector> +#include <fstream> +#include <pjsip.h> +#include <pjlib.h> +#include <pjsip_ua.h> +#include <pjlib-util.h> + +#include <map> +#include <list> +#include <stdexcept> + +#include "config.h" + +#if HAVE_IAX +#include <iax/iax-client.h> +#endif + +#define EMPTY_MESSAGE pj_str((char*)"") +#define MAXIMUM_MESSAGE_LENGTH 1560 /* PJSIP's sip message limit */ + +#define MODE_APPEND std::ios::out || std::ios::app +#define MODE_TEST std::ios::out + +namespace ring { namespace InstantMessaging { + +const std::string IM_XML_URI("uri"); +const std::string BOUNDARY("--boundary"); + +class InstantMessageException : public std::runtime_error { + public: + InstantMessageException(const std::string& str="") : + std::runtime_error("InstantMessageException occured: " + str) {} +}; + +typedef std::map<std::string, std::string> UriEntry; +typedef std::list<UriEntry> UriList; + +/* + * Write the text message to the right file + * The call ID is associated to a file descriptor, so it is easy then to retrieve the right file + * + * @param message The text message + * @param id The current call + * @return True if the message could have been successfully saved, False otherwise + */ +bool saveMessage(const std::string& message, const std::string& author, const std::string& id, int mode = MODE_APPEND); + +/* + * Send a SIP string message inside a call + * + * @param id The call ID we will retrieve the invite session from + * @param message The string message, as sent by the client + */ +void sip_send(pjsip_inv_session*, const std::string& id, const std::string&); + +void send_sip_message(pjsip_inv_session*, const std::string& id, const std::string&); +#if HAVE_IAX +void send_iax_message(iax_session *session, const std::string& id, const std::string&); +#endif + +std::vector<std::string> split_message(std::string); + +/** + * Generate Xml participant list for multi recipient based on RFC Draft 5365 + * + * @param A UriList of UriEntry + * + * @return A string containing the full XML formated information to be included in the + * sip instant message. + */ +std::string generateXmlUriList(UriList &list); + +/** + * Parse the Urilist from a SIP Instant Message provided by a UriList service. + * + * @param A XML formated string as obtained from a SIP instant message. + * + * @return An UriList of UriEntry containing parsed XML information as a map. + */ +UriList parseXmlUriList(const std::string &urilist); + +/** + * Format text message according to RFC 5365, append recipient-list to the message + * + * @param text to be displayed + * @param list containing the recipients + * + * @return formated text stored into a string to be included in sip MESSAGE + */ +std::string appendUriList(const std::string &text, UriList &list); + +/** + * Retreive the xml formated uri list in formated text data according to RFC 5365 + * + * @param text The formated text message as retreived in the SIP message + * + * @return A string containing the XML content + */ +std::string findTextUriList(const std::string &text); + +/** + * Retrive the plain text message in formated text data according to RFC 5365 + * + * @param text The formated text message as retreived in the SIP message + * + * @return A string containing the actual message + */ +std::string findTextMessage(const std::string &text); + +}} // namespace ring::InstantMessaging + +#endif // __INSTANT_MESSAGING_H_ diff --git a/src/intrin.h b/src/intrin.h new file mode 100644 index 0000000000..1757794a50 --- /dev/null +++ b/src/intrin.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * Author: Laurielle Lea <laurielle.lea@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef INTRIN_H_ +#define INTRIN_H_ + +#define UNUSED __attribute__((__unused__)) + +#endif // INTRIN_H_ diff --git a/src/ip_utils.cpp b/src/ip_utils.cpp new file mode 100644 index 0000000000..422f67a457 --- /dev/null +++ b/src/ip_utils.cpp @@ -0,0 +1,314 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "ip_utils.h" +#include "logger.h" + +#include "sip/sip_utils.h" + +#include <arpa/inet.h> +#include <arpa/nameser.h> +#include <resolv.h> + +#include <netdb.h> +#include <net/if.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <unistd.h> + +namespace ring { + +std::vector<IpAddr> +ip_utils::getAddrList(const std::string &name, pj_uint16_t family) +{ + std::vector<IpAddr> ipList; + if (name.empty()) + return ipList; + if (IpAddr::isValid(name, family)) { + ipList.push_back(name); + return ipList; + } + + static const unsigned MAX_ADDR_NUM = 128; + pj_addrinfo res[MAX_ADDR_NUM]; + unsigned addr_num = MAX_ADDR_NUM; + pj_str_t pjname; + pj_cstr(&pjname, name.c_str()); + auto status = pj_getaddrinfo(family, &pjname, &addr_num, res); + if (status != PJ_SUCCESS) { + RING_ERR("Error resolving %s :", name.c_str()); + sip_utils::sip_strerror(status); + return ipList; + } + + for (unsigned i=0; i<addr_num; i++) { + bool found = false; + for (const auto& ip : ipList) + if (!pj_sockaddr_cmp(&ip, &res[i].ai_addr)) { + found = true; + break; + } + if (!found) + ipList.push_back(res[i].ai_addr); + } + + return ipList; +} + +bool +ip_utils::haveCommonAddr(const std::vector<IpAddr>& a, const std::vector<IpAddr>& b) +{ + for (const auto &i : a) { + for (const auto &j : b) { + if (i == j) return true; + } + } + return false; +} + +IpAddr +ip_utils::getAnyHostAddr(pj_uint16_t family) +{ + if (family == pj_AF_UNSPEC()) { +#if HAVE_IPV6 + family = pj_AF_INET6(); +#else + family = pj_AF_INET(); +#endif + } + return IpAddr(family); +} + +IpAddr +ip_utils::getLocalAddr(pj_uint16_t family) +{ + if (family == pj_AF_UNSPEC()) { +#if HAVE_IPV6 + family = pj_AF_INET6(); +#else + family = pj_AF_INET(); +#endif + } + IpAddr ip_addr = {}; + pj_status_t status = pj_gethostip(family, ip_addr.pjPtr()); + if (status == PJ_SUCCESS) { + return ip_addr; + } +#if HAVE_IPV6 + RING_WARN("Could not get preferred address familly (%s)", (family == pj_AF_INET6()) ? "IPv6" : "IPv4"); + family = (family == pj_AF_INET()) ? pj_AF_INET6() : pj_AF_INET(); + status = pj_gethostip(family, ip_addr); + if (status == PJ_SUCCESS) return ip_addr; +#endif + RING_ERR("Could not get local IP"); + return ip_addr; +} + +IpAddr +ip_utils::getInterfaceAddr(const std::string &interface, pj_uint16_t family) +{ + if (interface == DEFAULT_INTERFACE) + return getLocalAddr(family); + + const auto unix_family = family == pj_AF_INET() ? AF_INET : AF_INET6; + IpAddr addr = {}; + + int fd = socket(unix_family, SOCK_DGRAM, 0); + if (fd < 0) { + RING_ERR("Could not open socket: %m"); + return addr; + } + + if (unix_family == AF_INET6) { + int val = family != pj_AF_UNSPEC(); + if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (void *) &val, sizeof(val)) < 0) { + RING_ERR("Could not setsockopt: %m"); + close(fd); + return addr; + } + } + + ifreq ifr; + strncpy(ifr.ifr_name, interface.c_str(), sizeof ifr.ifr_name); + // guarantee that ifr_name is NULL-terminated + ifr.ifr_name[sizeof(ifr.ifr_name) - 1] = '\0'; + + memset(&ifr.ifr_addr, 0, sizeof(ifr.ifr_addr)); + ifr.ifr_addr.sa_family = unix_family; + + ioctl(fd, SIOCGIFADDR, &ifr); + close(fd); + + addr = ifr.ifr_addr; + if (addr.isUnspecified()) + return getLocalAddr(addr.getFamily()); + + return addr; +} + +std::vector<std::string> +ip_utils::getAllIpInterfaceByName() +{ + static ifreq ifreqs[20]; + ifconf ifconf; + + std::vector<std::string> ifaceList; + ifaceList.push_back("default"); + + ifconf.ifc_buf = (char*) (ifreqs); + ifconf.ifc_len = sizeof(ifreqs); + + int sock = socket(AF_INET6, SOCK_STREAM, 0); + + if (sock >= 0) { + if (ioctl(sock, SIOCGIFCONF, &ifconf) >= 0) + for (unsigned i = 0; i < ifconf.ifc_len / sizeof(ifreq); ++i) + ifaceList.push_back(std::string(ifreqs[i].ifr_name)); + + close(sock); + } + + return ifaceList; +} + +std::vector<std::string> +ip_utils::getAllIpInterface() +{ + pj_sockaddr addrList[16]; + unsigned addrCnt = PJ_ARRAY_SIZE(addrList); + + std::vector<std::string> ifaceList; + + if (pj_enum_ip_interface(pj_AF_UNSPEC(), &addrCnt, addrList) == PJ_SUCCESS) { + for (unsigned i = 0; i < addrCnt; i++) { + char addr[PJ_INET6_ADDRSTRLEN]; + pj_sockaddr_print(&addrList[i], addr, sizeof(addr), 0); + ifaceList.push_back(std::string(addr)); + } + } + + return ifaceList; +} + +std::vector<IpAddr> +ip_utils::getLocalNameservers() +{ + std::vector<IpAddr> res; +#ifdef __ANDROID__ +#warning "Not implemented" +#else + if (not (_res.options & RES_INIT)) + res_init(); + res.insert(res.end(), _res.nsaddr_list, _res.nsaddr_list + _res.nscount); +#endif + return res; +} + +bool +IpAddr::isValid(const std::string &address, pj_uint16_t family) +{ + pj_str_t pjstring; + pj_cstr(&pjstring, address.c_str()); + pj_str_t ret_str; + pj_uint16_t ret_port; + int ret_family; + auto status = pj_sockaddr_parse2(pj_AF_UNSPEC(), 0, &pjstring, &ret_str, &ret_port, &ret_family); + if (status != PJ_SUCCESS || (family != pj_AF_UNSPEC() && ret_family != family)) + return false; + + char buf[PJ_INET6_ADDRSTRLEN]; + pj_str_t addr_with_null = {buf, 0}; + pj_strncpy_with_null(&addr_with_null, &ret_str, sizeof(buf)); + struct sockaddr sa; + return inet_pton(ret_family==pj_AF_INET6()?AF_INET6:AF_INET, buf, &(sa.sa_data)) == 1; +} + +bool +IpAddr::isUnspecified() const +{ + switch (addr.addr.sa_family) { + case AF_INET: + return IN_IS_ADDR_UNSPECIFIED(&addr.ipv4.sin_addr); + case AF_INET6: + return IN6_IS_ADDR_UNSPECIFIED(reinterpret_cast<const in6_addr*>(&addr.ipv6.sin6_addr)); + default: + return true; + } +} + +bool +IpAddr::isLoopback() const +{ + switch (addr.addr.sa_family) { + case AF_INET: { + uint8_t b1 = (uint8_t)(addr.ipv4.sin_addr.s_addr >> 24); + return b1 == 127; + } + case AF_INET6: + return IN6_IS_ADDR_LOOPBACK(reinterpret_cast<const in6_addr*>(&addr.ipv6.sin6_addr)); + default: + return false; + } +} + +bool +IpAddr::isPrivate() const +{ + if (isLoopback()) { + return true; + } + switch (addr.addr.sa_family) { + case AF_INET: + uint8_t b1, b2; + b1 = (uint8_t)(addr.ipv4.sin_addr.s_addr >> 24); + b2 = (uint8_t)((addr.ipv4.sin_addr.s_addr >> 16) & 0x0ff); + // 10.x.y.z + if (b1 == 10) + return true; + // 172.16.0.0 - 172.31.255.255 + if ((b1 == 172) && (b2 >= 16) && (b2 <= 31)) + return true; + // 192.168.0.0 - 192.168.255.255 + if ((b1 == 192) && (b2 == 168)) + return true; + return false; + case AF_INET6: { + const pj_uint8_t* addr6 = reinterpret_cast<const pj_uint8_t*>(&addr.ipv6.sin6_addr); + if (addr6[0] == 0xfc) + return true; + return false; + } + default: + return false; + } +} + +} // namespace ring diff --git a/src/ip_utils.h b/src/ip_utils.h new file mode 100644 index 0000000000..ee23b5c362 --- /dev/null +++ b/src/ip_utils.h @@ -0,0 +1,270 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef IP_UTILS_H_ +#define IP_UTILS_H_ + +#include <pjlib.h> + +#include <netinet/ip.h> + +#include <string> +#include <vector> + + +/* An IPv4 equivalent to IN6_IS_ADDR_UNSPECIFIED */ +#ifndef IN_IS_ADDR_UNSPECIFIED +#define IN_IS_ADDR_UNSPECIFIED(a) (((long int) (a)->s_addr) == 0x00000000) +#endif /* IN_IS_ADDR_UNSPECIFIED */ + +namespace ring { + +class IpAddr { +public: + IpAddr(uint16_t family = AF_UNSPEC) : addr() { + addr.addr.sa_family = family; + } + + // From a sockaddr-type structure + IpAddr(const IpAddr& other) : addr(other.addr) {} + IpAddr(const pj_sockaddr& ip) : addr(ip) {} + IpAddr(const sockaddr_in& ip) : addr() { + memcpy(&addr, &ip, sizeof(sockaddr_in)); + } + IpAddr(const sockaddr_in6& ip) : addr() { + memcpy(&addr, &ip, sizeof(sockaddr_in6)); + } + IpAddr(const sockaddr& ip) : addr() { + memcpy(&addr, &ip, ip.sa_family == AF_INET6 ? sizeof addr.ipv6 : sizeof addr.ipv4); + } + IpAddr(const sockaddr_storage& ip) : IpAddr(*reinterpret_cast<const sockaddr*>(&ip)) {} + IpAddr(const in6_addr& ip) : addr() { + addr.addr.sa_family = AF_INET6; + memcpy(&addr.ipv6.sin6_addr, &ip, sizeof(in6_addr)); + } + IpAddr(const in_addr& ip) : addr() { + addr.addr.sa_family = AF_INET; + memcpy(&addr.ipv4.sin_addr, &ip, sizeof(in_addr)); + } + + // From a string + IpAddr(const std::string& str, pj_uint16_t family = AF_UNSPEC) : addr() { + if (str.empty()) { + addr.addr.sa_family = AF_UNSPEC; + return; + } + pj_str_t pjstring; + pj_cstr(&pjstring, str.c_str()); + auto status = pj_sockaddr_parse(family, 0, &pjstring, &addr); + if (status != PJ_SUCCESS) + addr.addr.sa_family = AF_UNSPEC; + } + + inline bool operator==(const IpAddr& other) const { + return pj_sockaddr_cmp(&addr, &other.addr) == 0; + } + + // Is defined + inline operator bool() const { + return isIpv4() or isIpv6(); + } + + inline operator pj_sockaddr& () { + return addr; + } + + inline operator const pj_sockaddr& () const { + return addr; + } + + inline operator pj_sockaddr_in& () { + return addr.ipv4; + } + + inline operator const pj_sockaddr_in& () const { + assert(addr.addr.sa_family != AF_INET6); + return addr.ipv4; + } + + inline operator pj_sockaddr_in6& () { + return addr.ipv6; + } + + inline operator const pj_sockaddr_in6& () const { + assert(addr.addr.sa_family == AF_INET6); + return addr.ipv6; + } + + inline operator sockaddr& (){ + return reinterpret_cast<sockaddr&>(addr); + } + + inline operator sockaddr_storage (){ + sockaddr_storage ss; + memcpy(&ss, &addr, getLength()); + return ss; + } + + inline const pj_sockaddr* pjPtr() const { + return &addr; + } + + inline pj_sockaddr* pjPtr() { + return &addr; + } + + inline operator std::string () const { + return toString(); + } + + std::string toString(bool include_port=false, bool force_ipv6_brackets=false) const { + std::string str(PJ_INET6_ADDRSTRLEN, (char)0); + if (include_port) force_ipv6_brackets = true; + pj_sockaddr_print(&addr, &(*str.begin()), PJ_INET6_ADDRSTRLEN, (include_port?1:0)|(force_ipv6_brackets?2:0)); + str.resize(std::char_traits<char>::length(str.c_str())); + return str; + } + + void setPort(uint16_t port) { + pj_sockaddr_set_port(&addr, port); + } + + inline uint16_t getPort() const { + if (not *this) + return 0; + return pj_sockaddr_get_port(&addr); + } + + inline socklen_t getLength() const { + if (not *this) + return 0; + return pj_sockaddr_get_len(&addr); + } + + inline uint16_t getFamily() const { + return addr.addr.sa_family; + } + + inline bool isIpv4() const { + return addr.addr.sa_family == AF_INET; + } + + inline bool isIpv6() const { + return addr.addr.sa_family == AF_INET6; + } + + /** + * Return true if address is a loopback IP address. + */ + bool isLoopback() const; + + /** + * Return true if address is not a public IP address. + */ + bool isPrivate() const; + + bool isUnspecified() const; + + /** + * Return true if address is a valid IPv6. + */ + inline static bool isIpv6(const std::string& address) { + return isValid(address, AF_INET6); + } + + /** + * Return true if address is a valid IP address of specified family (if provided) or of any kind (default). + * Does not resolve hostnames. + */ + static bool isValid(const std::string& address, pj_uint16_t family = pj_AF_UNSPEC()); + +private: + pj_sockaddr addr; +}; + +namespace ip_utils { + +static const char *const DEFAULT_INTERFACE = "default"; + +/** + * Return the generic "any host" IP address of the specified family. + * If family is unspecified, default to pj_AF_INET6() (IPv6). + */ +IpAddr getAnyHostAddr(pj_uint16_t family = pj_AF_UNSPEC()); + +/** + * Return the first host IP address of the specified family. + * If no address of the specified family is found, another family will + * be tried. + * Ex. : if family is pj_AF_INET6() (IPv6/default) and the system does not + * have an IPv6 address, an IPv4 address will be returned if available. + * + * If family is unspecified, default to pj_AF_INET6() if compiled + * with IPv6, or pj_AF_INET() otherwise. + */ +IpAddr getLocalAddr(pj_uint16_t family = pj_AF_UNSPEC()); + +/** + * Get the IP address of the network interface interface with the specified + * address family, or of any address family if unspecified (default). + */ +IpAddr getInterfaceAddr(const std::string &interface, pj_uint16_t family = pj_AF_UNSPEC()); + +/** + * List all the interfaces on the system and return + * a vector list containing their name (eth0, eth0:1 ...). + * @param void + * @return std::vector<std::string> A std::string vector + * of interface name available on all of the interfaces on + * the system. + */ +std::vector<std::string> getAllIpInterfaceByName(); + +/** + * List all the interfaces on the system and return + * a vector list containing their IP address. + * @param void + * @return std::vector<std::string> A std::string vector + * of IP address available on all of the interfaces on + * the system. + */ +std::vector<std::string> getAllIpInterface(); + +std::vector<IpAddr> getAddrList(const std::string &name, pj_uint16_t family = pj_AF_UNSPEC()); + +bool haveCommonAddr(const std::vector<IpAddr>& a, const std::vector<IpAddr>& b); + +std::vector<IpAddr> getLocalNameservers(); + +} // namespace ip_utils +} // namespace ring + +#endif // IP_UTILS_H_ diff --git a/src/logger.c b/src/logger.c new file mode 100644 index 0000000000..9dd07e0cb4 --- /dev/null +++ b/src/logger.c @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Julien Bonjean <julien.bonjean@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> + +#include "logger.h" + +static int consoleLog; +static int debugMode; + +void logger(const int level, const char* format, ...) +{ + if (!debugMode && level == LOG_DEBUG) + return; + + va_list ap; + va_start(ap, format); + vlogger(level, format, ap); + va_end(ap); +} + +void vlogger(const int level, const char *format, va_list ap) +{ + if (!debugMode && level == LOG_DEBUG) + return; + + if (consoleLog) { + const char *color_prefix = ""; + + switch (level) { + case LOG_ERR: + color_prefix = RED; + break; + case LOG_WARNING: + color_prefix = YELLOW; + break; + } + + fputs(color_prefix, stderr); + vfprintf(stderr, format, ap); + fputs(END_COLOR"\n", stderr); + } else { + vsyslog(level, format, ap); + } +} + +void setConsoleLog(int c) +{ + consoleLog = c; +} + +void setDebugMode(int d) +{ + debugMode = d; +} + +int getDebugMode(void) +{ + return debugMode; +} + +void strErr(void) +{ +#ifdef __GLIBC__ + RING_ERR("%m"); +#else + char buf[1000]; + const char *errstr; + + switch (strerror_r(errno, buf, sizeof(buf))) { + case 0: + errstr = buf; + break; + case ERANGE: /* should never happen */ + errstr = "unknown (too big to display)"; + break; + default: + errstr = "unknown (invalid error number)"; + break; + } + + RING_ERR("%s", errstr); +#endif +} diff --git a/src/logger.h b/src/logger.h new file mode 100644 index 0000000000..379d9d7a11 --- /dev/null +++ b/src/logger.h @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Julien Bonjean <julien.bonjean@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef H_LOGGER +#define H_LOGGER + +#ifdef __cplusplus +extern "C" { +#endif + +#include <stdarg.h> + +/** + * Print something, coloring it depending on the level + */ +void logger(const int level, const char *format, ...); +void vlogger(const int level, const char *format, va_list); + +/** + * Allow writing on the console + */ +void setConsoleLog(int c); + +/** + * When debug mode is not set, logging will not print anything + */ +void setDebugMode(int d); + +/** + * Return the current mode + */ +int getDebugMode(void); + +/** + * Thread-safe function to print the stringified contents of errno + */ +void strErr(); + +#ifdef __linux__ + +#include <unistd.h> +#include <sys/syscall.h> + +#define LOG_FORMAT(M, ...) "%s:%d:0x%x: " M, FILE_NAME, __LINE__, \ + syscall(__NR_gettid) & 0xffff, \ + ##__VA_ARGS__ +#else + +#define LOG_FORMAT(M, ...) "%s:%d: " M, FILE_NAME, __LINE__, \ + ##__VA_ARGS__ +#endif + +#ifdef __ANDROID__ + +#include <android/log.h> + +#ifndef APP_NAME +#define APP_NAME "libdring" +#endif /* APP_NAME */ + +// Avoid printing whole path on android +#define FILE_NAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 \ + : __FILE__) + +// because everyone likes reimplementing the wheel +#define LOG_ERR ANDROID_LOG_ERROR +#define LOG_WARNING ANDROID_LOG_WARN +#define LOG_INFO ANDROID_LOG_INFO +#define LOG_DEBUG ANDROID_LOG_DEBUG + +#define LOGGER(M, LEVEL, ...) __android_log_print(LEVEL, APP_NAME, \ + LOG_FORMAT(M, ##__VA_ARGS__)) + +/* TODO: WINDOWS, Actually implement logging system. */ +#elif defined _WIN32 +#define LOG_ERR 0 +#define LOG_WARNING 1 +#define LOG_INFO 2 +#define LOG_DEBUG 3 + +#define LOGGER(M, LEVEL, ...) printf(M, ##__VA_ARGS__) + +#else + +#include <syslog.h> + +#define FILE_NAME __FILE__ + +#define LOGGER(M, LEVEL, ...) logger(LEVEL, LOG_FORMAT(M, ##__VA_ARGS__)) + +#endif /* __ANDROID__ _WIN32 */ + +#define RING_ERR(M, ...) LOGGER(M, LOG_ERR, ##__VA_ARGS__) +#define RING_WARN(M, ...) LOGGER(M, LOG_WARNING, ##__VA_ARGS__) +#define RING_INFO(M, ...) LOGGER(M, LOG_INFO, ##__VA_ARGS__) +#define RING_DBG(M, ...) LOGGER(M, LOG_DEBUG, ##__VA_ARGS__) + + +#define BLACK "\033[22;30m" +#define RED "\033[22;31m" +#define GREEN "\033[22;32m" +#define BROWN "\033[22;33m" +#define BLUE "\033[22;34m" +#define MAGENTA "\033[22;35m" +#define CYAN "\033[22;36m" +#define GREY "\033[22;37m" +#define DARK_GREY "\033[01;30m" +#define LIGHT_RED "\033[01;31m" +#define LIGHT_SCREEN "\033[01;32m" +#define YELLOW "\033[01;33m" +#define LIGHT_BLUE "\033[01;34m" +#define LIGHT_MAGENTA "\033[01;35m" +#define LIGHT_CYAN "\033[01;36m" +#define WHITE "\033[01;37m" +#define END_COLOR "\033[0m" + +#ifdef __cplusplus +} +#endif + +#endif // H_LOGGER diff --git a/src/manager.cpp b/src/manager.cpp new file mode 100644 index 0000000000..e5c92b9692 --- /dev/null +++ b/src/manager.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author : Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "manager.h" +#include "logger.h" + +namespace ring { + +ManagerImpl& Manager::instance() +{ + // Meyers singleton + static ManagerImpl instance_; + + // This will give a warning that can be ignored the first time instance() + // is called...subsequent warnings are more serious + if (not ManagerImpl::initialized) + RING_WARN("Not initialized"); + + return instance_; +} + +} // namespace ring diff --git a/src/manager.h b/src/manager.h new file mode 100644 index 0000000000..476a4717e9 --- /dev/null +++ b/src/manager.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author : Jean-Philippe Barrette-LaPierre + * <jean-philippe.barrette-lapierre@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef MANAGER_H_ +#define MANAGER_H_ + +// we could forward declare ManagerImpl BUT anyone who will call instance +// will need this include. +#include "managerimpl.h" + +namespace ring { namespace Manager { + +ManagerImpl& instance(); + +}} // namespace ring::Manager + +#endif // MANAGER_H_ + diff --git a/src/managerimpl.cpp b/src/managerimpl.cpp new file mode 100644 index 0000000000..d595750580 --- /dev/null +++ b/src/managerimpl.cpp @@ -0,0 +1,2892 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Bourget <alexandre.bourget@savoirfairelinux.com> + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * Author: Laurielle Lea <laurielle.lea@savoirfairelinux.com> + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author: Guillaume Carmel-Archambault <guillaume.carmel-archambault@savoirfairelinux.com> + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "logger.h" +#include "managerimpl.h" +#include "account_schema.h" +#include "plugin_manager.h" + +#include "fileutils.h" +#include "map_utils.h" +#include "account.h" +#include "string_utils.h" +#if HAVE_DHT +#include "ringdht/ringaccount.h" +#endif + +#include "call_factory.h" + +#include "sip/sip_utils.h" + +#include "im/instant_messaging.h" + +#include "numbercleaner.h" +#include "config/yamlparser.h" + +#if HAVE_ALSA +#include "audio/alsa/alsalayer.h" +#endif + +#include "audio/sound/tonelist.h" +#include "audio/sound/audiofile.h" +#include "audio/sound/dtmf.h" +#include "audio/ringbufferpool.h" +#include "manager.h" + +#ifdef RING_VIDEO +#include "client/videomanager.h" +#endif + +#include "conference.h" +#include "ice_transport.h" + +#include "client/signal.h" + +#if HAVE_TLS +#include "gnutls_support.h" +#endif + +#include "libav_utils.h" +#include "video/sinkclient.h" + +#include <cerrno> +#include <algorithm> +#include <ctime> +#include <cstdlib> +#include <iostream> +#include <fstream> +#include <sstream> +#include <sys/types.h> // mkdir(2) +#include <sys/stat.h> // mkdir(2) +#include <memory> +#include <mutex> + +namespace ring { + +std::atomic_bool ManagerImpl::initialized = {false}; + +static void +copy_over(const std::string &srcPath, const std::string &destPath) +{ + std::ifstream src(srcPath.c_str()); + std::ofstream dest(destPath.c_str()); + dest << src.rdbuf(); + src.close(); + dest.close(); +} + +// Creates a backup of the file at "path" with a .bak suffix appended +static void +make_backup(const std::string &path) +{ + const std::string backup_path(path + ".bak"); + copy_over(path, backup_path); +} + +// Restore last backup of the configuration file +static void +restore_backup(const std::string &path) +{ + const std::string backup_path(path + ".bak"); + copy_over(backup_path, path); +} + +/** + * Set pjsip's log level based on the SIPLOGLEVEL environment variable. + * SIPLOGLEVEL = 0 minimum logging + * SIPLOGLEVEL = 6 maximum logging + */ + +/** Environment variable used to set pjsip's logging level */ +static constexpr const char* SIPLOGLEVEL = "SIPLOGLEVEL"; + +static void +setSipLogLevel() +{ + char* envvar = getenv(SIPLOGLEVEL); + int level = 0; + + if (envvar != nullptr) { + if (not (std::istringstream(envvar) >> level)) + level = 0; + + // From 0 (min) to 6 (max) + level = std::max(0, std::min(level, 6)); + } + + pj_log_set_level(level); +} + +#if HAVE_TLS +/** + * Set gnutls's log level based on the RING_TLS_LOGLEVEL environment variable. + * RING_TLS_LOGLEVEL = 0 minimum logging (default) + * RING_TLS_LOGLEVEL = 9 maximum logging + */ + +static constexpr int RING_TLS_LOGLEVEL = 0; + +static void +tls_print_logs(int level, const char* msg) +{ + RING_DBG("GnuTLS [%d]: %s", level, msg); +} + +static void +setGnuTlsLogLevel() +{ + char* envvar = getenv("RING_TLS_LOGLEVEL"); + int level = RING_TLS_LOGLEVEL; + + if (envvar != nullptr) { + int var_level; + if (std::istringstream(envvar) >> var_level) + level = var_level; + + // From 0 (min) to 9 (max) + level = std::max(0, std::min(level, 9)); + } + + gnutls_global_set_log_level(level); + gnutls_global_set_log_function(tls_print_logs); +} +#endif // HAVE_TLS + +void +ManagerImpl::loadDefaultAccountMap() +{ + accountFactory_.initIP2IPAccount(); +} + +ManagerImpl::ManagerImpl() : + pluginManager_(new PluginManager) + , preferences(), voipPreferences(), + hookPreference(), audioPreference(), shortcutPreferences(), + hasTriedToRegister_(false), + currentCallMutex_(), dtmfKey_(), dtmfBuf_(0, AudioFormat::MONO()), + toneMutex_(), telephoneTone_(), audiofile_(), audioLayerMutex_(), + waitingCalls_(), waitingCallsMutex_(), path_() + , ringbufferpool_(new RingBufferPool) + , callFactory(), conferenceMap_() + , accountFactory_(), ice_tf_() + , gnutlGIG_ {tls::GnuTlsGlobalInit::make_guard()} +{ + // initialize random generator + // mt19937_64 should be seeded with 2 x 32 bits + std::random_device rdev; + std::seed_seq seed {rdev(), rdev()}; + rand_.seed(seed); + + ring::libav_utils::ring_avcodec_init(); +} + +ManagerImpl::~ManagerImpl() +{} + +bool +ManagerImpl::parseConfiguration() +{ + bool result = true; + + try { + YAML::Node parsedFile = YAML::LoadFile(path_); + const int error_count = loadAccountMap(parsedFile); + + if (error_count > 0) { + RING_WARN("Errors while parsing %s", path_.c_str()); + result = false; + } + } catch (const YAML::BadFile &e) { + RING_WARN("Could not open config file: creating default account map"); + loadDefaultAccountMap(); + } + + return result; +} + +void +ManagerImpl::init(const std::string &config_file) +{ + // FIXME: this is no good + initialized = true; + +#define PJSIP_TRY(ret) do { \ + if (ret != PJ_SUCCESS) \ + throw std::runtime_error(#ret " failed"); \ + } while (0) + + srand(time(NULL)); // to get random number for RANDOM_PORT + + // Initialize PJSIP (SIP and ICE implementation) + PJSIP_TRY(pj_init()); + setSipLogLevel(); + PJSIP_TRY(pjlib_util_init()); + PJSIP_TRY(pjnath_init()); +#undef TRY + + RING_DBG("pjsip version %s for %s initialized", + pj_get_version(), PJ_OS_NAME); + +#if HAVE_TLS + setGnuTlsLogLevel(); + RING_DBG("GNU TLS version %s initialized", gnutls_check_version(nullptr)); +#endif + + ice_tf_.reset(new IceTransportFactory()); + + path_ = config_file.empty() ? retrieveConfigPath() : config_file; + RING_DBG("Configuration file path: %s", path_.c_str()); + + bool no_errors = true; + + // manager can restart without being recreated (android) + finished_ = false; + + try { + no_errors = parseConfiguration(); + } catch (const YAML::Exception &e) { + RING_ERR("%s", e.what()); + no_errors = false; + } + + // always back up last error-free configuration + if (no_errors) { + make_backup(path_); + } else { + // restore previous configuration + RING_WARN("Restoring last working configuration"); + + try { + // remove accounts from broken configuration + removeAccounts(); + restore_backup(path_); + parseConfiguration(); + } catch (const YAML::Exception &e) { + RING_ERR("%s", e.what()); + RING_WARN("Restoring backup failed, creating default account map"); + loadDefaultAccountMap(); + } + } + + initAudioDriver(); + + { + std::lock_guard<std::mutex> lock(audioLayerMutex_); + + if (audiodriver_) { + { + std::lock_guard<std::mutex> toneLock(toneMutex_); + telephoneTone_.reset(new TelephoneTone(preferences.getZoneToneChoice(), audiodriver_->getSampleRate())); + } + dtmfKey_.reset(new DTMF(getRingBufferPool().getInternalSamplingRate())); + } + } + + registerAccounts(); +} + +void +ManagerImpl::setPath(const std::string&) +{ + // FIME: needed? +} + +void +ManagerImpl::finish() noexcept +{ + bool expected = false; + if (not finished_.compare_exchange_strong(expected, true)) + return; + + try { + // Forbid call creation + callFactory.forbid(); + + // Hangup all remaining active calls + RING_DBG("Hangup %zu remaining call(s)", callFactory.callCount()); + for (const auto call : callFactory.getAllCalls()) + hangupCall(call->getCallId()); + callFactory.clear(); + + saveConfig(); + + // Disconnect accounts, close link stacks and free allocated ressources + unregisterAccounts(); + accountFactory_.clear(); + + { + std::lock_guard<std::mutex> lock(audioLayerMutex_); + + audiodriver_.reset(); + } + + ice_tf_.reset(); + pj_shutdown(); + } catch (const VoipLinkException &err) { + RING_ERR("%s", err.what()); + } +} + +bool +ManagerImpl::isCurrentCall(const Call& call) const +{ + return currentCall_.get() == &call; +} + +bool +ManagerImpl::hasCurrentCall() const +{ + return static_cast<bool>(currentCall_); +} + +std::shared_ptr<Call> +ManagerImpl::getCurrentCall() const +{ + return currentCall_; +} + +const std::string +ManagerImpl::getCurrentCallId() const +{ + return currentCall_ ? currentCall_->getCallId() : ""; +} + +/** + * Set current call ID to empty string + */ +void +ManagerImpl::unsetCurrentCall() +{ + currentCall_.reset(); +} + +/** + * Switch of current call id + * @param id The new callid + */ +void +ManagerImpl::switchCall(std::shared_ptr<Call> call) +{ + std::lock_guard<std::mutex> m(currentCallMutex_); + RING_DBG("----- Switch current call id to '%s' -----", + call ? call->getCallId().c_str() : "<nullptr>"); + currentCall_ = call; +} + +/////////////////////////////////////////////////////////////////////////////// +// Management of events' IP-phone user +/////////////////////////////////////////////////////////////////////////////// +/* Main Thread */ + +std::string +ManagerImpl::outgoingCall(const std::string& preferred_account_id, + const std::string& to, + const std::string& conf_id) +{ + std::string current_call_id(getCurrentCallId()); + std::string prefix(hookPreference.getNumberAddPrefix()); + std::string to_cleaned(NumberCleaner::clean(to, prefix)); + std::shared_ptr<Call> call; + + try { + /* RING_WARN: after this call the account_id is obsolete + * as the factory may decide to use another account (like IP2IP). + */ + RING_DBG("New outgoing call to %s", to_cleaned.c_str()); + call = newOutgoingCall(to_cleaned, preferred_account_id); + } catch (const std::exception &e) { + RING_ERR("%s", e.what()); + return {}; + } + + if (not call) + return {}; + + auto call_id = call->getCallId(); + + stopTone(); + + // in any cases we have to detach from current communication + if (hasCurrentCall()) { + RING_DBG("Has current call (%s) put it onhold", current_call_id.c_str()); + + // if this is not a conference and this and is not a conference participant + if (not isConference(current_call_id) and not isConferenceParticipant(current_call_id)) + onHoldCall(current_call_id); + else if (isConference(current_call_id) and not isConferenceParticipant(call_id)) + detachParticipant(RingBufferPool::DEFAULT_ID); + } + + switchCall(call); + call->setConfId(conf_id); + + return call_id; +} + +//THREAD=Main : for outgoing Call +bool +ManagerImpl::answerCall(const std::string& call_id) +{ + bool result = true; + + auto call = getCallFromCallID(call_id); + if (!call) { + RING_ERR("Call %s is NULL", call_id.c_str()); + return false; + } + + // If ring is ringing + stopTone(); + + // store the current call id + std::string current_call_id(getCurrentCallId()); + + // in any cases we have to detach from current communication + if (hasCurrentCall()) { + + RING_DBG("Currently conversing with %s", current_call_id.c_str()); + + if (not isConference(current_call_id) and not isConferenceParticipant(current_call_id)) { + RING_DBG("Answer call: Put the current call (%s) on hold", current_call_id.c_str()); + onHoldCall(current_call_id); + } else if (isConference(current_call_id) and not isConferenceParticipant(call_id)) { + // if we are talking to a conference and we are answering an incoming call + RING_DBG("Detach main participant from conference"); + detachParticipant(RingBufferPool::DEFAULT_ID); + } + } + + try { + call->answer(); + } catch (const std::runtime_error &e) { + RING_ERR("%s", e.what()); + result = false; + } + + // if it was waiting, it's waiting no more + removeWaitingCall(call_id); + + // if we dragged this call into a conference already + if (isConferenceParticipant(call_id)) + switchCall(callFactory.getCall(call->getConfId())); + else + switchCall(call); + + // Connect streams + addStream(*call); + + // Start recording if set in preference + if (audioPreference.getIsAlwaysRecording()) + toggleRecordingCall(call_id); + + //callStateChanged(call_id, "CURRENT"); + emitSignal<DRing::CallSignal::StateChange>(call_id, "CURRENT", 0); + + return result; +} + +void +ManagerImpl::checkAudio() +{ + if (getCallList().empty()) { + std::lock_guard<std::mutex> lock(audioLayerMutex_); + if (audiodriver_) + audiodriver_->stopStream(); + } +} + +//THREAD=Main +bool +ManagerImpl::hangupCall(const std::string& callId) +{ + // store the current call id + std::string currentCallId(getCurrentCallId()); + + stopTone(); + + RING_DBG("Send call state change (HUNGUP) for id %s", callId.c_str()); + emitSignal<DRing::CallSignal::StateChange>(callId, "HUNGUP", 0); + + /* We often get here when the call was hungup before being created */ + auto call = getCallFromCallID(callId); + if (not call) { + RING_WARN("Could not hang up non-existant call %s", callId.c_str()); + checkAudio(); + return false; + } + + // Disconnect streams + removeStream(*call); + + if (isConferenceParticipant(callId)) { + removeParticipant(callId); + } else { + // we are not participating in a conference, current call switched to "" + if (not isConference(currentCallId)) + unsetCurrentCall(); + } + + try { + call->hangup(0); + checkAudio(); + } catch (const VoipLinkException &e) { + RING_ERR("%s", e.what()); + return false; + } + + return true; +} + +bool +ManagerImpl::hangupConference(const std::string& id) +{ + RING_DBG("Hangup conference %s", id.c_str()); + + ConferenceMap::iterator iter_conf = conferenceMap_.find(id); + + if (iter_conf != conferenceMap_.end()) { + auto conf = iter_conf->second; + + if (conf) { + ParticipantSet participants(conf->getParticipantList()); + + for (const auto &item : participants) + hangupCall(item); + } else { + RING_ERR("No such conference %s", id.c_str()); + return false; + } + } + + unsetCurrentCall(); + + return true; +} + +//THREAD=Main +bool +ManagerImpl::onHoldCall(const std::string& callId) +{ + bool result = true; + + stopTone(); + + std::string current_call_id(getCurrentCallId()); + + if (auto call = getCallFromCallID(callId)) { + try { + call->onhold(); + removeStream(*call); // Unbind calls in main buffer + } catch (const VoipLinkException &e) { + RING_ERR("%s", e.what()); + result = false; + } + } else { + RING_DBG("CallID %s doesn't exist in call onHold", callId.c_str()); + return false; + } + + // Remove call from teh queue if it was still there + removeWaitingCall(callId); + + // keeps current call id if the action is not holding this call or a new outgoing call + // this could happen in case of a conference + if (current_call_id == callId) + unsetCurrentCall(); + + emitSignal<DRing::CallSignal::StateChange>(callId, "HOLD", 0); + + return result; +} + +//THREAD=Main +bool +ManagerImpl::offHoldCall(const std::string& callId) +{ + bool result = true; + + stopTone(); + + const std::string currentCallId(getCurrentCallId()); + + // Place current call on hold if it isn't + if (hasCurrentCall()) { + if (not isConference(currentCallId) and not isConferenceParticipant(currentCallId)) { + RING_DBG("Has current call (%s), put on hold", currentCallId.c_str()); + //FIXME: ebail + // if 2 consecutive offHoldCall done, the second one should be ignored (already offhold) + // this call put the call onHold + onHoldCall(currentCallId); + } else if (isConference(currentCallId) && callId != currentCallId) { + holdConference(currentCallId); + } else if (isConference(currentCallId) and not isConferenceParticipant(callId)) + detachParticipant(RingBufferPool::DEFAULT_ID); + } + + std::shared_ptr<Call> call; + try { + call = getCallFromCallID(callId); + if (call) + call->offhold(); + else + result = false; + } catch (const VoipLinkException &e) { + RING_ERR("%s", e.what()); + return false; + } + + emitSignal<DRing::CallSignal::StateChange>(callId, "UNHOLD", 0); + + if (isConferenceParticipant(callId)) + switchCall(getCallFromCallID(call->getConfId())); + else + switchCall(call); + + addStream(*call); + + return result; +} + +//THREAD=Main +bool +ManagerImpl::transferCall(const std::string& callId, const std::string& to) +{ + if (isConferenceParticipant(callId)) { + removeParticipant(callId); + } else if (not isConference(getCurrentCallId())) + unsetCurrentCall(); + + if (auto call = getCallFromCallID(callId)) + call->transfer(to); + else + return false; + + // remove waiting call in case we make transfer without even answer + removeWaitingCall(callId); + + return true; +} + +void +ManagerImpl::transferFailed() +{ + emitSignal<DRing::CallSignal::TransferFailed>(); +} + +void +ManagerImpl::transferSucceeded() +{ + transferSucceeded(); +} + +bool +ManagerImpl::attendedTransfer(const std::string& transferID, + const std::string& targetID) +{ + if (auto call = getCallFromCallID(transferID)) + return call->attendedTransfer(targetID); + + return false; +} + +//THREAD=Main : Call:Incoming +bool +ManagerImpl::refuseCall(const std::string& id) +{ + auto call = getCallFromCallID(id); + if (!call) + return false; + + stopTone(); + + if (getCallList().size() <= 1) { + std::lock_guard<std::mutex> lock(audioLayerMutex_); + audiodriver_->stopStream(); + } + + call->refuse(); + + checkAudio(); + + removeWaitingCall(id); + + emitSignal<DRing::CallSignal::StateChange>(id, "HUNGUP", 0); + + // Disconnect streams + removeStream(*call); + + return true; +} + +std::shared_ptr<Conference> +ManagerImpl::createConference(const std::string& id1, const std::string& id2) +{ + RING_DBG("Create conference with call %s and %s", id1.c_str(), id2.c_str()); + + auto conf = std::make_shared<Conference>(); + + conf->add(id1); + conf->add(id2); + + // Add conference to map + conferenceMap_.insert(std::make_pair(conf->getConfID(), conf)); + + emitSignal<DRing::CallSignal::ConferenceCreated>(conf->getConfID()); + + return conf; +} + +void +ManagerImpl::removeConference(const std::string& conference_id) +{ + RING_DBG("Remove conference %s", conference_id.c_str()); + RING_DBG("number of participants: %u", conferenceMap_.size()); + ConferenceMap::iterator iter = conferenceMap_.find(conference_id); + + std::shared_ptr<Conference> conf; + + if (iter != conferenceMap_.end()) + conf = iter->second; + + if (not conf) { + RING_ERR("Conference not found"); + return; + } + + emitSignal<DRing::CallSignal::ConferenceRemoved>(conference_id); + + // We now need to bind the audio to the remain participant + + // Unbind main participant audio from conference + getRingBufferPool().unBindAll(RingBufferPool::DEFAULT_ID); + + ParticipantSet participants(conf->getParticipantList()); + + // bind main participant audio to remaining conference call + ParticipantSet::iterator iter_p = participants.begin(); + + if (iter_p != participants.end()) + getRingBufferPool().bindCallID(*iter_p, RingBufferPool::DEFAULT_ID); + + // Then remove the conference from the conference map + if (conferenceMap_.erase(conference_id)) + RING_DBG("Conference %s removed successfully", conference_id.c_str()); + else + RING_ERR("Cannot remove conference: %s", conference_id.c_str()); +} + +std::shared_ptr<Conference> +ManagerImpl::getConferenceFromCallID(const std::string& call_id) +{ + auto call = getCallFromCallID(call_id); + if (!call) + return nullptr; + + ConferenceMap::const_iterator iter(conferenceMap_.find(call->getConfId())); + + if (iter != conferenceMap_.end()) + return iter->second; + else + return nullptr; +} + +bool +ManagerImpl::holdConference(const std::string& id) +{ + ConferenceMap::iterator iter_conf = conferenceMap_.find(id); + + if (iter_conf == conferenceMap_.end()) + return false; + + auto conf = iter_conf->second; + + bool isRec = conf->getState() == Conference::ACTIVE_ATTACHED_REC or + conf->getState() == Conference::ACTIVE_DETACHED_REC or + conf->getState() == Conference::HOLD_REC; + + ParticipantSet participants(conf->getParticipantList()); + + for (const auto &item : participants) { + switchCall(getCallFromCallID(item)); + onHoldCall(item); + } + + conf->setState(isRec ? Conference::HOLD_REC : Conference::HOLD); + + emitSignal<DRing::CallSignal::ConferenceChanged>(conf->getConfID(), conf->getStateStr()); + + return true; +} + +bool +ManagerImpl::unHoldConference(const std::string& id) +{ + ConferenceMap::iterator iter_conf = conferenceMap_.find(id); + + if (iter_conf == conferenceMap_.end() or iter_conf->second == 0) + return false; + + auto conf = iter_conf->second; + + bool isRec = conf->getState() == Conference::ACTIVE_ATTACHED_REC or + conf->getState() == Conference::ACTIVE_DETACHED_REC or + conf->getState() == Conference::HOLD_REC; + + ParticipantSet participants(conf->getParticipantList()); + + for (const auto &item : participants) { + if (auto call = getCallFromCallID(item)) { + // if one call is currently recording, the conference is in state recording + isRec |= call->isRecording(); + + switchCall(call); + offHoldCall(item); + } + } + + conf->setState(isRec ? Conference::ACTIVE_ATTACHED_REC : Conference::ACTIVE_ATTACHED); + + emitSignal<DRing::CallSignal::ConferenceChanged>(conf->getConfID(), conf->getStateStr()); + + return true; +} + +bool +ManagerImpl::isConference(const std::string& id) const +{ + return conferenceMap_.find(id) != conferenceMap_.end(); +} + +bool +ManagerImpl::isConferenceParticipant(const std::string& call_id) +{ + auto call = getCallFromCallID(call_id); + return call and not call->getConfId().empty(); +} + +bool +ManagerImpl::addParticipant(const std::string& callId, + const std::string& conferenceId) +{ + RING_DBG("Add participant %s to %s", callId.c_str(), conferenceId.c_str()); + ConferenceMap::iterator iter = conferenceMap_.find(conferenceId); + + if (iter == conferenceMap_.end()) { + RING_ERR("Conference id is not valid"); + return false; + } + + auto call = getCallFromCallID(callId); + if (!call) { + RING_ERR("Call id %s is not valid", callId.c_str()); + return false; + } + + // ensure that calls are only in one conference at a time + if (isConferenceParticipant(callId)) + detachParticipant(callId); + + // store the current call id (it will change in offHoldCall or in answerCall) + std::string current_call_id(getCurrentCallId()); + + // detach from prior communication and switch to this conference + if (current_call_id != callId) { + if (isConference(current_call_id)) + detachParticipant(RingBufferPool::DEFAULT_ID); + else + onHoldCall(current_call_id); + } + + // TODO: remove this ugly hack => There should be different calls when double clicking + // a conference to add main participant to it, or (in this case) adding a participant + // toconference + unsetCurrentCall(); + + // Add main participant + addMainParticipant(conferenceId); + + auto conf = iter->second; + switchCall(getCallFromCallID(conf->getConfID())); + + // Add coresponding IDs in conf and call + call->setConfId(conf->getConfID()); + conf->add(callId); + + // Connect new audio streams together + getRingBufferPool().unBindAll(callId); + + std::map<std::string, std::string> callDetails(getCallDetails(callId)); + std::string callState(callDetails.find("CALL_STATE")->second); + + if (callState == "HOLD") { + conf->bindParticipant(callId); + offHoldCall(callId); + } else if (callState == "INCOMING") { + conf->bindParticipant(callId); + answerCall(callId); + } else if (callState == "CURRENT") + conf->bindParticipant(callId); + + ParticipantSet participants(conf->getParticipantList()); + + if (participants.empty()) + RING_ERR("Participant list is empty for this conference"); + + // Connect stream + addStream(*call); + return true; +} + +bool +ManagerImpl::addMainParticipant(const std::string& conference_id) +{ + if (hasCurrentCall()) { + std::string current_call_id(getCurrentCallId()); + + if (isConference(current_call_id)) + detachParticipant(RingBufferPool::DEFAULT_ID); + else + onHoldCall(current_call_id); + } + + { + std::lock_guard<std::mutex> lock(audioLayerMutex_); + + ConferenceMap::const_iterator iter = conferenceMap_.find(conference_id); + + if (iter == conferenceMap_.end() or iter->second == 0) + return false; + + auto conf = iter->second; + + ParticipantSet participants(conf->getParticipantList()); + + for (const auto &item_p : participants) { + getRingBufferPool().bindCallID(item_p, RingBufferPool::DEFAULT_ID); + // Reset ringbuffer's readpointers + getRingBufferPool().flush(item_p); + } + + getRingBufferPool().flush(RingBufferPool::DEFAULT_ID); + + if (conf->getState() == Conference::ACTIVE_DETACHED) + conf->setState(Conference::ACTIVE_ATTACHED); + else if (conf->getState() == Conference::ACTIVE_DETACHED_REC) + conf->setState(Conference::ACTIVE_ATTACHED_REC); + else + RING_WARN("Invalid conference state while adding main participant"); + + emitSignal<DRing::CallSignal::ConferenceChanged>(conference_id, conf->getStateStr()); + } + + switchCall(getCallFromCallID(conference_id)); + return true; +} + +std::shared_ptr<Call> +ManagerImpl::getCallFromCallID(const std::string& callID) +{ + return callFactory.getCall(callID); +} + +bool +ManagerImpl::joinParticipant(const std::string& callId1, + const std::string& callId2) +{ + if (callId1 == callId2) { + RING_ERR("Cannot join participant %s to itself", callId1.c_str()); + return false; + } + + // Set corresponding conference ids for call 1 + auto call1 = getCallFromCallID(callId1); + if (!call1) { + RING_ERR("Could not find call %s", callId1.c_str()); + return false; + } + + // Set corresponding conderence details + auto call2 = getCallFromCallID(callId2); + if (!call2) { + RING_ERR("Could not find call %s", callId2.c_str()); + return false; + } + + // ensure that calls are only in one conference at a time + if (isConferenceParticipant(callId1)) + detachParticipant(callId1); + if (isConferenceParticipant(callId2)) + detachParticipant(callId2); + + std::map<std::string, std::string> call1Details(getCallDetails(callId1)); + std::map<std::string, std::string> call2Details(getCallDetails(callId2)); + + std::string current_call_id(getCurrentCallId()); + RING_DBG("Current Call ID %s", current_call_id.c_str()); + + // detach from the conference and switch to this conference + if ((current_call_id != callId1) and (current_call_id != callId2)) { + // If currently in a conference + if (isConference(current_call_id)) + detachParticipant(RingBufferPool::DEFAULT_ID); + else + onHoldCall(current_call_id); // currently in a call + } + + + auto conf = createConference(callId1, callId2); + + call1->setConfId(conf->getConfID()); + getRingBufferPool().unBindAll(callId1); + + call2->setConfId(conf->getConfID()); + getRingBufferPool().unBindAll(callId2); + + // Process call1 according to its state + std::string call1_state_str(call1Details.find("CALL_STATE")->second); + RING_DBG("Process call %s state: %s", callId1.c_str(), call1_state_str.c_str()); + + if (call1_state_str == "HOLD") { + conf->bindParticipant(callId1); + offHoldCall(callId1); + } else if (call1_state_str == "INCOMING") { + conf->bindParticipant(callId1); + answerCall(callId1); + } else if (call1_state_str == "CURRENT") { + conf->bindParticipant(callId1); + } else if (call1_state_str == "INACTIVE") { + conf->bindParticipant(callId1); + answerCall(callId1); + } else + RING_WARN("Call state not recognized"); + + // Process call2 according to its state + std::string call2_state_str(call2Details.find("CALL_STATE")->second); + RING_DBG("Process call %s state: %s", callId2.c_str(), call2_state_str.c_str()); + + if (call2_state_str == "HOLD") { + conf->bindParticipant(callId2); + offHoldCall(callId2); + } else if (call2_state_str == "INCOMING") { + conf->bindParticipant(callId2); + answerCall(callId2); + } else if (call2_state_str == "CURRENT") { + conf->bindParticipant(callId2); + } else if (call2_state_str == "INACTIVE") { + conf->bindParticipant(callId2); + answerCall(callId2); + } else + RING_WARN("Call state not recognized"); + + // Switch current call id to this conference + switchCall(getCallFromCallID(conf->getConfID())); + conf->setState(Conference::ACTIVE_ATTACHED); + + // set recording sampling rate + conf->setRecordingFormat(ringbufferpool_->getInternalAudioFormat()); + + return true; +} + +void +ManagerImpl::createConfFromParticipantList(const std::vector< std::string > &participantList) +{ + // we must at least have 2 participant for a conference + if (participantList.size() <= 1) { + RING_ERR("Participant number must be higher or equal to 2"); + return; + } + + auto conf = std::make_shared<Conference>(); + + int successCounter = 0; + + for (const auto &p : participantList) { + std::string numberaccount(p); + std::string tostr(numberaccount.substr(0, numberaccount.find(","))); + std::string account(numberaccount.substr(numberaccount.find(",") + 1, numberaccount.size())); + + unsetCurrentCall(); + + // Create call + auto call_id = outgoingCall(account, tostr, conf->getConfID()); + if (call_id.empty()) + continue; + + // Manager methods may behave differently if the call id participates in a conference + conf->add(call_id); + + emitSignal<DRing::CallSignal::NewCallCreated>(account, call_id, tostr); + successCounter++; + } + + // Create the conference if and only if at least 2 calls have been successfully created + if (successCounter >= 2) { + conferenceMap_[conf->getConfID()] = conf; + emitSignal<DRing::CallSignal::ConferenceCreated>(conf->getConfID()); + conf->setRecordingFormat(ringbufferpool_->getInternalAudioFormat()); + } +} + +bool +ManagerImpl::detachParticipant(const std::string& call_id) +{ + const std::string current_call_id(getCurrentCallId()); + + if (call_id != RingBufferPool::DEFAULT_ID) { + auto call = getCallFromCallID(call_id); + if (!call) { + RING_ERR("Could not find call %s", call_id.c_str()); + return false; + } + + auto conf = getConferenceFromCallID(call_id); + + if (conf == nullptr) { + RING_ERR("Call is not conferencing, cannot detach"); + return false; + } + + std::map<std::string, std::string> call_details(getCallDetails(call_id)); + std::map<std::string, std::string>::iterator iter_details(call_details.find("CALL_STATE")); + + if (iter_details == call_details.end()) { + RING_ERR("Could not find CALL_STATE"); + return false; + } + + // Don't hold ringing calls when detaching them from conferences + if (iter_details->second != "RINGING") + onHoldCall(call_id); + + removeParticipant(call_id); + + } else { + RING_DBG("Unbind main participant from conference %d"); + getRingBufferPool().unBindAll(RingBufferPool::DEFAULT_ID); + + if (not isConference(current_call_id)) { + RING_ERR("Current call id (%s) is not a conference", current_call_id.c_str()); + return false; + } + + ConferenceMap::iterator iter = conferenceMap_.find(current_call_id); + + auto conf = iter->second; + if (iter == conferenceMap_.end() or conf == 0) { + RING_DBG("Conference is NULL"); + return false; + } + + if (conf->getState() == Conference::ACTIVE_ATTACHED) + conf->setState(Conference::ACTIVE_DETACHED); + else if (conf->getState() == Conference::ACTIVE_ATTACHED_REC) + conf->setState(Conference::ACTIVE_DETACHED_REC); + else + RING_WARN("Undefined behavior, invalid conference state in detach participant"); + + emitSignal<DRing::CallSignal::ConferenceChanged>(conf->getConfID(), conf->getStateStr()); + + unsetCurrentCall(); + } + + return true; +} + +void +ManagerImpl::removeParticipant(const std::string& call_id) +{ + RING_DBG("Remove participant %s", call_id.c_str()); + + // this call is no longer a conference participant + auto call = getCallFromCallID(call_id); + if (!call) { + RING_ERR("Call not found"); + return; + } + + ConferenceMap::const_iterator iter = conferenceMap_.find(call->getConfId()); + + auto conf = iter->second; + if (iter == conferenceMap_.end() or conf == 0) { + RING_ERR("No conference with id %s, cannot remove participant", call->getConfId().c_str()); + return; + } + + conf->remove(call_id); + call->setConfId(""); + + removeStream(*call); + + emitSignal<DRing::CallSignal::ConferenceChanged>(conf->getConfID(), conf->getStateStr()); + + processRemainingParticipants(*conf); +} + +void +ManagerImpl::processRemainingParticipants(Conference &conf) +{ + const std::string current_call_id(getCurrentCallId()); + ParticipantSet participants(conf.getParticipantList()); + const size_t n = participants.size(); + RING_DBG("Process remaining %d participant(s) from conference %s", + n, conf.getConfID().c_str()); + + if (n > 1) { + // Reset ringbuffer's readpointers + for (const auto &p : participants) + getRingBufferPool().flush(p); + + getRingBufferPool().flush(RingBufferPool::DEFAULT_ID); + } else if (n == 1) { + // this call is the last participant, hence + // the conference is over + ParticipantSet::iterator p = participants.begin(); + + if (auto call = getCallFromCallID(*p)) { + call->setConfId(""); + // if we are not listening to this conference + if (current_call_id != conf.getConfID()) + onHoldCall(call->getCallId()); + else + switchCall(call); + } + + RING_DBG("No remaining participants, remove conference"); + removeConference(conf.getConfID()); + } else { + RING_DBG("No remaining participants, remove conference"); + removeConference(conf.getConfID()); + unsetCurrentCall(); + } +} + +bool +ManagerImpl::joinConference(const std::string& conf_id1, + const std::string& conf_id2) +{ + if (conferenceMap_.find(conf_id1) == conferenceMap_.end()) { + RING_ERR("Not a valid conference ID: %s", conf_id1.c_str()); + return false; + } + + if (conferenceMap_.find(conf_id2) == conferenceMap_.end()) { + RING_ERR("Not a valid conference ID: %s", conf_id2.c_str()); + return false; + } + + auto conf = conferenceMap_.find(conf_id1)->second; + ParticipantSet participants(conf->getParticipantList()); + + for (const auto &p : participants) + addParticipant(p, conf_id2); + + return true; +} + +void +ManagerImpl::addStream(Call& call) +{ + const auto call_id = call.getCallId(); + RING_DBG("Add audio stream %s", call_id.c_str()); + + if (isConferenceParticipant(call_id)) { + RING_DBG("Add stream to conference"); + + // bind to conference participant + ConferenceMap::iterator iter = conferenceMap_.find(call_id); + if (iter != conferenceMap_.end() and iter->second) { + auto conf = iter->second; + conf->bindParticipant(call_id); + } + } else { + RING_DBG("Add stream to call"); + + // bind to main + getRingBufferPool().bindCallID(call_id, RingBufferPool::DEFAULT_ID); + + std::lock_guard<std::mutex> lock(audioLayerMutex_); + if (!audiodriver_) { + RING_ERR("Audio driver not initialized"); + return; + } + audiodriver_->flushUrgent(); + audiodriver_->flushMain(); + } + startAudioDriverStream(); +} + +void +ManagerImpl::removeStream(Call& call) +{ + const auto call_id = call.getCallId(); + RING_DBG("Remove audio stream %s", call_id.c_str()); + getRingBufferPool().unBindAll(call_id); +} + +// Not thread-safe, SHOULD be called in same thread that run poolEvents() +void +ManagerImpl::registerEventHandler(uintptr_t handlerId, EventHandler handler) +{ + eventHandlerMap_[handlerId] = handler; +} + +// Not thread-safe, SHOULD be called in same thread that run poolEvents() +void +ManagerImpl::unregisterEventHandler(uintptr_t handlerId) +{ + auto iter = eventHandlerMap_.find(handlerId); + if (iter != eventHandlerMap_.end()) { + if (iter == nextEventHandler_) + nextEventHandler_ = eventHandlerMap_.erase(iter); + else + eventHandlerMap_.erase(iter); + } +} + +// Not thread-safe, SHOULD be called in same thread that run poolEvents() +void +ManagerImpl::addTask(const std::function<bool()>&& task) +{ + pendingTaskList_.emplace_back(task); +} + +// Must be invoked periodically by a timer from the main event loop +void ManagerImpl::pollEvents() +{ + //-- Handlers + { + auto iter = eventHandlerMap_.begin(); + while (iter != eventHandlerMap_.end()) { + if (finished_) + return; + + // WARN: following callback can do anything and typically + // calls (un)registerEventHandler. + // Think twice before modify this code. + + nextEventHandler_ = std::next(iter); + iter->second(); + iter = nextEventHandler_; + } + } + + //-- Tasks + { + auto tmpList = std::move(pendingTaskList_); + pendingTaskList_.clear(); + auto iter = std::begin(tmpList); + while (iter != tmpList.cend()) { + if (finished_) + return; + + auto next = std::next(iter); + if (not (*iter)()) + tmpList.erase(iter); + iter = next; + } + pendingTaskList_.splice(std::end(pendingTaskList_), tmpList); + } +} + +//THREAD=Main +void +ManagerImpl::saveConfig() +{ + RING_DBG("Saving Configuration to XDG directory %s", path_.c_str()); + + if (audiodriver_) { + audioPreference.setVolumemic(audiodriver_->getCaptureGain()); + audioPreference.setVolumespkr(audiodriver_->getPlaybackGain()); + audioPreference.setCaptureMuted(audiodriver_->isCaptureMuted()); + audioPreference.setPlaybackMuted(audiodriver_->isPlaybackMuted()); + } + + try { + YAML::Emitter out; + + // FIXME maybe move this into accountFactory? + out << YAML::BeginMap << YAML::Key << "accounts"; + out << YAML::Value << YAML::BeginSeq; + + for (const auto& account : accountFactory_.getAllAccounts()) { + account->serialize(out); + } + out << YAML::EndSeq; + + // FIXME: this is a hack until we get rid of accountOrder + preferences.verifyAccountOrder(getAccountList()); + preferences.serialize(out); + voipPreferences.serialize(out); + hookPreference.serialize(out); + audioPreference.serialize(out); +#ifdef RING_VIDEO + getVideoDeviceMonitor().serialize(out); +#endif + shortcutPreferences.serialize(out); + + std::ofstream fout(path_); + fout << out.c_str(); + } catch (const YAML::Exception &e) { + RING_ERR("%s", e.what()); + } catch (const std::runtime_error &e) { + RING_ERR("%s", e.what()); + } +} + +//THREAD=Main | VoIPLink +void +ManagerImpl::playDtmf(char code) +{ + stopTone(); + + if (not voipPreferences.getPlayDtmf()) { + RING_DBG("Do not have to play a tone..."); + return; + } + + // length in milliseconds + int pulselen = voipPreferences.getPulseLength(); + + if (pulselen == 0) { + RING_DBG("Pulse length is not set..."); + return; + } + + std::lock_guard<std::mutex> lock(audioLayerMutex_); + + // numbers of int = length in milliseconds / 1000 (number of seconds) + // = number of seconds * SAMPLING_RATE by SECONDS + + // fast return, no sound, so no dtmf + if (not audiodriver_ or not dtmfKey_) { + RING_DBG("No audio layer..."); + return; + } + + // number of data sampling in one pulselen depends on samplerate + // size (n sampling) = time_ms * sampling/s + // --------------------- + // ms/s + int size = (int)((pulselen * (float) audiodriver_->getSampleRate()) / 1000); + dtmfBuf_.resize(size); + + // Handle dtmf + dtmfKey_->startTone(code); + + // copy the sound + if (dtmfKey_->generateDTMF(*dtmfBuf_.getChannel(0))) { + // Put buffer to urgentRingBuffer + // put the size in bytes... + // so size * 1 channel (mono) * sizeof (bytes for the data) + // audiolayer->flushUrgent(); + audiodriver_->startStream(); + + // FIXME: do real synchronization + int tries = 10; + while (not audiodriver_->isStarted() and tries--) { + RING_WARN("Audio layer not ready yet"); + usleep(10000); + } + audiodriver_->putUrgent(dtmfBuf_); + } + + // TODO Cache the DTMF +} + +// Multi-thread +bool +ManagerImpl::incomingCallsWaiting() +{ + std::lock_guard<std::mutex> m(waitingCallsMutex_); + return not waitingCalls_.empty(); +} + +void +ManagerImpl::addWaitingCall(const std::string& id) +{ + std::lock_guard<std::mutex> m(waitingCallsMutex_); + waitingCalls_.insert(id); +} + +void +ManagerImpl::removeWaitingCall(const std::string& id) +{ + std::lock_guard<std::mutex> m(waitingCallsMutex_); + waitingCalls_.erase(id); +} + +/////////////////////////////////////////////////////////////////////////////// +// Management of event peer IP-phone +//////////////////////////////////////////////////////////////////////////////// +// SipEvent Thread +void +ManagerImpl::incomingCall(Call &call, const std::string& accountId) +{ + stopTone(); + const std::string callID(call.getCallId()); + + if (accountId.empty()) + call.setIPToIP(true); + else { + // strip sip: which is not required and bring confusion with ip to ip calls + // when placing new call from history (if call is IAX, do nothing) + std::string peerNumber(call.getPeerNumber()); + + const char SIP_PREFIX[] = "sip:"; + size_t startIndex = peerNumber.find(SIP_PREFIX); + + if (startIndex != std::string::npos) + call.setPeerNumber(peerNumber.substr(startIndex + sizeof(SIP_PREFIX) - 1)); + } + + if (not hasCurrentCall()) { + call.setConnectionState(Call::RINGING); + playRingtone(accountId); + } + + addWaitingCall(callID); + + std::string number(call.getPeerNumber()); + + std::string from("<" + number + ">"); + + emitSignal<DRing::CallSignal::IncomingCall>(accountId, callID, call.getDisplayName() + " " + from); +} + +//THREAD=VoIP +#if HAVE_INSTANT_MESSAGING +void +ManagerImpl::incomingMessage(const std::string& callID, + const std::string& from, + const std::string& message) +{ + if (isConferenceParticipant(callID)) { + auto conf = getConferenceFromCallID(callID); + + ParticipantSet participants(conf->getParticipantList()); + + for (const auto &item_p : participants) { + + if (item_p == callID) + continue; + + RING_DBG("Send message to %s", item_p.c_str()); + + if (auto call = getCallFromCallID(item_p)) { + call->sendTextMessage(message, from); + } else { + RING_ERR("Failed to get call while sending instant message"); + return; + } + } + + // in case of a conference we must notify client using conference id + emitSignal<DRing::CallSignal::IncomingMessage>(conf->getConfID(), from, message); + } else + emitSignal<DRing::CallSignal::IncomingMessage>(callID, from, message); +} + +//THREAD=VoIP +bool +ManagerImpl::sendTextMessage(const std::string& callID, + const std::string& message, + const std::string& from) +{ + if (isConference(callID)) { + RING_DBG("Is a conference, send instant message to everyone"); + ConferenceMap::iterator it = conferenceMap_.find(callID); + + if (it == conferenceMap_.end()) + return false; + + auto conf = it->second; + + if (!conf) + return false; + + ParticipantSet participants(conf->getParticipantList()); + + for (const auto &participant_id : participants) { + + if (auto call = getCallFromCallID(participant_id)) { + call->sendTextMessage(message, from); + } else { + RING_ERR("Failed to get call while sending instant message"); + return false; + } + } + + return true; + } + + if (isConferenceParticipant(callID)) { + RING_DBG("Call is participant in a conference, send instant message to everyone"); + auto conf = getConferenceFromCallID(callID); + + if (!conf) + return false; + + ParticipantSet participants(conf->getParticipantList()); + + for (const auto &participant_id : participants) { + + if (auto call = getCallFromCallID(participant_id)) { + call->sendTextMessage(message, from); + } else { + RING_ERR("Failed to get call while sending instant message"); + return false; + } + } + } else { + if (auto call = getCallFromCallID(callID)) { + call->sendTextMessage(message, from); + } else { + RING_ERR("Failed to get call while sending instant message"); + return false; + } + } + return true; +} +#endif // HAVE_INSTANT_MESSAGING + +//THREAD=VoIP CALL=Outgoing +void +ManagerImpl::peerAnsweredCall(Call& call) +{ + const auto call_id = call.getCallId(); + RING_DBG("Peer answered call %s", call_id.c_str()); + + // The if statement is usefull only if we sent two calls at the same time. + if (isCurrentCall(call)) + stopTone(); + + // Connect audio streams + addStream(call); + + if (audiodriver_) { + std::lock_guard<std::mutex> lock(audioLayerMutex_); + audiodriver_->flushMain(); + audiodriver_->flushUrgent(); + } + + if (audioPreference.getIsAlwaysRecording()) + toggleRecordingCall(call_id); + + emitSignal<DRing::CallSignal::StateChange>(call_id, "CURRENT", 0); +} + +//THREAD=VoIP Call=Outgoing +void +ManagerImpl::peerRingingCall(Call& call) +{ + const auto call_id = call.getCallId(); + RING_DBG("Peer call %s ringing", call_id.c_str()); + + if (isCurrentCall(call)) + ringback(); + + emitSignal<DRing::CallSignal::StateChange>(call_id, "RINGING", 0); +} + +//THREAD=VoIP Call=Outgoing/Ingoing +void +ManagerImpl::peerHungupCall(Call& call) +{ + const auto call_id = call.getCallId(); + RING_DBG("Peer hungup call %s", call_id.c_str()); + + if (isConferenceParticipant(call_id)) { + removeParticipant(call_id); + } else if (isCurrentCall(call)) { + stopTone(); + unsetCurrentCall(); + } + + call.peerHungup(); + + emitSignal<DRing::CallSignal::StateChange>(call_id, "HUNGUP", 0); + + checkAudio(); + removeWaitingCall(call_id); + if (not incomingCallsWaiting()) + stopTone(); + + removeStream(call); +} + +//THREAD=VoIP +void +ManagerImpl::callBusy(Call& call) +{ + const auto call_id = call.getCallId(); + + emitSignal<DRing::CallSignal::StateChange>(call_id, "BUSY", 0); + + if (isCurrentCall(call)) { + playATone(Tone::TONE_BUSY); + unsetCurrentCall(); + } + + checkAudio(); + removeWaitingCall(call_id); +} + +//THREAD=VoIP +void +ManagerImpl::callFailure(Call& call, int code) +{ + const auto call_id = call.getCallId(); + + emitSignal<DRing::CallSignal::StateChange>(call_id, "FAILURE", code); + + if (isCurrentCall(call)) { + playATone(Tone::TONE_BUSY); + unsetCurrentCall(); + } + + if (isConferenceParticipant(call_id)) { + RING_DBG("Call %s participating in a conference failed", call_id.c_str()); + // remove this participant + removeParticipant(call_id); + } + + checkAudio(); + removeWaitingCall(call_id); +} + +//THREAD=VoIP +void +ManagerImpl::startVoiceMessageNotification(const std::string& accountId, + int nb_msg) +{ + emitSignal<DRing::CallSignal::VoiceMailNotify>(accountId, nb_msg); +} + +/** + * Multi Thread + */ +void +ManagerImpl::playATone(Tone::TONEID toneId) +{ + if (not voipPreferences.getPlayTones()) + return; + + { + std::lock_guard<std::mutex> lock(audioLayerMutex_); + + if (not audiodriver_) { + RING_ERR("Audio layer not initialized"); + return; + } + + audiodriver_->flushUrgent(); + audiodriver_->startStream(); + } + + { + std::lock_guard<std::mutex> lock(toneMutex_); + if (telephoneTone_) + telephoneTone_->setCurrentTone(toneId); + } +} + +/** + * Multi Thread + */ +void +ManagerImpl::stopTone() +{ + if (not voipPreferences.getPlayTones()) + return; + + std::lock_guard<std::mutex> lock(toneMutex_); + if (telephoneTone_) + telephoneTone_->setCurrentTone(Tone::TONE_NULL); + + if (audiofile_) { + std::string filepath(audiofile_->getFilePath()); + + emitSignal<DRing::CallSignal::RecordPlaybackStopped>(filepath); + audiofile_.reset(); + } +} + +/** + * Multi Thread + */ +void +ManagerImpl::playTone() +{ + playATone(Tone::TONE_DIALTONE); +} + +/** + * Multi Thread + */ +void +ManagerImpl::playToneWithMessage() +{ + playATone(Tone::TONE_CONGESTION); +} + +/** + * Multi Thread + */ +void +ManagerImpl::congestion() +{ + playATone(Tone::TONE_CONGESTION); +} + +/** + * Multi Thread + */ +void +ManagerImpl::ringback() +{ + playATone(Tone::TONE_RINGTONE); +} + +// Caller must hold toneMutex +void +ManagerImpl::updateAudioFile(const std::string &file, int sampleRate) +{ + audiofile_.reset(new AudioFile(file, sampleRate)); +} + +/** + * Multi Thread + */ +void +ManagerImpl::playRingtone(const std::string& accountID) +{ + const auto account = getAccount(accountID); + + if (!account) { + RING_WARN("Invalid account in ringtone"); + return; + } + + if (!account->getRingtoneEnabled()) { + ringback(); + return; + } + + std::string ringchoice = account->getRingtonePath(); + + if (ringchoice.find(DIR_SEPARATOR_STR) == std::string::npos) { + // check inside global share directory + static const char * const RINGDIR = "ringtones"; + ringchoice = std::string(PROGSHAREDIR) + DIR_SEPARATOR_STR + + RINGDIR + DIR_SEPARATOR_STR + ringchoice; + } + + int audioLayerSmplr = 8000; + { + std::lock_guard<std::mutex> lock(audioLayerMutex_); + + if (not audiodriver_) { + RING_ERR("no audio layer in ringtone"); + return; + } + + audioLayerSmplr = audiodriver_->getSampleRate(); + } + + bool doFallback = false; + + { + std::lock_guard<std::mutex> m(toneMutex_); + + if (audiofile_) { + emitSignal<DRing::CallSignal::RecordPlaybackStopped>(audiofile_->getFilePath()); + audiofile_.reset(); + } + + try { + updateAudioFile(ringchoice, audioLayerSmplr); + } catch (const AudioFileException &e) { + RING_WARN("Ringtone error: %s", e.what()); + doFallback = true; // do ringback once lock is out of scope + } + } // leave mutex + + if (doFallback) { + ringback(); + return; + } + + std::lock_guard<std::mutex> lock(audioLayerMutex_); + // start audio if not started AND flush all buffers (main and urgent) + audiodriver_->startStream(); +} + +AudioLoop* +ManagerImpl::getTelephoneTone() +{ + std::lock_guard<std::mutex> m(toneMutex_); + if (telephoneTone_) + return telephoneTone_->getCurrentTone(); + else + return nullptr; +} + +AudioLoop* +ManagerImpl::getTelephoneFile() +{ + std::lock_guard<std::mutex> m(toneMutex_); + return audiofile_.get(); +} + +/////////////////////////////////////////////////////////////////////////////// +// Private functions +/////////////////////////////////////////////////////////////////////////////// +/** + * Initialization: Main Thread + */ +std::string +ManagerImpl::retrieveConfigPath() const +{ +#ifdef __ANDROID__ + std::string configdir = "/data/data/cx.ring"; +#elif __APPLE__ + std::string configdir = fileutils::get_home_dir() + DIR_SEPARATOR_STR + + "Library" + DIR_SEPARATOR_STR + "Application Support" + + DIR_SEPARATOR_STR + PACKAGE; +#else + std::string configdir = fileutils::get_home_dir() + DIR_SEPARATOR_STR + + ".config" + DIR_SEPARATOR_STR + PACKAGE; +#endif + + const std::string xdg_env(XDG_CONFIG_HOME); + if (not xdg_env.empty()) + configdir = xdg_env + DIR_SEPARATOR_STR + PACKAGE; + + if (mkdir(configdir.data(), 0700) != 0) { + // If directory creation failed + if (errno != EEXIST) + RING_DBG("Cannot create directory: %s!", configdir.c_str()); + } + + static const char * const PROGNAME = "dring"; + return configdir + DIR_SEPARATOR_STR + PROGNAME + ".yml"; +} + +/** + * Set input audio plugin + */ +void +ManagerImpl::setAudioPlugin(const std::string& audioPlugin) +{ + std::lock_guard<std::mutex> lock(audioLayerMutex_); + + audioPreference.setAlsaPlugin(audioPlugin); + + bool wasStarted = audiodriver_->isStarted(); + + // Recreate audio driver with new settings + audiodriver_.reset(audioPreference.createAudioLayer()); + + if (audiodriver_ and wasStarted) + audiodriver_->startStream(); + else + RING_ERR("No audio layer created, possibly built without audio support"); +} + +/** + * Set audio output device + */ +void +ManagerImpl::setAudioDevice(int index, DeviceType type) +{ + std::lock_guard<std::mutex> lock(audioLayerMutex_); + + if (not audiodriver_) { + RING_ERR("Audio driver not initialized"); + return ; + } + + const bool wasStarted = audiodriver_->isStarted(); + audiodriver_->updatePreference(audioPreference, index, type); + + // Recreate audio driver with new settings + audiodriver_.reset(audioPreference.createAudioLayer()); + + if (audiodriver_ and wasStarted) + audiodriver_->startStream(); +} + +/** + * Get list of supported audio output device + */ +std::vector<std::string> +ManagerImpl::getAudioOutputDeviceList() +{ + std::lock_guard<std::mutex> lock(audioLayerMutex_); + return audiodriver_->getPlaybackDeviceList(); +} + +/** + * Get list of supported audio input device + */ +std::vector<std::string> +ManagerImpl::getAudioInputDeviceList() +{ + std::lock_guard<std::mutex> lock(audioLayerMutex_); + return audiodriver_->getCaptureDeviceList(); +} + +/** + * Get string array representing integer indexes of output and input device + */ +std::vector<std::string> +ManagerImpl::getCurrentAudioDevicesIndex() +{ + std::lock_guard<std::mutex> lock(audioLayerMutex_); + + std::vector<std::string> v; + + std::stringstream ssi, sso, ssr; + sso << audiodriver_->getIndexPlayback(); + v.push_back(sso.str()); + ssi << audiodriver_->getIndexCapture(); + v.push_back(ssi.str()); + ssr << audiodriver_->getIndexRingtone(); + v.push_back(ssr.str()); + + return v; +} + +bool +ManagerImpl::switchInput(const std::string& call_id, const std::string& res) +{ + auto call = getCallFromCallID(call_id); + if (!call) { + RING_ERR("Call %s is NULL", call_id.c_str()); + return false; + } + call->switchInput(res); + return true; +} + +int +ManagerImpl::isRingtoneEnabled(const std::string& id) +{ + const auto account = getAccount(id); + + if (!account) { + RING_WARN("Invalid account in ringtone enabled"); + return 0; + } + + return account->getRingtoneEnabled(); +} + +void +ManagerImpl::ringtoneEnabled(const std::string& id) +{ + const auto account = getAccount(id); + + if (!account) { + RING_WARN("Invalid account in ringtone enabled"); + return; + } + + account->getRingtoneEnabled() ? account->setRingtoneEnabled(false) : account->setRingtoneEnabled(true); +} + +bool +ManagerImpl::getIsAlwaysRecording() const +{ + return audioPreference.getIsAlwaysRecording(); +} + +void +ManagerImpl::setIsAlwaysRecording(bool isAlwaysRec) +{ + return audioPreference.setIsAlwaysRecording(isAlwaysRec); +} + +bool +ManagerImpl::toggleRecordingCall(const std::string& id) +{ + std::shared_ptr<Recordable> rec; + + ConferenceMap::const_iterator it(conferenceMap_.find(id)); + if (it == conferenceMap_.end()) { + RING_DBG("toggle recording for call %s", id.c_str()); + rec = getCallFromCallID(id); + } else { + RING_DBG("toggle recording for conference %s", id.c_str()); + auto conf = it->second; + if (conf) { + rec = conf; + if (conf->isRecording()) + conf->setState(Conference::ACTIVE_ATTACHED); + else + conf->setState(Conference::ACTIVE_ATTACHED_REC); + } + } + + if (!rec) { + RING_ERR("Could not find recordable instance %s", id.c_str()); + return false; + } + + const bool result = rec->toggleRecording(); + emitSignal<DRing::CallSignal::RecordPlaybackFilepath>(id, rec->getFilename()); + emitSignal<DRing::CallSignal::RecordingStateChanged>(id, result); + return result; +} + +bool +ManagerImpl::isRecording(const std::string& id) +{ + auto call = getCallFromCallID(id); + return call and (static_cast<Recordable*>(call.get()))->isRecording(); +} + +bool +ManagerImpl::startRecordedFilePlayback(const std::string& filepath) +{ + RING_DBG("Start recorded file playback %s", filepath.c_str()); + + int sampleRate; + { + std::lock_guard<std::mutex> lock(audioLayerMutex_); + + if (not audiodriver_) { + RING_ERR("No audio layer in start recorded file playback"); + return false; + } + + sampleRate = audiodriver_->getSampleRate(); + } + + { + std::lock_guard<std::mutex> m(toneMutex_); + + if (audiofile_) { + emitSignal<DRing::CallSignal::RecordPlaybackStopped>(audiofile_->getFilePath()); + audiofile_.reset(); + } + + try { + updateAudioFile(filepath, sampleRate); + if (not audiofile_) + return false; + } catch (const AudioFileException &e) { + RING_WARN("Audio file error: %s", e.what()); + return false; + } + } // release toneMutex + + { + std::lock_guard<std::mutex> lock(audioLayerMutex_); + audiodriver_->startStream(); + } + + return true; +} + +void ManagerImpl::recordingPlaybackSeek(const double value) +{ + std::lock_guard<std::mutex> m(toneMutex_); + if (audiofile_) + audiofile_.get()->seek(value); +} + +void ManagerImpl::stopRecordedFilePlayback(const std::string& filepath) +{ + RING_DBG("Stop recorded file playback %s", filepath.c_str()); + + checkAudio(); + + { + std::lock_guard<std::mutex> m(toneMutex_); + audiofile_.reset(); + } + emitSignal<DRing::CallSignal::RecordPlaybackStopped>(filepath); +} + +void ManagerImpl::setHistoryLimit(int days) +{ + RING_DBG("Set history limit"); + preferences.setHistoryLimit(days); + saveConfig(); +} + +int +ManagerImpl::getHistoryLimit() const +{ + return preferences.getHistoryLimit(); +} + +bool +ManagerImpl::setAudioManager(const std::string &api) +{ + { + std::lock_guard<std::mutex> lock(audioLayerMutex_); + + if (not audiodriver_) + return false; + + if (api == audioPreference.getAudioApi()) { + RING_DBG("Audio manager chosen already in use. No changes made. "); + return true; + } + } + + { + std::lock_guard<std::mutex> lock(audioLayerMutex_); + + bool wasStarted = audiodriver_->isStarted(); + audioPreference.setAudioApi(api); + audiodriver_.reset(audioPreference.createAudioLayer()); + + if (audiodriver_ and wasStarted) + audiodriver_->startStream(); + } + + saveConfig(); + + // ensure that we completed the transition (i.e. no fallback was used) + return api == audioPreference.getAudioApi(); +} + +std::string +ManagerImpl::getAudioManager() const +{ + return audioPreference.getAudioApi(); +} + +int +ManagerImpl::getAudioInputDeviceIndex(const std::string &name) +{ + std::lock_guard<std::mutex> lock(audioLayerMutex_); + + if (not audiodriver_) { + RING_ERR("Audio layer not initialized"); + return 0; + } + + return audiodriver_->getAudioDeviceIndex(name, DeviceType::CAPTURE); +} + +int +ManagerImpl::getAudioOutputDeviceIndex(const std::string &name) +{ + std::lock_guard<std::mutex> lock(audioLayerMutex_); + + if (not audiodriver_) { + RING_ERR("Audio layer not initialized"); + return 0; + } + + return audiodriver_->getAudioDeviceIndex(name, DeviceType::PLAYBACK); +} + +std::string +ManagerImpl::getCurrentAudioOutputPlugin() const +{ + return audioPreference.getAlsaPlugin(); +} + +bool +ManagerImpl::getNoiseSuppressState() const +{ + return audioPreference.getNoiseReduce(); +} + +void +ManagerImpl::setNoiseSuppressState(bool state) +{ + audioPreference.setNoiseReduce(state); +} + +bool +ManagerImpl::isAGCEnabled() const +{ + return audioPreference.isAGCEnabled(); +} + +void +ManagerImpl::setAGCState(bool state) +{ + audioPreference.setAGCState(state); +} + +/** + * Initialization: Main Thread + */ +void +ManagerImpl::initAudioDriver() +{ + std::lock_guard<std::mutex> lock(audioLayerMutex_); + audiodriver_.reset(audioPreference.createAudioLayer()); +} + +AudioFormat +ManagerImpl::hardwareAudioFormatChanged(AudioFormat format) +{ + return audioFormatUsed(format); +} + +AudioFormat +ManagerImpl::audioFormatUsed(AudioFormat format) +{ + AudioFormat currentFormat = ringbufferpool_->getInternalAudioFormat(); + format.nb_channels = std::max(currentFormat.nb_channels, std::min(format.nb_channels, 2u)); // max 2 channels. + format.sample_rate = std::max(currentFormat.sample_rate, format.sample_rate); + + if (currentFormat == format) + return format; + + RING_DBG("Audio format changed: %s -> %s", currentFormat.toString().c_str(), format.toString().c_str()); + + ringbufferpool_->setInternalAudioFormat(format); + + { + std::lock_guard<std::mutex> toneLock(toneMutex_); + telephoneTone_.reset(new TelephoneTone(preferences.getZoneToneChoice(), format.sample_rate)); + } + dtmfKey_.reset(new DTMF(format.sample_rate)); + return format; +} + +void +ManagerImpl::setAccountsOrder(const std::string& order) +{ + RING_DBG("Set accounts order : %s", order.c_str()); + // Set the new config + + preferences.setAccountOrder(order); + + saveConfig(); +} + +std::vector<std::string> +ManagerImpl::getAccountList() const +{ + // TODO: this code looks weird. need further investigation! + + using std::vector; + using std::string; + vector<string> account_order(loadAccountOrder()); + + // The IP2IP profile is always available, and first in the list + + vector<string> v; + + // Concatenate all account pointers in a single map + const auto& allAccounts = accountFactory_.getAllAccounts(); + + // If no order has been set, load the default one ie according to the creation date. + if (account_order.empty()) { + for (const auto &account : allAccounts) { + if (account->isIP2IP()) + continue; + v.push_back(account->getAccountID()); + } + } else { + const auto& ip2ipAccountID = getIP2IPAccount()->getAccountID(); + for (const auto& id : account_order) { + if (id.empty() or id == ip2ipAccountID) + continue; + + if (accountFactory_.hasAccount(id)) + v.push_back(id); + } + } + + if (const auto& account = getIP2IPAccount()) + v.push_back(account->getAccountID()); + else + RING_ERR("could not find IP2IP profile in getAccount list"); + + return v; +} + +std::map<std::string, std::string> +ManagerImpl::getAccountDetails(const std::string& accountID) const +{ + const auto account = getAccount(accountID); + + if (account) { + return account->getAccountDetails(); + } else { + RING_ERR("Could not get account details on a non-existing accountID %s", accountID.c_str()); + // return an empty map since we can't throw an exception to D-Bus + return std::map<std::string, std::string>(); + } +} + +std::map<std::string, std::string> +ManagerImpl::getVolatileAccountDetails(const std::string& accountID) const +{ + const auto account = getAccount(accountID); + + if (account) { + return account->getVolatileAccountDetails(); + } else { + RING_ERR("Could not get volatile account details on a non-existing accountID %s", accountID.c_str()); + return {{}}; + } +} + + +// method to reduce the if/else mess. +// Even better, switch to XML ! + +void +ManagerImpl::setAccountDetails(const std::string& accountID, + const std::map<std::string, std::string>& details) +{ + RING_DBG("Set account details for %s", accountID.c_str()); + + const auto account = getAccount(accountID); + + if (account == nullptr) { + RING_ERR("Could not find account %s", accountID.c_str()); + return; + } + + // Ignore if nothing has changed + if (details == account->getAccountDetails()) + return; + + // Unregister before modifying any account information + account->doUnregister([&](bool /* transport_free */) { + account->setAccountDetails(details); + // Serialize configuration to disk once it is done + saveConfig(); + + if (account->isEnabled()) { + account->doRegister(); + } else + account->doUnregister(); + + // Update account details to the client side + emitSignal<DRing::ConfigurationSignal::AccountsChanged>(); + }); +} + +std::string +ManagerImpl::addAccount(const std::map<std::string, std::string>& details) +{ + /** @todo Deal with both the accountMap_ and the Configuration */ + std::string newAccountID; + static std::uniform_int_distribution<uint64_t> rand_acc_id; + + const std::vector<std::string> accountList(getAccountList()); + + do { + std::ostringstream accId; + accId << std::hex << rand_acc_id(rand_); + newAccountID = accId.str(); + } while (std::find(accountList.begin(), accountList.end(), newAccountID) + != accountList.end()); + + // Get the type + + const char* accountType; + if (details.find(Conf::CONFIG_ACCOUNT_TYPE) != details.end()) + accountType = (*details.find(Conf::CONFIG_ACCOUNT_TYPE)).second.c_str(); + else + accountType = AccountFactory::DEFAULT_ACCOUNT_TYPE; + + RING_DBG("Adding account %s", newAccountID.c_str()); + + auto newAccount = accountFactory_.createAccount(accountType, newAccountID); + if (!newAccount) { + RING_ERR("Unknown %s param when calling addAccount(): %s", + Conf::CONFIG_ACCOUNT_TYPE, accountType); + return ""; + } + + newAccount->setAccountDetails(details); + + preferences.addAccount(newAccountID); + + newAccount->doRegister(); + + saveConfig(); + + emitSignal<DRing::ConfigurationSignal::AccountsChanged>(); + + return newAccountID; +} + +void ManagerImpl::removeAccounts() +{ + for (const auto &acc : getAccountList()) + removeAccount(acc); +} + +void ManagerImpl::removeAccount(const std::string& accountID) +{ + // Get it down and dying + if (const auto& remAccount = getAccount(accountID)) { + remAccount->doUnregister(); + accountFactory_.removeAccount(*remAccount); + } + + preferences.removeAccount(accountID); + + saveConfig(); + + emitSignal<DRing::ConfigurationSignal::AccountsChanged>(); +} + +bool +ManagerImpl::isValidCall(const std::string& callID) +{ + return static_cast<bool>(getCallFromCallID(callID)); +} + +std::string +ManagerImpl::getNewCallID() +{ + static std::uniform_int_distribution<uint64_t> rand_call_id; + std::ostringstream random_id; + + // generate something like s7ea037947eb9fb2f + do { + random_id.clear(); + random_id << rand_call_id(rand_); + } while (isValidCall(random_id.str())); + + return random_id.str(); +} + +std::vector<std::string> +ManagerImpl::loadAccountOrder() const +{ + return split_string(preferences.getAccountOrder(), '/'); +} + +void +ManagerImpl::loadAccount(const YAML::Node &node, int &errorCount, + const std::string &accountOrder) +{ + using yaml_utils::parseValue; + + std::string accountType; + parseValue(node, "type", accountType); + + std::string accountid; + parseValue(node, "id", accountid); + + std::string accountAlias; + parseValue(node, "alias", accountAlias); + const auto inAccountOrder = [&](const std::string & id) { + return accountOrder.find(id + "/") != std::string::npos; + }; + + if (!accountid.empty() and !accountAlias.empty()) { + const auto& ip2ipAccountID = getIP2IPAccount()->getAccountID(); + if (not inAccountOrder(accountid) and accountid != ip2ipAccountID) { + RING_WARN("Dropping account %s, which is not in account order", accountid.c_str()); + } else if (accountFactory_.isSupportedType(accountType.c_str())) { + std::shared_ptr<Account> a; + if (accountid != ip2ipAccountID) + a = accountFactory_.createAccount(accountType.c_str(), accountid); + else + a = accountFactory_.getIP2IPAccount(); + if (a) { + a->unserialize(node); + } else { + RING_ERR("Failed to create account type \"%s\"", accountType.c_str()); + ++errorCount; + } + } else { + RING_WARN("Ignoring unknown account type \"%s\"", accountType.c_str()); + } + } +} + +int +ManagerImpl::loadAccountMap(const YAML::Node &node) +{ + accountFactory_.initIP2IPAccount(); + + // build preferences + preferences.unserialize(node); + voipPreferences.unserialize(node); + hookPreference.unserialize(node); + audioPreference.unserialize(node); + shortcutPreferences.unserialize(node); + + int errorCount = 0; + try { +#ifdef RING_VIDEO + getVideoDeviceMonitor().unserialize(node); +#endif + } catch (const YAML::Exception &e) { + RING_ERR("%s: No video node in config file", e.what()); + ++errorCount; + } + + const std::string accountOrder = preferences.getAccountOrder(); + + // load saved preferences for IP2IP account from configuration file + const auto &accountList = node["accounts"]; + + for (auto &a : accountList) { + loadAccount(a, errorCount, accountOrder); + } + + return errorCount; +} + +std::map<std::string, std::string> +ManagerImpl::getCallDetails(const std::string &callID) +{ + if (auto call = getCallFromCallID(callID)) { + return call->getDetails(); + } else { + RING_ERR("Call is NULL"); + // FIXME: is this even useful? + return Call::getNullDetails(); + } +} + +std::vector<std::string> +ManagerImpl::getCallList() const +{ + return callFactory.getCallIDs(); +} + +std::map<std::string, std::string> +ManagerImpl::getConferenceDetails( + const std::string& confID) const +{ + std::map<std::string, std::string> conf_details; + ConferenceMap::const_iterator iter_conf = conferenceMap_.find(confID); + + if (iter_conf != conferenceMap_.end()) { + conf_details["CONFID"] = confID; + conf_details["CONF_STATE"] = iter_conf->second->getStateStr(); + } + + return conf_details; +} + +std::vector<std::string> +ManagerImpl::getConferenceList() const +{ + std::vector<std::string> v; + map_utils::vectorFromMapKeys(conferenceMap_, v); + return v; +} + +std::vector<std::string> +ManagerImpl::getDisplayNames(const std::string& confID) const +{ + std::vector<std::string> v; + ConferenceMap::const_iterator iter_conf = conferenceMap_.find(confID); + + if (iter_conf != conferenceMap_.end()) { + return iter_conf->second->getDisplayNames(); + } else { + RING_WARN("Did not find conference %s", confID.c_str()); + } + + return v; +} + +std::vector<std::string> +ManagerImpl::getParticipantList(const std::string& confID) const +{ + std::vector<std::string> v; + ConferenceMap::const_iterator iter_conf = conferenceMap_.find(confID); + + if (iter_conf != conferenceMap_.end()) { + const ParticipantSet participants(iter_conf->second->getParticipantList()); + std::copy(participants.begin(), participants.end(), std::back_inserter(v));; + } else + RING_WARN("Did not find conference %s", confID.c_str()); + + return v; +} + +std::string +ManagerImpl::getConferenceId(const std::string& callID) +{ + if (auto call = getCallFromCallID(callID)) + return call->getConfId(); + + RING_ERR("Call is NULL"); + return ""; +} + +void +ManagerImpl::startAudioDriverStream() +{ + std::lock_guard<std::mutex> lock(audioLayerMutex_); + if (!audiodriver_) { + RING_ERR("Audio driver not initialized"); + return; + } + audiodriver_->startStream(); +} + +void +ManagerImpl::registerAccounts() +{ + auto allAccounts(getAccountList()); + + for (auto &item : allAccounts) { + const auto a = getAccount(item); + + if (!a) + continue; + + a->loadConfig(); + + if (a->isEnabled()) { + a->doRegister(); + } + } +} + +void +ManagerImpl::unregisterAccounts() +{ + for (const auto& account : getAllAccounts()) { + if (account->isEnabled()) + account->doUnregister(); + } +} + +void +ManagerImpl::sendRegister(const std::string& accountID, bool enable) +{ + const auto acc = getAccount(accountID); + if (!acc) + return; + + acc->setEnabled(enable); + acc->loadConfig(); + + Manager::instance().saveConfig(); + + if (acc->isEnabled()) { + acc->doRegister(); + } else + acc->doUnregister(); +} + +std::shared_ptr<AudioLayer> +ManagerImpl::getAudioDriver() +{ + return audiodriver_; +} + +std::shared_ptr<Call> +ManagerImpl::newOutgoingCall(const std::string& toUrl, + const std::string& preferredAccountId) +{ + auto account = Manager::instance().getIP2IPAccount(); + auto preferred = getAccount(preferredAccountId); + std::string finalToUrl = toUrl; + +#if HAVE_DHT + if (toUrl.find("ring:") != std::string::npos) { + if (preferred && preferred->getAccountType() == RingAccount::ACCOUNT_TYPE) + return preferred->newOutgoingCall(finalToUrl); + auto dhtAcc = getAllAccounts<RingAccount>(); + for (const auto& acc : dhtAcc) + if (acc->isEnabled()) + return acc->newOutgoingCall(finalToUrl); + } +#endif + + // FIXME: have a generic version to remove sip dependency + sip_utils::stripSipUriPrefix(finalToUrl); + + if (!IpAddr::isValid(finalToUrl)) { + account = preferred; + if (account) + finalToUrl = toUrl; + else + RING_WARN("Preferred account %s doesn't exist, using IP2IP account", + preferredAccountId.c_str()); + } else + RING_WARN("IP Url detected, using IP2IP account"); + + if (!account) { + RING_ERR("No suitable account found to create outgoing call"); + return nullptr; + } + + return account->newOutgoingCall(finalToUrl); +} + +#ifdef RING_VIDEO +std::shared_ptr<video::SinkClient> +ManagerImpl::createSinkClient(const std::string& id) +{ + const auto& iter = sinkMap_.find(id); + if (iter != std::end(sinkMap_)) { + if (iter->second.expired()) + sinkMap_.erase(iter); + else + return nullptr; + } + + auto sink = std::make_shared<video::SinkClient>(id); + sinkMap_.emplace(id, sink); + return sink; +} + +std::shared_ptr<video::SinkClient> +ManagerImpl::getSinkClient(const std::string& id) +{ + const auto& iter = sinkMap_.find(id); + if (iter != std::end(sinkMap_)) + if (auto sink = iter->second.lock()) + return sink; + return nullptr; +} +#endif // RING_VIDEO + +} // namespace ring diff --git a/src/managerimpl.h b/src/managerimpl.h new file mode 100644 index 0000000000..53d9f3990b --- /dev/null +++ b/src/managerimpl.h @@ -0,0 +1,1011 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * Author: Laurielle Lea <laurielle.lea@savoirfairelinux.com> + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Guillaume Carmel-Archambault <guillaume.carmel-archambault@savoirfairelinux.com> + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef MANAGER_IMPL_H_ +#define MANAGER_IMPL_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <string> +#include <vector> +#include <list> +#include <map> +#include <memory> +#include <mutex> +#include <random> +#include <atomic> +#include <functional> + +#include "conference.h" + +#include "account_factory.h" +#include "call_factory.h" + +#include "audio/audiolayer.h" +#include "audio/sound/tone.h" // for Tone::TONEID declaration + +#include "preferences.h" +#include "noncopyable.h" + +namespace ring { + +namespace Conf { +class YamlParser; +class YamlEmitter; +} + +namespace tls { +class GnuTlsGlobalInit; +} + +namespace video { +class SinkClient; +} + +class PluginManager; +class AudioFile; +class DTMF; +class TelephoneTone; + +/** To send multiple string */ +typedef std::list<std::string> TokenList; + +/** To store conference objects by conference ids */ +typedef std::map<std::string, std::shared_ptr<Conference> > ConferenceMap; + +typedef std::set<std::string> CallIDSet; + +static const char * const default_conf = "conf"; + +typedef std::set<std::string> CallIDSet; + +/** Manager (controller) of Ring daemon */ +class ManagerImpl { + private: + std::unique_ptr<PluginManager> pluginManager_; + + public: + ManagerImpl(); + ~ManagerImpl(); + + /** + * General preferences configuration + */ + Preferences preferences; + + /** + * Voip related preferences + */ + VoipPreference voipPreferences; + + /** + * Hook preferences + */ + HookPreference hookPreference; + + /** + * Audio preferences + */ + AudioPreference audioPreference; + + /** + * Shortcut preferences + */ + ShortcutPreferences shortcutPreferences; + + // Manager should not be accessed until initialized. + // FIXME this is an evil hack! + static std::atomic_bool initialized; + + /** + * Initialisation of thread (sound) and map. + * Init a new VoIPLink, audio codec and audio driver + */ + void init(const std::string &config_file); + + void setPath(const std::string &path); + + /* + * Terminate all threads and exit DBus loop + */ + void finish() noexcept; + + /** + * Accessor to audiodriver. + * it's multi-thread and use mutex internally + * @return AudioLayer* The audio layer object + */ + std::shared_ptr<AudioLayer> getAudioDriver(); + + void startAudioDriverStream(); + + /** + * Functions which occur with a user's action + * Place a new call + * @param accountId The account to make the call with + * @param to The recipient of the call + * @param conf_id The conference identifier if any + * @return id The call ID on success, empty string otherwise + */ + std::string outgoingCall(const std::string& accountId, + const std::string& to, + const std::string& conf_id = ""); + + /** + * Functions which occur with a user's action + * Answer the call + * @param id The call identifier + */ + bool answerCall(const std::string& id); + + /** + * Functions which occur with a user's action + * Hangup the call + * @param id The call identifier + */ + bool hangupCall(const std::string& id); + + + /** + * Functions which occur with a user's action + * Hangup the conference (hangup every participants) + * @param id The call identifier + */ + bool hangupConference(const std::string& id); + + /** + * Functions which occur with a user's action + * Put the call on hold + * @param id The call identifier + */ + bool onHoldCall(const std::string& id); + + /** + * Functions which occur with a user's action + * Put the call off hold + * @param id The call identifier + */ + bool offHoldCall(const std::string& id); + + /** + * Functions which occur with a user's action + * Transfer the call + * @param id The call identifier + * @param to The recipient of the transfer + */ + bool transferCall(const std::string& id, const std::string& to); + + /** + * Attended transfer + * @param The call id to be transfered + * @param The target + */ + bool attendedTransfer(const std::string& transferID, const std::string& targetID); + + /** + * Notify the client the transfer is successful + */ + void transferSucceeded(); + + /** + * Notify the client that the transfer failed + */ + void transferFailed(); + + /** + * Functions which occur with a user's action + * Refuse the call + * @param id The call identifier + */ + bool refuseCall(const std::string& id); + + /** + * Create a new conference given two participant + * @param the first participant ID + * @param the second participant ID + */ + std::shared_ptr<Conference> + createConference(const std::string& id1, const std::string& id2); + + /** + * Delete this conference + * @param the conference ID + */ + void removeConference(const std::string& conference_id); + + /** + * Return the conference id for which this call is attached + * @ param the call id + */ + std::shared_ptr<Conference> + getConferenceFromCallID(const std::string& call_id); + + /** + * Hold every participant to a conference + * @param the conference id + */ + bool holdConference(const std::string& conference_id); + + /** + * Unhold all conference participants + * @param the conference id + */ + bool unHoldConference(const std::string& conference_id); + + /** + * Test if this id is a conference (usefull to test current call) + * @param the call id + */ + bool isConference(const std::string& call_id) const; + + /** + * Test if a call id corresponds to a conference participant + * @param the call id + */ + bool isConferenceParticipant(const std::string& call_id); + + /** + * Add a participant to a conference + * @param the call id + * @param the conference id + */ + bool addParticipant(const std::string& call_id, const std::string& conference_id); + + /** + * Bind the main participant to a conference (mainly called on a double click action) + * @param the conference id + */ + bool addMainParticipant(const std::string& conference_id); + + /** + * Join two participants to create a conference + * @param the fist call id + * @param the second call id + */ + bool joinParticipant(const std::string& call_id1, + const std::string& call_id2); + + /** + * Create a conference from a list of participant + * @param A vector containing the list of participant + */ + void createConfFromParticipantList(const std::vector< std::string > &); + + /** + * Detach a participant from a conference, put the call on hold, do not hangup it + * @param call id + * @param the current call id + */ + bool detachParticipant(const std::string& call_id); + + /** + * Remove the conference participant from a conference + * @param call id + */ + void removeParticipant(const std::string& call_id); + + /** + * Join two conference together into one unique conference + */ + bool joinConference(const std::string& conf_id1, const std::string& conf_id2); + + void addStream(Call& call); + + void removeStream(Call& call); + + /** + * Save config to file + */ + void saveConfig(); + + /** + * @return true if we tried to register once + */ + bool hasTriedToRegister_; + + /** + * Play a ringtone + */ + void playTone(); + + /** + * Play a special ringtone ( BUSY ) if there's at least one message on the voice mail + */ + void playToneWithMessage(); + + /** + * Acts on the audio streams and audio files + */ + void stopTone(); + + /** + * Handle incoming call and notify user + * @param call A call pointer + * @param accountId an account id + */ + void incomingCall(Call &call, const std::string& accountId); + + /** + * Notify the user that the recipient of the call has answered and the put the + * call in Current state + * @param id The call identifier + */ + void peerAnsweredCall(Call& call); + + /** + * Rings back because the outgoing call is ringing and the put the + * call in Ringing state + * @param id The call identifier + */ + void peerRingingCall(Call& call); + + /** + * Put the call in Hungup state, remove the call from the list + * @param id The call identifier + */ + void peerHungupCall(Call& call); + +#if HAVE_INSTANT_MESSAGING + /** + * Notify the client with an incoming message + * @param accountId The account identifier + * @param message The content of the message + */ + void incomingMessage(const std::string& callID, const std::string& from, const std::string& message); + + + /** + * Send a new text message to the call, if participate to a conference, send to all participant. + * @param callID The call to send the message + * @param message The content of the message + * @param from The sender of this message (could be another participant of a conference) + */ + bool sendTextMessage(const std::string& callID, const std::string& message, const std::string& from); +#endif // HAVE_INSTANT_MESSAGING + + /** + * Notify the client he has voice mails + * @param accountId The account identifier + * @param nb_msg The number of messages + */ + void startVoiceMessageNotification(const std::string& accountId, int nb_msg); + + /** + * ConfigurationManager - Send registration request + * @param accountId The account to register/unregister + * @param enable The flag for the type of registration + * false for unregistration request + * true for registration request + */ + void sendRegister(const std::string& accountId, bool enable); + + /** + * Get account list + * @return std::vector<std::string> A list of accoundIDs + */ + std::vector<std::string> getAccountList() const; + + /** + * Set the account order in the config file + */ + void setAccountsOrder(const std::string& order); + + /** + * Retrieve details about a given account + * @param accountID The account identifier + * @return std::map< std::string, std::string > The account details + */ + std::map<std::string, std::string> getAccountDetails(const std::string& accountID) const; + + /** + * Retrieve volatile details such as recent registration errors + * @param accountID The account identifier + * @return std::map< std::string, std::string > The account volatile details + */ + std::map<std::string, std::string> getVolatileAccountDetails(const std::string& accountID) const; + + /** + * Retrieve details about a given call + * @param callID The account identifier + * @return std::map< std::string, std::string > The call details + */ + std::map<std::string, std::string> getCallDetails(const std::string& callID); + + /** + * Get call list + * @return std::vector<std::string> A list of call IDs + */ + std::vector<std::string> getCallList() const; + + /** + * Retrieve details about a given call + * @param callID The account identifier + * @return std::map< std::string, std::string > The call details + */ + std::map<std::string, std::string> getConferenceDetails(const std::string& callID) const; + + /** + * Get call list + * @return std::vector<std::string> A list of call IDs + */ + std::vector<std::string> getConferenceList() const; + + + /** + * Get a list of participant to a conference + * @return std::vector<std::string> A list of call IDs + */ + std::vector<std::string> getParticipantList(const std::string& confID) const; + + /** + * Get a list of the display names for everyone in a conference + * @return std::vector<std::string> A list of display names + */ + std::vector<std::string> getDisplayNames(const std::string& confID) const; + + std::string getConferenceId(const std::string& callID); + + /** + * Save the details of an existing account, given the account ID + * This will load the configuration map with the given data. + * It will also register/unregister links where the 'Enabled' switched. + * @param accountID The account identifier + * @param details The account parameters + */ + void setAccountDetails(const std::string& accountID, + const std::map<std::string, ::std::string > &details); + + /** + * Add a new account, and give it a new account ID automatically + * @param details The new account parameters + * @return The account Id given to the new account + */ + std::string addAccount(const std::map<std::string, std::string> &details); + + /** + * Delete an existing account, unregister VoIPLink associated, and + * purge from configuration. + * @param accountID The account unique ID + */ + void removeAccount(const std::string& accountID); + + /** + * Set input audio plugin + * @param audioPlugin The audio plugin + */ + void setAudioPlugin(const std::string& audioPlugin); + + /** + * Set audio device + * @param index The index of the soundcard + * @param the type of stream, either PLAYBACK, CAPTURE, RINGTONE + */ + void setAudioDevice(int index, DeviceType streamType); + + /** + * Get list of supported audio output device + * @return std::vector<std::string> A list of the audio devices supporting playback + */ + std::vector<std::string> getAudioOutputDeviceList(); + + /** + * Get list of supported audio input device + * @return std::vector<std::string> A list of the audio devices supporting capture + */ + std::vector<std::string> getAudioInputDeviceList(); + + /** + * Get string array representing integer indexes of output, input, and ringtone device + * @return std::vector<std::string> A list of the current audio devices + */ + std::vector<std::string> getCurrentAudioDevicesIndex(); + + /** + * Get index of an audio device + * @param name The string description of an audio device + * @return int His index + */ + int getAudioInputDeviceIndex(const std::string &name); + int getAudioOutputDeviceIndex(const std::string &name); + + /** + * Get current alsa plugin + * @return std::string The Alsa plugin + */ + std::string getCurrentAudioOutputPlugin() const; + + /** + * Get the noise reduction engin state from + * the current audio layer. + */ + bool getNoiseSuppressState() const; + + /** + * Set the noise reduction engin state in the current + * audio layer. + */ + void setNoiseSuppressState(bool state); + + bool isAGCEnabled() const; + void setAGCState(bool enabled); + + bool switchInput(const std::string& callid, const std::string& res); + + /** + * Ringtone option. + * If ringtone is enabled, ringtone on incoming call use custom choice. If not, only standart tone. + * @return int 1 if enabled + * 0 otherwise + */ + int isRingtoneEnabled(const std::string& id); + + /** + * Set the ringtone option + * Inverse current value + */ + void ringtoneEnabled(const std::string& id); + + /** + * Get is always recording functionality + */ + bool getIsAlwaysRecording() const; + + /** + * Set is always recording functionality, every calls will then be set in RECORDING mode + * once answered + */ + void setIsAlwaysRecording(bool isAlwaysRec); + + /** + * Set recording on / off + * Start recording + * @param id The call identifier + * Returns true if the call was set to record + */ + bool toggleRecordingCall(const std::string& id); + + /** + * Return true if the call is currently recorded + */ + bool isRecording(const std::string& id); + + /** + * Start playback fo a recorded file if and only if audio layer is not already started. + * @param File path of the file to play + */ + bool startRecordedFilePlayback(const std::string&); + + void recordingPlaybackSeek(const double value); + + /** + * Stop playback of recorded file + * @param File of the file to stop + */ + void stopRecordedFilePlayback(const std::string&); + + /** + * Set the maximum number of days to keep in the history + * @param calls The number of days + */ + void setHistoryLimit(int days); + + /** + * Get the maximum number of days to keep in the history + * @return double The number of days + */ + int getHistoryLimit() const; + + /** + * Configure the start-up option + * @return int 1 if Ring should start in the system tray + * 0 otherwise + */ + int isStartHidden(); + + /** + * Configure the start-up option + * At startup, Ring can be displayed or start hidden in the system tray + */ + void startHidden(); + + /** + * Get the audio manager + * @return int The audio manager + * "alsa" + * "pulseaudio" + */ + std::string getAudioManager() const; + + /** + * Set the audio manager + * @return true if api is now in use, false otherwise + */ + bool setAudioManager(const std::string &api); + + /** + * Callback called when the audio layer initialised with its + * preferred format. + */ + AudioFormat hardwareAudioFormatChanged(AudioFormat format); + + /** + * Should be called by any component dealing with an external + * audio source, indicating the format used so the mixer format + * can be eventually adapted. + * @returns the new format used by the main buffer. + */ + AudioFormat audioFormatUsed(AudioFormat format); + + /** + * Handle audio sounds heard by a caller while they wait for their + * connection to a called party to be completed. + */ + void ringback(); + + /** + * Handle played music when an incoming call occurs + */ + void playRingtone(const std::string& accountID); + + /** + * Handle played music when a congestion occurs + */ + void congestion(); + + /** + * Play the dtmf-associated sound + * @param code The pressed key + */ + void playDtmf(char code); + + /** + * Handle played sound when a call can not be conpleted because of a busy recipient + */ + void callBusy(Call& call); + + /** + * Handle played sound when a failure occurs + */ + void callFailure(Call& call, int code=0); + + /** + * Retrieve the current telephone tone + * @return AudioLoop* The audio tone or 0 if no tone (init before calling this function) + */ + AudioLoop* getTelephoneTone(); + + /** + * Retrieve the current telephone file + * @return AudioLoop* The audio file or 0 if the wav is stopped + */ + AudioLoop* getTelephoneFile(); + + /** + * @return true is there is one or many incoming call waiting + * new call, not anwsered or refused + */ + bool incomingCallsWaiting(); + + /** + * Return a new random callid that is not present in the list + * @return std::string A brand new callid + */ + std::string getNewCallID(); + + /** + * Get the current call + * @return std::shared_ptr<Call> A call shared pointer (could be empty) + */ + std::shared_ptr<Call> getCurrentCall() const; + + /** + * Get the current call id + * @return std::string The call id or "" + */ + const std::string getCurrentCallId() const; + + /** + * Check if a call is the current one + * @param call the new call + * @return bool True if the call is the current + */ + bool isCurrentCall(const Call& call) const; + + void initAudioDriver(); + + /** + * Load the accounts order set by the user from the dringrc config file + * @return std::vector<std::string> A vector containing the account ID's + */ + std::vector<std::string> loadAccountOrder() const; + + + private: + void removeAccounts(); + + bool parseConfiguration(); + + // Set the ringtone or recorded call to be played + void updateAudioFile(const std::string &file, int sampleRate); + + /** + * Get the Call referred to by callID. If the Call does not exist, return NULL + */ + std::shared_ptr<Call> getCallFromCallID(const std::string &callID); + + /** + * Process remaining participant given a conference and the current call id. + * Mainly called when a participant is detached or hagned up + * @param current call id + * @param conference pointer + */ + void processRemainingParticipants(Conference &conf); + + /** + * Create config directory in home user and return configuration file path + */ + std::string retrieveConfigPath() const; + + void unsetCurrentCall(); + + void switchCall(std::shared_ptr<Call> call); + + /* + * Play one tone + * @return false if the driver is uninitialize + */ + void playATone(Tone::TONEID toneId); + + /** Current Call ID */ + std::shared_ptr<Call> currentCall_ = nullptr; + + /** Protected current call access */ + std::mutex currentCallMutex_; + + /** Audio layer */ + std::shared_ptr<AudioLayer> audiodriver_{nullptr}; + + // Main thread + std::unique_ptr<DTMF> dtmfKey_; + + /** Buffer to generate DTMF */ + AudioBuffer dtmfBuf_; + + ///////////////////// + // Protected by Mutex + ///////////////////// + std::mutex toneMutex_; + std::unique_ptr<TelephoneTone> telephoneTone_; + std::unique_ptr<AudioFile> audiofile_; + + // To handle volume control + // short speakerVolume_; + // short micVolume_; + // End of sound variable + + /** + * Mutex used to protect audio layer + */ + std::mutex audioLayerMutex_; + + /** + * Waiting Call Vectors + */ + CallIDSet waitingCalls_; + + /** + * Protect waiting call list, access by many voip/audio threads + */ + std::mutex waitingCallsMutex_; + + /** + * Add incoming callid to the waiting list + * @param id std::string to add + */ + void addWaitingCall(const std::string& id); + + /** + * Remove incoming callid to the waiting list + * @param id std::string to remove + */ + void removeWaitingCall(const std::string& id); + + /** + * Path of the ConfigFile + */ + std::string path_; + + /** + * Load the account map from configuration + */ + int loadAccountMap(const YAML::Node &node); + + /** + * Instance of the RingBufferPool for the whole application + * + * In order to send signal to other parts of the application, one must pass through the RingBufferMananger. + * Audio instances must be registered into the RingBufferMananger and bound together via the ManagerImpl. + * + */ + std::unique_ptr<RingBufferPool> ringbufferpool_; + + public: + + /** + * Return a pointer to the instance of the RingBufferPool + */ + RingBufferPool& getRingBufferPool() { return *ringbufferpool_; } + + /** + * Tell if there is a current call processed + * @return bool True if there is a current call + */ + bool hasCurrentCall() const; + + /** + * Get an account pointer, looks for both SIP and IAX + * @param accountID account ID to get + * @return std::shared_ptr<Account> Shared pointer on an Account instance or nullptr if not found + */ + template <class T=Account> + std::shared_ptr<T> getAccount(const std::string& accountID) const { + return accountFactory_.getAccount<T>(accountID); + } + + template <class T=Account> + std::vector<std::shared_ptr<T> > getAllAccounts() const { + return accountFactory_.getAllAccounts<T>(); + } + + template <class T=Account> + bool accountCount() const { + return accountFactory_.accountCount<T>(); + } + + std::shared_ptr<Account> getIP2IPAccount() const { + return accountFactory_.getIP2IPAccount(); + } + + // only used by test framework + bool hasAccount(const std::string& accountID) { + return accountFactory_.hasAccount(accountID); + } + + /** + * Send registration for all enabled accounts + */ + void registerAccounts(); + + /** + * Suspends Ring's audio processing if no calls remain, allowing + * other applications to resume audio. + * See: + * https://projects.savoirfairelinux.com/issues/7037 + */ + void checkAudio(); + + /** + * Call periodically to poll for VoIP events */ + void + pollEvents(); + + /** + * Create a new outgoing call + * @param toUrl The address to call + * @param preferredAccountId The IP of preferred account to use. + * This is not necessary the account used. + * @return Call* A shared pointer on a valid call. + * @note This function raises VoipLinkException() on errors. + */ + std::shared_ptr<Call> newOutgoingCall(const std::string& toUrl, + const std::string& preferredAccountId); + + CallFactory callFactory; + + using EventHandler = std::function<void()>; + + /** + * Install an event handler called periodically by pollEvents(). + * @param handlerId an unique identifier for the handler. + * @param handler the event handler function. + */ + void registerEventHandler(uintptr_t handlerId, EventHandler handler); + + /** + * Remove a previously registered event handler. + * @param handlerId id of handler to remove. + */ + void unregisterEventHandler(uintptr_t handlerId); + + IceTransportFactory& getIceTransportFactory() { return *ice_tf_; } + + void addTask(const std::function<bool()>&& task); + +#ifdef RING_VIDEO + std::shared_ptr<video::SinkClient> createSinkClient(const std::string& id=""); + + std::shared_ptr<video::SinkClient> getSinkClient(const std::string& id); +#endif // RING_VIDEO + + private: + NON_COPYABLE(ManagerImpl); + + std::map<uintptr_t, EventHandler> eventHandlerMap_; + decltype(eventHandlerMap_)::iterator nextEventHandler_; + + std::list<std::function<bool()>> pendingTaskList_; + + /** + * Test if call is a valid call, i.e. have been created and stored in + * call-account map + * @param callID the std::string to be tested + * @return true if call is created and present in the call-account map + */ + bool isValidCall(const std::string& callID); + + /** + * Send unregister for all enabled accounts + */ + void unregisterAccounts(); + + + // Map containing conference pointers + ConferenceMap conferenceMap_; + + std::atomic_bool finished_ {false}; + + AccountFactory accountFactory_; + + std::mt19937_64 rand_; + + void loadDefaultAccountMap(); + + void loadAccount(const YAML::Node &item, int &errorCount, + const std::string &accountOrder); + + /* ICE support */ + std::unique_ptr<IceTransportFactory> ice_tf_; + + std::unique_ptr<tls::GnuTlsGlobalInit> gnutlGIG_; + + /* Sink ID mapping */ + std::map<std::string, std::weak_ptr<video::SinkClient>> sinkMap_; +}; + +} // namespace ring + +#endif // MANAGER_IMPL_H_ diff --git a/src/map_utils.h b/src/map_utils.h new file mode 100644 index 0000000000..2f7eb81553 --- /dev/null +++ b/src/map_utils.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2013-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef MAP_UTILS_H_ +#define MAP_UTILS_H_ + +#include <vector> +#include <map> + +namespace ring { namespace map_utils { + +template <typename M, typename V> +void vectorFromMapKeys(const M &m, V &v) +{ + for (typename M::const_iterator it = m.begin(); it != m.end(); ++it) + v.push_back(it->first); +} + +template <typename M, typename V> +void vectorFromMapValues(const M &m, V &v) +{ + for (typename M::const_iterator it = m.begin(); it != m.end(); ++it) + v.push_back(it->second); +} + +template <typename M, typename V> +typename M::const_iterator +findByValue(const M &m, V &v) { + for (typename M::const_iterator it = m.begin(); it != m.end(); ++it) + if (it->second == v) + return it; + return m.cend(); +} + +}} // namespace ring::map_utils + +#endif // MAP_UTILS_H_ diff --git a/src/media/Makefile.am b/src/media/Makefile.am new file mode 100644 index 0000000000..ec6072a4fc --- /dev/null +++ b/src/media/Makefile.am @@ -0,0 +1,48 @@ +include $(top_srcdir)/globals.mak + +noinst_LTLIBRARIES = libmedia.la + +SUBDIRS = audio + +if RING_VIDEO +SUBDIRS += video +endif + +libmedia_la_SOURCES = \ + libav_utils.cpp \ + socket_pair.cpp \ + media_buffer.cpp \ + media_decoder.cpp \ + media_encoder.cpp \ + media_io_handle.cpp \ + media_codec.cpp \ + system_codec_container.cpp \ + srtp.c + +noinst_HEADERS = \ + rtp_session.h \ + libav_utils.h \ + libav_deps.h \ + socket_pair.h \ + media_buffer.h \ + media_decoder.h \ + media_encoder.h \ + media_io_handle.h \ + media_device.h \ + media_codec.h \ + system_codec_container.h \ + srtp.h + +libmedia_la_LIBADD = \ + ./audio/libaudio.la + +if RING_VIDEO +libmedia_la_libADD = \ + ./video/libvideo.la +endif + +libmedia_la_LDFLAGS = @LIBAVCODEC_LIBS@ @LIBAVFORMAT_LIBS@ @LIBAVDEVICE_LIBS@ @LIBSWSCALE_LIBS@ @LIBAVUTIL_LIBS@ + +AM_CFLAGS=@LIBAVCODEC_CFLAGS@ @LIBAVFORMAT_CFLAGS@ @LIBAVDEVICE_CFLAGS@ @LIBSWSCALE_CFLAGS@ + +AM_CXXFLAGS=@LIBAVCODEC_CFLAGS@ @LIBAVFORMAT_CFLAGS@ @LIBAVDEVICE_CFLAGS@ @LIBSWSCALE_CFLAGS@ diff --git a/src/media/audio/Makefile.am b/src/media/audio/Makefile.am new file mode 100644 index 0000000000..d39e5851b2 --- /dev/null +++ b/src/media/audio/Makefile.am @@ -0,0 +1,91 @@ +include $(top_srcdir)/globals.mak + +noinst_LTLIBRARIES = libaudio.la + +SUBDIRS = sound + +if BUILD_OPENSL +SUBDIRS += opensl +endif + +if BUILD_ALSA +SUBDIRS += alsa +endif + +if BUILD_PULSE +SUBDIRS += pulseaudio +endif + +if BUILD_JACK +SUBDIRS += jack +endif + +if HAVE_OSX +SUBDIRS += coreaudio +endif + +if BUILD_SPEEXDSP +RING_SPEEXDSP_SRC=dsp.cpp +RING_SPEEXDSP_HEAD=dsp.h +endif + +libaudio_la_SOURCES = \ + audiobuffer.cpp \ + audioloop.cpp \ + ringbuffer.cpp \ + ringbufferpool.cpp \ + audiorecord.cpp \ + audiorecorder.cpp \ + recordable.cpp \ + audiolayer.cpp \ + resampler.cpp \ + $(RING_SPEEXDSP_SRC) \ + dcblocker.cpp \ + audio_rtp_session.cpp + +libaudio_la_CXXFLAGS = -I$(top_srcdir)/src +libaudio_la_LDFLAGS = + +if BUILD_SPEEXDSP +libaudio_la_CXXFLAGS += @SPEEXDSP_CFLAGS@ +libaudio_la_LDFLAGS += @SPEEXDSP_LIBS@ +endif + +noinst_HEADERS = \ + audiobuffer.h \ + audioloop.h \ + ringbuffer.h \ + ringbufferpool.h \ + audiorecord.h \ + audiorecorder.h \ + audiolayer.h \ + recordable.h \ + $(RING_SPEEXDSP_HEAD) \ + dcblocker.h \ + resampler.h \ + audio_rtp_session.h + +libaudio_la_LIBADD = \ + ./sound/libsound.la + +if BUILD_PULSE +libaudio_la_LIBADD += ./pulseaudio/libpulselayer.la +endif + +if BUILD_JACK +libaudio_la_LIBADD += ./jack/libjacklayer.la +libaudio_la_LDFLAGS += @JACK_LIBS@ -pthread +endif + +if BUILD_ALSA +libaudio_la_LIBADD += ./alsa/libalsalayer.la +endif + +if HAVE_OSX +libaudio_la_LIBADD += ./coreaudio/libcoreaudiolayer.la +endif + +if BUILD_OPENSL +libaudio_la_LIBADD += ./opensl/libopensl.la +libaudio_la_LDFLAGS += -lOpenSLES +endif diff --git a/src/media/audio/alsa/Makefile.am b/src/media/audio/alsa/Makefile.am new file mode 100644 index 0000000000..ce6f2ad100 --- /dev/null +++ b/src/media/audio/alsa/Makefile.am @@ -0,0 +1,11 @@ +include $(top_srcdir)/globals.mak + +if BUILD_ALSA + +noinst_LTLIBRARIES = libalsalayer.la + +libalsalayer_la_SOURCES = alsalayer.cpp + +noinst_HEADERS = alsalayer.h + +endif diff --git a/src/media/audio/alsa/alsalayer.cpp b/src/media/audio/alsa/alsalayer.cpp new file mode 100644 index 0000000000..05e3733972 --- /dev/null +++ b/src/media/audio/alsa/alsalayer.cpp @@ -0,0 +1,854 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author: Андрей Лухнов <aol.nnov@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "alsalayer.h" +#include "logger.h" +#include "manager.h" +#include "noncopyable.h" +#include "client/signal.h" +#include "audio/ringbufferpool.h" +#include "audio/ringbuffer.h" +#include "audio/resampler.h" + +#include <thread> +#include <atomic> + +namespace ring { + +class AlsaThread { + public: + AlsaThread(AlsaLayer *alsa); + ~AlsaThread(); + void initAudioLayer(); + void start(); + bool isRunning() const; + + private: + void run(); + + NON_COPYABLE(AlsaThread); + std::thread thread_; + AlsaLayer* alsa_; + std::atomic<bool> running_; +}; + +AlsaThread::AlsaThread(AlsaLayer *alsa) + : thread_(), alsa_(alsa), running_(false) +{} + +bool AlsaThread::isRunning() const +{ + return running_; +} + +AlsaThread::~AlsaThread() +{ + running_ = false; + if (thread_.joinable()) + thread_.join(); +} + +void AlsaThread::start() +{ + running_ = true; + thread_ = std::thread(&AlsaThread::run, this); +} + +void AlsaThread::initAudioLayer(void) +{ + std::string pcmp; + std::string pcmr; + std::string pcmc; + + if (alsa_->audioPlugin_ == PCM_DMIX_DSNOOP) { + pcmp = alsa_->buildDeviceTopo(PCM_DMIX, alsa_->indexOut_); + pcmr = alsa_->buildDeviceTopo(PCM_DMIX, alsa_->indexRing_); + pcmc = alsa_->buildDeviceTopo(PCM_DSNOOP, alsa_->indexIn_); + } else { + pcmp = alsa_->buildDeviceTopo(alsa_->audioPlugin_, alsa_->indexOut_); + pcmr = alsa_->buildDeviceTopo(alsa_->audioPlugin_, alsa_->indexRing_); + pcmc = alsa_->buildDeviceTopo(alsa_->audioPlugin_, alsa_->indexIn_); + } + + if (not alsa_->is_capture_open_) { + alsa_->is_capture_open_ = alsa_->openDevice(&alsa_->captureHandle_, pcmc, SND_PCM_STREAM_CAPTURE); + + if (not alsa_->is_capture_open_) + emitSignal<DRing::ConfigurationSignal::Error>(ALSA_CAPTURE_DEVICE); + } + + if (not alsa_->is_playback_open_) { + alsa_->is_playback_open_ = alsa_->openDevice(&alsa_->playbackHandle_, pcmp, SND_PCM_STREAM_PLAYBACK); + + if (not alsa_->is_playback_open_) + emitSignal<DRing::ConfigurationSignal::Error>(ALSA_PLAYBACK_DEVICE); + + if (alsa_->getIndexPlayback() != alsa_->getIndexRingtone()) + if (!alsa_->openDevice(&alsa_->ringtoneHandle_, pcmr, SND_PCM_STREAM_PLAYBACK)) + emitSignal<DRing::ConfigurationSignal::Error>(ALSA_PLAYBACK_DEVICE); + } + + alsa_->hardwareFormatAvailable(alsa_->getFormat()); + + alsa_->prepareCaptureStream(); + alsa_->preparePlaybackStream(); + + alsa_->startCaptureStream(); + alsa_->startPlaybackStream(); + + alsa_->flushMain(); + alsa_->flushUrgent(); +} + +/** + * Reimplementation of run() + */ +void AlsaThread::run() +{ + initAudioLayer(); + alsa_->isStarted_ = true; + + while (alsa_->isStarted_ and running_) { + alsa_->audioCallback(); + } +} + +AlsaLayer::AlsaLayer(const AudioPreference &pref) + : AudioLayer(pref) + , indexIn_(pref.getAlsaCardin()) + , indexOut_(pref.getAlsaCardout()) + , indexRing_(pref.getAlsaCardring()) + , playbackHandle_(nullptr) + , ringtoneHandle_(nullptr) + , captureHandle_(nullptr) + , audioPlugin_(pref.getAlsaPlugin()) + , playbackBuff_(0, audioFormat_) + , captureBuff_(0, audioFormat_) + , playbackIBuff_(1024) + , captureIBuff_(1024) + , is_playback_prepared_(false) + , is_capture_prepared_(false) + , is_playback_running_(false) + , is_capture_running_(false) + , is_playback_open_(false) + , is_capture_open_(false) + , audioThread_(nullptr) + , mainRingBuffer_(Manager::instance().getRingBufferPool().getRingBuffer(RingBufferPool::DEFAULT_ID)) +{} + +AlsaLayer::~AlsaLayer() +{ + isStarted_ = false; + delete audioThread_; + + /* Then close the audio devices */ + closeCaptureStream(); + closePlaybackStream(); +} + +// Retry approach taken from pa_linux_alsa.c, part of PortAudio +bool AlsaLayer::openDevice(snd_pcm_t **pcm, const std::string &dev, snd_pcm_stream_t stream) +{ + RING_DBG("Alsa: Opening %s", dev.c_str()); + + static const int MAX_RETRIES = 100; + int err = snd_pcm_open(pcm, dev.c_str(), stream, 0); + + // Retry if busy, since dmix plugin may not have released the device yet + for (int tries = 0; tries < MAX_RETRIES and err == -EBUSY; ++tries) { + const struct timespec req = {0, 100000000L}; + nanosleep(&req, 0); + err = snd_pcm_open(pcm, dev.c_str(), stream, 0); + } + + if (err < 0) { + RING_ERR("Alsa: couldn't open device %s : %s", dev.c_str(), + snd_strerror(err)); + return false; + } + + if (!alsa_set_params(*pcm)) { + snd_pcm_close(*pcm); + return false; + } + + return true; +} + +void +AlsaLayer::startStream() +{ + dcblocker_.reset(); + + if (is_playback_running_ and is_capture_running_) + return; + + if (audioThread_ == NULL) { + audioThread_ = new AlsaThread(this); + audioThread_->start(); + } else if (!audioThread_->isRunning()) { + audioThread_->start(); + } +} + +void +AlsaLayer::stopStream() +{ + isStarted_ = false; + + delete audioThread_; + audioThread_ = NULL; + + closeCaptureStream(); + closePlaybackStream(); + + playbackHandle_ = NULL; + captureHandle_ = NULL; + ringtoneHandle_ = NULL; + + /* Flush the ring buffers */ + flushUrgent(); + flushMain(); +} + +/* + * GCC extension : statement expression + * + * ALSA_CALL(function_call, error_string) will: + * call the function + * display an error if the function failed + * return the function return value + */ +#define ALSA_CALL(call, error) ({ \ + int err_code = call; \ + if (err_code < 0) \ + RING_ERR(error ": %s", snd_strerror(err_code)); \ + err_code; \ + }) + +void AlsaLayer::stopCaptureStream() +{ + if (captureHandle_ && ALSA_CALL(snd_pcm_drop(captureHandle_), "couldn't stop capture") >= 0) { + is_capture_running_ = false; + is_capture_prepared_ = false; + } +} + +void AlsaLayer::closeCaptureStream() +{ + if (is_capture_prepared_ and is_capture_running_) + stopCaptureStream(); + + if (is_capture_open_ && ALSA_CALL(snd_pcm_close(captureHandle_), "Couldn't close capture") >= 0) + is_capture_open_ = false; +} + +void AlsaLayer::startCaptureStream() +{ + if (captureHandle_ and not is_capture_running_) + if (ALSA_CALL(snd_pcm_start(captureHandle_), "Couldn't start capture") >= 0) + is_capture_running_ = true; +} + +void AlsaLayer::stopPlaybackStream() +{ + if (ringtoneHandle_ and is_playback_running_) + ALSA_CALL(snd_pcm_drop(ringtoneHandle_), "Couldn't stop ringtone"); + + if (playbackHandle_ and is_playback_running_) { + if (ALSA_CALL(snd_pcm_drop(playbackHandle_), "Couldn't stop playback") >= 0) { + is_playback_running_ = false; + is_playback_prepared_ = false; + } + } +} + + +void AlsaLayer::closePlaybackStream() +{ + if (is_playback_prepared_ and is_playback_running_) + stopPlaybackStream(); + + if (is_playback_open_) { + if (ringtoneHandle_) + ALSA_CALL(snd_pcm_close(ringtoneHandle_), "Couldn't stop ringtone"); + + if (ALSA_CALL(snd_pcm_close(playbackHandle_), "Coulnd't close playback") >= 0) + is_playback_open_ = false; + } + +} + +void AlsaLayer::startPlaybackStream() +{ + is_playback_running_ = true; +} + +void AlsaLayer::prepareCaptureStream() +{ + if (is_capture_open_ and not is_capture_prepared_) + if (ALSA_CALL(snd_pcm_prepare(captureHandle_), "Couldn't prepare capture") >= 0) + is_capture_prepared_ = true; +} + +void AlsaLayer::preparePlaybackStream() +{ + is_playback_prepared_ = true; +} + +bool AlsaLayer::alsa_set_params(snd_pcm_t *pcm_handle) +{ +#define TRY(call, error) do { \ + if (ALSA_CALL(call, error) < 0) \ + return false; \ +} while(0) + + snd_pcm_hw_params_t *hwparams; + snd_pcm_hw_params_alloca(&hwparams); + + const unsigned RING_ALSA_PERIOD_SIZE = 160; + const unsigned RING_ALSA_NB_PERIOD = 8; + const unsigned RING_ALSA_BUFFER_SIZE = RING_ALSA_PERIOD_SIZE * RING_ALSA_NB_PERIOD; + + snd_pcm_uframes_t period_size = RING_ALSA_PERIOD_SIZE; + snd_pcm_uframes_t buffer_size = RING_ALSA_BUFFER_SIZE; + unsigned int periods = RING_ALSA_NB_PERIOD; + + snd_pcm_uframes_t period_size_min = 0; + snd_pcm_uframes_t period_size_max = 0; + snd_pcm_uframes_t buffer_size_min = 0; + snd_pcm_uframes_t buffer_size_max = 0; + +#define HW pcm_handle, hwparams /* hardware parameters */ + TRY(snd_pcm_hw_params_any(HW), "hwparams init"); + + TRY(snd_pcm_hw_params_set_access(HW, SND_PCM_ACCESS_RW_INTERLEAVED), "access type"); + TRY(snd_pcm_hw_params_set_format(HW, SND_PCM_FORMAT_S16_LE), "sample format"); + + TRY(snd_pcm_hw_params_set_rate_resample(HW, 0), "hardware sample rate"); /* prevent software resampling */ + TRY(snd_pcm_hw_params_set_rate_near(HW, &audioFormat_.sample_rate, nullptr), "sample rate"); + + // TODO: use snd_pcm_query_chmaps or similar to get hardware channel num + audioFormat_.nb_channels = 2; + TRY(snd_pcm_hw_params_set_channels_near(HW, &audioFormat_.nb_channels), "channel count"); + + snd_pcm_hw_params_get_buffer_size_min(hwparams, &buffer_size_min); + snd_pcm_hw_params_get_buffer_size_max(hwparams, &buffer_size_max); + snd_pcm_hw_params_get_period_size_min(hwparams, &period_size_min, nullptr); + snd_pcm_hw_params_get_period_size_max(hwparams, &period_size_max, nullptr); + RING_DBG("Buffer size range from %lu to %lu", buffer_size_min, buffer_size_max); + RING_DBG("Period size range from %lu to %lu", period_size_min, period_size_max); + buffer_size = buffer_size > buffer_size_max ? buffer_size_max : buffer_size; + buffer_size = buffer_size < buffer_size_min ? buffer_size_min : buffer_size; + period_size = period_size > period_size_max ? period_size_max : period_size; + period_size = period_size < period_size_min ? period_size_min : period_size; + + TRY(snd_pcm_hw_params_set_buffer_size_near(HW, &buffer_size), "Unable to set buffer size for playback"); + TRY(snd_pcm_hw_params_set_period_size_near(HW, &period_size, nullptr), "Unable to set period size for playback"); + TRY(snd_pcm_hw_params_set_periods_near(HW, &periods, nullptr), "Unable to set number of periods for playback"); + TRY(snd_pcm_hw_params(HW), "hwparams"); + + snd_pcm_hw_params_get_buffer_size(hwparams, &buffer_size); + snd_pcm_hw_params_get_period_size(hwparams, &period_size, nullptr); + snd_pcm_hw_params_get_rate(hwparams, &audioFormat_.sample_rate, nullptr); + snd_pcm_hw_params_get_channels(hwparams, &audioFormat_.nb_channels); + RING_DBG("Was set period_size = %lu", period_size); + RING_DBG("Was set buffer_size = %lu", buffer_size); + + if (2 * period_size > buffer_size) { + RING_ERR("buffer to small, could not use"); + return false; + } + +#undef HW + + RING_DBG("%s using format %s", + (snd_pcm_stream(pcm_handle) == SND_PCM_STREAM_PLAYBACK) ? "playback" : "capture", + audioFormat_.toString().c_str() ); + + snd_pcm_sw_params_t *swparams = NULL; + snd_pcm_sw_params_alloca(&swparams); + +#define SW pcm_handle, swparams /* software parameters */ + snd_pcm_sw_params_current(SW); + TRY(snd_pcm_sw_params_set_start_threshold(SW, period_size * 2), "start threshold"); + TRY(snd_pcm_sw_params(SW), "sw parameters"); +#undef SW + + return true; + +#undef TRY +} + +// TODO first frame causes broken pipe (underrun) because not enough data is sent +// we should wait until the handle is ready +void +AlsaLayer::write(AudioSample* buffer, int frames, snd_pcm_t * handle) +{ + // Skip empty buffers + if (!frames) + return; + + int err = snd_pcm_writei(handle, (const void*)buffer, frames); + + if (err < 0) + snd_pcm_recover(handle, err, 0); + + if (err >= 0) + return; + + switch (err) { + + case -EPIPE: + case -ESTRPIPE: + case -EIO: { + snd_pcm_status_t* status; + snd_pcm_status_alloca(&status); + + if (ALSA_CALL(snd_pcm_status(handle, status), "Cannot get playback handle status") >= 0) + if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN) { + stopPlaybackStream(); + preparePlaybackStream(); + startPlaybackStream(); + } + + ALSA_CALL(snd_pcm_writei(handle, (const void*)buffer, frames), "XRUN handling failed"); + break; + } + + case -EBADFD: { + snd_pcm_status_t* status; + snd_pcm_status_alloca(&status); + + if (ALSA_CALL(snd_pcm_status(handle, status), "Cannot get playback handle status") >= 0) { + if (snd_pcm_status_get_state(status) == SND_PCM_STATE_SETUP) { + RING_ERR("Writing in state SND_PCM_STATE_SETUP, should be " + "SND_PCM_STATE_PREPARED or SND_PCM_STATE_RUNNING"); + int error = snd_pcm_prepare(handle); + + if (error < 0) { + RING_ERR("Failed to prepare handle: %s", snd_strerror(error)); + stopPlaybackStream(); + } + } + } + + break; + } + + default: + RING_ERR("Unknown write error, dropping frames: %s", snd_strerror(err)); + stopPlaybackStream(); + break; + } +} + +int +AlsaLayer::read(AudioSample* buffer, int frames) +{ + if (snd_pcm_state(captureHandle_) == SND_PCM_STATE_XRUN) { + prepareCaptureStream(); + startCaptureStream(); + } + + int err = snd_pcm_readi(captureHandle_, (void*)buffer, frames); + + if (err >= 0) + return err; + + switch (err) { + case -EPIPE: + case -ESTRPIPE: + case -EIO: { + snd_pcm_status_t* status; + snd_pcm_status_alloca(&status); + + if (ALSA_CALL(snd_pcm_status(captureHandle_, status), "Get status failed") >= 0) + if (snd_pcm_status_get_state(status) == SND_PCM_STATE_XRUN) { + stopCaptureStream(); + prepareCaptureStream(); + startCaptureStream(); + } + + RING_ERR("XRUN capture ignored (%s)", snd_strerror(err)); + break; + } + + case -EPERM: + RING_ERR("Can't capture, EPERM (%s)", snd_strerror(err)); + prepareCaptureStream(); + startCaptureStream(); + break; + } + + return 0; +} + +std::string +AlsaLayer::buildDeviceTopo(const std::string &plugin, int card) +{ + std::stringstream ss; + std::string pcm(plugin); + + if (pcm == PCM_DEFAULT) + return pcm; + + ss << ":" << card; + + return pcm + ss.str(); +} + +static bool +safeUpdate(snd_pcm_t *handle, int &samples) +{ + samples = snd_pcm_avail_update(handle); + + if (samples < 0) { + samples = snd_pcm_recover(handle, samples, 0); + + if (samples < 0) { + RING_ERR("Got unrecoverable error from snd_pcm_avail_update: %s", snd_strerror(samples)); + return false; + } + } + + return true; +} + +static std::vector<std::string> +getValues(const std::vector<HwIDPair> &deviceMap) +{ + std::vector<std::string> audioDeviceList; + + for (const auto & dev : deviceMap) + audioDeviceList.push_back(dev.second); + + return audioDeviceList; +} + +std::vector<std::string> +AlsaLayer::getCaptureDeviceList() const +{ + return getValues(getAudioDeviceIndexMap(true)); +} + +std::vector<std::string> +AlsaLayer::getPlaybackDeviceList() const +{ + return getValues(getAudioDeviceIndexMap(false)); +} + +std::vector<HwIDPair> +AlsaLayer::getAudioDeviceIndexMap(bool getCapture) const +{ + snd_ctl_t* handle; + snd_ctl_card_info_t *info; + snd_pcm_info_t* pcminfo; + snd_ctl_card_info_alloca(&info); + snd_pcm_info_alloca(&pcminfo); + + int numCard = -1; + + std::vector<HwIDPair> audioDevice; + + if (snd_card_next(&numCard) < 0 || numCard < 0) + return audioDevice; + + do { + std::stringstream ss; + ss << numCard; + std::string name = "hw:" + ss.str(); + + if (snd_ctl_open(&handle, name.c_str(), 0) == 0) { + if (snd_ctl_card_info(handle, info) == 0) { + snd_pcm_info_set_device(pcminfo, 0); + snd_pcm_info_set_stream(pcminfo, getCapture ? SND_PCM_STREAM_CAPTURE : SND_PCM_STREAM_PLAYBACK); + + int err; + + if ((err = snd_ctl_pcm_info(handle, pcminfo)) < 0) { + RING_WARN("Cannot get info for %s %s: %s", getCapture ? + "capture device" : "playback device", name.c_str(), + snd_strerror(err)); + } else { + RING_DBG("card %i : %s [%s]", + numCard, + snd_ctl_card_info_get_id(info), + snd_ctl_card_info_get_name(info)); + std::string description = snd_ctl_card_info_get_name(info); + description.append(" - "); + description.append(snd_pcm_info_get_name(pcminfo)); + + // The number of the sound card is associated with a string description + audioDevice.push_back(HwIDPair(numCard, description)); + } + } + + snd_ctl_close(handle); + } + } while (snd_card_next(&numCard) >= 0 && numCard >= 0); + + + return audioDevice; +} + + +bool +AlsaLayer::soundCardIndexExists(int card, DeviceType stream) +{ + snd_pcm_info_t *pcminfo; + snd_pcm_info_alloca(&pcminfo); + std::string name("hw:"); + std::stringstream ss; + ss << card; + name.append(ss.str()); + + snd_ctl_t* handle; + + if (snd_ctl_open(&handle, name.c_str(), 0) != 0) + return false; + + snd_pcm_info_set_stream(pcminfo, stream == DeviceType::PLAYBACK ? SND_PCM_STREAM_PLAYBACK : SND_PCM_STREAM_CAPTURE); + bool ret = snd_ctl_pcm_info(handle, pcminfo) >= 0; + snd_ctl_close(handle); + return ret; +} + +int +AlsaLayer::getAudioDeviceIndex(const std::string &description, DeviceType type) const +{ + std::vector<HwIDPair> devices = getAudioDeviceIndexMap(type == DeviceType::CAPTURE); + + for (const auto & dev : devices) + if (dev.second == description) + return dev.first; + + // else return the default one + return 0; +} + +std::string +AlsaLayer::getAudioDeviceName(int index, DeviceType type) const +{ + // a bit ugly and wrong.. i do not know how to implement it better in alsalayer. + // in addition, for now it is used in pulselayer only due to alsa and pulse layers api differences. + // but after some tweaking in alsalayer, it could be used in it too. + switch (type) { + case DeviceType::PLAYBACK: + case DeviceType::RINGTONE: + return getPlaybackDeviceList().at(index); + + case DeviceType::CAPTURE: + return getCaptureDeviceList().at(index); + default: + // Should never happen + RING_ERR("Unexpected type"); + return ""; + } +} + +void AlsaLayer::capture() +{ + AudioFormat mainBufferFormat = Manager::instance().getRingBufferPool().getInternalAudioFormat(); + + int toGetFrames = snd_pcm_avail_update(captureHandle_); + + if (toGetFrames < 0) + RING_ERR("Audio: Mic error: %s", snd_strerror(toGetFrames)); + + if (toGetFrames <= 0) + return; + + const int framesPerBufferAlsa = 2048; + toGetFrames = std::min(framesPerBufferAlsa, toGetFrames); + captureIBuff_.resize(toGetFrames * audioFormat_.nb_channels); + + if (read(captureIBuff_.data(), toGetFrames) != toGetFrames) { + RING_ERR("ALSA MIC : Couldn't read!"); + return; + } + + captureBuff_.deinterleave(captureIBuff_, audioFormat_); + captureBuff_.applyGain(isCaptureMuted_ ? 0.0 : captureGain_); + + if (audioFormat_.nb_channels != mainBufferFormat.nb_channels) { + captureBuff_.setChannelNum(mainBufferFormat.nb_channels, true); + } + if (audioFormat_.sample_rate != mainBufferFormat.sample_rate) { + int outFrames = toGetFrames * (static_cast<double>(audioFormat_.sample_rate) / mainBufferFormat.sample_rate); + AudioBuffer rsmpl_in(outFrames, mainBufferFormat); + resampler_->resample(captureBuff_, rsmpl_in); + dcblocker_.process(rsmpl_in); + mainRingBuffer_->put(rsmpl_in); + } else { + dcblocker_.process(captureBuff_); + mainRingBuffer_->put(captureBuff_); + } +} + +void AlsaLayer::playback(int maxFrames) +{ + unsigned framesToGet = Manager::instance().getRingBufferPool().availableForGet(RingBufferPool::DEFAULT_ID); + + // no audio available, play tone or silence + if (framesToGet <= 0) { + // FIXME: not thread safe! we only lock the mutex when we get the + // pointer, we have no guarantee that it will stay safe to use + AudioLoop *tone = Manager::instance().getTelephoneTone(); + AudioLoop *file_tone = Manager::instance().getTelephoneFile(); + + playbackBuff_.setFormat(audioFormat_); + playbackBuff_.resize(maxFrames); + + if (tone) + tone->getNext(playbackBuff_, playbackGain_); + else if (file_tone && !ringtoneHandle_) + file_tone->getNext(playbackBuff_, playbackGain_); + else + playbackBuff_.reset(); + + playbackBuff_.interleave(playbackIBuff_); + write(playbackIBuff_.data(), playbackBuff_.frames(), playbackHandle_); + } else { + // play the regular sound samples + const AudioFormat mainBufferFormat = Manager::instance().getRingBufferPool().getInternalAudioFormat(); + const bool resample = audioFormat_.sample_rate != mainBufferFormat.sample_rate; + + double resampleFactor = 1.0; + unsigned maxNbFramesToGet = maxFrames; + + if (resample) { + resampleFactor = static_cast<double>(audioFormat_.sample_rate) / mainBufferFormat.sample_rate; + maxNbFramesToGet = maxFrames / resampleFactor; + } + + framesToGet = std::min(framesToGet, maxNbFramesToGet); + + playbackBuff_.setFormat(mainBufferFormat); + playbackBuff_.resize(framesToGet); + + Manager::instance().getRingBufferPool().getData(playbackBuff_, RingBufferPool::DEFAULT_ID); + playbackBuff_.applyGain(isPlaybackMuted_ ? 0.0 : playbackGain_); + + if (audioFormat_.nb_channels != mainBufferFormat.nb_channels) { + playbackBuff_.setChannelNum(audioFormat_.nb_channels, true); + } + if (resample) { + const size_t outFrames = framesToGet * resampleFactor; + AudioBuffer rsmpl_out(outFrames, audioFormat_); + resampler_->resample(playbackBuff_, rsmpl_out); + rsmpl_out.interleave(playbackIBuff_); + write(playbackIBuff_.data(), outFrames, playbackHandle_); + } else { + playbackBuff_.interleave(playbackIBuff_); + write(playbackIBuff_.data(), framesToGet, playbackHandle_); + } + } +} + +void AlsaLayer::audioCallback() +{ + if (!playbackHandle_ or !captureHandle_) + return; + + notifyIncomingCall(); + + snd_pcm_wait(playbackHandle_, 20); + + int playbackAvailFrames = 0; + + if (not safeUpdate(playbackHandle_, playbackAvailFrames)) + return; + + unsigned framesToGet = urgentRingBuffer_.availableForGet(RingBufferPool::DEFAULT_ID); + + if (framesToGet > 0) { + // Urgent data (dtmf, incoming call signal) come first. + framesToGet = std::min(framesToGet, (unsigned)playbackAvailFrames); + playbackBuff_.setFormat(audioFormat_); + playbackBuff_.resize(framesToGet); + urgentRingBuffer_.get(playbackBuff_, RingBufferPool::DEFAULT_ID); + playbackBuff_.applyGain(isPlaybackMuted_ ? 0.0 : playbackGain_); + playbackBuff_.interleave(playbackIBuff_); + write(playbackIBuff_.data(), framesToGet, playbackHandle_); + // Consume the regular one as well (same amount of frames) + Manager::instance().getRingBufferPool().discard(framesToGet, RingBufferPool::DEFAULT_ID); + } else { + // regular audio data + playback(playbackAvailFrames); + } + + if (ringtoneHandle_) { + AudioLoop *file_tone = Manager::instance().getTelephoneFile(); + int ringtoneAvailFrames = 0; + + if (not safeUpdate(ringtoneHandle_, ringtoneAvailFrames)) + return; + + playbackBuff_.setFormat(audioFormat_); + playbackBuff_.resize(ringtoneAvailFrames); + + if (file_tone) { + RING_DBG("playback gain %d", playbackGain_); + file_tone->getNext(playbackBuff_, playbackGain_); + } + + playbackBuff_.interleave(playbackIBuff_); + write(playbackIBuff_.data(), ringtoneAvailFrames, ringtoneHandle_); + } + + // Additionally handle the mic's audio stream + if (is_capture_running_) + capture(); +} + +void AlsaLayer::updatePreference(AudioPreference &preference, int index, DeviceType type) +{ + switch (type) { + case DeviceType::PLAYBACK: + preference.setAlsaCardout(index); + break; + + case DeviceType::CAPTURE: + preference.setAlsaCardin(index); + break; + + case DeviceType::RINGTONE: + preference.setAlsaCardring(index); + break; + + default: + break; + } +} + +} // namespace ring diff --git a/src/media/audio/alsa/alsalayer.h b/src/media/audio/alsa/alsalayer.h new file mode 100644 index 0000000000..f1a64c3d2c --- /dev/null +++ b/src/media/audio/alsa/alsalayer.h @@ -0,0 +1,269 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author: Андрей Лухнов <aol.nnov@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef ALSA_LAYER_H_ +#define ALSA_LAYER_H_ + +#include "audio/audiolayer.h" +#include "noncopyable.h" +#include <alsa/asoundlib.h> + +#include <memory> + +#define PCM_DMIX "plug:dmix" /** Alsa plugin for software mixing */ + +// Error codes for error handling +#define ALSA_CAPTURE_DEVICE 0x0001 /** Error while opening capture device */ +#define ALSA_PLAYBACK_DEVICE 0x0010 /** Error while opening playback device */ + + +/** + * @file AlsaLayer.h + * @brief Main sound class. Manages the data transfers between the application and the hardware. + */ + +namespace ring { + +class AlsaThread; +class RingBuffer; +class AudioPreference; + +/** Associate a sound card index to its string description */ +typedef std::pair<int , std::string> HwIDPair; + +class AlsaLayer : public AudioLayer { + public: + /** + * Constructor + */ + AlsaLayer(const AudioPreference &pref); + + /** + * Destructor + */ + ~AlsaLayer(); + + /** + * Start the capture stream and prepare the playback stream. + * The playback starts accordingly to its threshold + * ALSA Library API + */ + virtual void startStream(); + + /** + * Stop the playback and capture streams. + * Drops the pending frames and put the capture and playback handles to PREPARED state + * ALSA Library API + */ + virtual void stopStream(); + + /** + * Concatenate two strings. Used to build a valid pcm device name. + * @param plugin the alsa PCM name + * @param card the sound card number + * @return std::string the concatenated string + */ + std::string buildDeviceTopo(const std::string &plugin, int card); + + /** + * Scan the sound card available on the system + * @return std::vector<std::string> The vector containing the string description of the card + */ + virtual std::vector<std::string> getCaptureDeviceList() const; + virtual std::vector<std::string> getPlaybackDeviceList() const; + + /** + * Check if the given index corresponds to an existing sound card and supports the specified streaming mode + * @param card An index + * @param stream The stream mode + * DeviceType::CAPTURE + * DeviceType::PLAYBACK + * DeviceType::RINGTONE + * @return bool True if it exists and supports the mode + * false otherwise + */ + static bool soundCardIndexExists(int card, DeviceType stream); + + /** + * An index is associated with its string description + * @param description The string description + * @return int Its index + */ + int getAudioDeviceIndex(const std::string &description, DeviceType type) const; + std::string getAudioDeviceName(int index, DeviceType type) const; + + void playback(int maxSamples); + void capture(); + + void audioCallback(); + + /** + * Get the index of the audio card for capture + * @return int The index of the card used for capture + * 0 for the first available card on the system, 1 ... + */ + virtual int getIndexCapture() const { + return indexIn_; + } + + /** + * Get the index of the audio card for playback + * @return int The index of the card used for playback + * 0 for the first available card on the system, 1 ... + */ + virtual int getIndexPlayback() const { + return indexOut_; + } + + /** + * Get the index of the audio card for ringtone (could be differnet from playback) + * @return int The index of the card used for ringtone + * 0 for the first available card on the system, 1 ... + */ + virtual int getIndexRingtone() const { + return indexRing_; + } + + private: + friend class AlsaThread; + /** + * Returns a map of audio device hardware description and index + */ + std::vector<HwIDPair> getAudioDeviceIndexMap(bool getCapture) const; + + /** + * Calls snd_pcm_open and retries if device is busy, since dmix plugin + * will often hold on to a device temporarily after it has been opened + * and closed. + */ + bool openDevice(snd_pcm_t **pcm, const std::string &dev, snd_pcm_stream_t stream); + + /** + * Number of audio cards on which capture stream has been opened + */ + int indexIn_; + + /** + * Number of audio cards on which playback stream has been opened + */ + int indexOut_; + + /** + * Number of audio cards on which ringtone stream has been opened + */ + int indexRing_; + + NON_COPYABLE(AlsaLayer); + + /** + * Drop the pending frames and close the capture device + * ALSA Library API + */ + void closeCaptureStream(); + void stopCaptureStream(); + void startCaptureStream(); + void prepareCaptureStream(); + + void closePlaybackStream(); + void stopPlaybackStream(); + void startPlaybackStream(); + void preparePlaybackStream(); + + bool alsa_set_params(snd_pcm_t *pcm_handle); + + /** + * Copy a data buffer in the internal ring buffer + * ALSA Library API + * @param buffer The non-interleaved data to be copied + * @param frames Frames in the buffer + */ + void write(AudioSample* buffer, int frames, snd_pcm_t *handle); + + /** + * Read data from the internal ring buffer + * ALSA Library API + * @param buffer The buffer to stock the read data + * @param frames The number of frames to get + * @return int The number of frames actually read + */ + int read(AudioSample* buffer, int frames); + + virtual void updatePreference(AudioPreference &pref, int index, DeviceType type); + + /** + * Handles to manipulate playback stream + * ALSA Library API + */ + snd_pcm_t* playbackHandle_; + + /** + * Handles to manipulate ringtone stream + * + */ + snd_pcm_t *ringtoneHandle_; + + /** + * Handles to manipulate capture stream + * ALSA Library API + */ + snd_pcm_t* captureHandle_; + + /** + * name of the alsa audio plugin used + */ + std::string audioPlugin_; + + /** Vector to manage all soundcard index - description association of the system */ + // std::vector<HwIDPair> IDSoundCards_; + + /** Non-interleaved audio buffers */ + AudioBuffer playbackBuff_; + AudioBuffer captureBuff_; + + /** Interleaved buffer */ + std::vector<AudioSample> playbackIBuff_; + std::vector<AudioSample> captureIBuff_; + + bool is_playback_prepared_; + bool is_capture_prepared_; + bool is_playback_running_; + bool is_capture_running_; + bool is_playback_open_; + bool is_capture_open_; + + AlsaThread *audioThread_; + std::shared_ptr<RingBuffer> mainRingBuffer_; +}; + +} // namespace ring + +#endif // ALSA_LAYER_H_ diff --git a/src/media/audio/audio_rtp_session.cpp b/src/media/audio/audio_rtp_session.cpp new file mode 100644 index 0000000000..e99dda3a1e --- /dev/null +++ b/src/media/audio/audio_rtp_session.cpp @@ -0,0 +1,465 @@ +/* + * Copyright (C) 2014-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#include "audio_rtp_session.h" + +#include "logger.h" +#include "noncopyable.h" +#include "sip/sdp.h" + +#ifdef RING_VIDEO +#include "video/video_base.h" +#endif //RING_VIDEO + +#include "socket_pair.h" +#include "media_encoder.h" +#include "media_decoder.h" +#include "media_io_handle.h" +#include "media_device.h" + +#include "audio/audiobuffer.h" +#include "audio/ringbufferpool.h" +#include "audio/resampler.h" +#include "manager.h" +#include <sstream> + +namespace ring { + +class AudioSender { + public: + AudioSender(const std::string& id, + const std::string& dest, + const MediaDescription& args, + SocketPair& socketPair); + ~AudioSender(); + + private: + NON_COPYABLE(AudioSender); + + bool setup(SocketPair& socketPair); + + std::string id_; + std::string dest_; + MediaDescription args_; + std::unique_ptr<MediaEncoder> audioEncoder_; + std::unique_ptr<MediaIOHandle> muxContext_; + std::unique_ptr<Resampler> resampler_; + + AudioBuffer micData_; + AudioBuffer resampledData_; + + using seconds = std::chrono::duration<double, std::ratio<1>>; + const seconds secondsPerPacket_ {0.02}; // 20 ms + + ThreadLoop loop_; + void process(); + void cleanup(); +}; + +AudioSender::AudioSender(const std::string& id, + const std::string& dest, + const MediaDescription& args, + SocketPair& socketPair) : + id_(id), + dest_(dest), + args_(args), + loop_([&] { return setup(socketPair); }, + std::bind(&AudioSender::process, this), + std::bind(&AudioSender::cleanup, this)) +{ + loop_.start(); +} + +AudioSender::~AudioSender() +{ + loop_.join(); +} + +bool +AudioSender::setup(SocketPair& socketPair) +{ + audioEncoder_.reset(new MediaEncoder); + muxContext_.reset(socketPair.createIOContext()); + + try { + /* Encoder setup */ + RING_WARN("audioEncoder_->openOutput %s", dest_.c_str()); + audioEncoder_->openOutput(dest_.c_str(), args_); + audioEncoder_->setIOContext(muxContext_); + audioEncoder_->startIO(); + } catch (const MediaEncoderException &e) { + RING_ERR("%s", e.what()); + return false; + } + + std::string sdp; + audioEncoder_->print_sdp(sdp); + RING_WARN("\n%s", sdp.c_str()); + + return true; +} + +void +AudioSender::cleanup() +{ + audioEncoder_.reset(); + muxContext_.reset(); + micData_.clear(); + resampledData_.clear(); +} + +void +AudioSender::process() +{ + auto& mainBuffer = Manager::instance().getRingBufferPool(); + auto mainBuffFormat = mainBuffer.getInternalAudioFormat(); + + // compute nb of byte to get corresponding to 1 audio frame + const std::size_t samplesToGet = std::chrono::duration_cast<std::chrono::seconds>(mainBuffFormat.sample_rate * secondsPerPacket_).count(); + + if (mainBuffer.availableForGet(id_) < samplesToGet) { + const auto wait_time = std::chrono::duration_cast<std::chrono::milliseconds>(secondsPerPacket_); + if (not mainBuffer.waitForDataAvailable(id_, samplesToGet, wait_time)) + return; + } + + // get data + micData_.setFormat(mainBuffFormat); + micData_.resize(samplesToGet); + const auto samples = mainBuffer.getData(micData_, id_); + if (samples != samplesToGet) { + RING_ERR("Asked for %d samples from bindings on call '%s', got %d", + samplesToGet, id_.c_str(), samples); + return; + } + + // down/upmix as needed + auto accountAudioCodec = std::static_pointer_cast<AccountAudioCodecInfo>(args_.codec); + micData_.setChannelNum(accountAudioCodec->audioformat.nb_channels, true); + + if (mainBuffFormat.sample_rate != accountAudioCodec->audioformat.sample_rate) { + if (not resampler_) { + RING_DBG("Creating audio resampler"); + resampler_.reset(new Resampler(accountAudioCodec->audioformat)); + } + resampledData_.setFormat(accountAudioCodec->audioformat); + resampledData_.resize(samplesToGet); + resampler_->resample(micData_, resampledData_); + if (audioEncoder_->encode_audio(resampledData_) < 0) + RING_ERR("encoding failed"); + } else { + if (audioEncoder_->encode_audio(micData_) < 0) + RING_ERR("encoding failed"); + } +} + +class AudioReceiveThread +{ + public: + AudioReceiveThread(const std::string &id, + const AudioFormat& format, + const std::string& sdp); + ~AudioReceiveThread(); + void addIOContext(SocketPair &socketPair); + void startLoop(); + + private: + NON_COPYABLE(AudioReceiveThread); + + static constexpr auto SDP_FILENAME = "dummyFilename"; + + static int interruptCb(void *ctx); + static int readFunction(void *opaque, uint8_t *buf, int buf_size); + + void openDecoder(); + bool decodeFrame(); + + /*-----------------------------------------------------------------*/ + /* These variables should be used in thread (i.e. process()) only! */ + /*-----------------------------------------------------------------*/ + const std::string id_; + const AudioFormat& format_; + + DeviceParams args_; + + std::istringstream stream_; + std::unique_ptr<MediaDecoder> audioDecoder_; + std::unique_ptr<MediaIOHandle> sdpContext_; + std::unique_ptr<MediaIOHandle> demuxContext_; + + std::shared_ptr<RingBuffer> ringbuffer_; + + ThreadLoop loop_; + bool setup(); + void process(); + void cleanup(); +}; + +AudioReceiveThread::AudioReceiveThread(const std::string& id, + const AudioFormat& format, + const std::string& sdp) + : id_(id) + , format_(format) + , stream_(sdp) + , sdpContext_(new MediaIOHandle(sdp.size(), false, &readFunction, + 0, 0, this)) + , loop_(std::bind(&AudioReceiveThread::setup, this), + std::bind(&AudioReceiveThread::process, this), + std::bind(&AudioReceiveThread::cleanup, this)) +{} + +AudioReceiveThread::~AudioReceiveThread() +{ + loop_.join(); +} + + +bool +AudioReceiveThread::setup() +{ + audioDecoder_.reset(new MediaDecoder()); + audioDecoder_->setInterruptCallback(interruptCb, this); + // custom_io so the SDP demuxer will not open any UDP connections + args_.input = SDP_FILENAME; + args_.format = "sdp"; + args_.sdp_flags = "custom_io"; + EXIT_IF_FAIL(not stream_.str().empty(), "No SDP loaded"); + audioDecoder_->setIOContext(sdpContext_.get()); + EXIT_IF_FAIL(not audioDecoder_->openInput(args_), + "Could not open input \"%s\"", SDP_FILENAME); + // Now replace our custom AVIOContext with one that will read packets + audioDecoder_->setIOContext(demuxContext_.get()); + + EXIT_IF_FAIL(not audioDecoder_->setupFromAudioData(format_), + "decoder IO startup failed"); + + ringbuffer_ = Manager::instance().getRingBufferPool().getRingBuffer(id_); + return true; +} + +void +AudioReceiveThread::process() +{ + AudioFormat mainBuffFormat = Manager::instance().getRingBufferPool().getInternalAudioFormat(); + AudioFrame decodedFrame; + + switch (audioDecoder_->decode(decodedFrame)) { + + case MediaDecoder::Status::FrameFinished: + audioDecoder_->writeToRingBuffer(decodedFrame, *ringbuffer_, + mainBuffFormat); + return; + + case MediaDecoder::Status::DecodeError: + RING_WARN("decoding failure, trying to reset decoder..."); + if (not setup()) { + RING_ERR("fatal error, rx thread re-setup failed"); + loop_.stop(); + break; + } + if (not audioDecoder_->setupFromAudioData(format_)) { + RING_ERR("fatal error, a-decoder setup failed"); + loop_.stop(); + break; + } + break; + + case MediaDecoder::Status::ReadError: + RING_ERR("fatal error, read failed"); + loop_.stop(); + break; + + default: + break; + } +} + +void +AudioReceiveThread::cleanup() +{ + audioDecoder_.reset(); + demuxContext_.reset(); +} + +int +AudioReceiveThread::readFunction(void* opaque, uint8_t* buf, int buf_size) +{ + std::istream& is = static_cast<AudioReceiveThread*>(opaque)->stream_; + is.read(reinterpret_cast<char*>(buf), buf_size); + return is.gcount(); +} + +// This callback is used by libav internally to break out of blocking calls +int +AudioReceiveThread::interruptCb(void* data) +{ + auto context = static_cast<AudioReceiveThread*>(data); + return not context->loop_.isRunning(); +} + +void +AudioReceiveThread::addIOContext(SocketPair& socketPair) +{ + demuxContext_.reset(socketPair.createIOContext()); +} + +void +AudioReceiveThread::startLoop() +{ + loop_.start(); +} + +AudioRtpSession::AudioRtpSession(const std::string& id) + : RtpSession(id) +{ + // don't move this into the initializer list or Cthulus will emerge + ringbuffer_ = Manager::instance().getRingBufferPool().createRingBuffer(callID_); +} + +AudioRtpSession::~AudioRtpSession() +{ + stop(); +} + +void +AudioRtpSession::startSender() +{ + if (not send_.enabled or send_.holding) { + RING_WARN("Audio sending disabled"); + if (sender_) { + if (socketPair_) + socketPair_->interrupt(); + sender_.reset(); + } + return; + } + + if (sender_) + RING_WARN("Restarting audio sender"); + + try { + sender_.reset(new AudioSender(callID_, getRemoteRtpUri(), send_, + *socketPair_)); + } catch (const MediaEncoderException &e) { + RING_ERR("%s", e.what()); + send_.enabled = false; + } +} + +void +AudioRtpSession::startReceiver() +{ + if (not receive_.enabled or receive_.holding) { + RING_WARN("Audio receiving disabled"); + receiveThread_.reset(); + return; + } + + if (receiveThread_) + RING_WARN("Restarting audio receiver"); + + auto accountAudioCodec = std::static_pointer_cast<AccountAudioCodecInfo>(receive_.codec); + receiveThread_.reset(new AudioReceiveThread(callID_, accountAudioCodec->audioformat, + receive_.receiving_sdp)); + receiveThread_->addIOContext(*socketPair_); + receiveThread_->startLoop(); +} + +void +AudioRtpSession::start() +{ + std::lock_guard<std::recursive_mutex> lock(mutex_); + + if (not receive_.enabled and not receive_.enabled) { + stop(); + return; + } + + try { + socketPair_.reset( + new SocketPair(getRemoteRtpUri().c_str(), receive_.addr.getPort()) + ); + if (send_.crypto and receive_.crypto) { + socketPair_->createSRTP(receive_.crypto.getCryptoSuite().c_str(), + receive_.crypto.getSrtpKeyInfo().c_str(), + send_.crypto.getCryptoSuite().c_str(), + send_.crypto.getSrtpKeyInfo().c_str()); + } + } catch (const std::runtime_error &e) { + RING_ERR("Socket creation failed on port %d: %s", receive_.addr.getPort(), e.what()); + return; + } + + startSender(); + startReceiver(); +} + +void +AudioRtpSession::start(std::unique_ptr<IceSocket> rtp_sock, + std::unique_ptr<IceSocket> rtcp_sock) +{ + std::lock_guard<std::recursive_mutex> lock(mutex_); + + if (not send_.enabled and not receive_.enabled) { + stop(); + return; + } + + try { + socketPair_.reset(new SocketPair(std::move(rtp_sock), + std::move(rtcp_sock))); + if (send_.crypto and receive_.crypto) { + socketPair_->createSRTP(receive_.crypto.getCryptoSuite().c_str(), + receive_.crypto.getSrtpKeyInfo().c_str(), + send_.crypto.getCryptoSuite().c_str(), + send_.crypto.getSrtpKeyInfo().c_str()); + } + } catch (const std::runtime_error &e) { + RING_ERR("Socket creation failed"); + return; + } + + startSender(); + startReceiver(); +} + +void +AudioRtpSession::stop() +{ + std::lock_guard<std::recursive_mutex> lock(mutex_); + + if (socketPair_) + socketPair_->interrupt(); + + receiveThread_.reset(); + sender_.reset(); + socketPair_.reset(); +} + +} // namespace ring diff --git a/src/media/audio/audio_rtp_session.h b/src/media/audio/audio_rtp_session.h new file mode 100644 index 0000000000..5d9e8ad4c2 --- /dev/null +++ b/src/media/audio/audio_rtp_session.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2014-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef AUDIO_RTP_SESSION_H__ +#define AUDIO_RTP_SESSION_H__ + +#include "threadloop.h" +#include "media/rtp_session.h" +#include "media/audio/audiobuffer.h" + +#include <string> +#include <memory> + +namespace ring { + +class RingBuffer; +class AudioSender; +class AudioReceiveThread; +class IceSocket; + +class AudioRtpSession : public RtpSession { + public: + AudioRtpSession(const std::string& id); + virtual ~AudioRtpSession(); + + void start(); + void start(std::unique_ptr<IceSocket> rtp_sock, + std::unique_ptr<IceSocket> rtcp_sock); + void stop(); + + private: + void startSender(); + void startReceiver(); + + std::unique_ptr<AudioSender> sender_; + std::unique_ptr<AudioReceiveThread> receiveThread_; + std::shared_ptr<RingBuffer> ringbuffer_; +}; + +} // namespace ring + +#endif // __AUDIO_RTP_SESSION_H__ diff --git a/src/media/audio/audiobuffer.cpp b/src/media/audio/audiobuffer.cpp new file mode 100644 index 0000000000..c691572546 --- /dev/null +++ b/src/media/audio/audiobuffer.cpp @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Adrien Beraud <adrien.beraud@wisdomvibes.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "audiobuffer.h" +#include "logger.h" + +namespace ring { + +std::ostream& operator <<(std::ostream& stream, const AudioFormat& f) { + stream << f.toString(); + return stream; +} + +AudioBuffer::AudioBuffer(size_t sample_num, AudioFormat format) + : sampleRate_(format.sample_rate), + samples_(std::max(1U, format.nb_channels), + std::vector<AudioSample>(sample_num, 0)) +{ +} + +AudioBuffer::AudioBuffer(const AudioSample* in, size_t sample_num, AudioFormat format) + : sampleRate_(format.sample_rate), + samples_((std::max(1U, format.nb_channels)), std::vector<AudioSample>(sample_num, 0)) +{ + deinterleave(in, sample_num, format.nb_channels); +} + +AudioBuffer::AudioBuffer(const AudioBuffer& other, bool copy_content /* = false */) + : sampleRate_(other.sampleRate_), + samples_(copy_content ? other.samples_ : + std::vector<std::vector<AudioSample> >(other.samples_.size(), std::vector<AudioSample>(other.frames()))) +{} + +AudioBuffer& AudioBuffer::operator=(const AudioBuffer& other) { + samples_ = other.samples_; + sampleRate_ = other.sampleRate_; + return *this; +} + +AudioBuffer& AudioBuffer::operator=(AudioBuffer&& other) { + samples_ = std::move( other.samples_ ); + sampleRate_ = other.sampleRate_; + return *this; +} + +int AudioBuffer::getSampleRate() const +{ + return sampleRate_; +} + +void AudioBuffer::setSampleRate(int sr) +{ + sampleRate_ = sr; +} + +void AudioBuffer::setChannelNum(unsigned n, bool mix /* = false */) +{ + const unsigned c = samples_.size(); + if (n == c) + return; + + n = std::max(1U, n); + + if (!mix or c == 0) { + if (n < c) + samples_.resize(n); + else + samples_.resize(n, std::vector<AudioSample>(frames(), 0)); + return; + } + + // 2ch->1ch + if (n == 1) { + std::vector<AudioSample>& chan1 = samples_[0]; + std::vector<AudioSample>& chan2 = samples_[1]; + for (unsigned i = 0, f = frames(); i < f; i++) + chan1[i] = chan1[i] / 2 + chan2[i] / 2; + samples_.resize(1); + return; + } + + // 1ch->Nch + if (c == 1) { + samples_.resize(n, samples_[0]); + return; + } + + RING_WARN("Unsupported channel mixing: %dch->%dch", c, n); + samples_.resize(n, samples_[0]); +} + +void AudioBuffer::setFormat(AudioFormat format) +{ + setChannelNum(format.nb_channels); + setSampleRate(format.sample_rate); +} + +void AudioBuffer::resize(size_t sample_num) +{ + if (frames() == sample_num) + return; + + // will add zero padding if buffer is growing + for (auto &s : samples_) + s.resize(sample_num, 0); +} + +std::vector<AudioSample> * AudioBuffer::getChannel(unsigned chan /* = 0 */) +{ + if (chan < samples_.size()) + return &samples_[chan]; + + RING_ERR("Audio channel %u out of range", chan); + return nullptr; +} + +void AudioBuffer::applyGain(double gain) +{ + if (gain == 1.0) return; + + const double g = std::max(std::min(1.0, gain), -1.0); + if (g != gain) + RING_DBG("Normalizing %f to [-1.0, 1.0]", gain); + + for (auto &channel : samples_) + for (auto &sample : channel) + sample *= g; +} + +size_t AudioBuffer::channelToFloat(float* out, const int& channel) const +{ + + for (int i=0, f=frames(); i < f; i++) + *out++ = (float) samples_[channel][i] * .000030517578125f; + + return frames() * samples_.size(); +} + +size_t AudioBuffer::interleave(AudioSample* out) const +{ + for (unsigned i=0, f=frames(), c=channels(); i < f; ++i) + for (unsigned j = 0; j < c; ++j) + *out++ = samples_[j][i]; + + return frames() * channels(); +} + +size_t AudioBuffer::interleave(std::vector<AudioSample>& out) const +{ + out.resize(capacity()); + return interleave(out.data()); +} + +std::vector<AudioSample> AudioBuffer::interleave() const +{ + std::vector<AudioSample> data(capacity()); + interleave(data.data()); + return data; +} + +size_t AudioBuffer::interleaveFloat(float* out) const +{ + for (unsigned i=0, f=frames(), c=channels(); i < f; i++) + for (unsigned j = 0; j < c; j++) + *out++ = (float) samples_[j][i] * .000030517578125f; + + return frames() * samples_.size(); +} + +void AudioBuffer::deinterleave(const AudioSample* in, size_t frame_num, unsigned nb_channels) +{ + if (in == nullptr) + return; + + // Resize buffer + setChannelNum(nb_channels); + resize(frame_num); + + for (unsigned i=0, f=frames(), c=channels(); i < f; i++) + for (unsigned j = 0; j < c; j++) + samples_[j][i] = *in++; +} + +void AudioBuffer::deinterleave(const std::vector<AudioSample>& in, AudioFormat format) +{ + sampleRate_ = format.sample_rate; + deinterleave(in.data(), in.size()/format.nb_channels, format.nb_channels); +} + +void AudioBuffer::convertFloatPlanarToSigned16(uint8_t** extended_data, size_t frame_num, unsigned nb_channels) +{ + if (extended_data == nullptr) + return; + + // Resize buffer + setChannelNum(nb_channels); + resize(frame_num); + + for (unsigned j = 0, c = channels(); j < c; j++){ + float* inputChannel = (float*)extended_data[j]; + for (unsigned i=0, f=frames(); i < f; i++){ + float inputChannelVal = *inputChannel++; + // avoid saturation: limit val between -1 and 1 + inputChannelVal = std::max(-1.0f, std::min(inputChannelVal, 1.0f)); + samples_[j][i] = (int16_t) (inputChannelVal * 32768.0f); + } + } +} + +size_t AudioBuffer::mix(const AudioBuffer& other, bool up /* = true */) +{ + const bool upmix = up && (other.samples_.size() < samples_.size()); + const size_t samp_num = std::min(frames(), other.frames()); + const unsigned chan_num = upmix ? samples_.size() : std::min(samples_.size(), other.samples_.size()); + + for (unsigned i = 0; i < chan_num; i++) { + unsigned src_chan = upmix ? std::min<unsigned>(i, other.samples_.size() - 1) : i; + + for (unsigned j = 0; j < samp_num; j++) + samples_[i][j] += other.samples_[src_chan][j]; + } + + return samp_num; +} + +size_t AudioBuffer::copy(AudioBuffer& in, int sample_num /* = -1 */, size_t pos_in /* = 0 */, size_t pos_out /* = 0 */, bool up /* = true */) +{ + if (sample_num == -1) + sample_num = in.frames(); + + int to_copy = std::min((int)in.frames() - (int)pos_in, sample_num); + + if (to_copy <= 0) return 0; + + const bool upmix = up && (in.samples_.size() < samples_.size()); + const size_t chan_num = upmix ? samples_.size() : std::min(in.samples_.size(), samples_.size()); + + if ((pos_out + to_copy) > frames()) + resize(pos_out + to_copy); + + sampleRate_ = in.sampleRate_; + + for (unsigned i = 0; i < chan_num; i++) { + size_t src_chan = upmix ? std::min<size_t>(i, in.samples_.size() - 1U) : i; + std::copy(in.samples_[src_chan].begin() + pos_in, in.samples_[src_chan].begin() + pos_in + to_copy, samples_[i].begin() + pos_out); + } + + return to_copy; +} + +size_t AudioBuffer::copy(AudioSample* in, size_t sample_num, size_t pos_out /* = 0 */) +{ + if (in == nullptr || sample_num == 0) return 0; + + if ((pos_out + sample_num) > frames()) + resize(pos_out + sample_num); + + const size_t chan_num = samples_.size(); + for (unsigned i = 0; i < chan_num; i++) + std::copy(in, in + sample_num, samples_[i].begin() + pos_out); + + return sample_num; +} + +} // namespace ring diff --git a/src/media/audio/audiobuffer.h b/src/media/audio/audiobuffer.h new file mode 100644 index 0000000000..d3a5254695 --- /dev/null +++ b/src/media/audio/audiobuffer.h @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Adrien Beraud <adrien.beraud@wisdomvibes.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef _AUDIO_BUFFER_H +#define _AUDIO_BUFFER_H + +#include <vector> +#include <string> +#include <sstream> +#include <cstddef> // for size_t + +#include "ring_types.h" + +namespace ring { + +/** + * Structure to hold sample rate and channel number associated with audio data. + */ +struct AudioFormat { + unsigned sample_rate; + unsigned nb_channels; + + constexpr AudioFormat(unsigned sr, unsigned c) : sample_rate(sr), nb_channels(c) {} + + inline bool operator == (const AudioFormat &b) const { + return ( (b.sample_rate == sample_rate) && (b.nb_channels == nb_channels) ); + } + + inline bool operator != (const AudioFormat &b) const { + return !(*this == b); + } + + inline std::string toString() const { + std::stringstream ss; + ss << "{" << nb_channels << " channels, " << sample_rate << "Hz}"; + return ss.str(); + } + + /** + * Returns bytes necessary to hold one frame of audio data. + */ + inline size_t getBytesPerFrame() const { + return sizeof(AudioSample)*nb_channels; + } + + /** + * Bytes per second (default), or bytes necessary + * to hold delay_ms milliseconds of audio data. + */ + inline size_t getBandwidth(unsigned delay_ms=1000) const { + return (getBytesPerFrame() * sample_rate * delay_ms) / 1000; + } + + static const constexpr unsigned DEFAULT_SAMPLE_RATE = 48000; + static const AudioFormat DEFAULT() { return AudioFormat{16000, 1}; } + static const AudioFormat NONE() { return AudioFormat{0, 0}; } + static const AudioFormat MONO() { return AudioFormat{DEFAULT_SAMPLE_RATE, 1}; } + static const AudioFormat STEREO() { return AudioFormat{DEFAULT_SAMPLE_RATE, 2}; } +}; + +std::ostream& operator <<(std::ostream& stream, const AudioFormat& f); + +class AudioBuffer { + public: + /** + * Default constructor + */ + AudioBuffer() : AudioBuffer {0, AudioFormat::NONE()} {} + + /** + * Construct from given sample number and audio format + */ + AudioBuffer(size_t sample_num, AudioFormat format); + + /** + * Construct from existing interleaved data (copied into the buffer). + */ + AudioBuffer(const AudioSample* in, size_t sample_num, AudioFormat format); + + /** + * Copy constructor that by default only copies the buffer parameters (channel number, sample rate and buffer size). + * If copy_content is set to true, the other buffer content is also copied. + */ + AudioBuffer(const AudioBuffer& other, bool copy_content = false); + + /** + * Move constructor + */ + AudioBuffer(AudioBuffer&& other) : sampleRate_(other.sampleRate_), samples_( std::move(other.samples_) ) {}; + + /** + * Copy operator + */ + AudioBuffer& operator=(const AudioBuffer& other); + + /** + * Move operator + */ + AudioBuffer& operator=(AudioBuffer&& other); + + /** + * Buffer description for debugging + */ + inline std::string toString() const { + std::stringstream ss; + ss << "[AudioBuffer " << frames() << " frames; " << getFormat().toString() << "]"; + return ss.str(); + } + + inline size_t size() const { + return frames() * channels() * sizeof(AudioSample); + } + + /** + * Returns the sample rate (in samples/sec) associated to this buffer. + */ + int getSampleRate() const; + + /** + * Set the sample rate (in samples/sec) associated to this buffer. + */ + void setSampleRate(int sr); + + /** + * Returns the number of channels in this buffer. + */ + inline unsigned channels() const { + return samples_.size(); + } + + /** + * Set the number of channels of this buffer, eventually with up or downmixing. + * + * Currently supported remixing: 1ch->Nch, 2ch->1ch. + * + * @param n: the new number of channels. + * + * @param mix: if false, no buffer data is changed, removed channels + * are lost and new channels are initialised to 0. + * If true, up or downmixing happens. + */ + void setChannelNum(unsigned n, bool mix = false); + + /** + * Set the buffer format (channels and sample rate). + * No data conversion is performed. + */ + void setFormat(AudioFormat format); + + inline AudioFormat getFormat() const { + return AudioFormat(sampleRate_, samples_.size()); + } + + /** + * Returns the number of (multichannel) frames in this buffer. + */ + inline size_t frames() const { + if (not samples_.empty()) + return samples_[0].size(); + else + return 0; + } + + /** + * Return the total number of single samples in the buffer + */ + inline size_t capacity() const { + return frames() * channels(); + } + + /** + * Resize the buffer to make it able to hold sample_num + * multichannel samples. + * If the requested size is larger than the current buffer size, + * the new samples are set to zero. + */ + void resize(size_t sample_num); + + /** + * Resize the buffer to 0. All samples are lost but the number of channels and sample rate are kept. + */ + void clear() { + for(auto& c : samples_) + c.clear(); + } + + /** + * Set all samples in this buffer to 0. Buffer size is not changed. + */ + void reset() { + for(auto& c : samples_) + std::fill(c.begin(), c.end(), 0); + } + + /** + * Return the data (audio samples) for a given channel number. + * Channel data can be modified but size of individual channel vectors should not be changed by the user. + */ + std::vector<AudioSample> *getChannel(unsigned chan); + + /** + * Return a pointer to the raw data in this buffer. + */ + inline std::vector<std::vector<AudioSample> > &getData() { + return samples_; + } + + /** + * Returns pointers to non-interleaved raw data. + * Caller should not store result because pointer validity is + * limited in time. + */ + inline const std::vector<AudioSample*> getDataRaw() { + const unsigned chans = samples_.size(); + std::vector<AudioSample*> raw_data(chans, nullptr); + for(unsigned i=0; i<chans; i++) + raw_data[i] = samples_[i].data(); + return raw_data; + } + + /** + * Convert fixed-point channel to float and write in the out buffer (Float 32-bits). + * The out buffer must be at least of size capacity()*sizeof(float) bytes. + * + * @returns Number of samples writen. + */ + size_t channelToFloat(float* out, const int& channel) const; + + /** + * Write interleaved multichannel data to the out buffer (fixed-point 16-bits). + * The out buffer must be at least of size capacity()*sizeof(AudioSample) bytes. + * + * @returns Number of samples writen. + */ + size_t interleave(AudioSample* out) const; + + /** + * Write interleaved multichannel data to the out buffer (fixed-point 16-bits). + * The out buffer is resized to hold the full content of this buffer. + * + * @returns Number of samples writen. + */ + size_t interleave(std::vector<AudioSample>& out) const; + + /** + * Returns vector of interleaved data (fixed-point 16-bits). + */ + std::vector<AudioSample> interleave() const; + + /** + * Write interleaved multichannel data to the out buffer, while samples are converted to float. + * The out buffer must be at least of size capacity()*sizeof(float) bytes. + * + * @returns Number of samples writen. + */ + size_t interleaveFloat(float* out) const; + + /** + * Import interleaved multichannel data. Internal buffer is resized as needed. + * Function will read sample_num*channel_num elements of the in buffer. + */ + void deinterleave(const AudioSample* in, size_t frame_num, unsigned nb_channels = 1); + + /** + * Import interleaved multichannel data. Internal buffer is resized as needed. + * Sample rate is set according to format. + */ + void deinterleave(const std::vector<AudioSample>& in, AudioFormat format); + + /** + * convert float planar data to signed 16 + */ + void convertFloatPlanarToSigned16(uint8_t** extended_data, size_t frame_num, unsigned nb_channels = 1); + + + /** + * In-place gain transformation. + * + * @param gain: 0.0 -> 1.0 scale + */ + void applyGain(double gain); + + /** + * Mix samples from the other buffer within this buffer (in-place simple addition). + * If the other buffer has more channels than this one, only the first this.channels() channels are imported. + * If the other buffer has less channels than this one, behavior depends on upmix. + * Sample rate is not considered by this function. + * + * TODO: some kind of check for overflow/saturation. + * + * @param other: the other buffer to mix in this one. + * @param upmix: if true, upmixing occurs when other.channels() < this.channels(). + * If false, only the first other.channels() channels are edited in this buffer. + * + * @returns Number of samples modified. + */ + size_t mix(const AudioBuffer& other, bool upmix = true); + + /** + * Copy sample_num samples from in (from sample sample pos_in) to this buffer (at sample sample pos_out). + * If sample_num is -1 (the default), the entire in buffer is copied. + * + * Buffer sample number is increased if required to hold the new requested samples. + */ + size_t copy(AudioBuffer& in, int sample_num = -1, size_t pos_in = 0, size_t pos_out = 0, bool upmix = true); + + /** + * Copy sample_num samples from in to this buffer (at sample pos_out). + * Input data is treated as mono and samples are duplicated in the case of a multichannel buffer. + * + * Buffer sample number is increased if required to hold the new requested samples. + */ + size_t copy(AudioSample* in, size_t sample_num, size_t pos_out = 0); + + private: + int sampleRate_; + + // buffers holding data for each channels + std::vector<std::vector<AudioSample> > samples_; +}; + +} // namespace ring + +#endif // _AUDIO_BUFFER_H diff --git a/src/media/audio/audiolayer.cpp b/src/media/audio/audiolayer.cpp new file mode 100644 index 0000000000..fb38538dbe --- /dev/null +++ b/src/media/audio/audiolayer.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "audiolayer.h" +#include "audio/dcblocker.h" +#include "logger.h" +#include "manager.h" +#include "audio/ringbufferpool.h" +#include "audio/resampler.h" + +#include <ctime> + +namespace ring { + +AudioLayer::AudioLayer(const AudioPreference &pref) + : isCaptureMuted_(pref.getCaptureMuted()) + , isPlaybackMuted_(pref.getPlaybackMuted()) + , captureGain_(pref.getVolumemic()) + , playbackGain_(pref.getVolumespkr()) + , isStarted_(false) + , audioFormat_(Manager::instance().getRingBufferPool().getInternalAudioFormat()) + , audioInputFormat_(Manager::instance().getRingBufferPool().getInternalAudioFormat()) + , urgentRingBuffer_("urgentRingBuffer_id", SIZEBUF, audioFormat_) + , mutex_() + , dcblocker_() + , resampler_(new Resampler{audioFormat_.sample_rate}) + , inputResampler_(new Resampler{audioInputFormat_.sample_rate}) + , lastNotificationTime_(0) +{ + urgentRingBuffer_.createReadOffset(RingBufferPool::DEFAULT_ID); +} + +AudioLayer::~AudioLayer() +{} + +void AudioLayer::hardwareFormatAvailable(AudioFormat playback) +{ + std::lock_guard<std::mutex> lock(mutex_); + RING_DBG("Hardware audio format available : %s", playback.toString().c_str()); + audioFormat_ = Manager::instance().hardwareAudioFormatChanged(playback); + urgentRingBuffer_.setFormat(audioFormat_); + resampler_->setFormat(audioFormat_); +} + +void AudioLayer::hardwareInputFormatAvailable(AudioFormat capture) +{ + inputResampler_->setFormat(capture); +} + +void AudioLayer::flushMain() +{ + std::lock_guard<std::mutex> lock(mutex_); + // should pass call id + Manager::instance().getRingBufferPool().flushAllBuffers(); +} + +void AudioLayer::flushUrgent() +{ + std::lock_guard<std::mutex> lock(mutex_); + urgentRingBuffer_.flushAll(); +} + +void AudioLayer::putUrgent(AudioBuffer& buffer) +{ + std::lock_guard<std::mutex> lock(mutex_); + urgentRingBuffer_.put(buffer); +} + +// Notify (with a beep) an incoming call when there is already a call in progress +void AudioLayer::notifyIncomingCall() +{ + if (!Manager::instance().incomingCallsWaiting()) + return; + + time_t now = time(NULL); + + // Notify maximum once every 5 seconds + if (difftime(now, lastNotificationTime_) < 5) + return; + + lastNotificationTime_ = now; + + // Enable notification only if more than one call + if (!Manager::instance().hasCurrentCall()) + return; + + Tone tone("440/160", getSampleRate()); + unsigned int nbSample = tone.getSize(); + AudioBuffer buf(nbSample, AudioFormat::MONO()); + tone.getNext(buf, 1.0); + + /* Put the data in the urgent ring buffer */ + flushUrgent(); + putUrgent(buf); +} + +} // namespace ring diff --git a/src/media/audio/audiolayer.h b/src/media/audio/audiolayer.h new file mode 100644 index 0000000000..78fad4f1d3 --- /dev/null +++ b/src/media/audio/audiolayer.h @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * Author: Jerome Oufella <jerome.oufella@savoirfairelinux.com> + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Authro: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef AUDIO_LAYER_H_ +#define AUDIO_LAYER_H_ + + +#include "ringbuffer.h" +#include "dcblocker.h" +#include "noncopyable.h" + +#include <sys/time.h> +#include <mutex> +#include <vector> + +/** + * @file audiolayer.h + * @brief Main sound class. Manages the data transfers between the application and the hardware. + */ + +// Define the audio api +#define PULSEAUDIO_API_STR "pulseaudio" +#define ALSA_API_STR "alsa" +#define JACK_API_STR "jack" +#define COREAUDIO_API_STR "coreaudio" + +#define PCM_DEFAULT "default" // Default ALSA plugin +#define PCM_DSNOOP "plug:dsnoop" // Alsa plugin for microphone sharing +#define PCM_DMIX_DSNOOP "dmix/dsnoop" // Audio profile using Alsa dmix/dsnoop + +namespace ring { + +class AudioPreference; +class Resampler; + +enum class DeviceType { + PLAYBACK, /** To open playback device only */ + CAPTURE, /** To open capture device only */ + RINGTONE /** To open the ringtone device only */ +}; + +class AudioLayer { + + private: + NON_COPYABLE(AudioLayer); + + public: + + AudioLayer(const AudioPreference &); + virtual ~AudioLayer(); + + virtual std::vector<std::string> getCaptureDeviceList() const = 0; + virtual std::vector<std::string> getPlaybackDeviceList() const = 0; + + virtual int getAudioDeviceIndex(const std::string& name, DeviceType type) const = 0; + virtual std::string getAudioDeviceName(int index, DeviceType type) const = 0; + virtual int getIndexCapture() const = 0; + virtual int getIndexPlayback() const = 0; + virtual int getIndexRingtone() const = 0; + + /** + * Start the capture stream and prepare the playback stream. + * The playback starts accordingly to its threshold + * ALSA Library API + */ + virtual void startStream() = 0; + + /** + * Stop the playback and capture streams. + * Drops the pending frames and put the capture and playback handles to PREPARED state + * ALSA Library API + */ + virtual void stopStream() = 0; + + /** + * Determine wether or not the audio layer is active (i.e. stream opened) + */ + bool isStarted() const { + return isStarted_; + } + + /** + * Send a chunk of data to the hardware buffer to start the playback + * Copy data in the urgent buffer. + * @param buffer The buffer containing the data to be played ( ringtones ) + */ + void putUrgent(AudioBuffer& buffer); + + /** + * Flush main buffer + */ + void flushMain(); + + /** + * Flush urgent buffer + */ + void flushUrgent(); + + bool isCaptureMuted() const { + return isCaptureMuted_; + } + + /** + * Mute capture (microphone) + */ + void muteCapture(bool muted) { + isCaptureMuted_ = muted; + } + + bool isPlaybackMuted() const { + return isPlaybackMuted_; + } + + /** + * Mute playback + */ + void mutePlayback(bool muted) { + isPlaybackMuted_ = muted; + } + + /** + * Set capture stream gain (microphone) + * Range should be [-1.0, 1.0] + */ + void setCaptureGain(double gain) { + captureGain_ = gain; + } + + /** + * Get capture stream gain (microphone) + */ + double getCaptureGain() const { + return captureGain_; + } + + /** + * Set playback stream gain (speaker) + * Range should be [-1.0, 1.0] + */ + void setPlaybackGain(double gain) { + playbackGain_ = gain; + } + + /** + * Get playback stream gain (speaker) + */ + double getPlaybackGain() const { + return playbackGain_; + } + + /** + * Get the sample rate of the audio layer + * @return unsigned int The sample rate + * default: 44100 HZ + */ + unsigned int getSampleRate() const { + return audioFormat_.sample_rate; + } + + /** + * Get the audio format of the layer (sample rate & channel number). + */ + AudioFormat getFormat() const { + return audioFormat_; + } + + /** + * Emit an audio notification on incoming calls + */ + void notifyIncomingCall(); + + virtual void updatePreference(AudioPreference &pref, int index, DeviceType type) = 0; + + protected: + /** + * Callback to be called by derived classes when the audio output is opened. + */ + void hardwareFormatAvailable(AudioFormat playback); + + /** + * Set the input format on necessary objects. + */ + void hardwareInputFormatAvailable(AudioFormat capture); + + /** + * True if capture is not to be used + */ + bool isCaptureMuted_; + + /** + * True if playback is not to be used + */ + bool isPlaybackMuted_; + + /** + * Gain applied to mic signal + */ + double captureGain_; + + /** + * Gain applied to playback signal + */ + double playbackGain_; + + /** + * Whether or not the audio layer stream is started + */ + bool isStarted_; + + /** + * Sample Rate Ring should send sound data to the sound card + */ + AudioFormat audioFormat_; + + /** + * Sample Rate for input. + */ + AudioFormat audioInputFormat_; + + /** + * Urgent ring buffer used for ringtones + */ + RingBuffer urgentRingBuffer_; + + /** + * Lock for the entire audio layer + */ + std::mutex mutex_; + + /** + * Remove audio offset that can be introduced by certain cheap audio device + */ + DcBlocker dcblocker_; + + /** + * Manage sampling rate conversion + */ + std::unique_ptr<Resampler> resampler_; + + /** + * Manage input sampling rate conversions + */ + std::unique_ptr<Resampler> inputResampler_; + + private: + + /** + * Time of the last incoming call notification + */ + time_t lastNotificationTime_; +}; + +} // namespace ring + +#endif // _AUDIO_LAYER_H_ diff --git a/src/media/audio/audioloop.cpp b/src/media/audio/audioloop.cpp new file mode 100644 index 0000000000..8a181791ae --- /dev/null +++ b/src/media/audio/audioloop.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * + * Inspired by tonegenerator of + * Laurielle Lea <laurielle.lea@savoirfairelinux.com> (2004) + * Inspired by ringbuffer of Audacity Project + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "audioloop.h" +#include "logger.h" + +#include <algorithm> // std::min + +namespace ring { + +AudioLoop::AudioLoop(unsigned int sampleRate) : + buffer_(new AudioBuffer(0, AudioFormat(sampleRate, 1))), pos_(0) +{} + +AudioLoop::~AudioLoop() +{ + delete buffer_; +} + +void +AudioLoop::seek(double relative_position) +{ + pos_ = static_cast<double>(buffer_->frames() * relative_position * 0.01); +} + +void +AudioLoop::getNext(AudioBuffer& output, double gain) +{ + if (!buffer_) { + RING_ERR("buffer is NULL"); + return; + } + + const size_t buf_samples = buffer_->frames(); + size_t pos = pos_; + size_t total_samples = output.frames(); + size_t output_pos = 0; + + if (buf_samples == 0) { + RING_ERR("Audio loop size is 0"); + return; + } else if (pos >= buf_samples) { + RING_ERR("Invalid loop position %d", pos); + return; + } + + while (total_samples != 0) { + size_t samples = std::min(total_samples, buf_samples - pos); + + output.copy(*buffer_, samples, pos, output_pos); + + output_pos += samples; + pos = (pos + samples) % buf_samples; + + total_samples -= samples; + } + + output.applyGain(gain); + + pos_ = pos; + + onBufferFinish(); +} + +void AudioLoop::onBufferFinish() {} + +} // namespace ring diff --git a/src/media/audio/audioloop.h b/src/media/audio/audioloop.h new file mode 100644 index 0000000000..e4087df2d9 --- /dev/null +++ b/src/media/audio/audioloop.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * + * Inspired by tonegenerator of + * Laurielle Lea <laurielle.lea@savoirfairelinux.com> (2004) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#ifndef __AUDIOLOOP_H__ +#define __AUDIOLOOP_H__ + +#include "ring_types.h" +#include "noncopyable.h" +#include "audiobuffer.h" + +/** + * @file audioloop.h + * @brief Loop on a sound file + */ + +namespace ring { + +class AudioLoop { + public: + AudioLoop(unsigned int sampleRate); + + virtual ~AudioLoop(); + + /** + * Get the next fragment of the tone + * the function change the intern position, and will loop + * @param output The data buffer + * @param nb of int16 to send + * @param gain The gain [-1.0, 1.0] + */ + void getNext(AudioBuffer& output, double gain); + + void seek(double relative_position); + + /** + * Reset the pointer position + */ + void reset() { + pos_ = 0; + } + + /** + * Accessor to the size of the buffer + * @return unsigned int The size + */ + size_t getSize() { + return buffer_->frames(); + } + + protected: + /** The data buffer */ + AudioBuffer * buffer_; + + /** current position, set to 0, when initialize */ + size_t pos_; + + private: + NON_COPYABLE(AudioLoop); + virtual void onBufferFinish(); +}; + +} // namespace ring + +#endif // __AUDIOLOOP_H__ diff --git a/src/media/audio/audiorecord.cpp b/src/media/audio/audiorecord.cpp new file mode 100644 index 0000000000..c1ed7f263a --- /dev/null +++ b/src/media/audio/audiorecord.cpp @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "audiorecord.h" +#include "logger.h" +#include "fileutils.h" + +#include <sndfile.hh> + +#include <algorithm> +#include <sstream> // for stringstream +#include <cstdio> +#include <unistd.h> + +namespace ring { + +static std::string +createFilename() +{ + time_t rawtime = time(NULL); + struct tm * timeinfo = localtime(&rawtime); + + std::stringstream out; + + // DATE + out << timeinfo->tm_year + 1900; + + if (timeinfo->tm_mon < 9) // january is 01, not 1 + out << 0; + + out << timeinfo->tm_mon + 1; + + if (timeinfo->tm_mday < 10) // 01 02 03, not 1 2 3 + out << 0; + + out << timeinfo->tm_mday; + + out << '-'; + + // hour + if (timeinfo->tm_hour < 10) // 01 02 03, not 1 2 3 + out << 0; + + out << timeinfo->tm_hour; + + out << ':'; + + if (timeinfo->tm_min < 10) // 01 02 03, not 1 2 3 + out << 0; + + out << timeinfo->tm_min; + + out << ':'; + + if (timeinfo->tm_sec < 10) // 01 02 03, not 1 2 3 + out << 0; + + out << timeinfo->tm_sec; + return out.str(); +} + +AudioRecord::AudioRecord() : fileHandle_(nullptr) + , sndFormat_(AudioFormat::MONO()) + , recordingEnabled_(false) + , filename_(createFilename()) + , savePath_() +{ + RING_WARN("Generate filename for this call %s ", filename_.c_str()); +} + +AudioRecord::~AudioRecord() +{ + delete fileHandle_; +} + +void AudioRecord::setSndFormat(AudioFormat format) +{ + sndFormat_ = format; +} + +void AudioRecord::setRecordingOptions(AudioFormat format, const std::string &path) +{ + std::string filePath; + + // use HOME directory if path is empty, or if path does not exist + if (path.empty() or not fileutils::check_dir(path.c_str())) { + filePath = fileutils::get_home_dir(); + } else { + filePath = path; + } + + sndFormat_ = format; + savePath_ = (*filePath.rbegin() == DIR_SEPARATOR_CH) ? filePath : filePath + DIR_SEPARATOR_STR; +} + +static bool +nonFilenameCharacter(char c) +{ + return not(std::isalnum(c) or c == '_' or c == '.'); +} + +// Replace any character that is inappropriate for a filename with '_' +static std::string +sanitize(std::string s) +{ + std::replace_if(s.begin(), s.end(), nonFilenameCharacter, '_'); + return s; +} + +void AudioRecord::initFilename(const std::string &peerNumber) +{ + std::string fName(filename_); + fName.append("-" + sanitize(peerNumber) + "-" PACKAGE); + + if (filename_.find(".wav") == std::string::npos) { + RING_DBG("Concatenate .wav file extension: name : %s", filename_.c_str()); + fName.append(".wav"); + } + + savePath_.append(fName); +} + +std::string AudioRecord::getFilename() const +{ + return savePath_; +} + +bool AudioRecord::openFile() +{ + bool result = false; + delete fileHandle_; + const bool doAppend = fileExists(); + const int access = doAppend ? SFM_RDWR : SFM_WRITE; + + RING_DBG("Opening file %s with format %s", savePath_.c_str(), sndFormat_.toString().c_str()); + fileHandle_ = new SndfileHandle(savePath_.c_str(), access, SF_FORMAT_WAV | SF_FORMAT_PCM_16, sndFormat_.nb_channels, sndFormat_.sample_rate); + + // check overloaded boolean operator + if (!*fileHandle_) { + RING_WARN("Could not open WAV file!"); + delete fileHandle_; + fileHandle_ = 0; + return false; + } + + if (doAppend and fileHandle_->seek(0, SEEK_END) < 0) + RING_WARN("Couldn't seek to the end of the file "); + + return result; +} + +void AudioRecord::closeFile() +{ + delete fileHandle_; + fileHandle_ = 0; +} + +bool AudioRecord::isOpenFile() const +{ + return fileHandle_ != 0; +} + +bool AudioRecord::fileExists() const +{ + return access(savePath_.c_str(), F_OK) != -1; +} + +bool AudioRecord::isRecording() const +{ + return recordingEnabled_; +} + +bool AudioRecord::toggleRecording() +{ + if (isOpenFile()) { + recordingEnabled_ = !recordingEnabled_; + } else { + openFile(); + recordingEnabled_ = true; + } + + return recordingEnabled_; +} + +void AudioRecord::stopRecording() +{ + RING_DBG("Stop recording"); + recordingEnabled_ = false; +} + +void AudioRecord::recData(AudioBuffer& buffer) +{ + if (not recordingEnabled_) + return; + + if (fileHandle_ == 0) { + RING_DBG("Can't record data, a file has not yet been opened!"); + return; + } + + auto interleaved = buffer.interleave(); + const int nSamples = interleaved.size(); + if (fileHandle_->write(interleaved.data(), nSamples) != nSamples) { + RING_WARN("Could not record data!"); + } else { + fileHandle_->writeSync(); + } +} + +} // namespace ring diff --git a/src/media/audio/audiorecord.h b/src/media/audio/audiorecord.h new file mode 100644 index 0000000000..d60b663940 --- /dev/null +++ b/src/media/audio/audiorecord.h @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef _AUDIO_RECORD_H +#define _AUDIO_RECORD_H + +#include "audiobuffer.h" +#include "noncopyable.h" + +#include <memory> +#include <string> +#include <cstdlib> + +class SndfileHandle; + +namespace ring { + +class AudioRecord { + + public: + AudioRecord(); + ~AudioRecord(); + + void setSndFormat(AudioFormat format); + void setRecordingOptions(AudioFormat format, const std::string &path); + + /** + * Init recording file path + */ + void initFilename(const std::string &peerNumber); + + /** + * Return the filepath of the recording + */ + std::string getFilename() const; + + /** + * Check if no otehr file is opened, then create a new one + * @param filename A string containing teh file (with/without extension) + * @param type The sound file format (FILE_RAW, FILE_WAVE) + * @param format Internal sound format (INT16 / INT32) + * @return bool True if file was opened + */ + bool openFile(); + + /** + * Close the opend recording file. If wave: cout the number of byte + */ + void closeFile(); + + /** + * Check if a file is already opened + */ + bool isOpenFile() const; + + /** + * Check if a file already exists + */ + bool fileExists() const; + + /** + * Check recording state + */ + bool isRecording() const; + + /** + * Toggle recording state + */ + bool toggleRecording(); + + /** + * Stop recording flag + */ + void stopRecording(); + + /** + * Record a chunk of data in an openend file + * @param buffer The data chunk to be recorded + * @param nSamples Number of samples (number of bytes) to be recorded + */ + void recData(AudioBuffer& buffer); + + protected: + + /** + * Open an existing raw file, used when the call is set on hold + */ + bool openExistingRawFile(); + + /** + * Open an existing wav file, used when the call is set on hold + */ + bool openExistingWavFile(); + + /** + * Compute the number of byte recorded and close the file + */ + void closeWavFile(); + + + /** + * Pointer to the recorded file + */ + SndfileHandle *fileHandle_; + + /** + * Number of channels + */ + AudioFormat sndFormat_; + + /** + * Recording flage + */ + bool recordingEnabled_; + + /** + * Filename for this recording + */ + std::string filename_; + + /** + * Path for this recording + */ + std::string savePath_; + + private: + NON_COPYABLE(AudioRecord); +}; + +} // namespace ring + +#endif // _AUDIO_RECORD_H diff --git a/src/media/audio/audiorecorder.cpp b/src/media/audio/audiorecorder.cpp new file mode 100644 index 0000000000..2e1600bce1 --- /dev/null +++ b/src/media/audio/audiorecorder.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "audiorecorder.h" +#include "audiorecord.h" +#include "ringbufferpool.h" +#include "logger.h" + +#include <chrono> +#include <sstream> +#include <unistd.h> + +namespace ring { + +int AudioRecorder::count_ = 0; + +AudioRecorder::AudioRecorder(AudioRecord *arec, RingBufferPool &rbp) + : recorderId_(), ringBufferPool_(rbp), arecord_(arec), running_(false) + , thread_() +{ + ++count_; + + std::string id("processid_"); + + // convert count into string + std::string s; + std::ostringstream out; + out << count_; + s = out.str(); + + recorderId_ = id.append(s); +} + +AudioRecorder::~AudioRecorder() +{ + running_ = false; + + if (thread_.joinable()) + thread_.join(); +} + +void AudioRecorder::init() { + if (!arecord_->isRecording()) { + arecord_->setSndFormat(ringBufferPool_.getInternalAudioFormat()); + } +} + +void AudioRecorder::start() +{ + if (running_) return; + running_ = true; + thread_ = std::thread(&AudioRecorder::run, this); +} + +/** + * Reimplementation of run() + */ +void AudioRecorder::run() +{ + static const size_t BUFFER_LENGTH = 10000; + static const std::chrono::milliseconds SLEEP_TIME(20); // 20 ms + + AudioBuffer buffer(BUFFER_LENGTH, ringBufferPool_.getInternalAudioFormat()); + + while (running_) { + const size_t availableSamples = ringBufferPool_.availableForGet(recorderId_); + buffer.resize(std::min(availableSamples, BUFFER_LENGTH)); + ringBufferPool_.getData(buffer, recorderId_); + + if (availableSamples > 0) + arecord_->recData(buffer); + + std::this_thread::sleep_for(SLEEP_TIME); + } +} + +} // namespace ring diff --git a/src/media/audio/audiorecorder.h b/src/media/audio/audiorecorder.h new file mode 100644 index 0000000000..8766119a83 --- /dev/null +++ b/src/media/audio/audiorecorder.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef AUDIORECORDER_H_ +#define AUDIORECORDER_H_ + +#include "noncopyable.h" + +#include <thread> +#include <atomic> +#include <string> + +namespace ring { + +class RingBufferPool; +class AudioRecord; + +class AudioRecorder { + + public: + AudioRecorder(AudioRecord *arec, RingBufferPool &rbp); + ~AudioRecorder(); + std::string getRecorderID() const { + return recorderId_; + } + + /** + * Set the record to the current audio format. + * Should be called before start() at least once. + */ + void init(); + + /** + * Call to start recording. + */ + void start(); + + private: + NON_COPYABLE(AudioRecorder); + void run(); + + static int count_; + std::string recorderId_; + RingBufferPool &ringBufferPool_; + AudioRecord *arecord_; + std::atomic<bool> running_; + std::thread thread_; +}; + +} // namespace ring + +#endif diff --git a/src/media/audio/coreaudio/Makefile.am b/src/media/audio/coreaudio/Makefile.am new file mode 100644 index 0000000000..1969b70316 --- /dev/null +++ b/src/media/audio/coreaudio/Makefile.am @@ -0,0 +1,8 @@ +include $(top_srcdir)/globals.mak + +if HAVE_OSX +noinst_LTLIBRARIES = libcoreaudiolayer.la +endif + +libcoreaudiolayer_la_SOURCES = corelayer.cpp corelayer.h audiodevice.cpp audiodevice.h +libcoreaudiolayer_la_CXXFLAGS = -I$(top_srcdir)/src diff --git a/src/media/audio/coreaudio/audiodevice.cpp b/src/media/audio/coreaudio/audiodevice.cpp new file mode 100644 index 0000000000..591154b56a --- /dev/null +++ b/src/media/audio/coreaudio/audiodevice.cpp @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Philippe Groarke <philippe.groarke@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "audiodevice.h" + +namespace ring { + +AudioDevice::AudioDevice(AudioDeviceID devid, bool isInput) +{ + init(devid, isInput); +} + +void AudioDevice::init(AudioDeviceID devid, bool isInput) +{ + id_ = devid; + isInput_ = isInput; + if (id_ == kAudioDeviceUnknown) return; + + name_ = getName(); + channels_ = countChannels(); + + UInt32 propsize = sizeof(Float32); + + AudioObjectPropertyScope theScope = isInput_ ? + kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; + + AudioObjectPropertyAddress theAddress = { + kAudioDevicePropertySafetyOffset, + theScope, + 0 }; // channel + + verify_noerr(AudioObjectGetPropertyData(id_, + &theAddress, + 0, + NULL, + &propsize, + &safetyOffset_)); + + + propsize = sizeof(UInt32); + theAddress.mSelector = kAudioDevicePropertyBufferFrameSize; + + verify_noerr(AudioObjectGetPropertyData(id_, + &theAddress, + 0, + NULL, + &propsize, + &bufferSizeFrames_)); + + propsize = sizeof(AudioStreamBasicDescription); + theAddress.mSelector = kAudioDevicePropertyStreamFormat; + + verify_noerr(AudioObjectGetPropertyData(id_, + &theAddress, + 0, + NULL, + &propsize, + &format_)); +} + +bool AudioDevice::valid() const +{ + return id_ != kAudioDeviceUnknown; +} + +void AudioDevice::setBufferSize(UInt32 size) +{ + + UInt32 propsize = sizeof(UInt32); + + AudioObjectPropertyScope theScope = isInput_ ? + kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; + + AudioObjectPropertyAddress theAddress = { + kAudioDevicePropertyBufferFrameSize, + theScope, + 0 }; // channel + + verify_noerr(AudioObjectSetPropertyData(id_, + &theAddress, + 0, + NULL, + propsize, + &size)); + + verify_noerr(AudioObjectGetPropertyData(id_, + &theAddress, + 0, + NULL, + &propsize, + &bufferSizeFrames_)); +} + +int AudioDevice::countChannels() const +{ + OSStatus err; + UInt32 propSize; + int result = 0; + + AudioObjectPropertyScope theScope = isInput_ ? + kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; + + AudioObjectPropertyAddress theAddress = { + kAudioDevicePropertyStreamConfiguration, + theScope, + 0 }; // channel + + err = AudioObjectGetPropertyDataSize(id_, + &theAddress, + 0, + NULL, + &propSize); + if (err) return 0; + + AudioBufferList *buflist = (AudioBufferList *)malloc(propSize); + err = AudioObjectGetPropertyData(id_, + &theAddress, + 0, + NULL, + &propSize, + buflist); + if (!err) { + for (UInt32 i = 0; i < buflist->mNumberBuffers; ++i) { + result += buflist->mBuffers[i].mNumberChannels; + } + } + free(buflist); + return result; +} + +std::string AudioDevice::getName() const +{ + char buf[256]; + UInt32 maxlen = sizeof(buf) - 1; + + AudioObjectPropertyScope theScope = isInput_ ? + kAudioDevicePropertyScopeInput : kAudioDevicePropertyScopeOutput; + + AudioObjectPropertyAddress theAddress = { + kAudioDevicePropertyDeviceName, + theScope, + 0 }; // channel + + verify_noerr(AudioObjectGetPropertyData(id_, + &theAddress, + 0, + NULL, + &maxlen, + buf)); + return buf; +} + +} // namespace ring diff --git a/src/media/audio/coreaudio/audiodevice.h b/src/media/audio/coreaudio/audiodevice.h new file mode 100644 index 0000000000..16016bc087 --- /dev/null +++ b/src/media/audio/coreaudio/audiodevice.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Philippe Groarke <philippe.groarke@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef AUDIO_DEVICE_H +#define AUDIO_DEVICE_H + +#include <CoreServices/CoreServices.h> +#include <CoreAudio/CoreAudio.h> + +#include <string> + +namespace ring { + +class AudioDevice { +public: + AudioDevice() : id_(kAudioDeviceUnknown) { } + AudioDevice(AudioDeviceID devid, bool isInput); + void init(AudioDeviceID devid, bool isInput); + bool valid() const; + void setBufferSize(UInt32 size); + +public: + AudioDeviceID id_; + std::string name_; + bool isInput_; + int channels_; + UInt32 safetyOffset_; + UInt32 bufferSizeFrames_; + AudioStreamBasicDescription format_; + +private: + int countChannels() const; + std::string getName() const; +}; + +} + +#endif /* defined(AUDIO_DEVICE_H) */ diff --git a/src/media/audio/coreaudio/corelayer.cpp b/src/media/audio/coreaudio/corelayer.cpp new file mode 100644 index 0000000000..beb40b7e72 --- /dev/null +++ b/src/media/audio/coreaudio/corelayer.cpp @@ -0,0 +1,579 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Philippe Groarke <philippe.groarke@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "corelayer.h" +#include "manager.h" +#include "noncopyable.h" +#include "audio/resampler.h" +#include "audio/ringbufferpool.h" +#include "audio/ringbuffer.h" +#include "audiodevice.h" + +#include <cmath> +#include <thread> +#include <atomic> + +namespace ring { + +// AudioLayer implementation. +CoreLayer::CoreLayer(const AudioPreference &pref) + : AudioLayer(pref) + , indexIn_(pref.getAlsaCardin()) + , indexOut_(pref.getAlsaCardout()) + , indexRing_(pref.getAlsaCardring()) + , playbackBuff_(0, audioFormat_) + , captureBuff_(0) + , is_playback_running_(false) + , is_capture_running_(false) + , mainRingBuffer_(Manager::instance().getRingBufferPool().getRingBuffer(RingBufferPool::DEFAULT_ID)) +{} + +CoreLayer::~CoreLayer() +{ + isStarted_ = false; + + if (captureBuff_) { + for (UInt32 i = 0; i < captureBuff_->mNumberBuffers; ++i) + free(captureBuff_->mBuffers[i].mData); + free(captureBuff_); + captureBuff_ = 0; + } +} + +std::vector<std::string> CoreLayer::getCaptureDeviceList() const +{ + std::vector<std::string> ret; + + for (auto x : getDeviceList(true)) + ret.push_back(x.name_); + + return ret; +} + +std::vector<std::string> CoreLayer::getPlaybackDeviceList() const +{ + std::vector<std::string> ret; + + for (auto x : getDeviceList(false)) + { + ret.push_back(x.name_); + } + + return ret; +} + +int CoreLayer::getAudioDeviceIndex(const std::string& name, DeviceType type) const +{ + return 0; +} + +std::string CoreLayer::getAudioDeviceName(int index, DeviceType type) const +{ + return ""; +} + +void CoreLayer::initAudioLayerPlayback() +{ + // OS X uses Audio Units for output. Steps: + // 1) Create a description. + // 2) Find the audio unit that fits that. + // 3) Set the audio unit callback. + // 4) Initialize everything. + // 5) Profit... + + RING_DBG("INIT AUDIO PLAYBACK"); + + AudioComponentDescription outputDesc = {0}; + outputDesc.componentType = kAudioUnitType_Output; + outputDesc.componentSubType = kAudioUnitSubType_DefaultOutput; + outputDesc.componentManufacturer = kAudioUnitManufacturer_Apple; + + AudioComponent outComp = AudioComponentFindNext(NULL, &outputDesc); + if (outComp == NULL) { + RING_ERR("Can't find default output audio component."); + return; + } + + checkErr(AudioComponentInstanceNew(outComp, &outputUnit_)); + + // Setup Callback. + AURenderCallbackStruct callback; + callback.inputProc = outputCallback; + callback.inputProcRefCon = this; + + checkErr(AudioUnitSetProperty(outputUnit_, + kAudioUnitProperty_SetRenderCallback, + kAudioUnitScope_Input, + 0, + &callback, + sizeof(callback))); + + + // Set stream format + AudioStreamBasicDescription info; + UInt32 size = sizeof(info); + checkErr(AudioUnitGetProperty(outputUnit_, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + 0, + &info, + &size)); + audioFormat_ = {(unsigned int)info.mSampleRate, (unsigned int)info.mChannelsPerFrame}; + checkErr(AudioUnitGetProperty(outputUnit_, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + 0, + &info, + &size)); + + info.mSampleRate = audioFormat_.sample_rate; // Only change sample rate. + checkErr(AudioUnitSetProperty(outputUnit_, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + 0, + &info, + size)); + + hardwareFormatAvailable(audioFormat_); + + // Initialize + checkErr(AudioUnitInitialize(outputUnit_)); + checkErr(AudioOutputUnitStart(outputUnit_)); + + is_playback_running_ = true; + is_capture_running_ = true; + + initAudioFormat(); +} + +void CoreLayer::initAudioLayerCapture() +{ + RING_DBG("INIT AUDIO INPUT"); + // HALUnit description. + AudioComponentDescription desc; + desc = {0}; + desc.componentType = kAudioUnitType_Output; + desc.componentSubType = kAudioUnitSubType_HALOutput; + desc.componentManufacturer = kAudioUnitManufacturer_Apple; + AudioComponent comp = AudioComponentFindNext(NULL, &desc); + if (comp == NULL) + RING_ERR("Can't find an input HAL unit that matches description."); + checkErr(AudioComponentInstanceNew(comp, &inputUnit_)); + + // HALUnit settings. + AudioUnitScope outputBus = 0; + AudioUnitScope inputBus = 1; + UInt32 enableIO = 1; + UInt32 disableIO = 0; + UInt32 size = 0; + + checkErr(AudioUnitSetProperty(inputUnit_, + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Input, + inputBus, + &enableIO, + sizeof(enableIO))); + + checkErr(AudioUnitSetProperty(inputUnit_, + kAudioOutputUnitProperty_EnableIO, + kAudioUnitScope_Output, + outputBus, + &disableIO, + sizeof(disableIO))); + + AudioDeviceID defaultDevice = kAudioObjectUnknown; + size = sizeof(defaultDevice); + AudioObjectPropertyAddress defaultDeviceProperty; + defaultDeviceProperty.mSelector = kAudioHardwarePropertyDefaultInputDevice; + defaultDeviceProperty.mScope = kAudioObjectPropertyScopeGlobal; + defaultDeviceProperty.mElement = kAudioObjectPropertyElementMaster; + + checkErr(AudioObjectGetPropertyData(kAudioObjectSystemObject, + &defaultDeviceProperty, + outputBus, + NULL, + &size, + &defaultDevice)); + + checkErr(AudioUnitSetProperty(inputUnit_, + kAudioOutputUnitProperty_CurrentDevice, + kAudioUnitScope_Global, + outputBus, + &defaultDevice, + sizeof(defaultDevice))); + + // Setup audio formats + AudioStreamBasicDescription info; + size = sizeof(AudioStreamBasicDescription); + checkErr(AudioUnitGetProperty(inputUnit_, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Input, + inputBus, + &info, + &size)); + + audioInputFormat_ = {(unsigned int)info.mSampleRate, (unsigned int)info.mChannelsPerFrame}; + hardwareInputFormatAvailable(audioInputFormat_); + + // Set format on output *SCOPE* in input *BUS*. + checkErr(AudioUnitGetProperty(inputUnit_, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + inputBus, + &info, + &size)); + + // Keep everything else and change only sample rate (or else SPLOSION!!!) + info.mSampleRate = audioInputFormat_.sample_rate; + checkErr(AudioUnitSetProperty(inputUnit_, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + inputBus, + &info, + size)); + + + // Input buffer setup. Note that ioData is empty and we have to store data + // in another buffer. + UInt32 bufferSizeFrames = 0; + size = sizeof(UInt32); + checkErr(AudioUnitGetProperty(inputUnit_, + kAudioDevicePropertyBufferFrameSize, + kAudioUnitScope_Global, + outputBus, + &bufferSizeFrames, + &size)); + + UInt32 bufferSizeBytes = bufferSizeFrames * sizeof(Float32); + size = offsetof(AudioBufferList, mBuffers[0]) + + (sizeof(AudioBuffer) * info.mChannelsPerFrame); + captureBuff_ = (AudioBufferList *)malloc(size); + captureBuff_->mNumberBuffers = info.mChannelsPerFrame; + + for (UInt32 i = 0; i < captureBuff_->mNumberBuffers; ++i) { + captureBuff_->mBuffers[i].mNumberChannels = 1; + captureBuff_->mBuffers[i].mDataByteSize = bufferSizeBytes; + captureBuff_->mBuffers[i].mData = malloc(bufferSizeBytes); + } + + // Input callback setup. + AURenderCallbackStruct inputCall; + inputCall.inputProc = inputCallback; + inputCall.inputProcRefCon = this; + + checkErr(AudioUnitSetProperty(inputUnit_, + kAudioOutputUnitProperty_SetInputCallback, + kAudioUnitScope_Global, + outputBus, + &inputCall, + sizeof(inputCall))); + + // Start it up. + checkErr(AudioUnitInitialize(inputUnit_)); + checkErr(AudioOutputUnitStart(inputUnit_)); + +} + +void CoreLayer::startStream() +{ + RING_DBG("START STREAM"); + dcblocker_.reset(); + + if (is_playback_running_ and is_capture_running_) + return; + + initAudioLayerPlayback(); + initAudioLayerCapture(); +} + +void CoreLayer::destroyAudioLayer() +{ + AudioOutputUnitStop(outputUnit_); + AudioUnitUninitialize(outputUnit_); + AudioComponentInstanceDispose(outputUnit_); + + AudioOutputUnitStop(inputUnit_); + AudioUnitUninitialize(inputUnit_); + AudioComponentInstanceDispose(inputUnit_); +} + +void CoreLayer::stopStream() +{ + RING_DBG("STOP STREAM"); + + isStarted_ = is_playback_running_ = is_capture_running_ = false; + + destroyAudioLayer(); + + /* Flush the ring buffers */ + flushUrgent(); + flushMain(); +} + + +//// PRIVATE ///// + + +void CoreLayer::initAudioFormat() +{ +} + + +OSStatus CoreLayer::outputCallback(void* inRefCon, + AudioUnitRenderActionFlags* ioActionFlags, + const AudioTimeStamp* inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList* ioData) +{ + static_cast<CoreLayer*>(inRefCon)->write(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); + return kAudioServicesNoError; +} + +void CoreLayer::write(AudioUnitRenderActionFlags* ioActionFlags, + const AudioTimeStamp* inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList* ioData) +{ + + // Checks for resampling + AudioFormat mainBufferAudioFormat = Manager::instance().getRingBufferPool().getInternalAudioFormat(); + bool resample = audioFormat_.sample_rate != mainBufferAudioFormat.sample_rate; + + unsigned urgentFramesToGet = urgentRingBuffer_.availableForGet(RingBufferPool::DEFAULT_ID); + + if (urgentFramesToGet > 0) { + RING_WARN("Getting urgent frames."); + size_t totSample = std::min(inNumberFrames, urgentFramesToGet); + + playbackBuff_.setFormat(audioFormat_); + playbackBuff_.resize(totSample); + urgentRingBuffer_.get(playbackBuff_, RingBufferPool::DEFAULT_ID); + + playbackBuff_.applyGain(isPlaybackMuted_ ? 0.0 : playbackGain_); + + for (int i = 0; i < audioFormat_.nb_channels; ++i) + playbackBuff_.channelToFloat((Float32*)ioData->mBuffers[i].mData, i); // Write + + Manager::instance().getRingBufferPool().discard(totSample, RingBufferPool::DEFAULT_ID); + } + + unsigned normalFramesToGet = Manager::instance().getRingBufferPool().availableForGet(RingBufferPool::DEFAULT_ID); + + if (normalFramesToGet > 0) { + + double resampleFactor = 1.0; + unsigned readableSamples = inNumberFrames; + + if (resample) { + resampleFactor = static_cast<double>(audioFormat_.sample_rate) / mainBufferAudioFormat.sample_rate; + readableSamples = std::ceil(inNumberFrames / resampleFactor); + } + readableSamples = std::min(readableSamples, normalFramesToGet); + size_t nResampled = (double) readableSamples * resampleFactor; + + playbackBuff_.setFormat(mainBufferAudioFormat); + playbackBuff_.resize(readableSamples); + Manager::instance().getRingBufferPool().getData( + playbackBuff_, RingBufferPool::DEFAULT_ID); + playbackBuff_.applyGain(isPlaybackMuted_ ? 0.0 : playbackGain_); + + if (resample) { + AudioBuffer resampledOutput(readableSamples, audioFormat_); + resampler_->resample(playbackBuff_, resampledOutput); + + for (int i = 0; i < audioFormat_.nb_channels; ++i) + resampledOutput.channelToFloat((Float32*)ioData->mBuffers[i].mData, i); + + } else { + for (int i = 0; i < audioFormat_.nb_channels; ++i) + playbackBuff_.channelToFloat((Float32*)ioData->mBuffers[i].mData, i); + } + } + + + + if (normalFramesToGet <= 0) { + AudioLoop* tone = Manager::instance().getTelephoneTone(); + AudioLoop* file_tone = Manager::instance().getTelephoneFile(); + + playbackBuff_.setFormat(audioFormat_); + playbackBuff_.resize(inNumberFrames); + + if (tone) { + tone->getNext(playbackBuff_, playbackGain_); + + } + else if (file_tone) { + file_tone->getNext(playbackBuff_, playbackGain_); + } + else { + playbackBuff_.reset(); + } + for (int i = 0; i < audioFormat_.nb_channels; ++i) { + playbackBuff_.channelToFloat((Float32*)ioData->mBuffers[i].mData, i); + } + } +} + + +OSStatus CoreLayer::inputCallback(void* inRefCon, + AudioUnitRenderActionFlags* ioActionFlags, + const AudioTimeStamp* inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList* ioData) +{ + static_cast<CoreLayer*>(inRefCon)->read(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, ioData); + return kAudioServicesNoError; +} + +void CoreLayer::read(AudioUnitRenderActionFlags* ioActionFlags, + const AudioTimeStamp* inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList* ioData) +{ + if (inNumberFrames <= 0) { + RING_WARN("No frames for input."); + return; + } + + // Write the mic samples in our buffer + checkErr(AudioUnitRender(inputUnit_, + ioActionFlags, + inTimeStamp, + inBusNumber, + inNumberFrames, + captureBuff_)); + + + AudioStreamBasicDescription info; + UInt32 size = sizeof(AudioStreamBasicDescription); + checkErr(AudioUnitGetProperty(inputUnit_, + kAudioUnitProperty_StreamFormat, + kAudioUnitScope_Output, + inBusNumber, + &info, + &size)); + + + // Add them to Ring ringbuffer. + const AudioFormat mainBufferFormat = Manager::instance().getRingBufferPool().getInternalAudioFormat(); + bool resample = info.mSampleRate != mainBufferFormat.sample_rate; + + // FIXME: Performance! There *must* be a better way. This is testing only. + AudioBuffer inBuff(inNumberFrames, audioInputFormat_); + + for (int i = 0; i < info.mChannelsPerFrame; ++i) { + Float32* data = (Float32*)captureBuff_->mBuffers[i].mData; + for (int j = 0; j < inNumberFrames; ++j) { + (*inBuff.getChannel(i))[j] = (AudioSample)((data)[j] / .000030517578125f); + } + } + + if (resample) { + //RING_WARN("Resampling Input."); + + //FIXME: May be a multiplication, check alsa vs pulse implementation. + + int outSamples = inNumberFrames / (static_cast<double>(audioInputFormat_.sample_rate) / mainBufferFormat.sample_rate); + AudioBuffer out(outSamples, mainBufferFormat); + inputResampler_->resample(inBuff, out); + dcblocker_.process(out); + mainRingBuffer_->put(out); + } else { + dcblocker_.process(inBuff); + mainRingBuffer_->put(inBuff); + } + + +} + +void CoreLayer::updatePreference(AudioPreference &preference, int index, DeviceType type) +{ + switch (type) { + case DeviceType::PLAYBACK: + preference.setAlsaCardout(index); + break; + + case DeviceType::CAPTURE: + preference.setAlsaCardin(index); + break; + + case DeviceType::RINGTONE: + preference.setAlsaCardring(index); + break; + + default: + break; + } +} + +std::vector<AudioDevice> CoreLayer::getDeviceList(bool getCapture) const +{ + + std::vector<AudioDevice> ret; + UInt32 propsize; + + AudioObjectPropertyAddress theAddress = { + kAudioHardwarePropertyDevices, + kAudioObjectPropertyScopeGlobal, + kAudioObjectPropertyElementMaster }; + + verify_noerr(AudioObjectGetPropertyDataSize(kAudioObjectSystemObject, + &theAddress, + 0, + NULL, + &propsize)); + + size_t nDevices = propsize / sizeof(AudioDeviceID); + AudioDeviceID *devids = new AudioDeviceID[nDevices]; + + verify_noerr(AudioObjectGetPropertyData(kAudioObjectSystemObject, + &theAddress, + 0, + NULL, + &propsize, + devids)); + + for (int i = 0; i < nDevices; ++i) { + AudioDevice dev(devids[i], getCapture); + if (dev.channels_ > 0) { // Channels < 0 if inactive. + ret.push_back(dev); + } + } + delete[] devids; + + return ret; +} + +} // namespace ring diff --git a/src/media/audio/coreaudio/corelayer.h b/src/media/audio/coreaudio/corelayer.h new file mode 100644 index 0000000000..d5b39493b8 --- /dev/null +++ b/src/media/audio/coreaudio/corelayer.h @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Philippe Groarke <philippe.groarke@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef CORE_LAYER_H_ +#define CORE_LAYER_H_ + +#include "audio/audiolayer.h" +#include "noncopyable.h" +#include <CoreFoundation/CoreFoundation.h> +#include <AudioToolbox/AudioToolbox.h> +#include <CoreAudio/AudioHardware.h> + +#define checkErr( err) \ + if(err) {\ + OSStatus error = static_cast<OSStatus>(err);\ + fprintf(stdout, "CoreAudio Error: %ld -> %s: %d\n", (long)error,\ + __FILE__, \ + __LINE__\ + );\ + fflush(stdout);\ + } + +/** + * @file CoreLayer.h + * @brief Main OSX sound class. Manages the data transfers between the application and the hardware. + */ + +namespace ring { + +class RingBuffer; +class AudioDevice; + +class CoreLayer : public AudioLayer { + public: + CoreLayer(const AudioPreference &pref); + ~CoreLayer(); + + /** + * Scan the sound card available on the system + * @return std::vector<std::string> The vector containing the string description of the card + */ + virtual std::vector<std::string> getCaptureDeviceList() const; + virtual std::vector<std::string> getPlaybackDeviceList() const; + + virtual int getAudioDeviceIndex(const std::string& name, DeviceType type) const; + virtual std::string getAudioDeviceName(int index, DeviceType type) const; + + /** + * Get the index of the audio card for capture + * @return int The index of the card used for capture + */ + virtual int getIndexCapture() const { + return indexIn_; + } + + /** + * Get the index of the audio card for playback + * @return int The index of the card used for playback + */ + virtual int getIndexPlayback() const { + return indexOut_; + } + + /** + * Get the index of the audio card for ringtone (could be differnet from playback) + * @return int The index of the card used for ringtone + */ + virtual int getIndexRingtone() const { + return indexRing_; + } + + void initAudioLayerPlayback(); + void initAudioLayerCapture(); + + /** + * Start the capture stream and prepare the playback stream. + * The playback starts accordingly to its threshold + * CoreAudio Library API + */ + + virtual void startStream(); + + void destroyAudioLayer(); + + /** + * Stop the playback and capture streams. + * Drops the pending frames and put the capture and playback handles to PREPARED state + * CoreAudio Library API + */ + virtual void stopStream(); + + + + private: + NON_COPYABLE(CoreLayer); + + void initAudioFormat(); + + static OSStatus outputCallback(void* inRefCon, + AudioUnitRenderActionFlags* ioActionFlags, + const AudioTimeStamp* inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList* ioData); + + void write(AudioUnitRenderActionFlags* ioActionFlags, + const AudioTimeStamp* inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList* ioData); + + static OSStatus inputCallback(void* inRefCon, + AudioUnitRenderActionFlags* ioActionFlags, + const AudioTimeStamp* inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList* ioData); + + void read(AudioUnitRenderActionFlags* ioActionFlags, + const AudioTimeStamp* inTimeStamp, + UInt32 inBusNumber, + UInt32 inNumberFrames, + AudioBufferList* ioData); + + virtual void updatePreference(AudioPreference &pref, int index, DeviceType type); + + /** + * Number of audio cards on which capture stream has been opened + */ + int indexIn_; + + /** + * Number of audio cards on which playback stream has been opened + */ + int indexOut_; + + /** + * Number of audio cards on which ringtone stream has been opened + */ + int indexRing_; + + /** Non-interleaved audio buffers */ + AudioBuffer playbackBuff_; + ::AudioBufferList* captureBuff_; // CoreAudio buffer. + + /** Interleaved buffer */ + std::vector<AudioSample> playbackIBuff_; + std::vector<AudioSample> captureIBuff_; + + AudioUnit outputUnit_; + AudioUnit inputUnit_; + std::shared_ptr<RingBuffer> mainRingBuffer_; + + bool is_playback_running_; + bool is_capture_running_; + + std::vector<AudioDevice> getDeviceList(bool getCapture) const; +}; + +} // namespace ring + +#endif // CORE_LAYER_H_ diff --git a/src/media/audio/dcblocker.cpp b/src/media/audio/dcblocker.cpp new file mode 100644 index 0000000000..418969bd9d --- /dev/null +++ b/src/media/audio/dcblocker.cpp @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "dcblocker.h" + +namespace ring { + +DcBlocker::DcBlocker(unsigned channels /* = 1 */) + : states(channels, (struct StreamState){0, 0, 0, 0}) +{} + +void DcBlocker::reset() +{ + states.assign(states.size(), (struct StreamState){0, 0, 0, 0}); +} + +void DcBlocker::doProcess(AudioSample *out, AudioSample *in, unsigned samples, struct StreamState * state) +{ + for (unsigned i = 0; i < samples; ++i) { + state->x_ = in[i]; + + + state->y_ = (AudioSample) ((float) state->x_ - (float) state->xm1_ + 0.9999 * (float) state->y_); + state->xm1_ = state->x_; + state->ym1_ = state->y_; + + out[i] = state->y_; + } +} + +void DcBlocker::process(AudioSample *out, AudioSample *in, int samples) +{ + if (out == NULL or in == NULL or samples == 0) return; + doProcess(out, in, samples, &states[0]); +} + +void DcBlocker::process(AudioBuffer& buf) +{ + const size_t chans = buf.channels(); + const size_t samples = buf.frames(); + if (chans > states.size()) + states.resize(buf.channels(), (struct StreamState){0, 0, 0, 0}); + + unsigned i; + for(i=0; i<chans; i++) { + AudioSample *chan = buf.getChannel(i)->data(); + doProcess(chan, chan, samples, &states[i]); + } +} + +} // namespace ring diff --git a/src/media/audio/dcblocker.h b/src/media/audio/dcblocker.h new file mode 100644 index 0000000000..aeac13f62e --- /dev/null +++ b/src/media/audio/dcblocker.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author: Adrien Beraud <adrien.beraud@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef DCBLOCKER_H +#define DCBLOCKER_H + +#include "ring_types.h" +#include "audiobuffer.h" + +namespace ring { + +class DcBlocker { + public: + DcBlocker(unsigned channels = 1); + void reset(); + + void process(AudioSample *out, AudioSample *in, int samples); + + /** + * In-place processing of all samples in buf (each channel treated independently) + */ + void process(AudioBuffer& buf); + + private: + struct StreamState { + AudioSample y_, x_, xm1_, ym1_; + }; + + void doProcess(AudioSample *out, AudioSample *in, unsigned samples, struct StreamState * state); + + std::vector<StreamState> states; +}; + +} // namespace ring + +#endif diff --git a/src/media/audio/dsp.cpp b/src/media/audio/dsp.cpp new file mode 100644 index 0000000000..2ffb5c9c19 --- /dev/null +++ b/src/media/audio/dsp.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "logger.h" +#include "dsp.h" +#include "audiobuffer.h" + +namespace ring { + +void +DSP::speexStateDeleter(SpeexPreprocessState *state) +{ + speex_preprocess_state_destroy(state); +} + +DSP::DSP(int smplPerFrame, int channels, int samplingRate) : + smplPerFrame_(smplPerFrame), + dspStates_() +{ + for (int c = 0; c < channels; ++c) + dspStates_.push_back( + {speex_preprocess_state_init(smplPerFrame_, samplingRate), + speexStateDeleter}); +} + +void DSP::enableAGC() +{ + // automatic gain control, range [1-32768] + for (const auto &state : dspStates_) { + int enable = 1; + speex_preprocess_ctl(state.get(), SPEEX_PREPROCESS_SET_AGC, &enable); + int target = 16000; + speex_preprocess_ctl(state.get(), SPEEX_PREPROCESS_SET_AGC_TARGET, &target); + } +} + +void DSP::disableAGC() +{ + for (const auto &state : dspStates_) { + int enable = 0; + speex_preprocess_ctl(state.get(), SPEEX_PREPROCESS_SET_AGC, &enable); + } +} + +void DSP::enableDenoise() +{ + for (const auto &state : dspStates_) { + int enable = 1; + speex_preprocess_ctl(state.get(), SPEEX_PREPROCESS_SET_DENOISE, &enable); + } +} + +void DSP::disableDenoise() +{ + for (const auto &state : dspStates_) { + int enable = 0; + speex_preprocess_ctl(state.get(), SPEEX_PREPROCESS_SET_DENOISE, &enable); + } +} + +void DSP::process(AudioBuffer& buff, int samples) +{ + if (samples != smplPerFrame_) { + RING_WARN("Unexpected amount of samples"); + return; + } + + auto &channelData = buff.getData(); + size_t index = 0; + for (auto &c : channelData) { + if (index < dspStates_.size() and dspStates_[index].get()) + speex_preprocess_run(dspStates_[index].get(), c.data()); + ++index; + } +} + +} // namespace ring diff --git a/src/media/audio/dsp.h b/src/media/audio/dsp.h new file mode 100644 index 0000000000..de65cd2a5b --- /dev/null +++ b/src/media/audio/dsp.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef DSP_H_ +#define DSP_H_ + +#include <cstdint> +#include <speex/speex_preprocess.h> +#include <vector> +#include <memory> +#include "noncopyable.h" + +namespace ring { + +class AudioBuffer; + +class DSP { + public: + DSP(int smplPerFrame, int channels, int samplingRate); + void enableAGC(); + void disableAGC(); + void enableDenoise(); + void disableDenoise(); + void process(AudioBuffer& buf, int samples); + + private: + NON_COPYABLE(DSP); + static void speexStateDeleter(SpeexPreprocessState *state); + typedef std::unique_ptr<SpeexPreprocessState, decltype(&speexStateDeleter)> SpeexStatePtr; + + int smplPerFrame_; + // one state per channel + std::vector<SpeexStatePtr> dspStates_; +}; + +} // namespace ring + +#endif // DSP_H_ diff --git a/src/media/audio/jack/Makefile.am b/src/media/audio/jack/Makefile.am new file mode 100644 index 0000000000..54d900f26c --- /dev/null +++ b/src/media/audio/jack/Makefile.am @@ -0,0 +1,6 @@ +include $(top_srcdir)/globals.mak + +if BUILD_JACK +noinst_LTLIBRARIES = libjacklayer.la +libjacklayer_la_SOURCES = jacklayer.cpp jacklayer.h +endif diff --git a/src/media/audio/jack/jacklayer.cpp b/src/media/audio/jack/jacklayer.cpp new file mode 100644 index 0000000000..3ace332b5c --- /dev/null +++ b/src/media/audio/jack/jacklayer.cpp @@ -0,0 +1,537 @@ +/* + * Copyright (C) 2014-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "jacklayer.h" +#include <cassert> +#include <climits> +#include "logger.h" +#include "audio/resampler.h" +#include "audio/ringbufferpool.h" +#include "audio/ringbuffer.h" +#include "manager.h" +#include "array_size.h" + +#include <unistd.h> + +/* TODO + * implement shutdown callback + * auto connect optional + */ + +namespace ring { + +namespace +{ +void connectPorts(jack_client_t *client, int portType, const std::vector<jack_port_t *> &ports) +{ + const char **physical_ports = jack_get_ports(client, NULL, NULL, portType | JackPortIsPhysical); + for (unsigned i = 0; physical_ports[i]; ++i) { + const char *port = jack_port_name(ports[i]); + if (portType & JackPortIsInput) { + if (jack_connect(client, port, physical_ports[i])) { + RING_ERR("Can't connect %s to %s", port, physical_ports[i]); + break; + } + } else { + if (jack_connect(client, physical_ports[i], port)) { + RING_ERR("Can't connect port %s to %s", physical_ports[i], port); + break; + } + } + } + free(physical_ports); +} + +bool ringbuffer_ready_for_read(const jack_ringbuffer_t *rb) +{ + // XXX 512 is arbitrary + return jack_ringbuffer_read_space(rb) > 512; +} +} + +void JackLayer::fillWithUrgent(AudioBuffer &buffer, size_t samplesToGet) +{ + // Urgent data (dtmf, incoming call signal) come first. + samplesToGet = std::min(samplesToGet, hardwareBufferSize_); + buffer.resize(samplesToGet); + urgentRingBuffer_.get(buffer, RingBufferPool::DEFAULT_ID); + buffer.applyGain(isPlaybackMuted_ ? 0.0 : playbackGain_); + + // Consume the regular one as well (same amount of samples) + Manager::instance().getRingBufferPool().discard(samplesToGet, RingBufferPool::DEFAULT_ID); +} + +void JackLayer::fillWithVoice(AudioBuffer &buffer, size_t samplesAvail) +{ + RingBufferPool &mainBuffer = Manager::instance().getRingBufferPool(); + + buffer.resize(samplesAvail); + mainBuffer.getData(buffer, RingBufferPool::DEFAULT_ID); + buffer.applyGain(isPlaybackMuted_ ? 0.0 : playbackGain_); + + if (audioFormat_.sample_rate != (unsigned) mainBuffer.getInternalSamplingRate()) { + RING_DBG("fillWithVoice sample_rate != mainBuffer.getInternalSamplingRate() \n"); + AudioBuffer out(buffer, false); + out.setSampleRate(audioFormat_.sample_rate); + resampler_->resample(buffer, out); + buffer = out; + } +} + +void JackLayer::fillWithToneOrRingtone(AudioBuffer &buffer) +{ + buffer.resize(hardwareBufferSize_); + AudioLoop *tone = Manager::instance().getTelephoneTone(); + AudioLoop *file_tone = Manager::instance().getTelephoneFile(); + + // In case of a dtmf, the pointers will be set to nullptr once the dtmf length is + // reached. For this reason we need to fill audio buffer with zeros if pointer is nullptr + if (tone) { + tone->getNext(buffer, playbackGain_); + } else if (file_tone) { + file_tone->getNext(buffer, playbackGain_); + } else { + buffer.reset(); + } +} + +void +JackLayer::playback() +{ + notifyIncomingCall(); + + const size_t samplesToGet = Manager::instance().getRingBufferPool().availableForGet(RingBufferPool::DEFAULT_ID); + const size_t urgentSamplesToGet = urgentRingBuffer_.availableForGet(RingBufferPool::DEFAULT_ID); + + if (urgentSamplesToGet > 0) { + fillWithUrgent(playbackBuffer_, urgentSamplesToGet); + } else { + if (samplesToGet > 0) { + fillWithVoice(playbackBuffer_, samplesToGet); + } else { + fillWithToneOrRingtone(playbackBuffer_); + } + } + + playbackFloatBuffer_.resize(playbackBuffer_.frames()); + write(playbackBuffer_, playbackFloatBuffer_); +} + +void +JackLayer::capture() +{ + // get audio from jack ringbuffer + read(captureBuffer_); + + const AudioFormat mainBufferFormat = Manager::instance().getRingBufferPool().getInternalAudioFormat(); + const bool resample = mainBufferFormat.sample_rate != audioFormat_.sample_rate; + + captureBuffer_.applyGain(isCaptureMuted_ ? 0.0 : captureGain_); + + if (resample) { + int outSamples = captureBuffer_.frames() * (static_cast<double>(audioFormat_.sample_rate) / mainBufferFormat.sample_rate); + AudioBuffer out(outSamples, mainBufferFormat); + resampler_->resample(captureBuffer_, out); + dcblocker_.process(out); + mainRingBuffer_->put(out); + } else { + dcblocker_.process(captureBuffer_); + mainRingBuffer_->put(captureBuffer_); + } +} + +static void +convertToFloat(const std::vector<AudioSample> &src, std::vector<float> &dest) +{ + static const float INV_SHORT_MAX = 1 / (float) SHRT_MAX; + if (dest.size() != src.size()) { + RING_ERR("MISMATCH"); + return; + } + for (size_t i = 0; i < dest.size(); ++i) + dest[i] = src[i] * INV_SHORT_MAX; +} + +static void +convertFromFloat(std::vector<float> &src, std::vector<AudioSample> &dest) +{ + if (dest.size() != src.size()) { + RING_ERR("MISMATCH"); + return; + } + for (size_t i = 0; i < dest.size(); ++i) + dest[i] = src[i] * SHRT_MAX; +} + +void +JackLayer::write(AudioBuffer &buffer, std::vector<float> &floatBuffer) +{ + for (unsigned i = 0; i < out_ringbuffers_.size(); ++i) { + const unsigned inChannel = std::min(i, buffer.channels() - 1); + convertToFloat(*buffer.getChannel(inChannel), floatBuffer); + + // write to output + const size_t to_ringbuffer = jack_ringbuffer_write_space(out_ringbuffers_[i]); + const size_t write_bytes = std::min(buffer.frames() * sizeof(floatBuffer[0]), to_ringbuffer); + // FIXME: while we have samples to write AND while we have space to write them + const size_t written_bytes = jack_ringbuffer_write(out_ringbuffers_[i], + (const char *) floatBuffer.data(), write_bytes); + if (written_bytes < write_bytes) + RING_WARN("Dropped %zu bytes for channel %u", write_bytes - written_bytes, i); + } +} + +void +JackLayer::read(AudioBuffer &buffer) +{ + for (unsigned i = 0; i < in_ringbuffers_.size(); ++i) { + + const size_t incomingSamples = jack_ringbuffer_read_space(in_ringbuffers_[i]) / sizeof(captureFloatBuffer_[0]); + if (!incomingSamples) + continue; + + captureFloatBuffer_.resize(incomingSamples); + buffer.resize(incomingSamples); + + // write to output + const size_t from_ringbuffer = jack_ringbuffer_read_space(in_ringbuffers_[i]); + const size_t expected_bytes = std::min(incomingSamples * sizeof(captureFloatBuffer_[0]), from_ringbuffer); + // FIXME: while we have samples to write AND while we have space to write them + const size_t read_bytes = jack_ringbuffer_read(in_ringbuffers_[i], + (char *) captureFloatBuffer_.data(), expected_bytes); + if (read_bytes < expected_bytes) { + RING_WARN("Dropped %zu bytes", expected_bytes - read_bytes); + break; + } + + /* Write the data one frame at a time. This is + * inefficient, but makes things simpler. */ + // FIXME: this is braindead, we should write blocks of samples at a time + // convert a vector of samples from 1 channel to a float vector + convertFromFloat(captureFloatBuffer_, *buffer.getChannel(i)); + } +} + +/* This thread can lock, do whatever it wants, and read from/write to the jack + * ring buffers + * XXX: Access to shared state (i.e. member variables) should be synchronized if needed */ +void +JackLayer::ringbuffer_worker() +{ + flushMain(); + flushUrgent(); + + while (true) { + + std::unique_lock<std::mutex> lock(ringbuffer_thread_mutex_); + + // may have changed, we don't want to wait for a notification we won't get + if (not workerAlive_) + return; + + // FIXME this is all kinds of evil + usleep(20000); + + capture(); + playback(); + + // wait until process() signals more data + // FIXME: this checks for spurious wakes, but the predicate + // is rather arbitrary. We should wait until ring has/needs data + // and jack has/needs data. + data_ready_.wait(lock, [&] { + // Note: lock is released while waiting, and held when woken + // up, so this predicate is called while holding the lock + return not workerAlive_ + or ringbuffer_ready_for_read(in_ringbuffers_[0]); + }); + } +} + +void +createPorts(jack_client_t *client, std::vector<jack_port_t *> &ports, + bool playback, std::vector<jack_ringbuffer_t *> &ringbuffers) +{ + + const char **physical_ports = jack_get_ports(client, NULL, NULL, + playback ? JackPortIsInput : JackPortIsOutput | JackPortIsPhysical); + for (unsigned i = 0; physical_ports[i]; ++i) { + char port_name[32] = {0}; + if (playback) + snprintf(port_name, sizeof(port_name), "out_%d", i + 1); + else + snprintf(port_name, sizeof(port_name), "in_%d", i + 1); + port_name[sizeof(port_name) - 1] = '\0'; + jack_port_t *port = jack_port_register(client, + port_name, JACK_DEFAULT_AUDIO_TYPE, playback ? JackPortIsOutput : JackPortIsInput, 0); + if (port == nullptr) + throw std::runtime_error("Could not register JACK output port"); + ports.push_back(port); + + static const unsigned RB_SIZE = 16384; + jack_ringbuffer_t *rb = jack_ringbuffer_create(RB_SIZE); + if (rb == nullptr) + throw std::runtime_error("Could not create JACK ringbuffer"); + if (jack_ringbuffer_mlock(rb)) + throw std::runtime_error("Could not lock JACK ringbuffer in memory"); + ringbuffers.push_back(rb); + } + free(physical_ports); +} + + +JackLayer::JackLayer(const AudioPreference &p) : + AudioLayer(p), + captureClient_(nullptr), + playbackClient_(nullptr), + out_ports_(), + in_ports_(), + out_ringbuffers_(), + in_ringbuffers_(), + ringbuffer_thread_(), + workerAlive_(false), + ringbuffer_thread_mutex_(), + data_ready_(), + playbackBuffer_(0, audioFormat_), + playbackFloatBuffer_(), + captureBuffer_(0, audioFormat_), + captureFloatBuffer_(), + hardwareBufferSize_(0), + mainRingBuffer_(Manager::instance().getRingBufferPool().getRingBuffer(RingBufferPool::DEFAULT_ID)) +{ + playbackClient_ = jack_client_open(PACKAGE_NAME, + (jack_options_t) (JackNullOption | JackNoStartServer), NULL); + if (!playbackClient_) + throw std::runtime_error("Could not open JACK client"); + + captureClient_ = jack_client_open(PACKAGE_NAME, + (jack_options_t) (JackNullOption | JackNoStartServer), NULL); + if (!captureClient_) + throw std::runtime_error("Could not open JACK client"); + + jack_set_process_callback(captureClient_, process_capture, this); + jack_set_process_callback(playbackClient_, process_playback, this); + + createPorts(playbackClient_, out_ports_, true, out_ringbuffers_); + createPorts(captureClient_, in_ports_, false, in_ringbuffers_); + + const auto playRate = jack_get_sample_rate(playbackClient_); + const auto captureRate = jack_get_sample_rate(captureClient_); + if (playRate != captureRate) + RING_ERR("Mismatch between capture rate %u and playback rate %u", playRate, captureRate); + + hardwareBufferSize_ = jack_get_buffer_size(playbackClient_); + + auto update_buffer = [] (AudioBuffer &buf, size_t size, unsigned rate, unsigned nbChannels) { + buf.setSampleRate(rate); + buf.resize(size); + buf.setChannelNum(nbChannels); + }; + + update_buffer(playbackBuffer_, hardwareBufferSize_, playRate, out_ports_.size()); + update_buffer(captureBuffer_, hardwareBufferSize_, captureRate, in_ports_.size()); + + jack_on_shutdown(playbackClient_, onShutdown, this); +} + +JackLayer::~JackLayer() +{ + stopStream(); + + for (auto p : out_ports_) + jack_port_unregister(playbackClient_, p); + for (auto p : in_ports_) + jack_port_unregister(captureClient_, p); + + if (jack_client_close(playbackClient_)) + RING_ERR("JACK client could not close"); + if (jack_client_close(captureClient_)) + RING_ERR("JACK client could not close"); + + for (auto r : out_ringbuffers_) + jack_ringbuffer_free(r); + for (auto r : in_ringbuffers_) + jack_ringbuffer_free(r); +} + +void +JackLayer::updatePreference(AudioPreference & /*pref*/, int /*index*/, DeviceType /*type*/) +{} + +std::vector<std::string> +JackLayer::getCaptureDeviceList() const +{ + return std::vector<std::string>(); +} + +std::vector<std::string> +JackLayer::getPlaybackDeviceList() const +{ + return std::vector<std::string>(); +} + +int +JackLayer::getAudioDeviceIndex(const std::string& /*name*/, DeviceType /*type*/) const { return 0; } + +std::string +JackLayer::getAudioDeviceName(int /*index*/, DeviceType /*type*/) const { return ""; } + +int +JackLayer::getIndexCapture() const { return 0; } + +int +JackLayer::getIndexPlayback() const { return 0; } + +int +JackLayer::getIndexRingtone() const { return 0; } + +int +JackLayer::process_capture(jack_nframes_t frames, void *arg) +{ + JackLayer *context = static_cast<JackLayer*>(arg); + + for (unsigned i = 0; i < context->in_ringbuffers_.size(); ++i) { + + // read from input + jack_default_audio_sample_t *in_buffers = static_cast<jack_default_audio_sample_t*>(jack_port_get_buffer(context->in_ports_[i], frames)); + + const size_t bytes_to_read = frames * sizeof(*in_buffers); + size_t bytes_to_rb = jack_ringbuffer_write(context->in_ringbuffers_[i], (char *) in_buffers, bytes_to_read); + + // fill the rest with silence + if (bytes_to_rb < bytes_to_read) { + // TODO: set some flag for underrun? + RING_WARN("Dropped %lu bytes", bytes_to_read - bytes_to_rb); + } + } + + /* Tell the ringbuffer thread there is work to do. If it is already + * running, the lock will not be available. We can't wait + * here in the process() thread, but we don't need to signal + * in that case, because the ringbuffer thread will read all the + * data queued before waiting again. */ + if (context->ringbuffer_thread_mutex_.try_lock()) { + context->data_ready_.notify_one(); + context->ringbuffer_thread_mutex_.unlock(); + } + + return 0; +} + +int +JackLayer::process_playback(jack_nframes_t frames, void *arg) +{ + JackLayer *context = static_cast<JackLayer*>(arg); + + for (unsigned i = 0; i < context->out_ringbuffers_.size(); ++i) { + // write to output + jack_default_audio_sample_t *out_buffers = static_cast<jack_default_audio_sample_t*>(jack_port_get_buffer(context->out_ports_[i], frames)); + + const size_t bytes_to_write = frames * sizeof(*out_buffers); + size_t bytes_from_rb = jack_ringbuffer_read(context->out_ringbuffers_[i], (char *) out_buffers, bytes_to_write); + + // fill the rest with silence + if (bytes_from_rb < bytes_to_write) { + const size_t frames_read = bytes_from_rb / sizeof(*out_buffers); + memset(out_buffers + frames_read, 0, bytes_to_write - bytes_from_rb); + } + } + + return 0; +} + +/** + * Start the capture and playback. + */ +void +JackLayer::startStream() +{ + if (isStarted_) + return; + + dcblocker_.reset(); + const auto hardwareFormat = AudioFormat(playbackBuffer_.getSampleRate(), out_ports_.size()); + hardwareFormatAvailable(hardwareFormat); + + workerAlive_ = true; + assert(not ringbuffer_thread_.joinable()); + ringbuffer_thread_ = std::thread(&JackLayer::ringbuffer_worker, this); + + if (jack_activate(playbackClient_) or jack_activate(captureClient_)) { + RING_ERR("Could not activate JACK client"); + workerAlive_ = false; + ringbuffer_thread_.join(); + isStarted_ = false; + return; + } else { + isStarted_ = true; + } + + connectPorts(playbackClient_, JackPortIsInput, out_ports_); + connectPorts(captureClient_, JackPortIsOutput, in_ports_); +} + +void +JackLayer::onShutdown(void * /* data */) +{ + RING_WARN("JACK server shutdown"); + // FIXME: handle this safely +} + +/** + * Stop playback and capture. + */ +void +JackLayer::stopStream() +{ + { + std::lock_guard<std::mutex> lock(ringbuffer_thread_mutex_); + workerAlive_ = false; + data_ready_.notify_one(); + } + + if (jack_deactivate(playbackClient_) or jack_deactivate(captureClient_)) { + RING_ERR("JACK client could not deactivate"); + } + + isStarted_ = false; + + if (ringbuffer_thread_.joinable()) + ringbuffer_thread_.join(); + + flushMain(); + flushUrgent(); +} + +} // namespace ring diff --git a/src/media/audio/jack/jacklayer.h b/src/media/audio/jack/jacklayer.h new file mode 100644 index 0000000000..8b18920735 --- /dev/null +++ b/src/media/audio/jack/jacklayer.h @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2014-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef JACK_LAYER_H_ +#define JACK_LAYER_H_ + +#include <jack/jack.h> +#include <jack/ringbuffer.h> +#include <thread> +#include <mutex> +#include <vector> +#include <condition_variable> +#include "noncopyable.h" +#include "audio/audiolayer.h" + +#include <memory> + +namespace ring { + +class RingBuffer; + +class JackLayer : public AudioLayer { + + private: + NON_COPYABLE(JackLayer); + jack_client_t *captureClient_; + jack_client_t *playbackClient_; + std::vector<jack_port_t *> out_ports_; + std::vector<jack_port_t *> in_ports_; + std::vector<jack_ringbuffer_t *> out_ringbuffers_; + std::vector<jack_ringbuffer_t *> in_ringbuffers_; + std::thread ringbuffer_thread_; + bool workerAlive_; + std::mutex ringbuffer_thread_mutex_; + std::condition_variable data_ready_; + AudioBuffer playbackBuffer_; + std::vector<float> playbackFloatBuffer_; + AudioBuffer captureBuffer_; + std::vector<float> captureFloatBuffer_; + size_t hardwareBufferSize_; + std::shared_ptr<RingBuffer> mainRingBuffer_; + + static int process_capture(jack_nframes_t frames, void *arg); + static int process_playback(jack_nframes_t frames, void *arg); + + // separate thread + void ringbuffer_worker(); + // called from ringbuffer_worker() + void playback(); + void capture(); + void fillWithUrgent(AudioBuffer &buffer, size_t samplesToGet); + void fillWithVoice(AudioBuffer &buffer, size_t samplesAvail); + void fillWithToneOrRingtone(AudioBuffer &buffer); + + void read(AudioBuffer &buffer); + void write(AudioBuffer &buffer, std::vector<float> &floatBuffer); + + + std::vector<std::string> getCaptureDeviceList() const; + std::vector<std::string> getPlaybackDeviceList() const; + + int getAudioDeviceIndex(const std::string& name, DeviceType type) const; + std::string getAudioDeviceName(int index, DeviceType type) const; + int getIndexCapture() const; + int getIndexPlayback() const; + int getIndexRingtone() const; + void updatePreference(AudioPreference &pref, int index, DeviceType type); + + /** + * Start the capture and playback. + */ + void startStream(); + + /** + * Stop playback and capture. + */ + void stopStream(); + + static void onShutdown(void *data); + + public: + + JackLayer(const AudioPreference &); + ~JackLayer(); +}; + +} + +#endif // JACK_LAYER_H_ diff --git a/src/media/audio/opensl/Makefile.am b/src/media/audio/opensl/Makefile.am new file mode 100644 index 0000000000..4e3c0d9e2a --- /dev/null +++ b/src/media/audio/opensl/Makefile.am @@ -0,0 +1,11 @@ +include $(top_srcdir)/globals.mak + +if BUILD_OPENSL + +noinst_LTLIBRARIES = libopensl.la + +libopensl_la_SOURCES = opensllayer.cpp + +noinst_HEADERS = opensllayer.h + +endif diff --git a/src/media/audio/opensl/opensllayer.cpp b/src/media/audio/opensl/opensllayer.cpp new file mode 100644 index 0000000000..b7c2b9199c --- /dev/null +++ b/src/media/audio/opensl/opensllayer.cpp @@ -0,0 +1,861 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "opensllayer.h" + +#include "client/configurationmanager.h" + +#include "manager.h" +#include "audio/resampler.h" +#include "audio/ringbufferpool.h" +#include "audio/ringbuffer.h" +#include "audio/dcblocker.h" +#include "logger.h" +#include "array_size.h" + +#include <thread> +#include <chrono> +#include <cstdio> +#include <cassert> +#include <unistd.h> + +#include "SLES/OpenSLES_AndroidConfiguration.h" + +/* available only from api 14 */ +#ifndef SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION +#define SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION ((SLuint32) 0x00000004) +#endif + +namespace ring { + +const int OpenSLLayer::NB_BUFFER_PLAYBACK_QUEUE = ANDROID_BUFFER_QUEUE_LENGTH; +const int OpenSLLayer::NB_BUFFER_CAPTURE_QUEUE = ANDROID_BUFFER_QUEUE_LENGTH; + +// Constructor +OpenSLLayer::OpenSLLayer(const AudioPreference &pref) + : AudioLayer(pref) + , indexIn_(0) + , indexOut_(0) + , indexRing_(0) + , audioThread_(nullptr) + , engineObject_(nullptr) + , engineInterface_(nullptr) + , outputMixer_(nullptr) + , playerObject_(nullptr) + , recorderObject_(nullptr) + , playerInterface_(nullptr) + , recorderInterface_(nullptr) + , playbackBufferQueue_(nullptr) + , recorderBufferQueue_(nullptr) + , playbackBufferIndex_(0) + , recordBufferIndex_(0) + , hardwareFormat_(AudioFormat::MONO()) + , hardwareBuffSize_(BUFFER_SIZE) + , playbackBufferStack_(ANDROID_BUFFER_QUEUE_LENGTH, AudioBuffer(hardwareBuffSize_, AudioFormat::MONO())) + , recordBufferStack_(ANDROID_BUFFER_QUEUE_LENGTH, AudioBuffer(hardwareBuffSize_, AudioFormat::MONO())) + , mainRingBuffer_(Manager::instance().getRingBufferPool().getRingBuffer(RingBufferPool::DEFAULT_ID)) +{ +} + +// Destructor +OpenSLLayer::~OpenSLLayer() +{ + isStarted_ = false; + + /* Then close the audio devices */ + stopAudioPlayback(); + stopAudioCapture(); +} + +void +OpenSLLayer::init() +{ + initAudioEngine(); + initAudioPlayback(); + initAudioCapture(); + + flushMain(); + flushUrgent(); +} + +void +OpenSLLayer::startStream() +{ + dcblocker_.reset(); + + if (isStarted_) + return; + + RING_DBG("Start OpenSL audio layer"); + + std::vector<int32_t> hw_infos = Manager::instance().getConfigurationManager()->getHardwareAudioFormat(); + hardwareFormat_ = AudioFormat(hw_infos[0], 1); // Mono on Android + hardwareBuffSize_ = hw_infos[1]; + + for(auto& buf : playbackBufferStack_) + buf.resize(hardwareBuffSize_); + for(auto& buf : recordBufferStack_) + buf.resize(hardwareBuffSize_); + + hardwareFormatAvailable(hardwareFormat_); + + std::thread launcher([this](){ + init(); + startAudioPlayback(); + startAudioCapture(); + isStarted_ = true; + }); + launcher.detach(); +} + +void +OpenSLLayer::stopStream() +{ + if (not isStarted_) + return; + + RING_DBG("Stop OpenSL audio layer"); + + stopAudioPlayback(); + stopAudioCapture(); + + isStarted_ = false; + + flushMain(); + flushUrgent(); +} + +void +OpenSLLayer::initAudioEngine() const +{ + SLresult result; + + RING_DBG("Create Audio Engine\n"); + result = slCreateEngine((const SLObjectItf_ * const **)&engineObject_, 0, nullptr, 0, nullptr, nullptr); + assert(SL_RESULT_SUCCESS == result); + + RING_DBG("Realize Audio Engine\n"); + result = (*engineObject_)->Realize(engineObject_, SL_BOOLEAN_FALSE); + assert(SL_RESULT_SUCCESS == result); + + RING_DBG("Create Audio Engine Interface\n"); + result = (*engineObject_)->GetInterface(engineObject_, SL_IID_ENGINE, + (void *)&engineInterface_); + assert(SL_RESULT_SUCCESS == result); + + RING_DBG("Create Output Mixer\n"); + result = (*engineInterface_)->CreateOutputMix(engineInterface_, + (const SLObjectItf_ * const **)&outputMixer_, + 0, nullptr, nullptr); + assert(SL_RESULT_SUCCESS == result); + + RING_DBG("Realize Output Mixer\n"); + result = (*outputMixer_)->Realize(outputMixer_, SL_BOOLEAN_FALSE); + assert(SL_RESULT_SUCCESS == result); + + RING_DBG("Audio Engine Initialization Done\n"); +} + +void +OpenSLLayer::shutdownAudioEngine() +{ + + // destroy buffer queue audio player object, and invalidate all associated interfaces + RING_DBG("Shutdown audio player\n"); + + if (playerObject_ != nullptr) { + (*playerObject_)->Destroy(playerObject_); + playerObject_ = nullptr; + playerInterface_ = nullptr; + playbackBufferQueue_ = nullptr; + } + + // destroy output mix object, and invalidate all associated interfaces + RING_DBG("Shutdown audio mixer\n"); + + if (outputMixer_ != nullptr) { + (*outputMixer_)->Destroy(outputMixer_); + outputMixer_ = nullptr; + } + + if (recorderObject_ != nullptr) { + (*recorderObject_)->Destroy(recorderObject_); + recorderObject_ = nullptr; + recorderInterface_ = nullptr; + recorderBufferQueue_ = nullptr; + } + + // destroy engine object, and invalidate all associated interfaces + RING_DBG("Shutdown audio engine\n"); + if (engineObject_ != nullptr) { + (*engineObject_)->Destroy(engineObject_); + engineObject_ = nullptr; + engineInterface_ = nullptr; + } +} + +void +OpenSLLayer::initAudioPlayback() const +{ + assert(nullptr != engineObject_); + assert(nullptr != engineInterface_); + assert(nullptr != outputMixer_); + + SLresult result; + + // Initialize the location of the buffer queue + RING_DBG("Create playback queue\n"); + SLDataLocator_AndroidSimpleBufferQueue bufferLocation = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, + NB_BUFFER_PLAYBACK_QUEUE + }; + + // Initnialize the audio format for this queue + RING_DBG("Setting audio format\n"); + SLDataFormat_PCM audioFormat = {SL_DATAFORMAT_PCM, + 1, + audioFormat_.sample_rate * 1000, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_SPEAKER_FRONT_CENTER, + SL_BYTEORDER_LITTLEENDIAN + }; + + // Create the audio source + RING_DBG("Set Audio Sources\n"); + SLDataSource audioSource = {&bufferLocation, &audioFormat}; + + RING_DBG("Get Output Mixer interface\n"); + result = (*outputMixer_)->GetInterface(outputMixer_, SL_IID_OUTPUTMIX, + (void *)&outputMixInterface_); + CheckErr(result); + + // Cofiguration fo the audio sink as an output mixer + RING_DBG("Set output mixer location\n"); + SLDataLocator_OutputMix mixerLocation = {SL_DATALOCATOR_OUTPUTMIX, outputMixer_}; + SLDataSink audioSink = {&mixerLocation, nullptr}; + + const SLInterfaceID ids[] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + SL_IID_VOLUME, + SL_IID_ANDROIDCONFIGURATION, + SL_IID_PLAY}; + const SLboolean req[] = {SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE, SL_BOOLEAN_TRUE}; + + const unsigned nbInterface = RING_ARRAYSIZE(ids); + + // create audio player + RING_DBG("Create audio player\n"); + result = (*engineInterface_)->CreateAudioPlayer(engineInterface_, + (const SLObjectItf_ * const **)&playerObject_, + &audioSource, + &audioSink, + nbInterface, + ids, req); + assert(SL_RESULT_SUCCESS == result); + + SLAndroidConfigurationItf playerConfig; + SLint32 streamType = SL_ANDROID_STREAM_VOICE; + + + result = (*playerObject_)->GetInterface(playerObject_, + SL_IID_ANDROIDCONFIGURATION, + (void *)&playerConfig); + + if (result == SL_RESULT_SUCCESS && playerConfig) { + result = (*playerConfig)->SetConfiguration( + playerConfig, SL_ANDROID_KEY_STREAM_TYPE, + &streamType, sizeof(SLint32)); + } + + RING_DBG("Realize audio player\n"); + result = (*playerObject_)->Realize(playerObject_, SL_BOOLEAN_FALSE); + assert(SL_RESULT_SUCCESS == result); + + if (result != SL_RESULT_SUCCESS) { + RING_ERR("Unable to set android player configuration"); + } + + // create audio interface + RING_DBG("Create audio player interface\n"); + result = (*playerObject_)->GetInterface(playerObject_, SL_IID_PLAY, + (void *)&playerInterface_); + assert(SL_RESULT_SUCCESS == result); + + // create the buffer queue interface + RING_DBG("Create buffer queue interface\n"); + result = (*playerObject_)->GetInterface(playerObject_, SL_IID_BUFFERQUEUE, + (void *)&playbackBufferQueue_); + assert(SL_RESULT_SUCCESS == result); + + // register the buffer queue on the buffer object + RING_DBG("Register audio callback\n"); + result = (*playbackBufferQueue_)->RegisterCallback(playbackBufferQueue_, + audioPlaybackCallback, + (void *)this); + assert(SL_RESULT_SUCCESS == result); + + RING_DBG("Audio Playback Initialization Done\n"); +} + +void +OpenSLLayer::initAudioCapture() const +{ + SLresult result; + + // configure audio source + RING_DBG("Configure audio source\n"); + SLDataLocator_IODevice deviceLocator = {SL_DATALOCATOR_IODEVICE, + SL_IODEVICE_AUDIOINPUT, + SL_DEFAULTDEVICEID_AUDIOINPUT, + nullptr + }; + + SLDataSource audioSource = {&deviceLocator, + nullptr + }; + + // configure audio sink + RING_DBG("Configure audio sink\n"); + + SLDataLocator_AndroidSimpleBufferQueue bufferLocator = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, + NB_BUFFER_CAPTURE_QUEUE + }; + + RING_DBG("Capture-> Sampling Rate: %d", audioFormat_.sample_rate); + RING_DBG("Capture-> getInternalSamplingRate: %d", Manager::instance().getRingBufferPool().getInternalSamplingRate()); + SLDataFormat_PCM audioFormat = {SL_DATAFORMAT_PCM, 1, + audioFormat_.sample_rate * 1000, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_PCMSAMPLEFORMAT_FIXED_16, + SL_SPEAKER_FRONT_CENTER, + SL_BYTEORDER_LITTLEENDIAN + }; + + SLDataSink audioSink = {&bufferLocator, + &audioFormat + }; + + // create audio recorder + // (requires the RECORD_AUDIO permission) + RING_DBG("Create audio recorder\n"); + const SLInterfaceID id[2] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + SL_IID_ANDROIDCONFIGURATION}; + const SLboolean req[2] ={SL_BOOLEAN_TRUE, + SL_BOOLEAN_TRUE}; + SLAndroidConfigurationItf recorderConfig; + + if (engineInterface_ != nullptr) { + result = (*engineInterface_)->CreateAudioRecorder(engineInterface_, + (const SLObjectItf_ * const **)&recorderObject_, + &audioSource, + &audioSink, + 2, id, req); + } + + if (SL_RESULT_SUCCESS != result) { + RING_DBG("Error: could not create audio recorder"); + return; + } + + /* Set Android configuration */ + result = (*recorderObject_)->GetInterface(recorderObject_, + SL_IID_ANDROIDCONFIGURATION, + (void *)&recorderConfig); + if (result == SL_RESULT_SUCCESS) { + SLint32 streamType = SL_ANDROID_RECORDING_PRESET_VOICE_COMMUNICATION; + result = (*recorderConfig)->SetConfiguration( + recorderConfig, SL_ANDROID_KEY_RECORDING_PRESET, + &streamType, sizeof(SLint32)); + } + + if (result != SL_RESULT_SUCCESS) { + RING_DBG("Warning: Unable to set android recorder configuration"); + return; + } + + // realize the audio recorder + RING_DBG("Realize the audio recorder\n"); + result = (*recorderObject_)->Realize(recorderObject_, SL_BOOLEAN_FALSE); + + if (SL_RESULT_SUCCESS != result) { + RING_DBG("Error: could not realize audio recorder"); + return; + } + + // get the record interface + RING_DBG("Create the record interface\n"); + result = (*recorderObject_)->GetInterface(recorderObject_, + SL_IID_RECORD, + (void *)&recorderInterface_); + assert(SL_RESULT_SUCCESS == result); + + // get the buffer queue interface + RING_DBG("Create the buffer queue interface\n"); + result = (*recorderObject_)->GetInterface(recorderObject_, SL_IID_ANDROIDSIMPLEBUFFERQUEUE, + (void *)&recorderBufferQueue_); + assert(SL_RESULT_SUCCESS == result); + + // register callback on the buffer queue + RING_DBG("Register the audio capture callback\n"); + result = (*recorderBufferQueue_)->RegisterCallback(recorderBufferQueue_, + audioCaptureCallback, + (void *)this); + assert(SL_RESULT_SUCCESS == result); + + RING_DBG("Audio capture initialized\n"); +} + + +void +OpenSLLayer::startAudioPlayback() +{ + assert(nullptr != playbackBufferQueue_); + + RING_DBG("Start audio playback\n"); + + SLresult result; + + for (int i = 0; i < NB_BUFFER_PLAYBACK_QUEUE; i++) { + AudioBuffer &buffer = getNextPlaybackBuffer(); + incrementPlaybackIndex(); + + buffer.reset(); + + result = (*playbackBufferQueue_)->Enqueue(playbackBufferQueue_, buffer.getChannel(0)->data(), buffer.frames()); + + if (SL_RESULT_SUCCESS != result) { + RING_DBG("Error could not enqueue initial buffers\n"); + } + } + + result = (*playerInterface_)->SetPlayState(playerInterface_, SL_PLAYSTATE_PLAYING); + assert(SL_RESULT_SUCCESS == result); + + RING_DBG("Audio playback started\n"); +} + +void +OpenSLLayer::startAudioCapture() +{ + assert(nullptr != playbackBufferQueue_); + + RING_DBG("Start audio capture\n"); + + SLresult result; + + + // in case already recording, stop recording and clear buffer queue + if (recorderInterface_ != nullptr) { + result = (*recorderInterface_)->SetRecordState(recorderInterface_, SL_RECORDSTATE_STOPPED); + assert(SL_RESULT_SUCCESS == result); + } + + RING_DBG("Clearing recorderBufferQueue\n"); + result = (*recorderBufferQueue_)->Clear(recorderBufferQueue_); + assert(SL_RESULT_SUCCESS == result); + + RING_DBG("getting next record buffer\n"); + // enqueue an empty buffer to be filled by the recorder + // (for streaming recording, we enqueue at least 2 empty buffers to start things off) + AudioBuffer &buffer = getNextRecordBuffer(); + incrementRecordIndex(); + + buffer.reset(); + + RING_DBG("Enqueue record buffer\n"); + result = (*recorderBufferQueue_)->Enqueue(recorderBufferQueue_, buffer.getChannel(0)->data(), buffer.frames()); + + // the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT, + // which for this code example would indicate a programming error + if (SL_RESULT_SUCCESS != result) { + RING_DBG("Error could not enqueue buffers in audio capture\n"); + return; + } + + // start recording + result = (*recorderInterface_)->SetRecordState(recorderInterface_, SL_RECORDSTATE_RECORDING); + assert(SL_RESULT_SUCCESS == result); + + RING_DBG("Audio capture started\n"); +} + +void +OpenSLLayer::stopAudioPlayback() +{ + RING_DBG("Stop audio playback\n"); + + if (playerInterface_ != nullptr) { + SLresult result; + result = (*playerInterface_)->SetPlayState(playerInterface_, SL_PLAYSTATE_STOPPED); + assert(SL_RESULT_SUCCESS == result); + } + + RING_DBG("Audio playback stopped\n"); +} + +void +OpenSLLayer::stopAudioCapture() +{ + RING_DBG("Stop audio capture\n"); + + if (recorderInterface_ != nullptr) { + SLresult result; + result = (*recorderInterface_)->SetRecordState(recorderInterface_, SL_RECORDSTATE_STOPPED); + assert(SL_RESULT_SUCCESS == result); + } + + RING_DBG("Audio capture stopped\n"); + +} + +std::vector<std::string> +OpenSLLayer::getCaptureDeviceList() const +{ + std::vector<std::string> captureDeviceList; + + + +// Although OpenSL ES specification allows enumerating +// available output (and also input) devices, NDK implementation is not mature enough to +// obtain or select proper one (SLAudioIODeviceCapabilitiesItf, the official interface +// to obtain such an information)-> SL_FEATURE_UNSUPPORTED + + SLuint32 InputDeviceIDs[MAX_NUMBER_INPUT_DEVICES]; + SLint32 numInputs = 0; + SLboolean mic_available = SL_BOOLEAN_FALSE; + SLuint32 mic_deviceID = 0; + + SLresult res; + + initAudioEngine(); + initAudioPlayback(); + initAudioCapture(); + + + // Get the Audio IO DEVICE CAPABILITIES interface, implicit + RING_DBG("Get the Audio IO DEVICE CAPABILITIES interface, implicit"); + + res = (*engineObject_)->GetInterface(engineObject_, SL_IID_AUDIOIODEVICECAPABILITIES, (void*)&AudioIODeviceCapabilitiesItf); + CheckErr(res); + + RING_DBG("Get the Audio IO DEVICE CAPABILITIES interface, implicit"); + numInputs = MAX_NUMBER_INPUT_DEVICES; + + res = (*AudioIODeviceCapabilitiesItf)->GetAvailableAudioInputs(AudioIODeviceCapabilitiesItf, &numInputs, InputDeviceIDs); + CheckErr(res); + + // Search for either earpiece microphone or headset microphone input + // device - with a preference for the latter + for (int i = 0; i < numInputs; i++) { + res = (*AudioIODeviceCapabilitiesItf)->QueryAudioInputCapabilities(AudioIODeviceCapabilitiesItf, + InputDeviceIDs[i], + (SLAudioInputDescriptor *)&AudioInputDescriptor); + CheckErr(res); + + if (AudioInputDescriptor.deviceConnection == SL_DEVCONNECTION_ATTACHED_WIRED and + AudioInputDescriptor.deviceScope == SL_DEVSCOPE_USER and + AudioInputDescriptor.deviceLocation == SL_DEVLOCATION_HEADSET) { + RING_DBG("SL_DEVCONNECTION_ATTACHED_WIRED : mic_deviceID: %d", InputDeviceIDs[i] ); + mic_deviceID = InputDeviceIDs[i]; + mic_available = SL_BOOLEAN_TRUE; + break; + } else if (AudioInputDescriptor.deviceConnection == SL_DEVCONNECTION_INTEGRATED and + AudioInputDescriptor.deviceScope == SL_DEVSCOPE_USER and + AudioInputDescriptor.deviceLocation == SL_DEVLOCATION_HANDSET) { + RING_DBG("SL_DEVCONNECTION_INTEGRATED : mic_deviceID: %d", InputDeviceIDs[i] ); + mic_deviceID = InputDeviceIDs[i]; + mic_available = SL_BOOLEAN_TRUE; + break; + } + } + + if (!mic_available) { + // Appropriate error message here + RING_ERR("No mic available quitting"); + exit(1); + } + + + return captureDeviceList; +} + +/* Checks for error. If any errors exit the application! */ +void +OpenSLLayer::CheckErr( SLresult res ) const +{ + if (res != SL_RESULT_SUCCESS) { + // Debug printing to be placed here + exit(1); + } +} + +std::vector<std::string> +OpenSLLayer::getPlaybackDeviceList() const +{ + std::vector<std::string> playbackDeviceList; + + return playbackDeviceList; +} + +void +OpenSLLayer::audioPlaybackCallback(SLAndroidSimpleBufferQueueItf queue, void *context) +{ + assert(nullptr != context); + //auto start = std::chrono::high_resolution_clock::now(); + static_cast<OpenSLLayer*>(context)->playback(queue); + //auto end = std::chrono::high_resolution_clock::now(); + //auto elapsed = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start); + //RING_DBG("Took %d us\n", elapsed/1000); +} + +void +OpenSLLayer::playback(SLAndroidSimpleBufferQueueItf queue) +{ + assert(nullptr != queue); + notifyIncomingCall(); + + AudioBuffer &buffer = getNextPlaybackBuffer(); + size_t urgentSamplesToGet = urgentRingBuffer_.availableForGet(RingBufferPool::DEFAULT_ID); + + bufferIsFilled_ = false; + if (urgentSamplesToGet > 0) { + bufferIsFilled_ = audioPlaybackFillWithUrgent(buffer, std::min(urgentSamplesToGet, hardwareBuffSize_)); + } else { + auto& main_buffer = Manager::instance().getRingBufferPool(); + buffer.resize(hardwareBuffSize_); + size_t samplesToGet = audioPlaybackFillWithVoice(buffer); + if (samplesToGet == 0) { + bufferIsFilled_ = audioPlaybackFillWithToneOrRingtone(buffer); + } else { + bufferIsFilled_ = true; + } + } + + if (bufferIsFilled_) { + SLresult result = (*queue)->Enqueue(queue, buffer.getChannel(0)->data(), buffer.frames()*sizeof(AudioSample)); + if (SL_RESULT_SUCCESS != result) { + RING_DBG("Error could not enqueue buffers in playback callback\n"); + } + incrementPlaybackIndex(); + } else { + RING_DBG("Error buffer not filled in audio playback\n"); + } +} + +void +OpenSLLayer::audioCaptureCallback(SLAndroidSimpleBufferQueueItf queue, void *context) +{ + assert(nullptr != context); + static_cast<OpenSLLayer*>(context)->capture(queue); +} + +void +OpenSLLayer::capture(SLAndroidSimpleBufferQueueItf queue) +{ + assert(nullptr != queue); + + AudioBuffer &old_buffer = getNextRecordBuffer(); + incrementRecordIndex(); + AudioBuffer &buffer = getNextRecordBuffer(); + + SLresult result; + // enqueue an empty buffer to be filled by the recorder + // (for streaming recording, we enqueue at least 2 empty buffers to start things off) + result = (*recorderBufferQueue_)->Enqueue(recorderBufferQueue_, buffer.getChannel(0)->data(), buffer.frames()*sizeof(AudioSample)); + + audioCaptureFillBuffer(old_buffer); + + // the most likely other result is SL_RESULT_BUFFER_INSUFFICIENT, + // which for this code example would indicate a programming error + assert(SL_RESULT_SUCCESS == result); +} + + + +void +OpenSLLayer::updatePreference(AudioPreference &preference, int index, DeviceType type) +{ +#ifdef OUTSIDE_TESTING + + switch (type) { + case Device::PLAYBACK: + break; + + case Device::CAPTURE: + break; + + case Device::RINGTONE: + break; + } + +#endif +} + +void OpenSLLayer::audioCaptureFillBuffer(AudioBuffer &buffer) +{ + RingBufferPool &mbuffer = Manager::instance().getRingBufferPool(); + + //const unsigned mainBufferSampleRate = mbuffer.getInternalSamplingRate(); + const AudioFormat mainBufferFormat = mbuffer.getInternalAudioFormat(); + const bool resample = mainBufferFormat.sample_rate != audioFormat_.sample_rate; + + buffer.applyGain(isCaptureMuted_ ? 0.0 : captureGain_); + + if (resample) { + int outSamples = buffer.frames() * (static_cast<double>(audioFormat_.sample_rate) / mainBufferFormat.sample_rate); + AudioBuffer out(outSamples, mainBufferFormat); + resampler_->resample(buffer, out); + dcblocker_.process(out); + mainRingBuffer_->put(out); + } else { + dcblocker_.process(buffer); + mainRingBuffer_->put(buffer); + } +} + +bool OpenSLLayer::audioPlaybackFillWithToneOrRingtone(AudioBuffer &buffer) +{ + buffer.resize(hardwareBuffSize_); + AudioLoop *tone = Manager::instance().getTelephoneTone(); + AudioLoop *file_tone = Manager::instance().getTelephoneFile(); + + // In case of a dtmf, the pointers will be set to nullptr once the dtmf length is + // reached. For this reason we need to fill audio buffer with zeros if pointer is nullptr + if (tone) { + tone->getNext(buffer, playbackGain_); + } else if (file_tone) { + file_tone->getNext(buffer, playbackGain_); + } else { + buffer.reset(); + } + + return true; +} + +bool OpenSLLayer::audioPlaybackFillWithUrgent(AudioBuffer &buffer, size_t samplesToGet) +{ + // Urgent data (dtmf, incoming call signal) come first. + samplesToGet = std::min(samplesToGet, hardwareBuffSize_); + buffer.resize(samplesToGet); + urgentRingBuffer_.get(buffer, RingBufferPool::DEFAULT_ID); + buffer.applyGain(isPlaybackMuted_ ? 0.0 : playbackGain_); + + // Consume the regular one as well (same amount of samples) + Manager::instance().getRingBufferPool().discard(samplesToGet, RingBufferPool::DEFAULT_ID); + + return true; +} + +size_t OpenSLLayer::audioPlaybackFillWithVoice(AudioBuffer &buffer) +{ + RingBufferPool &mainBuffer = Manager::instance().getRingBufferPool(); + size_t got = mainBuffer.getAvailableData(buffer, RingBufferPool::DEFAULT_ID); + buffer.resize(got); + buffer.applyGain(isPlaybackMuted_ ? 0.0 : playbackGain_); + if (audioFormat_.sample_rate != mainBuffer.getInternalSamplingRate()) { + RING_DBG("OpenSLLayer::audioPlaybackFillWithVoice sample_rate != mainBuffer.getInternalSamplingRate() \n"); + AudioBuffer out(buffer, false); + out.setSampleRate(audioFormat_.sample_rate); + resampler_->resample(buffer, out); + buffer = out; + } + return buffer.size(); +} + +void dumpAvailableEngineInterfaces() +{ + SLresult result; + RING_DBG("Engine Interfaces\n"); + SLuint32 numSupportedInterfaces; + result = slQueryNumSupportedEngineInterfaces(&numSupportedInterfaces); + assert(SL_RESULT_SUCCESS == result); + result = slQueryNumSupportedEngineInterfaces(NULL); + assert(SL_RESULT_PARAMETER_INVALID == result); + + RING_DBG("Engine number of supported interfaces %lu\n", numSupportedInterfaces); + for(SLuint32 i=0; i< numSupportedInterfaces; i++){ + SLInterfaceID pInterfaceId; + slQuerySupportedEngineInterfaces(i, &pInterfaceId); + const char* nm = "unknown iid"; + + if (pInterfaceId==SL_IID_NULL) nm="null"; + else if (pInterfaceId==SL_IID_OBJECT) nm="object"; + else if (pInterfaceId==SL_IID_AUDIOIODEVICECAPABILITIES) nm="audiodevicecapabilities"; + else if (pInterfaceId==SL_IID_LED) nm="led"; + else if (pInterfaceId==SL_IID_VIBRA) nm="vibra"; + else if (pInterfaceId==SL_IID_METADATAEXTRACTION) nm="metadataextraction"; + else if (pInterfaceId==SL_IID_METADATATRAVERSAL) nm="metadatatraversal"; + else if (pInterfaceId==SL_IID_DYNAMICSOURCE) nm="dynamicsource"; + else if (pInterfaceId==SL_IID_OUTPUTMIX) nm="outputmix"; + else if (pInterfaceId==SL_IID_PLAY) nm="play"; + else if (pInterfaceId==SL_IID_PREFETCHSTATUS) nm="prefetchstatus"; + else if (pInterfaceId==SL_IID_PLAYBACKRATE) nm="playbackrate"; + else if (pInterfaceId==SL_IID_SEEK) nm="seek"; + else if (pInterfaceId==SL_IID_RECORD) nm="record"; + else if (pInterfaceId==SL_IID_EQUALIZER) nm="equalizer"; + else if (pInterfaceId==SL_IID_VOLUME) nm="volume"; + else if (pInterfaceId==SL_IID_DEVICEVOLUME) nm="devicevolume"; + else if (pInterfaceId==SL_IID_BUFFERQUEUE) nm="bufferqueue"; + else if (pInterfaceId==SL_IID_PRESETREVERB) nm="presetreverb"; + else if (pInterfaceId==SL_IID_ENVIRONMENTALREVERB) nm="environmentalreverb"; + else if (pInterfaceId==SL_IID_EFFECTSEND) nm="effectsend"; + else if (pInterfaceId==SL_IID_3DGROUPING) nm="3dgrouping"; + else if (pInterfaceId==SL_IID_3DCOMMIT) nm="3dcommit"; + else if (pInterfaceId==SL_IID_3DLOCATION) nm="3dlocation"; + else if (pInterfaceId==SL_IID_3DDOPPLER) nm="3ddoppler"; + else if (pInterfaceId==SL_IID_3DSOURCE) nm="3dsource"; + else if (pInterfaceId==SL_IID_3DMACROSCOPIC) nm="3dmacroscopic"; + else if (pInterfaceId==SL_IID_MUTESOLO) nm="mutesolo"; + else if (pInterfaceId==SL_IID_DYNAMICINTERFACEMANAGEMENT) nm="dynamicinterfacemanagement"; + else if (pInterfaceId==SL_IID_MIDIMESSAGE) nm="midimessage"; + else if (pInterfaceId==SL_IID_MIDIMUTESOLO) nm="midimutesolo"; + else if (pInterfaceId==SL_IID_MIDITEMPO) nm="miditempo"; + else if (pInterfaceId==SL_IID_MIDITIME) nm="miditime"; + else if (pInterfaceId==SL_IID_AUDIODECODERCAPABILITIES) nm="audiodecodercapabilities"; + else if (pInterfaceId==SL_IID_AUDIOENCODERCAPABILITIES) nm="audioencodercapabilities"; + else if (pInterfaceId==SL_IID_AUDIOENCODER) nm="audioencoder"; + else if (pInterfaceId==SL_IID_BASSBOOST) nm="bassboost"; + else if (pInterfaceId==SL_IID_PITCH) nm="pitch"; + else if (pInterfaceId==SL_IID_RATEPITCH) nm="ratepitch"; + else if (pInterfaceId==SL_IID_VIRTUALIZER) nm="virtualizer"; + else if (pInterfaceId==SL_IID_VISUALIZATION) nm="visualization"; + else if (pInterfaceId==SL_IID_ENGINE) nm="engine"; + else if (pInterfaceId==SL_IID_ENGINECAPABILITIES) nm="enginecapabilities"; + else if (pInterfaceId==SL_IID_THREADSYNC) nm="theadsync"; + else if (pInterfaceId==SL_IID_ANDROIDEFFECT) nm="androideffect"; + else if (pInterfaceId==SL_IID_ANDROIDEFFECTSEND) nm="androideffectsend"; + else if (pInterfaceId==SL_IID_ANDROIDEFFECTCAPABILITIES) nm="androideffectcapabilities"; + else if (pInterfaceId==SL_IID_ANDROIDCONFIGURATION) nm="androidconfiguration"; + else if (pInterfaceId==SL_IID_ANDROIDSIMPLEBUFFERQUEUE) nm="simplebuferqueue"; + //else if (pInterfaceId==//SL_IID_ANDROIDBUFFERQUEUESOURCE) nm="bufferqueuesource"; + RING_DBG("%s,",nm); + } +} + +} // namespace ring diff --git a/src/media/audio/opensl/opensllayer.h b/src/media/audio/opensl/opensllayer.h new file mode 100644 index 0000000000..ac0a44de50 --- /dev/null +++ b/src/media/audio/opensl/opensllayer.h @@ -0,0 +1,274 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef _OPENSL_LAYER_H +#define _OPENSL_LAYER_H + +#include <SLES/OpenSLES.h> +#include <SLES/OpenSLES_Android.h> +#include <vector> + +#include "audio/audiolayer.h" + +class AudioPreference; + +#include "noncopyable.h" + +#include <memory> + +namespace ring { + +class OpenSLThread; +class RingBuffer; + +#define ANDROID_BUFFER_QUEUE_LENGTH 2U +#define BUFFER_SIZE 512U + +#define MAX_NUMBER_INTERFACES 5 +#define MAX_NUMBER_INPUT_DEVICES 3 + +/** + * @file OpenSLLayer.h + * @brief Main sound class for android. Manages the data transfers between the application and the hardware. + */ + +class OpenSLLayer : public AudioLayer { + public: + /** + * Constructor + */ + OpenSLLayer(const AudioPreference &pref); + + /** + * Destructor + */ + ~OpenSLLayer(); + + /** + * Start the capture stream and prepare the playback stream. + * The playback starts accordingly to its threshold + */ + virtual void startStream(); + + /** + * Stop the playback and capture streams. + * Drops the pending frames and put the capture and playback handles to PREPARED state + */ + virtual void stopStream(); + + /** + * Scan the sound card available for capture on the system + * @return std::vector<std::string> The vector containing the string description of the card + */ + virtual std::vector<std::string> getCaptureDeviceList() const; + + /** + * Scan the sound card available for capture on the system + * @return std::vector<std::string> The vector containing the string description of the card + */ + virtual std::vector<std::string> getPlaybackDeviceList() const; + + void init(); + + void initAudioEngine() const; + + void shutdownAudioEngine(); + + void initAudioPlayback() const; + + void initAudioCapture() const; + + void startAudioPlayback(); + + void startAudioCapture(); + + void stopAudioPlayback(); + + void stopAudioCapture(); + + virtual int getAudioDeviceIndex(const std::string&, DeviceType) const { + return 0; + } + + virtual std::string getAudioDeviceName(int, DeviceType) const { + return ""; + } + + private: + typedef std::vector<AudioBuffer> AudioBufferStack; + + + bool audioBufferFillWithZeros(AudioBuffer &buffer); + + /** + * Here fill the input buffer with tone or ringtone samples + */ + bool audioPlaybackFillWithToneOrRingtone(AudioBuffer &buffer); + + bool audioPlaybackFillWithUrgent(AudioBuffer &buffer, size_t bytesAvail); + + size_t audioPlaybackFillWithVoice(AudioBuffer &buffer); + + void audioCaptureFillBuffer(AudioBuffer &buffer); + + + /** + * This is the main audio playabck callback called by the OpenSL layer + */ + static void audioPlaybackCallback(SLAndroidSimpleBufferQueueItf bq, void *context); + + /** + * This is the main audio capture callback called by the OpenSL layer + */ + static void audioCaptureCallback(SLAndroidSimpleBufferQueueItf bq, void *context); + + /** + * Get the index of the audio card for capture + * @return int The index of the card used for capture + * 0 for the first available card on the system, 1 ... + */ + virtual int getIndexCapture() const { + return indexIn_; + } + + /** + * Get the index of the audio card for playback + * @return int The index of the card used for playback + * 0 for the first available card on the system, 1 ... + */ + virtual int getIndexPlayback() const { + return indexOut_; + } + + /** + * Get the index of the audio card for ringtone (could be differnet from playback) + * @return int The index of the card used for ringtone + * 0 for the first available card on the system, 1 ... + */ + virtual int getIndexRingtone() const { + return indexRing_; + } + + AudioBuffer &getNextPlaybackBuffer(void) { + return playbackBufferStack_[playbackBufferIndex_]; + } + + AudioBuffer &getNextRecordBuffer(void) { + return recordBufferStack_[recordBufferIndex_]; + } + + void incrementPlaybackIndex(void) { + playbackBufferIndex_ = (playbackBufferIndex_ + 1) % NB_BUFFER_PLAYBACK_QUEUE; + } + + void incrementRecordIndex(void) { + recordBufferIndex_ = (recordBufferIndex_ + 1) % NB_BUFFER_CAPTURE_QUEUE; + } + + void CheckErr( SLresult res ) const; + + void playback(SLAndroidSimpleBufferQueueItf queue); + void capture(SLAndroidSimpleBufferQueueItf queue); + + void dumpAvailableEngineInterfaces(); + friend class OpenSLThread; + + static const int NB_BUFFER_PLAYBACK_QUEUE; + + static const int NB_BUFFER_CAPTURE_QUEUE; + + /** + * Number of audio cards on which capture stream has been opened + */ + int indexIn_; + + /** + * Number of audio cards on which playback stream has been opened + */ + int indexOut_; + + /** + * Number of audio cards on which ringtone stream has been opened + */ + int indexRing_; + + NON_COPYABLE(OpenSLLayer); + + virtual void updatePreference(AudioPreference &pref, int index, DeviceType type); + + OpenSLThread *audioThread_; + + /** + * OpenSL standard object interface + */ + SLObjectItf engineObject_; + + /** + * OpenSL sound engine interface + */ + SLEngineItf engineInterface_; + + /** + * Output mix interface + */ + SLObjectItf outputMixer_; + SLObjectItf playerObject_; + SLObjectItf recorderObject_; + + + SLOutputMixItf outputMixInterface_; + SLPlayItf playerInterface_; + + SLRecordItf recorderInterface_; + + SLAudioIODeviceCapabilitiesItf AudioIODeviceCapabilitiesItf; + SLAudioInputDescriptor AudioInputDescriptor; + + /** + * OpenSL playback buffer + */ + SLAndroidSimpleBufferQueueItf playbackBufferQueue_; + SLAndroidSimpleBufferQueueItf recorderBufferQueue_; + + int playbackBufferIndex_; + int recordBufferIndex_; + + bool bufferIsFilled_; + AudioFormat hardwareFormat_; + size_t hardwareBuffSize_; + + AudioBufferStack playbackBufferStack_; + AudioBufferStack recordBufferStack_; + std::shared_ptr<RingBuffer> mainRingBuffer_; +}; + +} + +#endif // _OPENSL_LAYER_H_ diff --git a/src/media/audio/pulseaudio/Makefile.am b/src/media/audio/pulseaudio/Makefile.am new file mode 100644 index 0000000000..36aed1ff2b --- /dev/null +++ b/src/media/audio/pulseaudio/Makefile.am @@ -0,0 +1,16 @@ +include $(top_srcdir)/globals.mak + +if BUILD_PULSE + +noinst_LTLIBRARIES = libpulselayer.la + +libpulselayer_la_SOURCES = \ + audiostream.cpp \ + pulselayer.cpp + + +noinst_HEADERS = \ + audiostream.h \ + pulselayer.h + +endif diff --git a/src/media/audio/pulseaudio/audiostream.cpp b/src/media/audio/pulseaudio/audiostream.cpp new file mode 100644 index 0000000000..6476f878c2 --- /dev/null +++ b/src/media/audio/pulseaudio/audiostream.cpp @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "audiostream.h" +#include "pulselayer.h" +#include "logger.h" +#include "intrin.h" + +#include <stdexcept> + +namespace ring { + +AudioStream::AudioStream(pa_context *c, + pa_threaded_mainloop *m, + const char *desc, + int type, + unsigned samplrate, + const PaDeviceInfos* infos) + : audiostream_(0), mainloop_(m) +{ + const pa_channel_map channel_map = infos->channel_map; + + pa_sample_spec sample_spec = { + PA_SAMPLE_S16LE, // PA_SAMPLE_FLOAT32LE, + samplrate, + channel_map.channels + }; + + RING_DBG("%s: trying to create stream with device %s (%dHz, %d channels)", desc, infos->name.c_str(), samplrate, channel_map.channels); + + assert(pa_sample_spec_valid(&sample_spec)); + assert(pa_channel_map_valid(&channel_map)); + + audiostream_ = pa_stream_new(c, desc, &sample_spec, &channel_map); + + if (!audiostream_) { + RING_ERR("%s: pa_stream_new() failed : %s" , desc, pa_strerror(pa_context_errno(c))); + throw std::runtime_error("Could not create stream\n"); + } + + pa_buffer_attr attributes; + attributes.maxlength = pa_usec_to_bytes(160 * PA_USEC_PER_MSEC, &sample_spec); + attributes.tlength = pa_usec_to_bytes(80 * PA_USEC_PER_MSEC, &sample_spec); + attributes.prebuf = 0; + attributes.fragsize = pa_usec_to_bytes(80 * PA_USEC_PER_MSEC, &sample_spec); + attributes.minreq = (uint32_t) -1; + + { + PulseMainLoopLock lock(mainloop_); + const pa_stream_flags_t flags = static_cast<pa_stream_flags_t>(PA_STREAM_ADJUST_LATENCY | PA_STREAM_AUTO_TIMING_UPDATE); + + if (type == PLAYBACK_STREAM || type == RINGTONE_STREAM) { + pa_stream_connect_playback(audiostream_, + infos->name.empty() ? NULL : infos->name.c_str(), + &attributes, + flags, + NULL, NULL); + } else if (type == CAPTURE_STREAM) { + pa_stream_connect_record(audiostream_, + infos->name.empty() ? NULL : infos->name.c_str(), + &attributes, + flags); + } + } + + pa_stream_set_state_callback(audiostream_, stream_state_callback, NULL); +} + +AudioStream::~AudioStream() +{ + PulseMainLoopLock lock(mainloop_); + + pa_stream_disconnect(audiostream_); + + // make sure we don't get any further callback + pa_stream_set_state_callback(audiostream_, NULL, NULL); + pa_stream_set_write_callback(audiostream_, NULL, NULL); + pa_stream_set_read_callback(audiostream_, NULL, NULL); + pa_stream_set_moved_callback(audiostream_, NULL, NULL); + pa_stream_set_underflow_callback(audiostream_, NULL, NULL); + pa_stream_set_overflow_callback(audiostream_, NULL, NULL); + + pa_stream_unref(audiostream_); +} + +void +AudioStream::stream_state_callback(pa_stream* s, void* /*user_data*/) +{ + UNUSED char str[PA_SAMPLE_SPEC_SNPRINT_MAX]; + + switch (pa_stream_get_state(s)) { + case PA_STREAM_CREATING: + RING_DBG("Stream is creating..."); + break; + + case PA_STREAM_TERMINATED: + RING_DBG("Stream is terminating..."); + break; + + case PA_STREAM_READY: + RING_DBG("Stream successfully created, connected to %s", pa_stream_get_device_name(s)); + //RING_DBG("maxlength %u", pa_stream_get_buffer_attr(s)->maxlength); + //RING_DBG("tlength %u", pa_stream_get_buffer_attr(s)->tlength); + //RING_DBG("prebuf %u", pa_stream_get_buffer_attr(s)->prebuf); + //RING_DBG("minreq %u", pa_stream_get_buffer_attr(s)->minreq); + //RING_DBG("fragsize %u", pa_stream_get_buffer_attr(s)->fragsize); + //RING_DBG("samplespec %s", pa_sample_spec_snprint(str, sizeof(str), pa_stream_get_sample_spec(s))); + break; + + case PA_STREAM_UNCONNECTED: + RING_DBG("Stream unconnected"); + break; + + case PA_STREAM_FAILED: + default: + RING_ERR("Sink/Source doesn't exists: %s" , pa_strerror(pa_context_errno(pa_stream_get_context(s)))); + break; + } +} + +bool AudioStream::isReady() +{ + if (!audiostream_) + return false; + + return pa_stream_get_state(audiostream_) == PA_STREAM_READY; +} + +} // namespace ring diff --git a/src/media/audio/pulseaudio/audiostream.h b/src/media/audio/pulseaudio/audiostream.h new file mode 100644 index 0000000000..1107d8b554 --- /dev/null +++ b/src/media/audio/pulseaudio/audiostream.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef _AUDIO_STREAM_H +#define _AUDIO_STREAM_H + +#include "noncopyable.h" +#include "pulselayer.h" + +#include <pulse/pulseaudio.h> +#include <string> + +namespace ring { + +/** + * This data structure contains the different king of audio streams available + */ +enum STREAM_TYPE { + PLAYBACK_STREAM, CAPTURE_STREAM, RINGTONE_STREAM +}; + +class AudioStream { + public: + + /** + * Constructor + * + * @param context pulseaudio's application context. + * @param mainloop pulseaudio's main loop + * @param description + * @param types + * @param audio sampling rate + * @param pointer to pa_source_info or pa_sink_info (depending on type). + */ + AudioStream(pa_context *, pa_threaded_mainloop *, const char *, int, unsigned, const PaDeviceInfos*); + + ~AudioStream(); + + /** + * Accessor: Get the pulseaudio stream object + * @return pa_stream* The stream + */ + pa_stream* pulseStream() { + return audiostream_; + } + + const pa_sample_spec * sampleSpec() const { + return pa_stream_get_sample_spec(audiostream_); + } + + inline size_t sampleSize() const { + return pa_sample_size(sampleSpec()); + } + + inline uint8_t channels() const { + return sampleSpec()->channels; + } + + inline AudioFormat getFormat() const { + auto s = sampleSpec(); + return AudioFormat(s->rate, s->channels); + } + + bool isReady(); + + private: + NON_COPYABLE(AudioStream); + + /** + * Mandatory asynchronous callback on the audio stream state + */ + static void stream_state_callback(pa_stream* s, void* user_data); + + /** + * The pulse audio object + */ + pa_stream* audiostream_; + + /** + * A pointer to the opaque threaded main loop object + */ + pa_threaded_mainloop * mainloop_; +}; + +} + +#endif // _AUDIO_STREAM_H diff --git a/src/media/audio/pulseaudio/pulselayer.cpp b/src/media/audio/pulseaudio/pulselayer.cpp new file mode 100644 index 0000000000..85db5e7aa4 --- /dev/null +++ b/src/media/audio/pulseaudio/pulselayer.cpp @@ -0,0 +1,854 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author: Андрей Лухнов <aol.nnov@gmail.com> + * Author: Adrien Beraud <adrien.beraud@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <algorithm> // for std::find +#include <stdexcept> + +#include "intrin.h" +#include "audiostream.h" +#include "pulselayer.h" +#include "audio/resampler.h" +#include "audio/dcblocker.h" +#include "audio/resampler.h" +#include "audio/ringbufferpool.h" +#include "audio/ringbuffer.h" +#include "logger.h" +#include "manager.h" + +#include <unistd.h> +#include <cstdlib> +#include <fstream> + +namespace ring { + +static void +playback_callback(pa_stream * /*s*/, size_t /*bytes*/, void* userdata) +{ + static_cast<PulseLayer*>(userdata)->writeToSpeaker(); +} + +static void +capture_callback(pa_stream * /*s*/, size_t /*bytes*/, void* userdata) +{ + static_cast<PulseLayer*>(userdata)->readFromMic(); +} + +static void +ringtone_callback(pa_stream * /*s*/, size_t /*bytes*/, void* userdata) +{ + static_cast<PulseLayer*>(userdata)->ringtoneToSpeaker(); +} + +static void +stream_moved_callback(pa_stream *s, void *userdata UNUSED) +{ + RING_DBG("stream %d to %d", pa_stream_get_index(s), pa_stream_get_device_index(s)); +} + +PulseMainLoopLock::PulseMainLoopLock(pa_threaded_mainloop *loop) : loop_(loop), destroyLoop_(false) +{ + pa_threaded_mainloop_lock(loop_); +} + +// set this flag if we want the loop to be destroyed once it's unlocked +void PulseMainLoopLock::destroyLoop() +{ + destroyLoop_ = true; +} + +PulseMainLoopLock::~PulseMainLoopLock() +{ + pa_threaded_mainloop_unlock(loop_); + if (destroyLoop_) { + pa_threaded_mainloop_stop(loop_); + pa_threaded_mainloop_free(loop_); + } +} + +PulseLayer::PulseLayer(AudioPreference &pref) + : AudioLayer(pref) + , mainloop_(pa_threaded_mainloop_new()) + , preference_(pref) + , mainRingBuffer_(Manager::instance().getRingBufferPool().getRingBuffer(RingBufferPool::DEFAULT_ID)) +{ + setCaptureGain(pref.getVolumemic()); + setPlaybackGain(pref.getVolumespkr()); + muteCapture(pref.getCaptureMuted()); + + if (!mainloop_) + throw std::runtime_error("Couldn't create pulseaudio mainloop"); + + if (pa_threaded_mainloop_start(mainloop_) < 0) { + pa_threaded_mainloop_free(mainloop_); + throw std::runtime_error("Failed to start pulseaudio mainloop"); + } + + PulseMainLoopLock lock(mainloop_); + +#if PA_CHECK_VERSION(1, 0, 0) + pa_proplist *pl = pa_proplist_new(); + pa_proplist_sets(pl, PA_PROP_MEDIA_ROLE, "phone"); + + context_ = pa_context_new_with_proplist(pa_threaded_mainloop_get_api(mainloop_), PACKAGE_NAME, pl); + + if (pl) + pa_proplist_free(pl); + +#else + setenv("PULSE_PROP_media.role", "phone", 1); + context_ = pa_context_new(pa_threaded_mainloop_get_api(mainloop_), PACKAGE_NAME); +#endif + + if (!context_) { + lock.destroyLoop(); + throw std::runtime_error("Couldn't create pulseaudio context"); + } + + pa_context_set_state_callback(context_, context_state_callback, this); + + if (pa_context_connect(context_, nullptr , PA_CONTEXT_NOAUTOSPAWN , nullptr) < 0) { + lock.destroyLoop(); + throw std::runtime_error("Could not connect pulseaudio context to the server"); + } + + // wait until context is ready + for (;;) { + pa_context_state_t context_state = pa_context_get_state(context_); + if (not PA_CONTEXT_IS_GOOD(context_state)) { + lock.destroyLoop(); + throw std::runtime_error("Pulse audio context is bad"); + } + if (context_state == PA_CONTEXT_READY) + break; + pa_threaded_mainloop_wait(mainloop_); + } + + isStarted_ = true; +} + +PulseLayer::~PulseLayer() +{ + disconnectAudioStream(); + + { + PulseMainLoopLock lock(mainloop_); + + pa_context_set_state_callback(context_, NULL, NULL); + pa_context_set_subscribe_callback(context_, NULL, NULL); + pa_context_disconnect(context_); + pa_context_unref(context_); + } + + pa_threaded_mainloop_stop(mainloop_); + pa_threaded_mainloop_free(mainloop_); +} + +void PulseLayer::context_state_callback(pa_context* c, void *user_data) +{ + PulseLayer *pulse = static_cast<PulseLayer*>(user_data); + assert(c and pulse and pulse->mainloop_); + const pa_subscription_mask_t mask = (pa_subscription_mask_t) + (PA_SUBSCRIPTION_MASK_SINK | PA_SUBSCRIPTION_MASK_SOURCE); + + switch (pa_context_get_state(c)) { + case PA_CONTEXT_CONNECTING: + case PA_CONTEXT_AUTHORIZING: + case PA_CONTEXT_SETTING_NAME: + RING_DBG("Waiting...."); + break; + + case PA_CONTEXT_READY: + RING_DBG("Connection to PulseAudio server established"); + pa_threaded_mainloop_signal(pulse->mainloop_, 0); + pa_context_subscribe(c, mask, nullptr, pulse); + pa_context_set_subscribe_callback(c, context_changed_callback, pulse); + pulse->updateSinkList(); + pulse->updateSourceList(); + pulse->updateServerInfo(); + break; + + case PA_CONTEXT_TERMINATED: + break; + + case PA_CONTEXT_FAILED: + default: + RING_ERR("%s" , pa_strerror(pa_context_errno(c))); + pa_threaded_mainloop_signal(pulse->mainloop_, 0); + break; + } +} + + +void PulseLayer::updateSinkList() +{ + sinkList_.clear(); + enumeratingSinks_ = true; + pa_operation *op = pa_context_get_sink_info_list(context_, sink_input_info_callback, this); + + if (op != nullptr) + pa_operation_unref(op); +} + +void PulseLayer::updateSourceList() +{ + sourceList_.clear(); + enumeratingSources_ = true; + pa_operation *op = pa_context_get_source_info_list(context_, source_input_info_callback, this); + + if (op != nullptr) + pa_operation_unref(op); +} + +void PulseLayer::updateServerInfo() +{ + gettingServerInfo_ = true; + pa_operation *op = pa_context_get_server_info(context_, server_info_callback, this); + + if (op != nullptr) + pa_operation_unref(op); +} + +bool PulseLayer::inSinkList(const std::string &deviceName) +{ + const bool found = std::find_if(sinkList_.begin(), sinkList_.end(), PaDeviceInfos::NameComparator(deviceName)) != sinkList_.end(); + + RING_DBG("seeking for %s in sinks. %s found", deviceName.c_str(), found ? "" : "NOT"); + return found; +} + +bool PulseLayer::inSourceList(const std::string &deviceName) +{ + const bool found = std::find_if(sourceList_.begin(), sourceList_.end(), PaDeviceInfos::NameComparator(deviceName)) != sourceList_.end(); + + RING_DBG("seeking for %s in sources. %s found", deviceName.c_str(), found ? "" : "NOT"); + return found; +} + +std::vector<std::string> PulseLayer::getCaptureDeviceList() const +{ + std::vector<std::string> names; + names.reserve(sourceList_.size() + 1); + names.push_back("default"); + for (const auto& s : sourceList_) + names.push_back(s.description); + return names; +} + +std::vector<std::string> PulseLayer::getPlaybackDeviceList() const +{ + std::vector<std::string> names; + names.reserve(sinkList_.size() + 1); + names.push_back("default"); + for (const auto& s : sinkList_) + names.push_back(s.description); + return names; +} + +int PulseLayer::getAudioDeviceIndex(const std::string& descr, DeviceType type) const +{ + if (descr == "default") + return 0; + switch (type) { + case DeviceType::PLAYBACK: + case DeviceType::RINGTONE: + return 1 + std::distance(sinkList_.begin(), std::find_if(sinkList_.begin(), sinkList_.end(), PaDeviceInfos::DescriptionComparator(descr))); + case DeviceType::CAPTURE: + return 1 + std::distance(sourceList_.begin(), std::find_if(sourceList_.begin(), sourceList_.end(), PaDeviceInfos::DescriptionComparator(descr))); + default: + RING_ERR("Unexpected device type"); + return 0; + } +} + +int PulseLayer::getAudioDeviceIndexByName(const std::string& name, DeviceType type) const +{ + if (name.empty()) + return 0; + switch (type) { + case DeviceType::PLAYBACK: + case DeviceType::RINGTONE: + return 1 + std::distance(sinkList_.begin(), std::find_if(sinkList_.begin(), sinkList_.end(), PaDeviceInfos::NameComparator(name))); + case DeviceType::CAPTURE: + return 1 + std::distance(sourceList_.begin(), std::find_if(sourceList_.begin(), sourceList_.end(), PaDeviceInfos::NameComparator(name))); + default: + RING_ERR("Unexpected device type"); + return 0; + } +} + +const PaDeviceInfos* PulseLayer::getDeviceInfos(const std::vector<PaDeviceInfos>& list, const std::string& name) const +{ + std::vector<PaDeviceInfos>::const_iterator dev_info = std::find_if(list.begin(), list.end(), PaDeviceInfos::NameComparator(name)); + + if (dev_info == list.end()) return nullptr; + + return &(*dev_info); +} + +std::string PulseLayer::getAudioDeviceName(int index, DeviceType type) const +{ + if (index == 0) + return ""; + index--; + switch (type) { + case DeviceType::PLAYBACK: + case DeviceType::RINGTONE: + if (index < 0 or static_cast<size_t>(index) >= sinkList_.size()) { + RING_ERR("Index %d out of range", index); + return ""; + } + + return sinkList_[index].name; + + case DeviceType::CAPTURE: + if (index < 0 or static_cast<size_t>(index) >= sourceList_.size()) { + RING_ERR("Index %d out of range", index); + return ""; + } + + return sourceList_[index].name; + default: + // Should never happen + RING_ERR("Unexpected type"); + return ""; + } +} + +void PulseLayer::createStreams(pa_context* c) +{ + std::unique_lock<std::mutex> lk(readyMtx_); + readyCv_.wait(lk, [this] { + return !(enumeratingSinks_ or enumeratingSources_ or gettingServerInfo_); + }); + + hardwareFormatAvailable(defaultAudioFormat_); + + std::string playbackDevice(preference_.getPulseDevicePlayback()); + if (playbackDevice.empty()) + playbackDevice = defaultSink_; + std::string captureDevice(preference_.getPulseDeviceRecord()); + if (captureDevice.empty()) + captureDevice = defaultSource_; + std::string ringtoneDevice(preference_.getPulseDeviceRingtone()); + if (ringtoneDevice.empty()) + ringtoneDevice = defaultSink_; + + RING_DBG("playback: %s record: %s ringtone: %s", playbackDevice.c_str(), + captureDevice.c_str(), ringtoneDevice.c_str()); + + // Create playback stream + const PaDeviceInfos* dev_infos = getDeviceInfos(sinkList_, playbackDevice); + + if (dev_infos == nullptr) { + dev_infos = &sinkList_[0]; + RING_WARN("Prefered playback device %s not found in device list, selecting %s instead.", + playbackDevice.c_str(), dev_infos->name.c_str()); + } + + playback_ = new AudioStream(c, mainloop_, "Playback", PLAYBACK_STREAM, audioFormat_.sample_rate, dev_infos); + + pa_stream_set_write_callback(playback_->pulseStream(), playback_callback, this); + pa_stream_set_moved_callback(playback_->pulseStream(), stream_moved_callback, this); + + // Create capture stream + dev_infos = getDeviceInfos(sourceList_, captureDevice); + + if (dev_infos == nullptr) { + dev_infos = &sourceList_[0]; + RING_WARN("Prefered capture device %s not found in device list, selecting %s instead.", + captureDevice.c_str(), dev_infos->name.c_str()); + } + + record_ = new AudioStream(c, mainloop_, "Capture", CAPTURE_STREAM, audioFormat_.sample_rate, dev_infos); + + pa_stream_set_read_callback(record_->pulseStream() , capture_callback, this); + pa_stream_set_moved_callback(record_->pulseStream(), stream_moved_callback, this); + + // Create ringtone stream + dev_infos = getDeviceInfos(sinkList_, ringtoneDevice); + + if (dev_infos == nullptr) { + dev_infos = &sinkList_[0]; + RING_WARN("Prefered ringtone device %s not found in device list, selecting %s instead.", + ringtoneDevice.c_str(), dev_infos->name.c_str()); + } + + ringtone_ = new AudioStream(c, mainloop_, "Ringtone", RINGTONE_STREAM, audioFormat_.sample_rate, dev_infos); + + pa_stream_set_write_callback(ringtone_->pulseStream(), ringtone_callback, this); + pa_stream_set_moved_callback(ringtone_->pulseStream(), stream_moved_callback, this); + + pa_threaded_mainloop_signal(mainloop_, 0); + + flushMain(); + flushUrgent(); +} + +// Delete stream and zero out its pointer +static void +cleanupStream(AudioStream *&stream) +{ + delete stream; + stream = 0; +} + +void PulseLayer::disconnectAudioStream() +{ + cleanupStream(playback_); + cleanupStream(ringtone_); + cleanupStream(record_); +} + +void PulseLayer::startStream() +{ + // Create Streams + if (!playback_ or !record_) + createStreams(context_); + + // Flush outside the if statement: every time start stream is + // called is to notify a new event + flushUrgent(); + flushMain(); +} + + +void +PulseLayer::stopStream() +{ + { + PulseMainLoopLock lock(mainloop_); + + if (playback_) + pa_stream_flush(playback_->pulseStream(), nullptr, nullptr); + + if (record_) + pa_stream_flush(record_->pulseStream(), nullptr, nullptr); + } + + disconnectAudioStream(); +} + +void PulseLayer::writeToSpeaker() +{ + if (!playback_ or !playback_->isReady()) + return; + + pa_stream *s = playback_->pulseStream(); + const pa_sample_spec* sample_spec = pa_stream_get_sample_spec(s); + size_t sample_size = pa_frame_size(sample_spec); + const AudioFormat format(playback_->getFormat()); + + // available bytes to be written in pulseaudio internal buffer + int ret = pa_stream_writable_size(s); + + if (ret < 0) { + RING_ERR("Playback error : %s", pa_strerror(ret)); + return; + } else if (ret == 0) + return; + + size_t writableBytes = ret; + const size_t writableSamples = writableBytes / sample_size; + + notifyIncomingCall(); + + size_t urgentSamples = urgentRingBuffer_.availableForGet(RingBufferPool::DEFAULT_ID); + size_t urgentBytes = urgentSamples * sample_size; + + if (urgentSamples > writableSamples) { + urgentSamples = writableSamples; + urgentBytes = urgentSamples * sample_size; + } + + AudioSample* data = nullptr; + + if (urgentBytes) { + playbackBuffer_.setFormat(format); + playbackBuffer_.resize(urgentSamples); + pa_stream_begin_write(s, (void**)&data, &urgentBytes); + urgentRingBuffer_.get(playbackBuffer_, RingBufferPool::DEFAULT_ID); // retrive only the first sample_spec->channels channels + playbackBuffer_.applyGain(isPlaybackMuted_ ? 0.0 : playbackGain_); + playbackBuffer_.interleave(data); + pa_stream_write(s, data, urgentBytes, nullptr, 0, PA_SEEK_RELATIVE); + // Consume the regular one as well (same amount of samples) + Manager::instance().getRingBufferPool().discard(urgentSamples, RingBufferPool::DEFAULT_ID); + return; + } + + // FIXME: not thread safe! we only lock the mutex when we get the + // pointer, we have no guarantee that it will stay safe to use + AudioLoop *toneToPlay = Manager::instance().getTelephoneTone(); + + if (toneToPlay) { + if (playback_->isReady()) { + pa_stream_begin_write(s, (void**)&data, &writableBytes); + playbackBuffer_.setFormat(format); + playbackBuffer_.resize(writableSamples); + toneToPlay->getNext(playbackBuffer_, playbackGain_); // retrive only n_channels + playbackBuffer_.interleave(data); + pa_stream_write(s, data, writableBytes, nullptr, 0, PA_SEEK_RELATIVE); + } + + return; + } + + flushUrgent(); // flush remaining samples in _urgentRingBuffer + + size_t availSamples = Manager::instance().getRingBufferPool().availableForGet(RingBufferPool::DEFAULT_ID); + + if (availSamples == 0) { + pa_stream_begin_write(s, (void**)&data, &writableBytes); + memset(data, 0, writableBytes); + pa_stream_write(s, data, writableBytes, nullptr, 0, PA_SEEK_RELATIVE); + return; + } + + // how many samples we want to read from the buffer + size_t readableSamples = writableSamples; + + double resampleFactor = 1.; + + AudioFormat mainBufferAudioFormat = Manager::instance().getRingBufferPool().getInternalAudioFormat(); + bool resample = audioFormat_.sample_rate != mainBufferAudioFormat.sample_rate; + + if (resample) { + resampleFactor = (double) audioFormat_.sample_rate / mainBufferAudioFormat.sample_rate; + readableSamples = (double) readableSamples / resampleFactor; + } + + readableSamples = std::min(readableSamples, availSamples); + size_t nResampled = (double) readableSamples * resampleFactor; + size_t resampledBytes = nResampled * sample_size; + + pa_stream_begin_write(s, (void**)&data, &resampledBytes); + + playbackBuffer_.setFormat(format); + playbackBuffer_.resize(readableSamples); + Manager::instance().getRingBufferPool().getData(playbackBuffer_, RingBufferPool::DEFAULT_ID); + + if (resample) { + AudioBuffer rsmpl_out(nResampled, format); + resampler_->resample(playbackBuffer_, rsmpl_out); + rsmpl_out.applyGain(isPlaybackMuted_ ? 0.0 : playbackGain_); + rsmpl_out.interleave(data); + pa_stream_write(s, data, resampledBytes, nullptr, 0, PA_SEEK_RELATIVE); + } else { + playbackBuffer_.applyGain(isPlaybackMuted_ ? 0.0 : playbackGain_); + playbackBuffer_.interleave(data); + pa_stream_write(s, data, resampledBytes, nullptr, 0, PA_SEEK_RELATIVE); + } +} + +void PulseLayer::readFromMic() +{ + if (!record_ or !record_->isReady()) + return; + + const char *data = nullptr; + size_t bytes; + + const size_t sample_size = record_->sampleSize(); + const AudioFormat format(record_->getFormat()); + + if (pa_stream_peek(record_->pulseStream() , (const void**) &data , &bytes) < 0 or !data) + return; + + assert(format.nb_channels); + assert(sample_size); + const size_t samples = bytes / sample_size / format.nb_channels; + + micBuffer_.setFormat(format); + micBuffer_.resize(samples); + micBuffer_.deinterleave((AudioSample*)data, samples, format.nb_channels); + micBuffer_.applyGain(isCaptureMuted_ ? 0.0 : captureGain_); + + unsigned int mainBufferSampleRate = Manager::instance().getRingBufferPool().getInternalSamplingRate(); + bool resample = audioFormat_.sample_rate != mainBufferSampleRate; + + AudioBuffer* out; + if (resample) { + micResampleBuffer_.setSampleRate(mainBufferSampleRate); + resampler_->resample(micBuffer_, micResampleBuffer_); + out = &micResampleBuffer_; + } else { + out = &micBuffer_; + } + + dcblocker_.process(*out); + out->applyGain(isPlaybackMuted_ ? 0.0 : playbackGain_); + mainRingBuffer_->put(*out); + + if (pa_stream_drop(record_->pulseStream()) < 0) + RING_ERR("Capture stream drop failed: %s" , pa_strerror(pa_context_errno(context_))); +} + + +void PulseLayer::ringtoneToSpeaker() +{ + if (!ringtone_ or !ringtone_->isReady()) + return; + + pa_stream *s = ringtone_->pulseStream(); + size_t sample_size = ringtone_->sampleSize(); + + int writable = pa_stream_writable_size(s); + + if (writable < 0) + RING_ERR("Ringtone error : %s", pa_strerror(writable)); + + if (writable <= 0) + return; + + size_t bytes = writable; + void *data; + + pa_stream_begin_write(s, &data, &bytes); + AudioLoop *fileToPlay = Manager::instance().getTelephoneFile(); + + if (fileToPlay) { + const unsigned samples = (bytes / sample_size) / ringtone_->channels(); + AudioBuffer tmp(samples, ringtone_->getFormat()); + fileToPlay->getNext(tmp, playbackGain_); + tmp.interleave((AudioSample*) data); + } else { + memset(data, 0, bytes); + } + + pa_stream_write(s, data, bytes, nullptr, 0, PA_SEEK_RELATIVE); +} + +void +PulseLayer::context_changed_callback(pa_context* c, + pa_subscription_event_type_t type, + uint32_t idx UNUSED, void *userdata) +{ + PulseLayer *context = static_cast<PulseLayer*>(userdata); + + switch (type & PA_SUBSCRIPTION_EVENT_FACILITY_MASK) { + pa_operation *op; + + case PA_SUBSCRIPTION_EVENT_SINK: + switch (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) { + case PA_SUBSCRIPTION_EVENT_NEW: + case PA_SUBSCRIPTION_EVENT_REMOVE: + RING_DBG("Updating sink list"); + context->sinkList_.clear(); + op = pa_context_get_sink_info_list(c, sink_input_info_callback, userdata); + + if (op != nullptr) + pa_operation_unref(op); + + default: + break; + } + + break; + + case PA_SUBSCRIPTION_EVENT_SOURCE: + switch (type & PA_SUBSCRIPTION_EVENT_TYPE_MASK) { + case PA_SUBSCRIPTION_EVENT_NEW: + case PA_SUBSCRIPTION_EVENT_REMOVE: + RING_DBG("Updating source list"); + context->sourceList_.clear(); + op = pa_context_get_source_info_list(c, source_input_info_callback, userdata); + + if (op != nullptr) + pa_operation_unref(op); + + default: + break; + } + + break; + + default: + RING_DBG("Unhandled event type 0x%x", type); + break; + } +} + +void PulseLayer::server_info_callback(pa_context*, const pa_server_info *i, void *userdata) +{ + if (!i) return; + char s[PA_SAMPLE_SPEC_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + RING_DBG("PulseAudio server info:\n" + " Server name: %s\n" + " Server version: %s\n" + " Default Sink %s\n" + " Default Source %s\n" + " Default Sample Specification: %s\n" + " Default Channel Map: %s\n", + i->server_name, + i->server_version, + i->default_sink_name, + i->default_source_name, + pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map)); + + PulseLayer *context = static_cast<PulseLayer*>(userdata); + context->defaultSink_ = i->default_sink_name; + context->defaultSource_ = i->default_source_name; + context->defaultAudioFormat_ = {i->sample_spec.rate, i->sample_spec.channels}; + { + std::lock_guard<std::mutex> lk(context->readyMtx_); + context->gettingServerInfo_ = false; + } + context->readyCv_.notify_one(); +} + +void PulseLayer::source_input_info_callback(pa_context *c UNUSED, const pa_source_info *i, int eol, void *userdata) +{ + char s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + PulseLayer *context = static_cast<PulseLayer*>(userdata); + + if (eol) { + { + std::lock_guard<std::mutex> lk(context->readyMtx_); + context->enumeratingSources_ = false; + } + context->readyCv_.notify_one(); + return; + } + + RING_DBG("Source %u\n" + " Name: %s\n" + " Driver: %s\n" + " Description: %s\n" + " Sample Specification: %s\n" + " Channel Map: %s\n" + " Owner Module: %u\n" + " Volume: %s\n" + " Monitor if Sink: %u\n" + " Latency: %0.0f usec\n" + " Flags: %s%s%s\n", + i->index, + i->name, + i->driver, + i->description, + pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map), + i->owner_module, + i->mute ? "muted" : pa_cvolume_snprint(cv, sizeof(cv), &i->volume), + i->monitor_of_sink, + (double) i->latency, + i->flags & PA_SOURCE_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "", + i->flags & PA_SOURCE_LATENCY ? "LATENCY " : "", + i->flags & PA_SOURCE_HARDWARE ? "HARDWARE" : ""); + + if (not context->inSourceList(i->name)) { + context->sourceList_.push_back(*i); + } +} + +void PulseLayer::sink_input_info_callback(pa_context *c UNUSED, const pa_sink_info *i, int eol, void *userdata) +{ + char s[PA_SAMPLE_SPEC_SNPRINT_MAX], cv[PA_CVOLUME_SNPRINT_MAX], cm[PA_CHANNEL_MAP_SNPRINT_MAX]; + PulseLayer *context = static_cast<PulseLayer*>(userdata); + + if (eol) { + { + std::lock_guard<std::mutex> lk(context->readyMtx_); + context->enumeratingSinks_ = false; + } + context->readyCv_.notify_one(); + return; + } + + RING_DBG("Sink %u\n" + " Name: %s\n" + " Driver: %s\n" + " Description: %s\n" + " Sample Specification: %s\n" + " Channel Map: %s\n" + " Owner Module: %u\n" + " Volume: %s\n" + " Monitor Source: %u\n" + " Latency: %0.0f usec\n" + " Flags: %s%s%s\n", + i->index, + i->name, + i->driver, + i->description, + pa_sample_spec_snprint(s, sizeof(s), &i->sample_spec), + pa_channel_map_snprint(cm, sizeof(cm), &i->channel_map), + i->owner_module, + i->mute ? "muted" : pa_cvolume_snprint(cv, sizeof(cv), &i->volume), + i->monitor_source, + static_cast<double>(i->latency), + i->flags & PA_SINK_HW_VOLUME_CTRL ? "HW_VOLUME_CTRL " : "", + i->flags & PA_SINK_LATENCY ? "LATENCY " : "", + i->flags & PA_SINK_HARDWARE ? "HARDWARE" : ""); + + if (not context->inSinkList(i->name)) { + context->sinkList_.push_back(*i); + } +} + +void PulseLayer::updatePreference(AudioPreference &preference, int index, DeviceType type) +{ + const std::string devName(getAudioDeviceName(index, type)); + + switch (type) { + case DeviceType::PLAYBACK: + RING_DBG("setting %s for playback", devName.c_str()); + preference.setPulseDevicePlayback(devName); + break; + + case DeviceType::CAPTURE: + RING_DBG("setting %s for capture", devName.c_str()); + preference.setPulseDeviceRecord(devName); + break; + + case DeviceType::RINGTONE: + RING_DBG("setting %s for ringer", devName.c_str()); + preference.setPulseDeviceRingtone(devName); + break; + } +} + +int PulseLayer::getIndexCapture() const +{ + return getAudioDeviceIndexByName(preference_.getPulseDeviceRecord(), DeviceType::CAPTURE); +} + +int PulseLayer::getIndexPlayback() const +{ + return getAudioDeviceIndexByName(preference_.getPulseDevicePlayback(), DeviceType::PLAYBACK); +} + +int PulseLayer::getIndexRingtone() const +{ + return getAudioDeviceIndexByName(preference_.getPulseDeviceRingtone(), DeviceType::RINGTONE); +} + +} // namespace ring diff --git a/src/media/audio/pulseaudio/pulselayer.h b/src/media/audio/pulseaudio/pulselayer.h new file mode 100644 index 0000000000..7beddb34e2 --- /dev/null +++ b/src/media/audio/pulseaudio/pulselayer.h @@ -0,0 +1,238 @@ + +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author: Андрей Лухнов <aol.nnov@gmail.com> + * Author: Adrien Beraud <adrien.beraud@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef PULSE_LAYER_H_ +#define PULSE_LAYER_H_ + +#include <list> +#include <string> +#include <pulse/pulseaudio.h> +#include <pulse/stream.h> +#include "audio/audiolayer.h" +#include "noncopyable.h" +#include "logger.h" + +#include <memory> + +namespace ring { + +class AudioPreference; +class AudioStream; +class RingBuffer; + +/** + * Convenience structure to hold PulseAudio device propreties such as supported channel number etc. + */ +struct PaDeviceInfos { + uint32_t index; + std::string name; + std::string description; + pa_sample_spec sample_spec; + pa_channel_map channel_map; + + PaDeviceInfos(const pa_source_info& source) : + index(source.index), + name(source.name), + description(source.description), + sample_spec(source.sample_spec), + channel_map(source.channel_map) {} + + PaDeviceInfos(const pa_sink_info& source) : + index(source.index), + name(source.name), + description(source.description), + sample_spec(source.sample_spec), + channel_map(source.channel_map) {} + + /** + * Unary function to search for a device by name in a list using std functions. + */ + class NameComparator { + public: + explicit NameComparator(const std::string &ref) : baseline(ref) {} + bool operator()(const PaDeviceInfos &arg) { + return arg.name == baseline; + } + private: + const std::string &baseline; + }; + + class DescriptionComparator { + public: + explicit DescriptionComparator(const std::string &ref) : baseline(ref) {} + bool operator()(const PaDeviceInfos &arg) { + return arg.description == baseline; + } + private: + const std::string &baseline; + }; +}; + +class PulseMainLoopLock { + public: + explicit PulseMainLoopLock(pa_threaded_mainloop *loop); + void destroyLoop(); + ~PulseMainLoopLock(); + + private: + NON_COPYABLE(PulseMainLoopLock); + pa_threaded_mainloop *loop_; + bool destroyLoop_; +}; + +class PulseLayer : public AudioLayer { + public: + PulseLayer(AudioPreference &pref); + ~PulseLayer(); + + /** + * Write data from the ring buffer to the harware and read data from the hardware + */ + void readFromMic(); + void writeToSpeaker(); + void ringtoneToSpeaker(); + + void updateSinkList(); + void updateSourceList(); + void updateServerInfo(); + + bool inSinkList(const std::string &deviceName); + bool inSourceList(const std::string &deviceName); + + virtual std::vector<std::string> getCaptureDeviceList() const; + virtual std::vector<std::string> getPlaybackDeviceList() const; + int getAudioDeviceIndex(const std::string& descr, DeviceType type) const; + int getAudioDeviceIndexByName(const std::string& name, DeviceType type) const; + + std::string getAudioDeviceName(int index, DeviceType type) const; + + virtual void startStream(); + virtual void stopStream(); + + private: + static void context_state_callback(pa_context* c, void* user_data); + static void context_changed_callback(pa_context* c, + pa_subscription_event_type_t t, + uint32_t idx , void* userdata); + static void source_input_info_callback(pa_context *c, + const pa_source_info *i, + int eol, void *userdata); + static void sink_input_info_callback(pa_context *c, + const pa_sink_info *i, + int eol, void *userdata); + static void server_info_callback(pa_context*, + const pa_server_info *i, + void *userdata); + + virtual void updatePreference(AudioPreference &pref, int index, DeviceType type); + + virtual int getIndexCapture() const; + virtual int getIndexPlayback() const; + virtual int getIndexRingtone() const; + + NON_COPYABLE(PulseLayer); + + /** + * Create the audio streams into the given context + * @param c The pulseaudio context + */ + void createStreams(pa_context* c); + + /** + * Close the connection with the local pulseaudio server + */ + void disconnectAudioStream(); + + /** + * Returns a pointer to the PaEndpointInfos with the given name in sourceList_, or nullptr if not found. + */ + const PaDeviceInfos* getDeviceInfos(const std::vector<PaDeviceInfos>&, const std::string& name) const; + + /** + * A stream object to handle the pulseaudio playback stream + */ + AudioStream* playback_ {nullptr}; + + /** + * A stream object to handle the pulseaudio capture stream + */ + AudioStream* record_ {nullptr}; + + /** + * A special stream object to handle specific playback stream for ringtone + */ + AudioStream* ringtone_ {nullptr}; + + /** + * Contains the list of playback devices + */ + std::vector<PaDeviceInfos> sinkList_ {}; + + /** + * Contains the list of capture devices + */ + std::vector<PaDeviceInfos> sourceList_ {}; + + /* + * Buffers used to avoid doing malloc/free in the audio thread + */ + AudioBuffer micBuffer_; + AudioBuffer micResampleBuffer_; + + AudioBuffer playbackBuffer_; + AudioBuffer playbackResampleBuffer_; + + /** PulseAudio server defaults */ + AudioFormat defaultAudioFormat_ {AudioFormat::MONO()}; + std::string defaultSink_ {}; + std::string defaultSource_ {}; + + /** PulseAudio context and asynchronous loop */ + pa_context* context_ {nullptr}; + pa_threaded_mainloop* mainloop_ {nullptr}; + bool enumeratingSinks_ {false}; + bool enumeratingSources_ {false}; + bool gettingServerInfo_ {false}; + std::mutex readyMtx_ {}; + std::condition_variable readyCv_ {}; + + AudioPreference &preference_; + std::shared_ptr<RingBuffer> mainRingBuffer_; + + friend class AudioLayerTest; +}; + +} // namespace ring + +#endif // PULSE_LAYER_H_ diff --git a/src/media/audio/recordable.cpp b/src/media/audio/recordable.cpp new file mode 100644 index 0000000000..6c0832c355 --- /dev/null +++ b/src/media/audio/recordable.cpp @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "recordable.h" +#include "manager.h" +#include "logger.h" + +namespace ring { + +Recordable::Recordable() : recAudio_(), recorder_(&recAudio_, Manager::instance().getRingBufferPool()) +{ + RING_DBG("Set recording options: %s", Manager::instance().audioPreference.getRecordPath().c_str()); + recAudio_.setRecordingOptions(AudioFormat::MONO(), Manager::instance().audioPreference.getRecordPath()); +} + +Recordable::~Recordable() +{ + if (recAudio_.isOpenFile()) + recAudio_.closeFile(); +} + +void Recordable::initRecFilename(const std::string &filename) +{ + recAudio_.initFilename(filename); +} + +std::string Recordable::getFilename() const +{ + return recAudio_.getFilename(); +} + +void Recordable::setRecordingFormat(AudioFormat format) +{ + recAudio_.setSndFormat(format); +} + +} // namespace ring diff --git a/src/media/audio/recordable.h b/src/media/audio/recordable.h new file mode 100644 index 0000000000..cc8bb4325d --- /dev/null +++ b/src/media/audio/recordable.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef RECORDABLE_H +#define RECORDABLE_H + +#include "audiorecord.h" +#include "audiorecorder.h" + +namespace ring { + +class Recordable { + + public: + + Recordable(); + virtual ~Recordable(); + + /** + * Return recording state (true/false) + */ + bool isRecording() const { + return recAudio_.isRecording(); + } + + /** + * This method must be implemented for this interface as calls and conferences + * have different behavior. + * Implementations must call the super method. + */ + virtual bool toggleRecording() { + if (not isRecording()) + recorder_.init(); + return recAudio_.toggleRecording(); + } + + /** + * Stop recording + */ + void stopRecording() { + recAudio_.stopRecording(); + } + + /** + * Init the recording file name according to path specified in configuration + */ + void initRecFilename(const std::string &filename); + + /** + * Return the file path for this recording + */ + virtual std::string getFilename() const; + + /** + * Set recording sampling rate. + */ + void setRecordingFormat(AudioFormat format); + + protected: + AudioRecord recAudio_; + AudioRecorder recorder_; +}; + +} // namespace ring + +#endif diff --git a/src/media/audio/resampler.cpp b/src/media/audio/resampler.cpp new file mode 100644 index 0000000000..e4125a9f02 --- /dev/null +++ b/src/media/audio/resampler.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "resampler.h" +#include "logger.h" +#include "ring_types.h" + +#include <samplerate.h> + +namespace ring { + +class SrcState { + public: + SrcState(int nb_channels) + { + int err; + state_ = src_new(SRC_LINEAR, nb_channels, &err); + } + + ~SrcState() + { + src_delete(state_); + } + + void process(SRC_DATA *src_data) + { + src_process(state_, src_data); + } + + private: + SRC_STATE *state_ {nullptr}; +}; + +Resampler::Resampler(AudioFormat format) : floatBufferIn_(), + floatBufferOut_(), scratchBuffer_(), samples_(0), format_(format), src_state_() +{ + setFormat(format); +} + +Resampler::Resampler(unsigned sample_rate, unsigned channels) : floatBufferIn_(), + floatBufferOut_(), scratchBuffer_(), samples_(0), format_(sample_rate, channels), src_state_() +{ + setFormat(format_); +} + +Resampler::~Resampler() = default; + +void +Resampler::setFormat(AudioFormat format) +{ + format_ = format; + samples_ = (format.nb_channels * format.sample_rate * 20) / 1000; // start with 20 ms buffers + floatBufferIn_.resize(samples_); + floatBufferOut_.resize(samples_); + scratchBuffer_.resize(samples_); + + src_state_.reset(new SrcState(format.nb_channels)); +} + +void Resampler::resample(const AudioBuffer &dataIn, AudioBuffer &dataOut) +{ + const double inputFreq = dataIn.getSampleRate(); + const double outputFreq = dataOut.getSampleRate(); + const double sampleFactor = outputFreq / inputFreq; + + if (sampleFactor == 1.0) + return; + + const size_t nbFrames = dataIn.frames(); + const size_t nbChans = dataIn.channels(); + + if (nbChans != format_.nb_channels) { + // change channel num if needed + src_state_.reset(new SrcState(nbChans)); + format_.nb_channels = nbChans; + RING_DBG("SRC channel number changed."); + } + if (nbChans != dataOut.channels()) { + RING_DBG("Output buffer had the wrong number of channels (in: %d, out: %d).", nbChans, dataOut.channels()); + dataOut.setChannelNum(nbChans); + } + + size_t inSamples = nbChans * nbFrames; + size_t outSamples = inSamples * sampleFactor; + + // grow buffer if needed + floatBufferIn_.resize(inSamples); + floatBufferOut_.resize(outSamples); + scratchBuffer_.resize(outSamples); + + SRC_DATA src_data; + src_data.data_in = floatBufferIn_.data(); + src_data.data_out = floatBufferOut_.data(); + src_data.input_frames = nbFrames; + src_data.output_frames = nbFrames * sampleFactor; + src_data.src_ratio = sampleFactor; + src_data.end_of_input = 0; // More data will come + + dataIn.interleaveFloat(floatBufferIn_.data()); + + src_state_->process(&src_data); + /* + TODO: one-shot deinterleave and float-to-short conversion + */ + src_float_to_short_array(floatBufferOut_.data(), scratchBuffer_.data(), outSamples); + dataOut.deinterleave(scratchBuffer_.data(), src_data.output_frames, nbChans); +} + +} // namespace ring diff --git a/src/media/audio/resampler.h b/src/media/audio/resampler.h new file mode 100644 index 0000000000..bfceca866b --- /dev/null +++ b/src/media/audio/resampler.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef _SAMPLE_RATE_H +#define _SAMPLE_RATE_H + +#include <cmath> +#include <cstring> +#include <memory> + +#include "audiobuffer.h" +#include "ring_types.h" +#include "noncopyable.h" + +namespace ring { + +struct SrcState; + +class Resampler { + public: + /** + * Resampler is used for several situations: + * streaming conversion (RTP, IAX), audiolayer conversion, + * audio files conversion. Parameters are used to compute + * internal buffer size. Resampler must be reinitialized + * every time these parameters change + */ + Resampler(AudioFormat outFormat); + Resampler(unsigned sample_rate, unsigned channels=1); + // empty dtor, needed for unique_ptr + ~Resampler(); + + /** + * Change the converter sample rate and channel number. + * Internal state is lost. + */ + void setFormat(AudioFormat format); + + /** + * resample from the samplerate1 to the samplerate2 + * @param dataIn Input buffer + * @param dataOut Output buffer + * @param nbSamples The number of samples to process + */ + void resample(const AudioBuffer& dataIn, AudioBuffer& dataOut); + + private: + NON_COPYABLE(Resampler); + + /* temporary buffers */ + std::vector<float> floatBufferIn_; + std::vector<float> floatBufferOut_; + std::vector<AudioSample> scratchBuffer_; + + size_t samples_; // size in samples of temporary buffers + AudioFormat format_; // number of channels and max output frequency + + std::unique_ptr<SrcState> src_state_; +}; + +} // namespace ring + +#endif //_SAMPLE_RATE_H diff --git a/src/media/audio/ringbuffer.cpp b/src/media/audio/ringbuffer.cpp new file mode 100644 index 0000000000..2c03e867e1 --- /dev/null +++ b/src/media/audio/ringbuffer.cpp @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * Author: Laurielle Lea <laurielle.lea@savoirfairelinux.com> + * Author: Adrien Beraud <adrien.beraud@gmail.com> + * + * Portions (c) Dominic Mazzoni (Audacity) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "ringbuffer.h" +#include "logger.h" + +#include <chrono> +#include <utility> // for std::pair +#include <cstdlib> +#include <cstring> + +namespace ring { + +// corresponds to 160 ms (about 5 rtp packets) +static const size_t MIN_BUFFER_SIZE = 1024; + +// Create a ring buffer with 'size' bytes +RingBuffer::RingBuffer(const std::string& rbuf_id, size_t size, + AudioFormat format /* = MONO */) + : id(rbuf_id) + , endPos_(0) + , buffer_(std::max(size, MIN_BUFFER_SIZE), format) + , lock_() + , not_empty_() + , readoffsets_() +{} + +void +RingBuffer::flush(const std::string &call_id) +{ + storeReadOffset(endPos_, call_id); +} + + +void +RingBuffer::flushAll() +{ + ReadOffset::iterator iter; + + for (iter = readoffsets_.begin(); iter != readoffsets_.end(); ++iter) + iter->second = endPos_; +} + +size_t +RingBuffer::putLength() const +{ + const size_t buffer_size = buffer_.frames(); + if (buffer_size == 0) + return 0; + const size_t startPos = (not readoffsets_.empty()) ? getSmallestReadOffset() : 0; + return (endPos_ + buffer_size - startPos) % buffer_size; +} + +size_t RingBuffer::getLength(const std::string &call_id) const +{ + const size_t buffer_size = buffer_.frames(); + if (buffer_size == 0) + return 0; + return (endPos_ + buffer_size - getReadOffset(call_id)) % buffer_size; +} + +void +RingBuffer::debug() +{ + RING_DBG("Start=%d; End=%d; BufferSize=%d", getSmallestReadOffset(), endPos_, buffer_.frames()); +} + +size_t RingBuffer::getReadOffset(const std::string &call_id) const +{ + if (hasNoReadOffsets()) + return 0; + ReadOffset::const_iterator iter = readoffsets_.find(call_id); + return (iter != readoffsets_.end()) ? iter->second : 0; +} + +size_t +RingBuffer::getSmallestReadOffset() const +{ + if (hasNoReadOffsets()) + return 0; + size_t smallest = buffer_.frames(); + for(auto const& iter : readoffsets_) + smallest = std::min(smallest, iter.second); + return smallest; +} + +void +RingBuffer::storeReadOffset(size_t offset, const std::string &call_id) +{ + ReadOffset::iterator iter = readoffsets_.find(call_id); + + if (iter != readoffsets_.end()) + iter->second = offset; + else + RING_ERR("RingBuffer::storeReadOffset() failed: unknown call '%s'", call_id.c_str()); +} + + +void +RingBuffer::createReadOffset(const std::string &call_id) +{ + std::lock_guard<std::mutex> l(lock_); + if (!hasThisReadOffset(call_id)) + readoffsets_.insert(std::pair<std::string, int> (call_id, endPos_)); +} + + +void +RingBuffer::removeReadOffset(const std::string &call_id) +{ + std::lock_guard<std::mutex> l(lock_); + ReadOffset::iterator iter = readoffsets_.find(call_id); + + if (iter != readoffsets_.end()) + readoffsets_.erase(iter); +} + + +bool +RingBuffer::hasThisReadOffset(const std::string &call_id) const +{ + return readoffsets_.find(call_id) != readoffsets_.end(); +} + + +bool RingBuffer::hasNoReadOffsets() const +{ + return readoffsets_.empty(); +} + +// +// For the writer only: +// + +// This one puts some data inside the ring buffer. +void RingBuffer::put(AudioBuffer& buf) +{ + std::lock_guard<std::mutex> l(lock_); + const size_t sample_num = buf.frames(); + const size_t buffer_size = buffer_.frames(); + if (buffer_size == 0) + return; + + size_t len = putLength(); + if (buffer_size - len < sample_num) + discard(sample_num); + size_t toCopy = sample_num; + + // Add more channels if the input buffer holds more channels than the ring. + if (buffer_.channels() < buf.channels()) + buffer_.setChannelNum(buf.channels()); + + size_t in_pos = 0; + size_t pos = endPos_; + + while (toCopy) { + size_t block = toCopy; + + if (block > buffer_size - pos) // Wrap block around ring ? + block = buffer_size - pos; // Fill in to the end of the buffer + + buffer_.copy(buf, block, in_pos, pos); + in_pos += block; + pos = (pos + block) % buffer_size; + toCopy -= block; + } + + endPos_ = pos; + not_empty_.notify_all(); +} + +// +// For the reader only: +// + +size_t +RingBuffer::availableForGet(const std::string &call_id) const +{ + // Used space + return getLength(call_id); +} + +size_t RingBuffer::get(AudioBuffer& buf, const std::string &call_id) +{ + std::lock_guard<std::mutex> l(lock_); + + if (hasNoReadOffsets()) + return 0; + + if (not hasThisReadOffset(call_id)) + return 0; + + const size_t buffer_size = buffer_.frames(); + if (buffer_size == 0) + return 0; + + size_t len = getLength(call_id); + const size_t sample_num = buf.frames(); + size_t toCopy = std::min(sample_num, len); + if (toCopy and toCopy != sample_num) { + RING_DBG("Partial get: %d/%d", toCopy, sample_num); + } + + const size_t copied = toCopy; + + size_t dest = 0; + size_t startPos = getReadOffset(call_id); + + while (toCopy > 0) { + size_t block = toCopy; + + if (block > buffer_size - startPos) + block = buffer_size - startPos; + + buf.copy(buffer_, block, startPos, dest); + + dest += block; + startPos = (startPos + block) % buffer_size; + toCopy -= block; + } + + storeReadOffset(startPos, call_id); + return copied; +} + + +size_t RingBuffer::waitForDataAvailable(const std::string &call_id, const size_t min_data_length, const std::chrono::high_resolution_clock::time_point& deadline) const +{ + std::unique_lock<std::mutex> l(lock_); + const size_t buffer_size = buffer_.frames(); + if (buffer_size < min_data_length) return 0; + ReadOffset::const_iterator read_ptr = readoffsets_.find(call_id); + if (read_ptr == readoffsets_.end()) return 0; + size_t getl = 0; + if (deadline == std::chrono::high_resolution_clock::time_point()) { + not_empty_.wait(l, [=, &getl] { + getl = (endPos_ + buffer_size - read_ptr->second) % buffer_size; + return getl >= min_data_length; + }); + } else { + not_empty_.wait_until(l, deadline, [=, &getl]{ + getl = (endPos_ + buffer_size - read_ptr->second) % buffer_size; + return getl >= min_data_length; + }); + } + return getl; +} + +size_t +RingBuffer::discard(size_t toDiscard, const std::string &call_id) +{ + std::lock_guard<std::mutex> l(lock_); + + const size_t buffer_size = buffer_.frames(); + if (buffer_size == 0) + return 0; + + size_t len = getLength(call_id); + if (toDiscard > len) + toDiscard = len; + + size_t startPos = (getReadOffset(call_id) + toDiscard) % buffer_size; + storeReadOffset(startPos, call_id); + return toDiscard; +} + +size_t +RingBuffer::discard(size_t toDiscard) +{ + const size_t buffer_size = buffer_.frames(); + if (buffer_size == 0) + return 0; + + for (auto & r : readoffsets_) { + size_t dst = (r.second + buffer_size - endPos_) % buffer_size; + if (dst < toDiscard) { + RING_DBG("%s : discarding: %d frames", r.first.c_str(), toDiscard - dst); + r.second = (r.second + toDiscard - dst) % buffer_size; + } + } + return toDiscard; +} + +} // namespace ring diff --git a/src/media/audio/ringbuffer.h b/src/media/audio/ringbuffer.h new file mode 100644 index 0000000000..8d9aa9becd --- /dev/null +++ b/src/media/audio/ringbuffer.h @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2007-2015 Savoir-Faire Linux Inc. + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * Author: Laurielle Lea <laurielle.lea@savoirfairelinux.com> + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author: Adrien Beraud <adrien.beraud@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef __RING_BUFFER__ +#define __RING_BUFFER__ + +#include "audiobuffer.h" +#include "noncopyable.h" + +#include <condition_variable> +#include <mutex> +#include <chrono> +#include <map> +#include <vector> +#include <fstream> + +typedef std::map<std::string, size_t> ReadOffset; + +namespace ring { + +/** + * A ring buffer for mutichannel audio samples + */ +class RingBuffer { + public: + /** + * Constructor + * @param size Size of the buffer to create + */ + RingBuffer(const std::string& id, size_t size, + AudioFormat format=AudioFormat::MONO()); + + /** + * Reset the counters to 0 for this read offset + */ + void flush(const std::string &call_id); + + void flushAll(); + + inline AudioFormat getFormat() const { + return buffer_.getFormat(); + } + + inline void setFormat(AudioFormat format) { + buffer_.setFormat(format); + } + + /** + * Add a new readoffset for this ringbuffer + */ + void createReadOffset(const std::string &call_id); + + /** + * Remove a readoffset for this ringbuffer + */ + void removeReadOffset(const std::string &call_id); + + size_t readOffsetCount() const { return readoffsets_.size(); } + + bool hasNoReadOffsets() const; + + /** + * Write data in the ring buffer + * @param buffer Data to copied + * @param toCopy Number of bytes to copy + */ + void put(AudioBuffer& buf); + + /** + * To get how much samples are available in the buffer to read in + * @return int The available (multichannel) samples number + */ + size_t availableForGet(const std::string &call_id) const; + + /** + * Get data in the ring buffer + * @param buffer Data to copied + * @param toCopy Number of bytes to copy + * @return size_t Number of bytes copied + */ + size_t get(AudioBuffer& buf, const std::string &call_id); + + /** + * Discard data from the buffer + * @param toDiscard Number of samples to discard + * @return size_t Number of samples discarded + */ + size_t discard(size_t toDiscard, const std::string &call_id); + + /** + * Total length of the ring buffer which is available for "putting" + * @return int + */ + size_t putLength() const; + + size_t getLength(const std::string &call_id) const; + + inline bool isFull() const { + return putLength() == buffer_.frames(); + } + + inline bool isEmpty() const { + return putLength() == 0; + } + + + /** + * Blocks until min_data_length samples of data is available, or until deadline is missed. + * + * @param call_id The read offset for which data should be available. + * @param min_data_length Minimum number of samples that should be vailable for the call to return + * @param deadline The call is garenteed to end after this time point. If no deadline is provided, the the call blocks indefinitely. + * @return available data for call_id after the call returned (same as calling getLength(call_id) ). + */ + size_t waitForDataAvailable(const std::string &call_id, const size_t min_data_length, const std::chrono::high_resolution_clock::time_point& deadline) const; + + /** + * Debug function print mEnd, mStart, mBufferSize + */ + void debug(); + + const std::string id; + + private: + NON_COPYABLE(RingBuffer); + + /** + * Return the smalest readoffset. Usefull to evaluate if ringbuffer is full + */ + size_t getSmallestReadOffset() const; + + /** + * Get read offset coresponding to this call + */ + size_t getReadOffset(const std::string &call_id) const; + + /** + * Move readoffset forward by offset + */ + void storeReadOffset(size_t offset, const std::string &call_id); + + /** + * Test if readoffset coresponding to this call is still active + */ + bool hasThisReadOffset(const std::string &call_id) const; + + /** + * Discard data from all read offsets to make place for new data. + */ + size_t discard(size_t toDiscard); + + /** Offset on the last data */ + size_t endPos_; + + /** Data */ + AudioBuffer buffer_; + + mutable std::mutex lock_; + mutable std::condition_variable not_empty_; + + ReadOffset readoffsets_; +}; + +} // namespace ring + +#endif /* __RING_BUFFER__ */ diff --git a/src/media/audio/ringbufferpool.cpp b/src/media/audio/ringbufferpool.cpp new file mode 100644 index 0000000000..8104a57afe --- /dev/null +++ b/src/media/audio/ringbufferpool.cpp @@ -0,0 +1,428 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "ringbufferpool.h" +#include "ringbuffer.h" +#include "ring_types.h" // for SIZEBUF +#include "logger.h" + +#include <limits> +#include <utility> // for std::pair +#include <cstring> + +namespace ring { + +const char * const RingBufferPool::DEFAULT_ID = "audiolayer_id"; + +RingBufferPool::RingBufferPool() + : defaultRingBuffer_(createRingBuffer(DEFAULT_ID)) +{} + +RingBufferPool::~RingBufferPool() +{ + readBindingsMap_.clear(); + defaultRingBuffer_.reset(); + + // Verify ringbuffer not removed yet + // XXXX: With a good design this should never happen! :-P + for (const auto& item : ringBufferMap_) { + const auto& weak = item.second; + if (not weak.expired()) + RING_WARN("Leaking RingBuffer '%s'", item.first.c_str()); + } +} + +void +RingBufferPool::setInternalSamplingRate(unsigned sr) +{ + std::lock_guard<std::recursive_mutex> lk(stateLock_); + + if (sr != internalAudioFormat_.sample_rate) { + flushAllBuffers(); + internalAudioFormat_.sample_rate = sr; + } +} + +void +RingBufferPool::setInternalAudioFormat(AudioFormat format) +{ + std::lock_guard<std::recursive_mutex> lk(stateLock_); + + if (format != internalAudioFormat_) { + flushAllBuffers(); + internalAudioFormat_ = format; + } +} + +std::shared_ptr<RingBuffer> +RingBufferPool::getRingBuffer(const std::string& id) +{ + std::lock_guard<std::recursive_mutex> lk(stateLock_); + + const auto& it = ringBufferMap_.find(id); + if (it != ringBufferMap_.cend()) { + if (const auto& sptr = it->second.lock()) + return sptr; + ringBufferMap_.erase(it); + } + + return nullptr; +} + +std::shared_ptr<RingBuffer> +RingBufferPool::getRingBuffer(const std::string& id) const +{ + std::lock_guard<std::recursive_mutex> lk(stateLock_); + + const auto& it = ringBufferMap_.find(id); + if (it != ringBufferMap_.cend()) + return it->second.lock(); + + return nullptr; +} + +std::shared_ptr<RingBuffer> +RingBufferPool::createRingBuffer(const std::string& id) +{ + std::lock_guard<std::recursive_mutex> lk(stateLock_); + + auto rbuf = getRingBuffer(id); + if (rbuf) { + RING_DBG("Ringbuffer already exists for id '%s'", id.c_str()); + return rbuf; + } + + rbuf.reset(new RingBuffer(id, SIZEBUF)); + RING_DBG("Ringbuffer created with id '%s'", id.c_str()); + ringBufferMap_.insert(std::make_pair(id, std::weak_ptr<RingBuffer>(rbuf))); + return rbuf; +} + +const RingBufferPool::ReadBindings* +RingBufferPool::getReadBindings(const std::string& call_id) const +{ + const auto& iter = readBindingsMap_.find(call_id); + return iter != readBindingsMap_.cend() ? &iter->second : nullptr; +} + +RingBufferPool::ReadBindings* +RingBufferPool::getReadBindings(const std::string& call_id) +{ + const auto& iter = readBindingsMap_.find(call_id); + return iter != readBindingsMap_.cend() ? &iter->second : nullptr; +} + +void +RingBufferPool::removeReadBindings(const std::string& call_id) +{ + if (not readBindingsMap_.erase(call_id)) + RING_ERR("CallID set %s does not exist!", call_id.c_str()); +} + +/** + * Make given call ID a reader of given ring buffer + */ +void +RingBufferPool::addReaderToRingBuffer(std::shared_ptr<RingBuffer> rbuf, + const std::string& call_id) +{ + if (call_id != DEFAULT_ID and rbuf->id == call_id) + RING_WARN("RingBuffer has a readoffset on itself"); + + rbuf->createReadOffset(call_id); + readBindingsMap_[call_id].insert(rbuf); // bindings list created if not existing + RING_DBG("Bind rbuf '%s' to callid '%s'", rbuf->id.c_str(), call_id.c_str()); +} + +void +RingBufferPool::removeReaderFromRingBuffer(std::shared_ptr<RingBuffer> rbuf, + const std::string& call_id) +{ + if (auto bindings = getReadBindings(call_id)) { + bindings->erase(rbuf); + if (bindings->empty()) + removeReadBindings(call_id); + } + + rbuf->removeReadOffset(call_id); +} + +void +RingBufferPool::bindCallID(const std::string& call_id1, + const std::string& call_id2) +{ + const auto& rb_call1 = getRingBuffer(call_id1); + if (not rb_call1) { + RING_ERR("No ringbuffer associated to call '%s'", call_id1.c_str()); + return; + } + + const auto& rb_call2 = getRingBuffer(call_id2); + if (not rb_call2) { + RING_ERR("No ringbuffer associated to call '%s'", call_id2.c_str()); + return; + } + + std::lock_guard<std::recursive_mutex> lk(stateLock_); + + addReaderToRingBuffer(rb_call1, call_id2); + addReaderToRingBuffer(rb_call2, call_id1); +} + +void +RingBufferPool::bindHalfDuplexOut(const std::string& process_id, + const std::string& call_id) +{ + /* This method is used only for active calls, if this call does not exist, + * do nothing */ + if (const auto& rb = getRingBuffer(call_id)) { + std::lock_guard<std::recursive_mutex> lk(stateLock_); + + addReaderToRingBuffer(rb, process_id); + } +} + +void +RingBufferPool::unBindCallID(const std::string& call_id1, + const std::string& call_id2) +{ + const auto& rb_call1 = getRingBuffer(call_id1); + if (not rb_call1) { + RING_ERR("No ringbuffer associated to call '%s'", call_id1.c_str()); + return; + } + + const auto& rb_call2 = getRingBuffer(call_id2); + if (not rb_call2) { + RING_ERR("No ringbuffer associated to call '%s'", call_id2.c_str()); + return; + } + + std::lock_guard<std::recursive_mutex> lk(stateLock_); + + removeReaderFromRingBuffer(rb_call1, call_id2); + removeReaderFromRingBuffer(rb_call2, call_id1); +} + +void +RingBufferPool::unBindHalfDuplexOut(const std::string& process_id, + const std::string& call_id) +{ + std::lock_guard<std::recursive_mutex> lk(stateLock_); + + if (const auto& rb = getRingBuffer(call_id)) + removeReaderFromRingBuffer(rb, process_id); +} + +void +RingBufferPool::unBindAll(const std::string& call_id) +{ + const auto& rb_call = getRingBuffer(call_id); + if (not rb_call) { + RING_ERR("No ringbuffer associated to call '%s'", call_id.c_str()); + return; + } + + std::lock_guard<std::recursive_mutex> lk(stateLock_); + + auto bindings = getReadBindings(call_id); + if (not bindings) + return; + + const auto bindings_copy = *bindings; // temporary copy + for (const auto& rbuf : bindings_copy) { + removeReaderFromRingBuffer(rbuf, call_id); + removeReaderFromRingBuffer(rb_call, rbuf->id); + } +} + +size_t +RingBufferPool::getData(AudioBuffer& buffer, const std::string& call_id) +{ + std::lock_guard<std::recursive_mutex> lk(stateLock_); + + const auto bindings = getReadBindings(call_id); + if (not bindings) + return 0; + + // No mixing + if (bindings->size() == 1) + return (*bindings->cbegin())->get(buffer, call_id); + + buffer.reset(); + buffer.setFormat(internalAudioFormat_); + + size_t size = 0; + AudioBuffer mixBuffer(buffer); + + for (const auto& rbuf : *bindings) { + // XXX: is it normal to only return the last positive size? + size = rbuf->get(mixBuffer, call_id); + if (size > 0) + buffer.mix(mixBuffer); + } + + return size; +} + +bool +RingBufferPool::waitForDataAvailable(const std::string& call_id, + size_t min_frames, + const std::chrono::microseconds& max_wait) const +{ + std::unique_lock<std::recursive_mutex> lk(stateLock_); + + // convert to absolute time + const auto deadline = std::chrono::high_resolution_clock::now() + max_wait; + + auto bindings = getReadBindings(call_id); + if (not bindings) + return 0; + + const auto bindings_copy = *bindings; // temporary copy + for (const auto& rbuf : bindings_copy) { + lk.unlock(); + if (rbuf->waitForDataAvailable(call_id, min_frames, deadline) < min_frames) + return false; + lk.lock(); + } + return true; +} + +size_t +RingBufferPool::getAvailableData(AudioBuffer& buffer, const std::string& call_id) +{ + std::lock_guard<std::recursive_mutex> lk(stateLock_); + + auto bindings = getReadBindings(call_id); + if (not bindings) + return 0; + + // No mixing + if (bindings->size() == 1) { + return (*bindings->cbegin())->get(buffer, call_id); + } + + size_t availableSamples = std::numeric_limits<size_t>::max(); + + for (const auto& rbuf : *bindings) + availableSamples = std::min(availableSamples, + rbuf->availableForGet(call_id)); + + if (availableSamples == std::numeric_limits<size_t>::max()) + return 0; + + availableSamples = std::min(availableSamples, buffer.frames()); + + buffer.resize(availableSamples); + buffer.reset(); + buffer.setFormat(internalAudioFormat_); + + AudioBuffer mixBuffer(buffer); + + for (const auto &rbuf : *bindings) { + if (rbuf->get(mixBuffer, call_id) > 0) + buffer.mix(mixBuffer); + } + + return availableSamples; +} + +size_t +RingBufferPool::availableForGet(const std::string& call_id) const +{ + std::lock_guard<std::recursive_mutex> lk(stateLock_); + + const auto bindings = getReadBindings(call_id); + if (not bindings) + return 0; + + // No mixing + if (bindings->size() == 1) { + return (*bindings->begin())->availableForGet(call_id); + } + + size_t availableSamples = std::numeric_limits<size_t>::max(); + + for (const auto& rbuf : *bindings) { + const size_t nbSamples = rbuf->availableForGet(call_id); + if (nbSamples > 0) + availableSamples = std::min(availableSamples, nbSamples); + } + + return availableSamples != std::numeric_limits<size_t>::max() ? availableSamples : 0; +} + +size_t +RingBufferPool::discard(size_t toDiscard, const std::string& call_id) +{ + std::lock_guard<std::recursive_mutex> lk(stateLock_); + + const auto bindings = getReadBindings(call_id); + if (not bindings) + return 0; + + for (const auto& rbuf : *bindings) + rbuf->discard(toDiscard, call_id); + + return toDiscard; +} + +void +RingBufferPool::flush(const std::string& call_id) +{ + std::lock_guard<std::recursive_mutex> lk(stateLock_); + + const auto bindings = getReadBindings(call_id); + if (not bindings) + return; + + for (const auto& rbuf : *bindings) + rbuf->flush(call_id); +} + +void +RingBufferPool::flushAllBuffers() +{ + std::lock_guard<std::recursive_mutex> lk(stateLock_); + + for (auto item = ringBufferMap_.begin(); item != ringBufferMap_.end(); ) { + if (const auto rb = item->second.lock()) { + rb->flushAll(); + ++item; + } else { + // Use this version of erase to avoid using invalidated iterator + item = ringBufferMap_.erase(item); + } + } +} + +} // namespace ring diff --git a/src/media/audio/ringbufferpool.h b/src/media/audio/ringbufferpool.h new file mode 100644 index 0000000000..acba104a27 --- /dev/null +++ b/src/media/audio/ringbufferpool.h @@ -0,0 +1,169 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef RING_BUFFER_POOL_H_ +#define RING_BUFFER_POOL_H_ + +#include "audiobuffer.h" +#include "noncopyable.h" + +#include <map> +#include <set> +#include <string> +#include <mutex> +#include <memory> + +namespace ring { + +class RingBuffer; + +class RingBufferPool { + + public: + static const char * const DEFAULT_ID; + + RingBufferPool(); + + ~RingBufferPool(); + + int getInternalSamplingRate() const { + return internalAudioFormat_.sample_rate; + } + + AudioFormat getInternalAudioFormat() const { + return internalAudioFormat_; + } + + void setInternalSamplingRate(unsigned sr); + + void setInternalAudioFormat(AudioFormat format); + + /** + * Bind together two audio streams so taht a client will be able + * to put and get data specifying its callid only. + */ + void bindCallID(const std::string& call_id1, + const std::string& call_id2); + + /** + * Add a new call_id to unidirectional outgoing stream + * \param call_id New call id to be added for this stream + * \param process_id Process that require this stream + */ + void bindHalfDuplexOut(const std::string& process_id, + const std::string& call_id); + + /** + * Unbind two calls + */ + void unBindCallID(const std::string& call_id1, + const std::string& call_id2); + + /** + * Unbind a unidirectional stream + */ + void unBindHalfDuplexOut(const std::string& process_id, + const std::string& call_id); + + void unBindAll(const std::string& call_id); + + bool waitForDataAvailable(const std::string& call_id, + size_t min_data_length, + const std::chrono::microseconds& max_wait) const; + + size_t getData(AudioBuffer& buffer, const std::string& call_id); + + size_t getAvailableData(AudioBuffer& buffer, const std::string& call_id); + + size_t availableForGet(const std::string& call_id) const; + + size_t discard(size_t toDiscard, const std::string& call_id); + + void flush(const std::string& call_id); + + void flushAllBuffers(); + + /** + * Create a new ringbuffer with a default readoffset. + * This class keeps a weak reference on returned pointer, + * so the caller is responsible of the refered instance. + */ + std::shared_ptr<RingBuffer> createRingBuffer(const std::string& id); + + /** + * Obtain a shared pointer on a RingBuffer given by its ID. + * If the ID doesn't match to any RingBuffer, the shared pointer + * is empty. This non-const version flushes internal weak pointers + * if the ID was used and the associated RingBuffer has been deleted. + */ + std::shared_ptr<RingBuffer> getRingBuffer(const std::string& id); + + /** + * Works as non-const getRingBuffer, without the weak reference flush. + */ + std::shared_ptr<RingBuffer> getRingBuffer(const std::string& id) const; + + private: + NON_COPYABLE(RingBufferPool); + + // A set of RingBuffers readable by a call + typedef std::set<std::shared_ptr<RingBuffer>, + std::owner_less<std::shared_ptr<RingBuffer>> > ReadBindings; + + const RingBufferPool::ReadBindings* + getReadBindings(const std::string& call_id) const; + + RingBufferPool::ReadBindings* getReadBindings(const std::string& call_id); + + void removeReadBindings(const std::string& call_id); + + void addReaderToRingBuffer(std::shared_ptr<RingBuffer> rbuf, + const std::string& call_id); + + void removeReaderFromRingBuffer(std::shared_ptr<RingBuffer> rbuf, + const std::string& call_id); + + // A cache of created RingBuffers listed by IDs. + std::map<std::string, std::weak_ptr<RingBuffer> > ringBufferMap_ {}; + + // A map of which RingBuffers a call has some ReadOffsets + std::map<std::string, ReadBindings> readBindingsMap_ {}; + + mutable std::recursive_mutex stateLock_ {}; + + AudioFormat internalAudioFormat_ {AudioFormat::DEFAULT()}; + + std::shared_ptr<RingBuffer> defaultRingBuffer_; +}; + +} // namespace ring + +#endif // RING_BUFFER_POOL_H_ diff --git a/src/media/audio/sound/Makefile.am b/src/media/audio/sound/Makefile.am new file mode 100644 index 0000000000..f2ba42881f --- /dev/null +++ b/src/media/audio/sound/Makefile.am @@ -0,0 +1,17 @@ +include $(top_srcdir)/globals.mak + +noinst_LTLIBRARIES = libsound.la + +libsound_la_SOURCES = \ + audiofile.cpp \ + tone.cpp \ + tonelist.cpp \ + dtmf.cpp \ + dtmfgenerator.cpp + +noinst_HEADERS = \ + audiofile.h \ + tone.h \ + tonelist.h \ + dtmfgenerator.h \ + dtmf.h diff --git a/src/media/audio/sound/audiofile.cpp b/src/media/audio/sound/audiofile.cpp new file mode 100644 index 0000000000..f4622b4628 --- /dev/null +++ b/src/media/audio/sound/audiofile.cpp @@ -0,0 +1,142 @@ +/* Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * + * Inspired by tonegenerator of + * Laurielle Lea <laurielle.lea@savoirfairelinux.com> (2004) + * Inspired by ringbuffer of Audacity Project + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#include <fstream> +#include <cmath> +#include <samplerate.h> +#include <cstring> +#include <vector> +#include <climits> +#include <sndfile.hh> + +#include "audiofile.h" +#include "audio/resampler.h" +#include "manager.h" +#include "client/signal.h" + +#include "logger.h" + +namespace ring { + +void +AudioFile::onBufferFinish() +{ + // We want to send values in milisecond + const int divisor = buffer_->getSampleRate() / 1000; + + if (divisor == 0) { + RING_ERR("Error cannot update playback slider, sampling rate is 0"); + return; + } + + if ((updatePlaybackScale_ % 5) == 0) + emitSignal<DRing::CallSignal::UpdatePlaybackScale>(filepath_, + (unsigned)(pos_ / divisor), + (unsigned)(buffer_->frames() / divisor)); + + updatePlaybackScale_++; +} + +AudioFile::AudioFile(const std::string &fileName, unsigned int sampleRate) : + AudioLoop(sampleRate), filepath_(fileName), updatePlaybackScale_(0) +{ + int format; + bool hasHeader = true; + + if (filepath_.find(".wav") != std::string::npos) { + format = SF_FORMAT_WAV; + } else if (filepath_.find(".ul") != std::string::npos) { + format = SF_FORMAT_RAW | SF_FORMAT_ULAW; + hasHeader = false; + } else if (filepath_.find(".al") != std::string::npos) { + format = SF_FORMAT_RAW | SF_FORMAT_ALAW; + hasHeader = false; + } else if (filepath_.find(".au") != std::string::npos) { + format = SF_FORMAT_AU; + } else if (filepath_.find(".flac") != std::string::npos) { + format = SF_FORMAT_FLAC; + } else if (filepath_.find(".ogg") != std::string::npos) { + format = SF_FORMAT_OGG; + } else { + RING_WARN("No file extension, guessing WAV"); + format = SF_FORMAT_WAV; + } + + SndfileHandle fileHandle(fileName.c_str(), SFM_READ, format, hasHeader ? 0 : 1, + hasHeader ? 0 : 8000); + + if (!fileHandle) + throw AudioFileException("File handle " + fileName + " could not be created"); + if (fileHandle.error()) { + RING_ERR("%s", fileHandle.strError()); + throw AudioFileException("File " + fileName + " doesn't exist"); + } + + switch (fileHandle.channels()) { + case 1: + case 2: + break; + default: + throw AudioFileException("Unsupported number of channels"); + } + + // get # of bytes in file + const size_t fileSize = fileHandle.seek(0, SEEK_END); + fileHandle.seek(0, SEEK_SET); + + const sf_count_t nbFrames = hasHeader ? fileHandle.frames() : fileSize / fileHandle.channels(); + + AudioSample * interleaved = new AudioSample[nbFrames * fileHandle.channels()]; + + // get n "items", aka samples (not frames) + fileHandle.read(interleaved, nbFrames * fileHandle.channels()); + + AudioBuffer * buffer = new AudioBuffer(nbFrames, AudioFormat(fileHandle.samplerate(), fileHandle.channels())); + buffer->deinterleave(interleaved, nbFrames, fileHandle.channels()); + delete [] interleaved; + + const int rate = static_cast<int32_t>(sampleRate); + + if (fileHandle.samplerate() != rate) { + Resampler resampler(std::max(fileHandle.samplerate(), rate), fileHandle.channels()); + AudioBuffer * resampled = new AudioBuffer(nbFrames, AudioFormat(rate, fileHandle.channels())); + resampler.resample(*buffer, *resampled); + delete buffer; + delete buffer_; + buffer_ = resampled; + } else { + delete buffer_; + buffer_ = buffer; + } +} + +} // namespace ring diff --git a/src/media/audio/sound/audiofile.h b/src/media/audio/sound/audiofile.h new file mode 100644 index 0000000000..8a4c33074d --- /dev/null +++ b/src/media/audio/sound/audiofile.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * + * Inspired by tonegenerator of + * Laurielle Lea <laurielle.lea@savoirfairelinux.com> (2004) + * Inspired by ringbuffer of Audacity Project + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#ifndef __AUDIOFILE_H__ +#define __AUDIOFILE_H__ + +#include <stdexcept> +#include "audio/audioloop.h" + +namespace ring { + +class AudioFileException : public std::runtime_error { + public: + AudioFileException(const std::string &str) : + std::runtime_error("AudioFile: AudioFileException occured: " + str) {} +}; + +/** + * @brief Abstract interface for file readers + */ +class AudioFile : public AudioLoop { + public: + AudioFile(const std::string &filepath, unsigned int sampleRate); + + std::string getFilePath() const { + return filepath_; + } + + protected: + /** The absolute path to the sound file */ + std::string filepath_; + + private: + // override + void onBufferFinish(); + unsigned updatePlaybackScale_; +}; + +} // namespace ring + +#endif // __AUDIOFILE_H__ diff --git a/src/media/audio/sound/dtmf.cpp b/src/media/audio/sound/dtmf.cpp new file mode 100644 index 0000000000..09cf18d9e1 --- /dev/null +++ b/src/media/audio/sound/dtmf.cpp @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author : Yan Morin <yan.morin@savoirfairelinux.com> + * Author : Laurielle Lea <laurielle.lea@savoirfairelinux.com> + * + * Portions Copyright (c) 2000 Billy Biggs <bbiggs@div8.net> + * Portions Copyright (c) 2004 Wirlab <kphone@wirlab.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "dtmf.h" + +namespace ring { + +DTMF::DTMF(unsigned int sampleRate) + : currentTone_(0), newTone_(0), dtmfgenerator_(sampleRate) +{} + +void DTMF::startTone(char code) +{ + newTone_ = code; +} + +using std::vector; + +bool DTMF::generateDTMF(vector<AudioSample> &buffer) +{ + try { + if (currentTone_ != 0) { + // Currently generating a DTMF tone + if (currentTone_ == newTone_) { + // Continue generating the same tone + dtmfgenerator_.getNextSamples(buffer); + return true; + } else if (newTone_ != 0) { + // New tone requested + dtmfgenerator_.getSamples(buffer, newTone_); + currentTone_ = newTone_; + return true; + } else { + // Stop requested + currentTone_ = newTone_; + return false; + } + } else { + // Not generating any DTMF tone + if (newTone_) { + // Requested to generate a DTMF tone + dtmfgenerator_.getSamples(buffer, newTone_); + currentTone_ = newTone_; + return true; + } else + return false; + } + } catch (const DTMFException &e) { + // invalid key + return false; + } +} + +} // namespace ring diff --git a/src/media/audio/sound/dtmf.h b/src/media/audio/sound/dtmf.h new file mode 100644 index 0000000000..476500d782 --- /dev/null +++ b/src/media/audio/sound/dtmf.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author : Yan Morin <yan.morin@savoirfairelinux.com> + * Author: Laurielle Lea <laurielle.lea@savoirfairelinux.com> + * + * Portions Copyright (c) 2000 Billy Biggs <bbiggs@div8.net> + * Portions Copyright (c) 2004 Wirlab <kphone@wirlab.net> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __DTMF_H_ +#define __DTMF_H_ + +#include "dtmfgenerator.h" + +/** + * @file dtmf.h + * @brief DMTF library to generate a dtmf sample + */ + +namespace ring { + +class DTMF { + public: + /** + * Create a new DTMF. + * @param sampleRate frequency of the sample (ex: 8000 hz) + */ + DTMF(unsigned int sampleRate); + + /** + * Start the done for th given dtmf + * @param code The DTMF code + */ + void startTone(char code); + + /** + * Copy the sound inside the sampling* buffer + * @param buffer : a vector of AudioSample + */ + bool generateDTMF(std::vector<AudioSample> &buffer); + + private: + char currentTone_; + char newTone_; + + DTMFGenerator dtmfgenerator_; +}; + +} // namespace ring + +#endif // __KEY_DTMF_H_ diff --git a/src/media/audio/sound/dtmfgenerator.cpp b/src/media/audio/sound/dtmfgenerator.cpp new file mode 100644 index 0000000000..ab16eecb11 --- /dev/null +++ b/src/media/audio/sound/dtmfgenerator.cpp @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * Author: Laurielle Lea <laurielle.lea@savoirfairelinux.com> + * + * Portions (c) 2003 iptel.org + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <cmath> +#include <cassert> + +#include "dtmfgenerator.h" + +namespace ring { + +/* + * Tone frequencies + */ +const DTMFGenerator::DTMFTone DTMFGenerator::tones_[] = { + {'0', 941, 1336}, + {'1', 697, 1209}, + {'2', 697, 1336}, + {'3', 697, 1477}, + {'4', 770, 1209}, + {'5', 770, 1336}, + {'6', 770, 1477}, + {'7', 852, 1209}, + {'8', 852, 1336}, + {'9', 852, 1477}, + {'A', 697, 1633}, + {'B', 770, 1633}, + {'C', 852, 1633}, + {'D', 941, 1633}, + {'*', 941, 1209}, + {'#', 941, 1477} +}; + + +/* + * Initialize the generator + */ +DTMFGenerator::DTMFGenerator(unsigned int sampleRate) : state(), sampleRate_(sampleRate), tone_("", sampleRate) +{ + state.offset = 0; + state.sample = 0; + + for (int i = 0; i < NUM_TONES; i++) + toneBuffers_[i] = fillToneBuffer(i); +} + + +DTMFGenerator::~DTMFGenerator() +{ + for (int i = 0; i < NUM_TONES; i++) + delete [] toneBuffers_[i]; +} + +using std::vector; + +/* + * Get n samples of the signal of code code + */ +void DTMFGenerator::getSamples(vector<AudioSample> &buffer, unsigned char code) +{ + code = toupper(code); + + if (code >= '0' and code <= '9') + state.sample = toneBuffers_[code - '0']; + else if (code >= 'A' and code <= 'D') + state.sample = toneBuffers_[code - 'A' + 10]; + else { + switch (code) { + case '*': + state.sample = toneBuffers_[NUM_TONES - 2]; + break; + + case '#': + state.sample = toneBuffers_[NUM_TONES - 1]; + break; + + default: + throw DTMFException("Invalid code"); + break; + } + } + + size_t i; + const size_t n = buffer.size(); + + for (i = 0; i < n; ++i) + buffer[i] = state.sample[i % sampleRate_]; + + state.offset = i % sampleRate_; +} + +/* + * Get next n samples (continues where previous call to + * genSample or genNextSamples stopped + */ +void DTMFGenerator::getNextSamples(vector<AudioSample> &buffer) +{ + if (state.sample == 0) + throw DTMFException("DTMF generator not initialized"); + + size_t i; + const size_t n = buffer.size(); + + for (i = 0; i < n; i++) + buffer[i] = state.sample[(state.offset + i) % sampleRate_]; + + state.offset = (state.offset + i) % sampleRate_; +} + +AudioSample* DTMFGenerator::fillToneBuffer(int index) +{ + assert(index >= 0 and index < NUM_TONES); + AudioSample* ptr = new AudioSample[sampleRate_]; + tone_.genSin(ptr, tones_[index].higher, tones_[index].lower, sampleRate_); + return ptr; +} + +} // namespace ring diff --git a/src/media/audio/sound/dtmfgenerator.h b/src/media/audio/sound/dtmfgenerator.h new file mode 100644 index 0000000000..0f4b43dd51 --- /dev/null +++ b/src/media/audio/sound/dtmfgenerator.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * Author: Laurielle Lea <laurielle.lea@savoirfairelinux.com> + * + * Portions (c) 2003 iptel.org + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU Library General Public License as published by + * the Free Software Foundation; either version 3 of the License, or (at your + * option) any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public + * License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + * + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef DTMFGENERATOR_H +#define DTMFGENERATOR_H + +#include <stdexcept> +#include <string> +#include <vector> +#include "noncopyable.h" +#include "tone.h" + +#define NUM_TONES 16 + +/* + * @file dtmfgenerator.h + * @brief DMTF Generator Exception + */ + +namespace ring { + +class DTMFException : public std::runtime_error { + public: + DTMFException(const std::string& str) : std::runtime_error(str) {}; +}; + +/* + * @file dtmfgenerator.h + * @brief DTMF Tone Generator + */ +class DTMFGenerator { + private: + /** Struct to handle a DTMF */ + struct DTMFTone { + unsigned char code; /** Code of the tone */ + int lower; /** Lower frequency */ + int higher; /** Higher frequency */ + }; + + /** State of the DTMF generator */ + struct DTMFState { + unsigned int offset; /** Offset in the sample currently being played */ + AudioSample* sample; /** Currently generated code */ + }; + + /** State of the DTMF generator */ + DTMFState state; + + /** The different kind of tones */ + static const DTMFTone tones_[NUM_TONES]; + + /** Generated samples for each tone */ + AudioSample* toneBuffers_[NUM_TONES]; + + /** Sampling rate of generated dtmf */ + int sampleRate_; + + /** A tone object */ + Tone tone_; + + public: + /** + * DTMF Generator contains frequency of each keys + * and can build one DTMF. + * @param sampleRate frequency of the sample (ex: 8000 hz) + */ + DTMFGenerator(unsigned int sampleRate); + + ~DTMFGenerator(); + + NON_COPYABLE(DTMFGenerator); + + /* + * Get n samples of the signal of code code + * @param buffer a AudioSample vector + * @param code dtmf code to get sound + */ + void getSamples(std::vector<AudioSample> &buffer, unsigned char code); + + /* + * Get next n samples (continues where previous call to + * genSample or genNextSamples stopped + * @param buffer a AudioSample vector + */ + void getNextSamples(std::vector<AudioSample> &buffer); + + private: + + /** + * Fill tone buffer for a given index of the array of tones. + * @param index of the tone in the array tones_ + * @return AudioSample* The generated data + */ + AudioSample* fillToneBuffer(int index); +}; + +} // namespace ring + +#endif // DTMFGENERATOR_H diff --git a/src/media/audio/sound/tone.cpp b/src/media/audio/sound/tone.cpp new file mode 100644 index 0000000000..e3a919e3e2 --- /dev/null +++ b/src/media/audio/sound/tone.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * Inspired by tonegenerator of + * Laurielle Lea <laurielle.lea@savoirfairelinux.com> (2004) + * Inspired by ringbuffer of Audacity Project + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#include "tone.h" +#include "logger.h" +#include "ring_types.h" + +#include <vector> +#include <cmath> + +namespace ring { + +Tone::Tone(const std::string& definition, unsigned int sampleRate) : + AudioLoop(sampleRate) +{ + genBuffer(definition); // allocate memory with definition parameter +} + +void +Tone::genBuffer(const std::string& definition) +{ + if (definition.empty()) + return; + + size_t size = 0; + const int sampleRate = buffer_->getSampleRate(); + + std::vector<AudioSample> buffer(SIZEBUF); + size_t bufferPos(0); + + // Number of format sections + std::string::size_type posStart = 0; // position of precedent comma + std::string::size_type posEnd = 0; // position of the next comma + + std::string s; // portion of frequency + size_t count; // number of int for one sequence + + std::string::size_type deflen = definition.length(); + + do { + posEnd = definition.find(',', posStart); + + if (posEnd == std::string::npos) + posEnd = deflen; + + /* begin scope */ + { + // Sample string: "350+440" or "350+440/2000,244+655/2000" + int low, high, time; + s = definition.substr(posStart, posEnd - posStart); + + // The 1st frequency is before the first + or the / + size_t pos_plus = s.find('+'); + size_t pos_slash = s.find('/'); + size_t len = s.length(); + size_t endfrequency = 0; + + if (pos_slash == std::string::npos) { + time = 0; + endfrequency = len; + } else { + time = atoi(s.substr(pos_slash + 1, len - pos_slash - 1).c_str()); + endfrequency = pos_slash; + } + + // without a plus = 1 frequency + if (pos_plus == std::string::npos) { + low = atoi(s.substr(0, endfrequency).c_str()); + high = 0; + } else { + low = atoi(s.substr(0, pos_plus).c_str()); + high = atoi(s.substr(pos_plus + 1, endfrequency - pos_plus - 1).c_str()); + } + + // If there is time or if it's unlimited + if (time == 0) + count = sampleRate; + else + count = (sampleRate * time) / 1000; + + // Generate SAMPLING_RATE samples of sinus, buffer is the result + buffer.resize(size+count); + genSin(&(*(buffer.begin()+bufferPos)), low, high, count); + + // To concatenate the different buffers for each section. + size += count; + bufferPos += count; + } /* end scope */ + + posStart = posEnd + 1; + } while (posStart < deflen); + + buffer_->copy(buffer.data(), size); // fill the buffer +} + +void +Tone::genSin(AudioSample* buffer, int lowFrequency, int highFrequency, size_t nb) +{ + const double sr = (double)buffer_->getSampleRate(); + const double dx_h = sr ? 2.0 * M_PI * lowFrequency / sr : 0.0; + const double dx_l = sr ? 2.0 * M_PI * highFrequency / sr : 0.0; + static constexpr double DATA_AMPLITUDE = 2048; + for (size_t t = 0; t < nb; t ++) { + buffer[t] = DATA_AMPLITUDE * (sin(t*dx_h) + sin(t*dx_l)); + } +} + +} // namespace ring diff --git a/src/media/audio/sound/tone.h b/src/media/audio/sound/tone.h new file mode 100644 index 0000000000..ed80868119 --- /dev/null +++ b/src/media/audio/sound/tone.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * + * Inspired by tonegenerator of + * Laurielle Lea <laurielle.lea@savoirfairelinux.com> (2004) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#ifndef __TONE_H__ +#define __TONE_H__ + +#include <string> +#include "audio/audioloop.h" + +/** + * @file tone.h + * @brief Tone sample (dial, busy, ring, congestion) + */ + +namespace ring { + +class Tone : public AudioLoop { + public: + /** + * Constructor + * @param definition String that contain frequency/time of the tone + * @param sampleRate SampleRating of audio tone + */ + Tone(const std::string& definition, unsigned int sampleRate); + + /** The different kind of tones */ + enum TONEID { + TONE_DIALTONE = 0, + TONE_BUSY, + TONE_RINGTONE, + TONE_CONGESTION, + TONE_NULL + }; + + /** + * Add a simple or double sin to the buffer, it double the sin in stereo + * @param buffer The data + * @param frequency1 The first frequency + * @param frequency2 The second frequency + * @param nb the number of samples to generate + */ + void genSin(AudioSample* buffer, int frequency1, int frequency2, size_t nb); + + private: + + /** + * allocate the memory with the definition + * @param definition String that contain frequency/time of the tone. + */ + void genBuffer(const std::string& definition); +}; + +} // namespace ring + +#endif // __TONE_H__ diff --git a/src/media/audio/sound/tonelist.cpp b/src/media/audio/sound/tonelist.cpp new file mode 100644 index 0000000000..482c5a8294 --- /dev/null +++ b/src/media/audio/sound/tonelist.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * + * Inspired by tonegenerator of + * Laurielle Lea <laurielle.lea@savoirfairelinux.com> (2004) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#include "tonelist.h" + +namespace ring { + +static const char *toneZone[TelephoneTone::ZID_COUNTRIES][Tone::TONE_NULL] = { + { + // ZID_NORTH_AMERICA + "350+440", // Tone::TONE_DIALTONE + "480+620/500,0/500", // Tone::TONE_BUSY + "440+480/2000,0/4000", // Tone::TONE_RINGTONE + "480+620/250,0/250", // Tone::TONE_CONGESTION + }, + { + //ZID_FRANCE + "440", + "440/500,0/500", + "440/1500,0/3500", + "440/250,0/250", + }, + { + //ZID_AUSTRALIA + "413+438", + "425/375,0/375", + "413+438/400,0/200,413+438/400,0/2000", + "425/375,0/375,420/375,8/375", + }, + { + //ZID_UNITED_KINGDOM + "350+440", + "400/375,0/375", + "400+450/400,0/200,400+450/400,0/2000", + "400/400,0/350,400/225,0/525", + }, + { + //ZID_SPAIN + "425", + "425/200,0/200", + "425/1500,0/3000", + "425/200,0/200,425/200,0/200,425/200,0/600", + }, + { + //ZID_ITALY + "425/600,0/1000,425/200,0/200", + "425/500,0/500", + "425/1000,0/4000", + "425/200,0/200", + }, + { + //ZID_JAPAN + "400", + "400/500,0/500", + "400+15/1000,0/2000", + "400/500,0/500", + } +}; + + +TelephoneTone::COUNTRYID +TelephoneTone::getCountryId(const std::string& countryName) +{ + if (countryName == "North America") return ZID_NORTH_AMERICA; + else if (countryName == "France") return ZID_FRANCE; + else if (countryName == "Australia") return ZID_AUSTRALIA; + else if (countryName == "United Kingdom") return ZID_UNITED_KINGDOM; + else if (countryName == "Spain") return ZID_SPAIN; + else if (countryName == "Italy") return ZID_ITALY; + else if (countryName == "Japan") return ZID_JAPAN; + else return ZID_NORTH_AMERICA; // default +} + +TelephoneTone::TelephoneTone(const std::string& countryName, unsigned int sampleRate) : + currentTone_(Tone::TONE_NULL) +{ + TelephoneTone::COUNTRYID countryId = getCountryId(countryName); + + tone_[Tone::TONE_DIALTONE] = new Tone(toneZone[countryId][Tone::TONE_DIALTONE], sampleRate); + tone_[Tone::TONE_BUSY] = new Tone(toneZone[countryId][Tone::TONE_BUSY], sampleRate); + tone_[Tone::TONE_RINGTONE] = new Tone(toneZone[countryId][Tone::TONE_RINGTONE], sampleRate); + tone_[Tone::TONE_CONGESTION] = new Tone(toneZone[countryId][Tone::TONE_CONGESTION], sampleRate); +} + +TelephoneTone::~TelephoneTone() +{ + for (size_t i=0; i < Tone::TONE_NULL; i++) + delete tone_[i]; +} + +void +TelephoneTone::setCurrentTone(Tone::TONEID toneId) +{ + if (toneId != Tone::TONE_NULL && currentTone_ != toneId) + tone_[toneId]->reset(); + + currentTone_ = toneId; +} + +Tone* +TelephoneTone::getCurrentTone() +{ + if (currentTone_ < Tone::TONE_DIALTONE or currentTone_ >= Tone::TONE_NULL) + return NULL; + + return tone_[currentTone_]; +} + +} // namespace ring diff --git a/src/media/audio/sound/tonelist.h b/src/media/audio/sound/tonelist.h new file mode 100644 index 0000000000..83eed03734 --- /dev/null +++ b/src/media/audio/sound/tonelist.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * + * Inspired by tonegenerator of + * Laurielle Lea <laurielle.lea@savoirfairelinux.com> (2004) + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __TONELIST_H__ +#define __TONELIST_H__ + +#include "tone.h" + +namespace ring { + +class TelephoneTone { + public: + /** Countries */ + enum COUNTRYID { + ZID_NORTH_AMERICA = 0, + ZID_FRANCE, + ZID_AUSTRALIA, + ZID_UNITED_KINGDOM, + ZID_SPAIN, + ZID_ITALY, + ZID_JAPAN, + ZID_COUNTRIES, + }; + + TelephoneTone(const std::string& countryName, unsigned int sampleRate); + ~TelephoneTone(); + + void setCurrentTone(Tone::TONEID toneId); + Tone* getCurrentTone(); + + private: + NON_COPYABLE(TelephoneTone); + + static COUNTRYID getCountryId(const std::string& countryName); + + Tone* tone_[Tone::TONE_NULL]; + Tone::TONEID currentTone_; +}; + +} // namespace ring + +#endif diff --git a/src/media/libav_deps.h b/src/media/libav_deps.h new file mode 100644 index 0000000000..9038510eca --- /dev/null +++ b/src/media/libav_deps.h @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2013-2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __LIBAV_DEPS_H__ +#define __LIBAV_DEPS_H__ + +/* LIBAVFORMAT_VERSION_CHECK checks for the right version of libav and FFmpeg + * a is the major version + * b and c the minor and micro versions of libav + * d and e the minor and micro versions of FFmpeg */ +#define LIBAVFORMAT_VERSION_CHECK( a, b, c, d, e ) \ + ( (LIBAVFORMAT_VERSION_MICRO < 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT( a, b, c ) ) || \ + (LIBAVFORMAT_VERSION_MICRO >= 100 && LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT( a, d, e ) ) ) + +/* LIBAVCODEC_VERSION_CHECK checks for the right version of libav and FFmpeg + * a is the major version + * b and c the minor and micro versions of libav + * d and e the minor and micro versions of FFmpeg */ +#define LIBAVCODEC_VERSION_CHECK( a, b, c, d, e ) \ + ( (LIBAVCODEC_VERSION_MICRO < 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT( a, b, c ) ) || \ + (LIBAVCODEC_VERSION_MICRO >= 100 && LIBAVCODEC_VERSION_INT >= AV_VERSION_INT( a, d, e ) ) ) + +/* LIBAVUTIL_VERSION_CHECK checks for the right version of libav and FFmpeg + * a is the major version + * b and c the minor and micro versions of libav + * d and e the minor and micro versions of FFmpeg */ +#define LIBAVUTIL_VERSION_CHECK( a, b, c, d, e ) \ + ( (LIBAVUTIL_VERSION_MICRO < 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT( a, b, c ) ) || \ + (LIBAVUTIL_VERSION_MICRO >= 100 && LIBAVUTIL_VERSION_INT >= AV_VERSION_INT( a, d, e ) ) ) + +extern "C" { +#include <libavcodec/avcodec.h> +#include <libavformat/avformat.h> +#include <libavdevice/avdevice.h> +#include <libswscale/swscale.h> +#include <libavutil/avutil.h> +#if LIBAVUTIL_VERSION_CHECK(51, 33, 0, 60, 100) +#include <libavutil/time.h> +#endif +#include <libavutil/pixdesc.h> +#include <libavutil/opt.h> +#include <libavutil/channel_layout.h> +#include <libavutil/mathematics.h> // for av_rescale_q (old libav support) +#include <libavutil/imgutils.h> +#include <libavutil/intreadwrite.h> +} + +#include "libav_utils.h" + +#if !LIBAVFORMAT_VERSION_CHECK(54,20,3,59,103) +#error "Used libavformat doesn't support sdp custom_io" +#endif + +#if !LIBAVUTIL_VERSION_CHECK(51, 42, 0, 74, 100) && !defined(FF_API_PIX_FMT) +#define AVPixelFormat PixelFormat +#define PIXEL_FORMAT(FMT) PIX_FMT_ ## FMT + +static inline const AVPixFmtDescriptor *av_pix_fmt_desc_get(enum AVPixelFormat pix_fmt) +{ + if (pix_fmt < 0 || pix_fmt >= PIX_FMT_NB) + return NULL; + return &av_pix_fmt_descriptors[pix_fmt]; +} + +#else +#define PIXEL_FORMAT(FMT) AV_PIX_FMT_ ## FMT +#endif + +#if !LIBAVCODEC_VERSION_CHECK(54, 28, 0, 59, 100) +#define avcodec_free_frame(x) av_freep(x) +#endif + +// Especially for Fedora < 20 and UBUNTU < 14.10 +#define USE_OLD_AVU ! LIBAVUTIL_VERSION_CHECK(52, 8, 0, 19, 100) + +#if USE_OLD_AVU +#define av_frame_alloc avcodec_alloc_frame +#define av_frame_free avcodec_free_frame +#define av_frame_unref avcodec_get_frame_defaults +#define av_frame_get_buffer(x, y) avpicture_alloc((AVPicture *)(x), \ + (AVPixelFormat)(x)->format, \ + (x)->width, (x)->height) +#endif + + +#endif // __LIBAV_DEPS_H__ diff --git a/src/media/libav_utils.cpp b/src/media/libav_utils.cpp new file mode 100644 index 0000000000..e77cdf7a85 --- /dev/null +++ b/src/media/libav_utils.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * Author: Luca Barbato <lu_zero@gentoo.org> + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "libav_deps.h" // MUST BE INCLUDED FIRST + +#include "config.h" +#include "video/video_base.h" +#include "logger.h" + +#include <vector> +#include <algorithm> +#include <string> +#include <iostream> +#include <thread> +#include <mutex> +#include <exception> + +namespace ring { namespace libav_utils { + +// protect libav/ffmpeg access +static int +avcodecManageMutex(void **data, enum AVLockOp op) +{ + auto mutex = reinterpret_cast<std::mutex**>(data); + int ret = 0; + switch (op) { + case AV_LOCK_CREATE: + try { + *mutex = new std::mutex; + } catch (const std::bad_alloc& e) { + return AVERROR(ENOMEM); + } + break; + case AV_LOCK_OBTAIN: + (*mutex)->lock(); + break; + case AV_LOCK_RELEASE: + (*mutex)->unlock(); + break; + case AV_LOCK_DESTROY: + delete *mutex; + *mutex = nullptr; + break; + default: +#ifdef AVERROR_BUG + return AVERROR_BUG; +#else + break; +#endif + } + return AVERROR(ret); +} + + +static void init_once() +{ + av_register_all(); + avdevice_register_all(); +#if LIBAVFORMAT_VERSION_INT >= AV_VERSION_INT(53, 13, 0) + avformat_network_init(); +#endif + + av_lockmgr_register(avcodecManageMutex); + + if (getDebugMode()) + av_log_set_level(AV_LOG_VERBOSE); +} + +static std::once_flag already_called; + +void ring_avcodec_init() +{ + std::call_once(already_called, init_once); +} + + +int libav_pixel_format(int fmt) +{ + switch (fmt) { + case video::VIDEO_PIXFMT_BGRA: return PIXEL_FORMAT(BGRA); + case video::VIDEO_PIXFMT_RGBA: return PIXEL_FORMAT(RGBA); + case video::VIDEO_PIXFMT_YUV420P: return PIXEL_FORMAT(YUV420P); + } + return fmt; +} + +int ring_pixel_format(int fmt) +{ + switch (fmt) { + case PIXEL_FORMAT(YUV420P): return video::VIDEO_PIXFMT_YUV420P; + } + return fmt; +} + +void ring_url_split(const char *url, + char *hostname, size_t hostname_size, int *port, + char *path, size_t path_size) +{ + av_url_split(NULL, 0, NULL, 0, hostname, hostname_size, port, + path, path_size, url); +} + +}} // namespace ring::libav_utils diff --git a/src/media/libav_utils.h b/src/media/libav_utils.h new file mode 100644 index 0000000000..f12147b5d0 --- /dev/null +++ b/src/media/libav_utils.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __LIBAV_UTILS_H__ +#define __LIBAV_UTILS_H__ + +#include <vector> +#include <map> +#include <string> + +namespace ring { namespace libav_utils { + + void ring_avcodec_init(); + + int libav_pixel_format(int fmt); + int ring_pixel_format(int fmt); + + const char *const DEFAULT_H264_PROFILE_LEVEL_ID = "profile-level-id=428014"; + const char *const MAX_H264_PROFILE_LEVEL_ID = "profile-level-id=640034"; + + void ring_url_split(const char *url, + char *hostname, size_t hostname_size, int *port, + char *path, size_t path_size); + +}} // namespace ring::libav_utils + +#endif // __LIBAV_UTILS_H__ diff --git a/src/media/media_buffer.cpp b/src/media/media_buffer.cpp new file mode 100644 index 0000000000..2a0df42b81 --- /dev/null +++ b/src/media/media_buffer.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "libav_deps.h" // MUST BE INCLUDED FIRST +#include "media_buffer.h" + +#include <new> +#include <cstdlib> + +namespace ring { + +MediaFrame::MediaFrame() + : frame_ {av_frame_alloc(), [](AVFrame* frame){ av_frame_free(&frame); }} +{ + if (not frame_) + throw std::bad_alloc(); +} + +void +MediaFrame::reset() noexcept +{ + av_frame_unref(frame_.get()); +} + +#ifdef RING_VIDEO + +void +VideoFrame::reset() noexcept +{ + MediaFrame::reset(); +#if !USE_OLD_AVU + allocated_ = false; +#endif +} + +size_t +VideoFrame::size() const noexcept +{ + return videoFrameSize(format(), width(), height()); +} + +int +VideoFrame::format() const noexcept +{ + return libav_utils::ring_pixel_format(frame_->format); +} + +int +VideoFrame::width() const noexcept +{ + return frame_->width; +} + +int +VideoFrame::height() const noexcept +{ + return frame_->height; +} + +void +VideoFrame::setGeometry(int format, int width, int height) noexcept +{ + frame_->format = libav_utils::libav_pixel_format(format); + frame_->width = width; + frame_->height = height; +} + +void +VideoFrame::reserve(int format, int width, int height) +{ + auto libav_format = (AVPixelFormat)libav_utils::libav_pixel_format(format); + auto libav_frame = frame_.get(); + + if (allocated_) { + // nothing to do if same properties + if (width == libav_frame->width + and height == libav_frame->height + and libav_format == libav_frame->format) +#if USE_OLD_AVU + avpicture_free((AVPicture *) libav_frame); +#else + av_frame_unref(libav_frame); +#endif + } + + setGeometry(format, width, height); + if (av_frame_get_buffer(libav_frame, 32)) + throw std::bad_alloc(); + allocated_ = true; +} + +void +VideoFrame::setFromMemory(void* ptr, int format, int width, int height) noexcept +{ + reset(); + setGeometry(format, width, height); + if (not ptr) + return; + avpicture_fill((AVPicture*)frame_.get(), (uint8_t*)ptr, + (AVPixelFormat)frame_->format, width, height); +} + +void +VideoFrame::noise() +{ + auto f = frame_.get(); + if (f->data[0] == nullptr) + return; + for (std::size_t i=0 ; i < size(); ++i) { + f->data[0][i] = std::rand() & 255; + } +} + +VideoFrame& +VideoFrame::operator =(const VideoFrame& src) +{ + reserve(src.format(), src.width(), src.height()); + av_picture_copy((AVPicture *)frame_.get(), (AVPicture *)src.pointer(), + (AVPixelFormat)frame_->format, + frame_->width, frame_->height); + return *this; +} + +//=== HELPERS ================================================================== + +std::size_t +videoFrameSize(int format, int width, int height) +{ + return avpicture_get_size((AVPixelFormat)libav_utils::libav_pixel_format(format), + width, height); +} + +void +yuv422_clear_to_black(VideoFrame& frame) +{ + auto libav_frame = frame.pointer(); + + memset(libav_frame->data[0], 0, libav_frame->linesize[0] * libav_frame->height); + // 128 is the black level for U/V channels + memset(libav_frame->data[1], 128, libav_frame->linesize[1] * libav_frame->height / 2); + memset(libav_frame->data[2], 128, libav_frame->linesize[2] * libav_frame->height / 2); +} + +#endif // RING_VIDEO + +} // namespace ring diff --git a/src/media/media_buffer.h b/src/media/media_buffer.h new file mode 100644 index 0000000000..524fd8c41b --- /dev/null +++ b/src/media/media_buffer.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#pragma once + +#include "config.h" + +#include <memory> + +class AVFrame; + +namespace ring { + +class MediaFrame { + public: + // Construct an empty MediaFrame + MediaFrame(); + + virtual ~MediaFrame() = default; + + // Return a pointer on underlaying buffer + AVFrame* pointer() const noexcept { return frame_.get(); } + + // Reset internal buffers (return to an empty MediaFrame) + virtual void reset() noexcept; + + protected: + std::unique_ptr<AVFrame, void(*)(AVFrame*)> frame_; +}; + +struct AudioFrame: MediaFrame {}; + +#ifdef RING_VIDEO + +class VideoFrame: public MediaFrame { + public: + // Construct an empty VideoFrame + VideoFrame() = default; + + // Reset internal buffers (return to an empty VideoFrame) + void reset() noexcept override; + + // Return frame size in bytes + std::size_t size() const noexcept; + + // Return pixel format + int format() const noexcept; + + // Return frame width in pixels + int width() const noexcept; + + // Return frame height in pixels + int height() const noexcept; + + // Allocate internal pixel buffers following given specifications + void reserve(int format, int width, int height); + + // Set internal pixel buffers on given memory buffer + // This buffer must follow given specifications. + void setFromMemory(void* ptr, int format, int width, int height) noexcept; + + void noise(); + + // Copy-Assignement + VideoFrame& operator =(const VideoFrame& src); + + private: + bool allocated_ {false}; + void setGeometry(int format, int width, int height) noexcept; +}; + +// Some helpers +std::size_t videoFrameSize(int format, int width, int height); +void yuv422_clear_to_black(VideoFrame& frame); + +#endif // RING_VIDEO + +} // namespace ring diff --git a/src/media/media_codec.cpp b/src/media/media_codec.cpp new file mode 100644 index 0000000000..a5a0790a0d --- /dev/null +++ b/src/media/media_codec.cpp @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2015 Savoir-Faire Linux Inc. + * Author: Eloi BAIL <eloi.bail@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "libav_deps.h" // MUST BE INCLUDED FIRST +#include "media_codec.h" +#include "account_const.h" + +#include <string.h> +#include <sstream> + +namespace ring { + +static unsigned& +generateId() +{ + static unsigned id = 0; + return ++id; +} + +/* + * SystemCodecInfo + */ +SystemCodecInfo::SystemCodecInfo(unsigned avcodecId, const std::string name, + std::string libName, + MediaType mediaType, CodecType codecType, + unsigned bitrate, unsigned payloadType) + : id(generateId()) + , avcodecId(avcodecId) + , name(name) + , libName(libName) + , codecType(codecType) + , mediaType(mediaType) + , payloadType(payloadType) + , bitrate(bitrate) +{} + +SystemCodecInfo::~SystemCodecInfo() +{} + +std::string +SystemCodecInfo::to_string() const +{ + std::ostringstream out; + out << " type:" << (unsigned)codecType + << " , avcodecID:" << avcodecId + << " ,name:" << name + << " ,PT:" << payloadType + << " ,libName:" << libName + << " ,bitrate:" << bitrate; + + return out.str(); +} + +/* + * SystemAudioCodecInfo + */ +SystemAudioCodecInfo::SystemAudioCodecInfo(unsigned m_avcodecId, + const std::string m_name, + std::string m_libName, + CodecType m_type, unsigned m_bitrate, + unsigned m_sampleRate, + unsigned m_nbChannels, + unsigned m_payloadType) + : SystemCodecInfo(m_avcodecId, m_name, m_libName, MEDIA_AUDIO, m_type, m_bitrate, m_payloadType) + , audioformat{m_sampleRate, m_nbChannels} +{} + +SystemAudioCodecInfo::~SystemAudioCodecInfo() +{} + + +std::map<std::string, std::string> +SystemAudioCodecInfo::getCodecSpecifications() +{ + return { + {DRing::Account::ConfProperties::CodecInfo::NAME, name}, + {DRing::Account::ConfProperties::CodecInfo::TYPE, (mediaType & MEDIA_AUDIO ? "AUDIO" : "VIDEO")}, + {DRing::Account::ConfProperties::CodecInfo::BITRATE, std::to_string(bitrate)}, + {DRing::Account::ConfProperties::CodecInfo::SAMPLE_RATE, std::to_string(audioformat.sample_rate)}, + {DRing::Account::ConfProperties::CodecInfo::CHANNEL_NUMBER, std::to_string(audioformat.nb_channels)} + }; +} + +/* + * SystemVideoCodecInfo + */ +SystemVideoCodecInfo::SystemVideoCodecInfo(unsigned m_avcodecId, + const std::string m_name, + std::string m_libName, + CodecType m_type, + unsigned m_bitrate, + unsigned m_payloadType, + unsigned m_frameRate, + unsigned m_profileId) + : SystemCodecInfo(m_avcodecId, m_name, m_libName, MEDIA_VIDEO, + m_type, m_bitrate, m_payloadType) + , frameRate(m_frameRate), profileId(m_profileId) +{} + +SystemVideoCodecInfo::~SystemVideoCodecInfo() +{} + +std::map<std::string, std::string> +SystemVideoCodecInfo::getCodecSpecifications() +{ + return { + {DRing::Account::ConfProperties::CodecInfo::NAME, name}, + {DRing::Account::ConfProperties::CodecInfo::TYPE, (mediaType & MEDIA_AUDIO ? "AUDIO" : "VIDEO")}, + {DRing::Account::ConfProperties::CodecInfo::BITRATE, std::to_string(bitrate)}, + {DRing::Account::ConfProperties::CodecInfo::FRAME_RATE, std::to_string(frameRate)} + }; +} + +AccountCodecInfo::AccountCodecInfo(const SystemCodecInfo& sysCodecInfo) + : systemCodecInfo(sysCodecInfo) + , order(0) + , isActive(true) + , payloadType(sysCodecInfo.payloadType) + , bitrate(sysCodecInfo.bitrate) +{} + +AccountCodecInfo::~AccountCodecInfo() +{} + +AccountAudioCodecInfo::AccountAudioCodecInfo(const SystemAudioCodecInfo& sysCodecInfo) + : AccountCodecInfo(sysCodecInfo) + , audioformat{sysCodecInfo.audioformat} +{} + +std::map<std::string, std::string> +AccountAudioCodecInfo::getCodecSpecifications() +{ + return { + {DRing::Account::ConfProperties::CodecInfo::NAME, systemCodecInfo.name}, + {DRing::Account::ConfProperties::CodecInfo::TYPE, (systemCodecInfo.mediaType & MEDIA_AUDIO ? "AUDIO" : "VIDEO")}, + {DRing::Account::ConfProperties::CodecInfo::BITRATE, std::to_string(bitrate)}, + {DRing::Account::ConfProperties::CodecInfo::SAMPLE_RATE, std::to_string(audioformat.sample_rate)}, + {DRing::Account::ConfProperties::CodecInfo::CHANNEL_NUMBER, std::to_string(audioformat.nb_channels)} + }; +} + +void +AccountAudioCodecInfo::setCodecSpecifications(const std::map<std::string, std::string>& details) +{ + auto it = details.find(DRing::Account::ConfProperties::CodecInfo::BITRATE); + if (it != details.end()) + bitrate = std::stoi(it->second); + + it = details.find(DRing::Account::ConfProperties::CodecInfo::SAMPLE_RATE); + if (it != details.end()) + audioformat.sample_rate = std::stoi(it->second); + + it = details.find(DRing::Account::ConfProperties::CodecInfo::CHANNEL_NUMBER); + if (it != details.end()) + audioformat.nb_channels = std::stoi(it->second); +} + +bool +AccountAudioCodecInfo::isPCMG722() const +{ + return systemCodecInfo.avcodecId == AV_CODEC_ID_ADPCM_G722; +} + + +AccountAudioCodecInfo::~AccountAudioCodecInfo() +{} + +AccountVideoCodecInfo::AccountVideoCodecInfo(const SystemVideoCodecInfo& sysCodecInfo) + : AccountCodecInfo(sysCodecInfo) + , frameRate(sysCodecInfo.frameRate) + , profileId(sysCodecInfo.profileId) +{} + +std::map<std::string, std::string> +AccountVideoCodecInfo::getCodecSpecifications() +{ + return { + {DRing::Account::ConfProperties::CodecInfo::NAME, systemCodecInfo.name}, + {DRing::Account::ConfProperties::CodecInfo::TYPE, (systemCodecInfo.mediaType & MEDIA_AUDIO ? "AUDIO" : "VIDEO")}, + {DRing::Account::ConfProperties::CodecInfo::BITRATE, std::to_string(bitrate)}, + {DRing::Account::ConfProperties::CodecInfo::FRAME_RATE, std::to_string(frameRate)} + }; +} + +void +AccountVideoCodecInfo::setCodecSpecifications(const std::map<std::string, std::string>& details) +{ + auto it = details.find(DRing::Account::ConfProperties::CodecInfo::BITRATE); + if (it != details.end()) + bitrate = stoi(it->second); + + it = details.find(DRing::Account::ConfProperties::CodecInfo::FRAME_RATE); + if (it != details.end()) + frameRate = stoi(it->second); +} + +AccountVideoCodecInfo::~AccountVideoCodecInfo() +{} + +} // namespace ring diff --git a/src/media/media_codec.h b/src/media/media_codec.h new file mode 100644 index 0000000000..181d0bf2bd --- /dev/null +++ b/src/media/media_codec.h @@ -0,0 +1,268 @@ +/* + * Copyright (C) 2015 Savoir-Faire Linux Inc. + * Author: Eloi BAIL <eloi.bail@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __MEDIA_CODEC_H__ +#define __MEDIA_CODEC_H__ + +#include "audio/audiobuffer.h" // for AudioFormat +#include "ip_utils.h" + +#include <string> +#include <vector> +#include <map> +#include <iostream> +#include <unistd.h> + +namespace ring { + +enum CodecType : unsigned { + CODEC_NONE = 0, // indicates that no codec is used or defined + CODEC_ENCODER = 1, + CODEC_DECODER = 2, + CODEC_ENCODER_DECODER = CODEC_ENCODER | CODEC_DECODER +}; + +enum MediaType : unsigned { + MEDIA_NONE = 0, // indicates that no media is used or defined + MEDIA_AUDIO = 1, + MEDIA_VIDEO = 2, + MEDIA_ALL = MEDIA_AUDIO | MEDIA_VIDEO +}; + +/* + * SystemCodecInfo + * represent information of a codec available on the system (using libav) + * store default codec values + */ +struct SystemCodecInfo +{ + SystemCodecInfo(unsigned avcodecId, const std::string name, + std::string libName, MediaType mediaType, + CodecType codecType = CODEC_NONE, unsigned bitrate = 0, + unsigned payloadType = 0); + + virtual ~SystemCodecInfo(); + + /* generic codec information */ + unsigned id; /* id of the codec used with dbus */ + unsigned avcodecId; /* read as AVCodecID libav codec identifier */ + std::string name; + std::string libName; + CodecType codecType; + MediaType mediaType; + + /* default codec values */ + unsigned payloadType; + unsigned bitrate; + + std::string to_string() const; +}; + +/* + * SystemAudioCodecInfo + * represent information of a audio codec available on the system (using libav) + * store default codec values + */ +struct SystemAudioCodecInfo : SystemCodecInfo +{ + SystemAudioCodecInfo(unsigned avcodecId, const std::string name, + std::string libName, CodecType type, + unsigned bitrate = 0, + unsigned sampleRate = 0, unsigned nbChannels = 0, + unsigned payloadType = 0); + + ~SystemAudioCodecInfo(); + + std::map<std::string, std::string> getCodecSpecifications(); + + AudioFormat audioformat {AudioFormat::NONE()}; +}; + +/* + * SystemVideoCodecInfo + * represent information of a video codec available on the system (using libav) + * store default codec values + */ +struct SystemVideoCodecInfo : SystemCodecInfo +{ + SystemVideoCodecInfo(unsigned avcodecId, const std::string name, + std::string libName, CodecType type = CODEC_NONE, + unsigned bitrate = 0, + unsigned payloadType = 0, unsigned frameRate = 0, + unsigned profileId = 0); + + ~SystemVideoCodecInfo(); + + std::map<std::string, std::string> getCodecSpecifications(); + + unsigned frameRate; + unsigned profileId; + std::string parameters; +}; + +/* + * AccountCodecInfo + * represent information of a codec on a account + * store account codec values + */ +struct AccountCodecInfo +{ + AccountCodecInfo(const SystemCodecInfo& sysCodecInfo); + ~AccountCodecInfo(); + + const SystemCodecInfo& systemCodecInfo; + unsigned order; /*used to define prefered codec list order in UI*/ + bool isActive; + /* account custom values */ + unsigned payloadType; + unsigned bitrate; +}; + +struct AccountAudioCodecInfo : AccountCodecInfo +{ + AccountAudioCodecInfo(const SystemAudioCodecInfo& sysCodecInfo); + ~AccountAudioCodecInfo(); + + std::map<std::string, std::string> getCodecSpecifications(); + void setCodecSpecifications(const std::map<std::string, std::string>& details); + + /* account custom values */ + AudioFormat audioformat {AudioFormat::NONE()}; + bool isPCMG722() const; +}; + +struct AccountVideoCodecInfo : AccountCodecInfo +{ + AccountVideoCodecInfo(const SystemVideoCodecInfo& sysCodecInfo); + ~AccountVideoCodecInfo(); + + void setCodecSpecifications(const std::map<std::string, std::string>& details); + std::map<std::string, std::string> getCodecSpecifications(); + + /* account custom values */ + unsigned frameRate; + unsigned profileId; + std::string parameters; +}; +bool operator== (SystemCodecInfo codec1, SystemCodecInfo codec2); + +class CryptoAttribute { +public: + CryptoAttribute() {} + CryptoAttribute(const std::string& tag, + const std::string& cryptoSuite, + const std::string& srtpKeyMethod, + const std::string& srtpKeyInfo, + const std::string& lifetime, + const std::string& mkiValue, + const std::string& mkiLength) : + tag_(tag), + cryptoSuite_(cryptoSuite), + srtpKeyMethod_(srtpKeyMethod), + srtpKeyInfo_(srtpKeyInfo), + lifetime_(lifetime), + mkiValue_(mkiValue), + mkiLength_(mkiLength) { + } + + std::string getTag() const { + return tag_; + } + std::string getCryptoSuite() const { + return cryptoSuite_; + } + std::string getSrtpKeyMethod() const { + return srtpKeyMethod_; + } + std::string getSrtpKeyInfo() const { + return srtpKeyInfo_; + } + std::string getLifetime() const { + return lifetime_; + } + std::string getMkiValue() const { + return mkiValue_; + } + std::string getMkiLength() const { + return mkiLength_; + } + + operator bool() const { + return not tag_.empty(); + } + + std::string to_string() const { + return tag_+" "+cryptoSuite_+" "+srtpKeyMethod_+":"+srtpKeyInfo_; + } + +private: + std::string tag_; + std::string cryptoSuite_; + std::string srtpKeyMethod_; + std::string srtpKeyInfo_; + std::string lifetime_; + std::string mkiValue_; + std::string mkiLength_; +}; + +/** + * MediaDescription + * Negotiated RTP media slot + */ +struct MediaDescription { + /** Audio / video */ + MediaType type {}; + bool enabled {false}; + bool holding {false}; + + /** Endpoint socket address */ + IpAddr addr {}; + + /** RTP */ + std::shared_ptr<AccountCodecInfo> codec {}; + unsigned payload_type {}; + std::string receiving_sdp {}; + unsigned bitrate {}; + unsigned rtp_clockrate {8000}; + + /** Audio parameters */ + unsigned frame_size {}; + + /** Video parameters */ + std::string parameters {}; + + /** Crypto parameters */ + CryptoAttribute crypto {}; +}; + +}//namespace ring +#endif // __MEDIA_CODEC_H__ diff --git a/src/media/media_decoder.cpp b/src/media/media_decoder.cpp new file mode 100644 index 0000000000..e24b368a2f --- /dev/null +++ b/src/media/media_decoder.cpp @@ -0,0 +1,476 @@ +/* + * Copyright (C) 2013-2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "libav_deps.h" // MUST BE INCLUDED FIRST +#include "media_decoder.h" +#include "media_device.h" +#include "media_buffer.h" +#include "media_io_handle.h" +#include "audio/audiobuffer.h" +#include "audio/ringbuffer.h" +#include "audio/resampler.h" + +#include "string_utils.h" +#include "logger.h" + +#include <iostream> +#include <unistd.h> +#include <thread> // hardware_concurrency + +namespace ring { + +using std::string; + +MediaDecoder::MediaDecoder() : + inputCtx_(avformat_alloc_context()), + startTime_(AV_NOPTS_VALUE), + lastDts_(AV_NOPTS_VALUE) +{ +} + +MediaDecoder::~MediaDecoder() +{ + if (decoderCtx_) + avcodec_close(decoderCtx_); + + if (inputCtx_ and inputCtx_->nb_streams > 0) { +#if LIBAVFORMAT_VERSION_CHECK(53, 17, 0, 25, 0) + avformat_close_input(&inputCtx_); +#else + av_close_input_file(inputCtx_); +#endif + } +} + +int MediaDecoder::openInput(const DeviceParams& params) +{ + AVInputFormat *iformat = av_find_input_format(params.format.c_str()); + + if (!iformat) + RING_WARN("Cannot find format \"%s\"", params.format.c_str()); + + if (params.width and params.height) { + std::stringstream ss; + ss << params.width << "x" << params.height; + av_dict_set(&options_, "video_size", ss.str().c_str(), 0); + } + if (params.framerate) + av_dict_set(&options_, "framerate", ring::to_string(params.framerate).c_str(), 0); + if (params.channel) + av_dict_set(&options_, "channel", ring::to_string(params.channel).c_str(), 0); + av_dict_set(&options_, "loop", params.loop.c_str(), 0); + av_dict_set(&options_, "sdp_flags", params.sdp_flags.c_str(), 0); + + RING_DBG("Trying to open device %s with format %s", params.input.c_str(), + params.format.c_str()); + int ret = avformat_open_input( + &inputCtx_, + params.input.c_str(), + iformat, + options_ ? &options_ : NULL); + + if (ret) { + char errbuf[64]; + av_strerror(ret, errbuf, sizeof(errbuf)); + RING_ERR("avformat_open_input failed: %s", errbuf); + } else { + RING_DBG("Using format %s", params.format.c_str()); + } + + return ret; +} + +void MediaDecoder::setInterruptCallback(int (*cb)(void*), void *opaque) +{ + if (cb) { + inputCtx_->interrupt_callback.callback = cb; + inputCtx_->interrupt_callback.opaque = opaque; + } else { + inputCtx_->interrupt_callback.callback = 0; + } +} + +void MediaDecoder::setIOContext(MediaIOHandle *ioctx) +{ inputCtx_->pb = ioctx->getContext(); } + +int MediaDecoder::setupFromAudioData(const AudioFormat format) +{ + int ret; + + if (decoderCtx_) + avcodec_close(decoderCtx_); + + // Increase analyze time to solve synchronization issues between callers. + static const unsigned MAX_ANALYZE_DURATION = 30; // time in seconds + + inputCtx_->max_analyze_duration = MAX_ANALYZE_DURATION * AV_TIME_BASE; + + RING_DBG("Finding stream info"); +#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53, 8, 0) + ret = av_find_stream_info(inputCtx_); +#else + ret = avformat_find_stream_info(inputCtx_, NULL); +#endif + RING_DBG("Finding stream info DONE"); + + if (ret < 0) { + // workaround for this bug: + // http://patches.libav.org/patch/22541/ + if (ret == -1) + ret = AVERROR_INVALIDDATA; + char errBuf[64] = {0}; + // print nothing for unknown errors + if (av_strerror(ret, errBuf, sizeof errBuf) < 0) + errBuf[0] = '\0'; + + // always fail here + RING_ERR("Could not find stream info: %s", errBuf); + return -1; + } + + // find the first audio stream from the input + for (size_t i = 0; streamIndex_ == -1 && i < inputCtx_->nb_streams; ++i) + if (inputCtx_->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) + streamIndex_ = i; + + if (streamIndex_ == -1) { + RING_ERR("Could not find audio stream"); + return -1; + } + + // Get a pointer to the codec context for the video stream + decoderCtx_ = inputCtx_->streams[streamIndex_]->codec; + if (decoderCtx_ == 0) { + RING_ERR("Decoder context is NULL"); + return -1; + } + + // find the decoder for the video stream + inputDecoder_ = avcodec_find_decoder(decoderCtx_->codec_id); + if (!inputDecoder_) { + RING_ERR("Unsupported codec"); + return -1; + } + + decoderCtx_->thread_count = std::thread::hardware_concurrency(); + decoderCtx_->channels = format.nb_channels; + decoderCtx_->sample_rate = format.sample_rate; + + RING_WARN("Audio decoding using %s with %s", + inputDecoder_->name, format.toString().c_str()); + + if (emulateRate_) { + RING_DBG("Using framerate emulation"); + startTime_ = av_gettime(); + } + +#if LIBAVCODEC_VERSION_MAJOR >= 55 + decoderCtx_->refcounted_frames = 1; +#endif + ret = avcodec_open2(decoderCtx_, inputDecoder_, NULL); + if (ret) { + RING_ERR("Could not open codec"); + return -1; + } + + return 0; +} + +#ifdef RING_VIDEO +int MediaDecoder::setupFromVideoData() +{ + int ret; + + if (decoderCtx_) + avcodec_close(decoderCtx_); + + // Increase analyze time to solve synchronization issues between callers. + static const unsigned MAX_ANALYZE_DURATION = 30; // time in seconds + + inputCtx_->max_analyze_duration = MAX_ANALYZE_DURATION * AV_TIME_BASE; + + RING_DBG("Finding stream info"); +#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53, 8, 0) + ret = av_find_stream_info(inputCtx_); +#else + ret = avformat_find_stream_info(inputCtx_, NULL); +#endif + + if (ret < 0) { + // workaround for this bug: + // http://patches.libav.org/patch/22541/ + if (ret == -1) + ret = AVERROR_INVALIDDATA; + char errBuf[64] = {0}; + // print nothing for unknown errors + if (av_strerror(ret, errBuf, sizeof errBuf) < 0) + errBuf[0] = '\0'; + + // always fail here + RING_ERR("Could not find stream info: %s", errBuf); + return -1; + } + + // find the first video stream from the input + for (size_t i = 0; streamIndex_ == -1 && i < inputCtx_->nb_streams; ++i) + if (inputCtx_->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) + streamIndex_ = i; + + if (streamIndex_ == -1) { + RING_ERR("Could not find video stream"); + return -1; + } + + // Get a pointer to the codec context for the video stream + avStream_ = inputCtx_->streams[streamIndex_]; + decoderCtx_ = avStream_->codec; + if (decoderCtx_ == 0) { + RING_ERR("Decoder context is NULL"); + return -1; + } + + decoderCtx_->thread_count = std::thread::hardware_concurrency(); + + // find the decoder for the video stream + inputDecoder_ = avcodec_find_decoder(decoderCtx_->codec_id); + if (!inputDecoder_) { + RING_ERR("Unsupported codec"); + return -1; + } + + decoderCtx_->thread_count = 1; + if (emulateRate_) { + RING_DBG("Using framerate emulation"); + startTime_ = av_gettime(); + } + +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(53, 6, 0) + ret = avcodec_open(decoderCtx_, inputDecoder_); +#else +# if LIBAVCODEC_VERSION_MAJOR >= 55 + decoderCtx_->refcounted_frames = 1; +# endif + ret = avcodec_open2(decoderCtx_, inputDecoder_, NULL); +#endif + if (ret) { + RING_ERR("Could not open codec"); + return -1; + } + + return 0; +} + +MediaDecoder::Status +MediaDecoder::decode(VideoFrame& result, video::VideoPacket& video_packet) +{ + AVPacket *inpacket = video_packet.get(); + int ret = av_read_frame(inputCtx_, inpacket); + if (ret == AVERROR(EAGAIN)) { + return Status::Success; + } else if (ret == AVERROR_EOF) { + return Status::EOFError; + } else if (ret < 0) { + char errbuf[64]; + av_strerror(ret, errbuf, sizeof(errbuf)); + RING_ERR("Couldn't read frame: %s\n", errbuf); + return Status::ReadError; + } + + // is this a packet from the video stream? + if (inpacket->stream_index != streamIndex_) + return Status::Success; + + auto frame = result.pointer(); + int frameFinished = 0; + int len = avcodec_decode_video2(decoderCtx_, frame, + &frameFinished, inpacket); + if (len <= 0) + return Status::DecodeError; + + if (frameFinished) { + if (emulateRate_) { + if (frame->pkt_dts != AV_NOPTS_VALUE) { + const auto now = std::chrono::system_clock::now(); + const std::chrono::duration<double> seconds = now - lastFrameClock_; + const double dTB = av_q2d(inputCtx_->streams[streamIndex_]->time_base); + const double dts_diff = dTB * (frame->pkt_dts - lastDts_); + const double usDelay = 1e6 * (dts_diff - seconds.count()); + if (usDelay > 0.0) { +#if LIBAVUTIL_VERSION_CHECK(51, 34, 0, 61, 100) + av_usleep(usDelay); +#else + usleep(usDelay); +#endif + } + lastFrameClock_ = now; + lastDts_ = frame->pkt_dts; + } + } + return Status::FrameFinished; + } + + return Status::Success; +} +#endif // RING_VIDEO + +MediaDecoder::Status +MediaDecoder::decode(const AudioFrame& decodedFrame) +{ + const auto libav_frame = decodedFrame.pointer(); + + AVPacket inpacket; + memset(&inpacket, 0, sizeof(inpacket)); + av_init_packet(&inpacket); + inpacket.data = NULL; + inpacket.size = 0; + + int ret = av_read_frame(inputCtx_, &inpacket); + if (ret == AVERROR(EAGAIN)) { + return Status::Success; + } else if (ret == AVERROR_EOF) { + return Status::EOFError; + } else if (ret < 0) { + char errbuf[64]; + av_strerror(ret, errbuf, sizeof(errbuf)); + RING_ERR("Couldn't read frame: %s\n", errbuf); + return Status::ReadError; + } + + // is this a packet from the audio stream? + if (inpacket.stream_index != streamIndex_) + return Status::Success; + + int frameFinished = 0; + int len = avcodec_decode_audio4(decoderCtx_, libav_frame, + &frameFinished, &inpacket); + if (len <= 0) { + return Status::DecodeError; + } + + if (frameFinished) { + if (emulateRate_) { + if (libav_frame->pkt_dts != AV_NOPTS_VALUE) { + const auto now = std::chrono::system_clock::now(); + const std::chrono::duration<double> seconds = now - lastFrameClock_; + const double dTB = av_q2d(inputCtx_->streams[streamIndex_]->time_base); + const double dts_diff = dTB * (libav_frame->pkt_dts - lastDts_); + const double usDelay = 1e6 * (dts_diff - seconds.count()); + if (usDelay > 0.0) { +#if LIBAVUTIL_VERSION_CHECK(51, 34, 0, 61, 100) + av_usleep(usDelay); +#else + usleep(usDelay); +#endif + } + lastFrameClock_ = now; + lastDts_ = libav_frame->pkt_dts; + } + } + return Status::FrameFinished; + } + + return Status::Success; +} + +#ifdef RING_VIDEO +MediaDecoder::Status +MediaDecoder::flush(VideoFrame& result) +{ + AVPacket inpacket; + memset(&inpacket, 0, sizeof(inpacket)); + av_init_packet(&inpacket); + inpacket.data = NULL; + inpacket.size = 0; + + int frameFinished = 0; + auto len = avcodec_decode_video2(decoderCtx_, result.pointer(), + &frameFinished, &inpacket); + if (len <= 0) + return Status::DecodeError; + + if (frameFinished) + return Status::FrameFinished; + + return Status::Success; +} +#endif // RING_VIDEO + +int MediaDecoder::getWidth() const +{ return decoderCtx_->width; } + +int MediaDecoder::getHeight() const +{ return decoderCtx_->height; } + +int // TODO : use of float fps is more accurate +MediaDecoder::getFps() const +{ + return (avStream_->avg_frame_rate.den != 0 ? + (int)(avStream_->avg_frame_rate.num / avStream_->avg_frame_rate.den) + : 0); +} + +int MediaDecoder::getPixelFormat() const +{ return libav_utils::ring_pixel_format(decoderCtx_->pix_fmt); } + +void +MediaDecoder::writeToRingBuffer(const AudioFrame& decodedFrame, + RingBuffer& rb, const AudioFormat outFormat) +{ + const auto libav_frame = decodedFrame.pointer(); + decBuff_.setFormat(AudioFormat{ + (unsigned) libav_frame->sample_rate, + (unsigned) decoderCtx_->channels + }); + decBuff_.resize(libav_frame->nb_samples); + + if ( decoderCtx_->sample_fmt == AV_SAMPLE_FMT_FLTP ) { + decBuff_.convertFloatPlanarToSigned16(libav_frame->extended_data, + libav_frame->nb_samples, + decoderCtx_->channels); + } else if ( decoderCtx_->sample_fmt == AV_SAMPLE_FMT_S16 ) { + decBuff_.deinterleave(reinterpret_cast<const AudioSample*>(libav_frame->data[0]), + libav_frame->nb_samples, decoderCtx_->channels); + } + if ((unsigned)libav_frame->sample_rate != outFormat.sample_rate) { + if (!resampler_) { + RING_DBG("Creating audio resampler"); + resampler_.reset(new Resampler(outFormat)); + } + resamplingBuff_.setFormat({(unsigned) outFormat.sample_rate, (unsigned) decoderCtx_->channels}); + resamplingBuff_.resize(libav_frame->nb_samples); + resampler_->resample(decBuff_, resamplingBuff_); + rb.put(resamplingBuff_); + } else { + rb.put(decBuff_); + } +} + +} // namespace ring diff --git a/src/media/media_decoder.h b/src/media/media_decoder.h new file mode 100644 index 0000000000..0a20a3aa3c --- /dev/null +++ b/src/media/media_decoder.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2013-2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __MEDIA_DECODER_H__ +#define __MEDIA_DECODER_H__ + +#include "config.h" + +#ifdef RING_VIDEO +#include "video/video_base.h" +#include "video/video_scaler.h" +#endif // RING_VIDEO + +#include "audio/audiobuffer.h" + +#include "noncopyable.h" + +#include <map> +#include <string> +#include <memory> +#include <chrono> + +class AVCodecContext; +class AVStream; +class AVDictionary; +class AVFormatContext; +class AVCodec; + +namespace ring { + +#ifdef RING_VIDEO +namespace video { +class VideoPacket; +} // namespace ring::video +#endif // RING_VIDEO + +class AudioFrame; +class AudioFormat; +class RingBuffer; +class Resampler; +class MediaIOHandle; +class DeviceParams; + +class MediaDecoder { + public: + enum class Status { + Success, + FrameFinished, + EOFError, + ReadError, + DecodeError + }; + + MediaDecoder(); + ~MediaDecoder(); + + void emulateRate() { emulateRate_ = true; } + void setInterruptCallback(int (*cb)(void*), void *opaque); + int openInput(const DeviceParams&); + + void setIOContext(MediaIOHandle *ioctx); +#ifdef RING_VIDEO + int setupFromVideoData(); + Status decode(VideoFrame&, video::VideoPacket&); + Status flush(VideoFrame&); + #endif // RING_VIDEO + + int setupFromAudioData(const AudioFormat format); + Status decode(const AudioFrame&); + void writeToRingBuffer(const AudioFrame&, RingBuffer&, const AudioFormat); + + int getWidth() const; + int getHeight() const; + int getFps() const; + int getPixelFormat() const; + + void setOptions(const std::map<std::string, std::string>& options); + + private: + NON_COPYABLE(MediaDecoder); + + AVCodec *inputDecoder_ = nullptr; + AVCodecContext *decoderCtx_ = nullptr; + AVFormatContext *inputCtx_ = nullptr; + AVStream *avStream_ = nullptr; + std::unique_ptr<Resampler> resampler_; + int streamIndex_ = -1; + bool emulateRate_ = false; + int64_t startTime_; + int64_t lastDts_; + std::chrono::time_point<std::chrono::system_clock> lastFrameClock_ = {}; + + AudioBuffer decBuff_; + AudioBuffer resamplingBuff_; + + void extract(const std::map<std::string, std::string>& map, const std::string& key); + + protected: + AVDictionary *options_ = nullptr; +}; + +} // namespace ring + +#endif // __MEDIA_DECODER_H__ diff --git a/src/media/media_device.h b/src/media/media_device.h new file mode 100644 index 0000000000..dbec1dcff0 --- /dev/null +++ b/src/media/media_device.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015 Savoir-Faire Linux Inc. + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __MEDIA_DEVICE_H__ +#define __MEDIA_DEVICE_H__ + +#include <string> + +namespace ring { + +/** + * DeviceParams + * Parameters used by MediaDecoder and MediaEncoder + * to open a LibAV device/stream + */ +struct DeviceParams { + std::string input {}; // Device path (e.g. /dev/video0) + std::string format {}; + unsigned width {}, height {}; + unsigned framerate {}; + unsigned channel {}; // Channel number + std::string loop {}; + std::string sdp_flags {}; +}; + +} + +#endif // __MEDIA_DEVICE_H__ diff --git a/src/media/media_encoder.cpp b/src/media/media_encoder.cpp new file mode 100644 index 0000000000..963ea574f7 --- /dev/null +++ b/src/media/media_encoder.cpp @@ -0,0 +1,633 @@ +/* + * Copyright (C) 2013-2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "libav_deps.h" // MUST BE INCLUDED FIRST +#include "media_device.h" +#include "media_codec.h" +#include "media_encoder.h" +#include "media_buffer.h" +#include "media_io_handle.h" + +#include "audio/audiobuffer.h" +#include "string_utils.h" +#include "logger.h" + +#include <iostream> +#include <sstream> +#include <algorithm> +#include <thread> // hardware_concurrency + +namespace ring { + +using std::string; + +MediaEncoder::MediaEncoder() + : outputCtx_(avformat_alloc_context()) +{} + +MediaEncoder::~MediaEncoder() +{ + if (outputCtx_ and outputCtx_->priv_data) + av_write_trailer(outputCtx_); + + if (encoderCtx_) + avcodec_close(encoderCtx_); + + av_free(scaledFrameBuffer_); + +#if (LIBAVCODEC_VERSION_MAJOR < 54) + av_free(encoderBuffer_); +#endif +} + +void MediaEncoder::setDeviceOptions(const DeviceParams& args) +{ + if (args.width) + av_dict_set(&options_, "width", ring::to_string(args.width).c_str(), 0); + if (args.height) + av_dict_set(&options_, "height", ring::to_string(args.height).c_str(), 0); + if (args.framerate) + av_dict_set(&options_, "framerate", ring::to_string(args.framerate).c_str(), 0); +} + +void MediaEncoder::setOptions(const MediaDescription& args) +{ + av_dict_set(&options_, "payload_type", ring::to_string(args.payload_type).c_str(), 0); + av_dict_set(&options_, "bitrate", ring::to_string(args.codec->bitrate).c_str(), 0); + + auto accountAudioCodec = std::static_pointer_cast<AccountAudioCodecInfo>(args.codec); + if (accountAudioCodec->audioformat.sample_rate) + av_dict_set(&options_, "sample_rate", + ring::to_string(accountAudioCodec->audioformat.sample_rate).c_str(), 0); + + if (accountAudioCodec->audioformat.nb_channels) + av_dict_set(&options_, "channels", + ring::to_string(accountAudioCodec->audioformat.nb_channels).c_str(), 0); + + if (accountAudioCodec->audioformat.sample_rate && accountAudioCodec->audioformat.nb_channels) + av_dict_set(&options_, "frame_size", + ring::to_string(static_cast<unsigned>(0.02 * accountAudioCodec->audioformat.sample_rate)).c_str(), 0); + + if (not args.parameters.empty()) + av_dict_set(&options_, "parameters", args.parameters.c_str(), 0); +} + +void +MediaEncoder::openOutput(const char *filename, + const ring::MediaDescription& args) +{ + setOptions(args); + AVOutputFormat *oformat = av_guess_format("rtp", filename, nullptr); + + if (!oformat) { + RING_ERR("Unable to find a suitable output format for %s", filename); + throw MediaEncoderException("No output format"); + } + + outputCtx_->oformat = oformat; + strncpy(outputCtx_->filename, filename, sizeof(outputCtx_->filename)); + // guarantee that buffer is NULL terminated + outputCtx_->filename[sizeof(outputCtx_->filename) - 1] = '\0'; + + /* find the video encoder */ + outputEncoder_ = avcodec_find_encoder((AVCodecID)args.codec->systemCodecInfo.avcodecId); + if (!outputEncoder_) { + RING_ERR("Encoder \"%s\" not found!", args.codec->systemCodecInfo.name.c_str()); + throw MediaEncoderException("No output encoder"); + } + + prepareEncoderContext(args.codec->systemCodecInfo.mediaType == MEDIA_VIDEO); + + /* let x264 preset override our encoder settings */ + if (args.codec->systemCodecInfo.avcodecId == AV_CODEC_ID_H264) { + extractProfileLevelID(args.parameters, encoderCtx_); + forcePresetX264(); + } else if (args.codec->systemCodecInfo.avcodecId == AV_CODEC_ID_VP8) { + // Using information given on this page: + // http://www.webmproject.org/docs/encoder-parameters/ + av_opt_set(encoderCtx_->priv_data, "quality", "realtime", 0); + av_opt_set_int(encoderCtx_->priv_data, "error-resilient", 1, 0); + av_opt_set_int(encoderCtx_->priv_data, "cpu-used", 3, 0); + encoderCtx_->slices = 2; // VP8E_SET_TOKEN_PARTITIONS + encoderCtx_->qmin = 4; + encoderCtx_->qmax = 56; + encoderCtx_->gop_size = 999999; + } else if (args.codec->systemCodecInfo.avcodecId == AV_CODEC_ID_MPEG4) { + encoderCtx_->rc_buffer_size = encoderCtx_->bit_rate; + } + + int ret; +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(53, 6, 0) + ret = avcodec_open(encoderCtx_, outputEncoder_); +#else + ret = avcodec_open2(encoderCtx_, outputEncoder_, NULL); +#endif + if (ret) + throw MediaEncoderException("Could not open encoder"); + + // add video stream to outputformat context +#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53, 8, 0) + stream_ = av_new_stream(outputCtx_, 0); +#else + stream_ = avformat_new_stream(outputCtx_, 0); +#endif + if (!stream_) + throw MediaEncoderException("Could not allocate stream"); + + stream_->codec = encoderCtx_; +#ifdef RING_VIDEO + if (args.codec->systemCodecInfo.mediaType == MEDIA_VIDEO) { + // allocate buffers for both scaled (pre-encoder) and encoded frames + const int width = encoderCtx_->width; + const int height = encoderCtx_->height; + const int format = libav_utils::ring_pixel_format((int)encoderCtx_->pix_fmt); + scaledFrameBufferSize_ = videoFrameSize(format, width, height); + if (scaledFrameBufferSize_ <= FF_MIN_BUFFER_SIZE) + throw MediaEncoderException("buffer too small"); + +#if (LIBAVCODEC_VERSION_MAJOR < 54) + encoderBufferSize_ = scaledFrameBufferSize_; // seems to be ok + encoderBuffer_ = (uint8_t*) av_malloc(encoderBufferSize_); + if (!encoderBuffer_) + throw MediaEncoderException("Could not allocate encoder buffer"); +#endif + + scaledFrameBuffer_ = (uint8_t*) av_malloc(scaledFrameBufferSize_); + if (!scaledFrameBuffer_) + throw MediaEncoderException("Could not allocate scaled frame buffer"); + + scaledFrame_.setFromMemory(scaledFrameBuffer_, format, width, height); + } +#endif // RING_VIDEO +} + +void MediaEncoder::setInterruptCallback(int (*cb)(void*), void *opaque) +{ + if (cb) { + outputCtx_->interrupt_callback.callback = cb; + outputCtx_->interrupt_callback.opaque = opaque; + } else { + outputCtx_->interrupt_callback.callback = 0; + } +} + +void MediaEncoder::setIOContext(const std::unique_ptr<MediaIOHandle> &ioctx) +{ + outputCtx_->pb = ioctx->getContext(); + outputCtx_->packet_size = outputCtx_->pb->buffer_size; +} + +void +MediaEncoder::startIO() +{ + if (avformat_write_header(outputCtx_, options_ ? &options_ : NULL)) { + RING_ERR("Could not write header for output file... check codec parameters"); + throw MediaEncoderException("Failed to write output file header"); + } + + av_dump_format(outputCtx_, 0, outputCtx_->filename, 1); +} + +static void +print_averror(const char *funcname, int err) +{ + char errbuf[64]; + av_strerror(err, errbuf, sizeof(errbuf)); + RING_ERR("%s failed: %s", funcname, errbuf); +} + +#ifdef RING_VIDEO +int +MediaEncoder::encode(VideoFrame& input, bool is_keyframe, + int64_t frame_number) +{ + /* Prepare a frame suitable to our encoder frame format, + * keeping also the input aspect ratio. + */ + yuv422_clear_to_black(scaledFrame_); // to fill blank space left by the "keep aspect" + scaler_.scale_with_aspect(input, scaledFrame_); + + auto frame = scaledFrame_.pointer(); + frame->pts = frame_number; + + if (is_keyframe) { +#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(53, 20, 0) + frame->pict_type = AV_PICTURE_TYPE_I; +#else + frame->pict_type = FF_I_TYPE; +#endif + } else { + /* FIXME: Should be AV_PICTURE_TYPE_NONE for newer libavutil */ + frame->pict_type = (AVPictureType) 0; + } + + AVPacket pkt; + memset(&pkt, 0, sizeof(pkt)); + av_init_packet(&pkt); + +#if LIBAVCODEC_VERSION_MAJOR >= 54 + + int got_packet; + int ret = avcodec_encode_video2(encoderCtx_, &pkt, frame, &got_packet); + if (ret < 0) { + print_averror("avcodec_encode_video2", ret); + av_free_packet(&pkt); + return ret; + } + + if (pkt.size and got_packet) { + if (pkt.pts != AV_NOPTS_VALUE) + pkt.pts = av_rescale_q(pkt.pts, encoderCtx_->time_base, + stream_->time_base); + if (pkt.dts != AV_NOPTS_VALUE) + pkt.dts = av_rescale_q(pkt.dts, encoderCtx_->time_base, + stream_->time_base); + + pkt.stream_index = stream_->index; + + // write the compressed frame + ret = av_write_frame(outputCtx_, &pkt); + if (ret < 0) + print_averror("av_write_frame", ret); + } + +#else + + int ret = avcodec_encode_video(encoderCtx_, encoderBuffer_, + encoderBufferSize_, frame); + if (ret < 0) { + print_averror("avcodec_encode_video", ret); + av_free_packet(&pkt); + return ret; + } + + pkt.data = encoderBuffer_; + pkt.size = ret; + + // rescale pts from encoded video framerate to rtp clock rate + if (encoderCtx_->coded_frame->pts != static_cast<int64_t>(AV_NOPTS_VALUE)) { + pkt.pts = av_rescale_q(encoderCtx_->coded_frame->pts, + encoderCtx_->time_base, stream_->time_base); + } else { + pkt.pts = 0; + } + + // is it a key frame? + if (encoderCtx_->coded_frame->key_frame) + pkt.flags |= AV_PKT_FLAG_KEY; + pkt.stream_index = stream_->index; + + // write the compressed frame + ret = av_write_frame(outputCtx_, &pkt); + if (ret < 0) + print_averror("av_write_frame", ret); + +#endif // LIBAVCODEC_VERSION_MAJOR >= 54 + + av_free_packet(&pkt); + + return ret; +} +#endif // RING_VIDEO + +int MediaEncoder::encode_audio(const AudioBuffer &buffer) +{ + const int needed_bytes = av_samples_get_buffer_size(NULL, buffer.channels(), + buffer.frames(), + AV_SAMPLE_FMT_S16, 0); + if (needed_bytes < 0) { + RING_ERR("Couldn't calculate buffer size"); + return -1; + } + + AudioSample *sample_data = reinterpret_cast<AudioSample*>(av_malloc(needed_bytes)); + if (!sample_data) + return -1; + + AudioSample *offset_ptr = sample_data; + int nb_frames = buffer.frames(); + + buffer.interleave(sample_data); + const auto layout = buffer.channels() == 2 ? AV_CH_LAYOUT_STEREO : AV_CH_LAYOUT_MONO; + const auto sample_rate = buffer.getSampleRate(); + + while (nb_frames > 0) { + AVFrame *frame = av_frame_alloc(); + if (!frame) { + av_freep(&sample_data); + return -1; + } + + if (encoderCtx_->frame_size) + frame->nb_samples = std::min<int>(nb_frames, + encoderCtx_->frame_size); + else + frame->nb_samples = nb_frames; + + frame->format = AV_SAMPLE_FMT_S16; + frame->channel_layout = layout; + frame->sample_rate = sample_rate; + + frame->pts = sent_samples; + sent_samples += frame->nb_samples; + + const auto buffer_size = \ + av_samples_get_buffer_size(NULL, buffer.channels(), + frame->nb_samples, AV_SAMPLE_FMT_S16, 0); + + int err = avcodec_fill_audio_frame(frame, buffer.channels(), + AV_SAMPLE_FMT_S16, + reinterpret_cast<const uint8_t *>(offset_ptr), + buffer_size, 0); + if (err < 0) { + char errbuf[128]; + av_strerror(err, errbuf, sizeof(errbuf)); + RING_ERR("Couldn't fill audio frame: %s: %d %d", errbuf, + frame->nb_samples, buffer_size); + av_freep(&sample_data); + av_frame_free(&frame); + return -1; + } + nb_frames -= frame->nb_samples; + offset_ptr += frame->nb_samples * buffer.channels(); + + AVPacket pkt; + av_init_packet(&pkt); + pkt.data = NULL; // packet data will be allocated by the encoder + pkt.size = 0; + + int got_packet; + int ret = avcodec_encode_audio2(encoderCtx_, &pkt, frame, &got_packet); + if (ret < 0) { + print_averror("avcodec_encode_audio2", ret); + av_free_packet(&pkt); + av_freep(&sample_data); + av_frame_free(&frame); + return ret; + } + + if (pkt.size and got_packet) { + if (pkt.pts != AV_NOPTS_VALUE) + pkt.pts = av_rescale_q(pkt.pts, encoderCtx_->time_base, + stream_->time_base); + if (pkt.dts != AV_NOPTS_VALUE) + pkt.dts = av_rescale_q(pkt.dts, encoderCtx_->time_base, + stream_->time_base); + + pkt.stream_index = stream_->index; + + // write the compressed frame + ret = av_write_frame(outputCtx_, &pkt); + if (ret < 0) + print_averror("av_write_frame", ret); + } + + av_free_packet(&pkt); + av_frame_free(&frame); + } + + av_freep(&sample_data); + + return 0; +} + +int MediaEncoder::flush() +{ + AVPacket pkt; + memset(&pkt, 0, sizeof(pkt)); + av_init_packet(&pkt); + + int ret; +#if (LIBAVCODEC_VERSION_MAJOR >= 54) + + int got_packet; + + ret = avcodec_encode_video2(encoderCtx_, &pkt, NULL, &got_packet); + if (ret != 0) { + RING_ERR("avcodec_encode_video failed"); + av_free_packet(&pkt); + return -1; + } + + if (pkt.size and got_packet) { + // write the compressed frame + ret = av_write_frame(outputCtx_, &pkt); + if (ret < 0) + RING_ERR("write_frame failed"); + } +#else + ret = avcodec_encode_video(encoderCtx_, encoderBuffer_, + encoderBufferSize_, NULL); + if (ret < 0) { + RING_ERR("avcodec_encode_video failed"); + av_free_packet(&pkt); + return ret; + } + + pkt.data = encoderBuffer_; + pkt.size = ret; + + // write the compressed frame + ret = av_write_frame(outputCtx_, &pkt); + if (ret < 0) + RING_ERR("write_frame failed"); +#endif + av_free_packet(&pkt); + + return ret; +} + +void MediaEncoder::print_sdp(std::string &sdp_) +{ + /* theora sdp can be huge */ + const auto sdp_size = outputCtx_->streams[0]->codec->extradata_size + 2048; + std::string sdp(sdp_size, '\0'); + av_sdp_create(&outputCtx_, 1, &(*sdp.begin()), sdp_size); + std::istringstream iss(sdp); + string line; + sdp_ = ""; + while (std::getline(iss, line)) { + /* strip windows line ending */ + line = line.substr(0, line.length() - 1); + sdp_ += line + "\n"; + } + RING_DBG("Sending SDP: \n%s", sdp_.c_str()); +} + +void MediaEncoder::prepareEncoderContext(bool is_video) +{ +#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(53, 12, 0) + encoderCtx_ = avcodec_alloc_context(); + avcodec_get_context_defaults(encoderCtx_); + (void) outputEncoder_; +#else + encoderCtx_ = avcodec_alloc_context3(outputEncoder_); +#endif + + auto encoderName = encoderCtx_->av_class->item_name ? + encoderCtx_->av_class->item_name(encoderCtx_) : nullptr; + if (encoderName == nullptr) + encoderName = "encoder?"; + + // set some encoder settings here + encoderCtx_->bit_rate = 1000 * atoi(av_dict_get(options_, "bitrate", + NULL, 0)->value); + RING_DBG("[%s] Using bitrate %d", encoderName, encoderCtx_->bit_rate); + + // Use constant bitrate (video only) + if (is_video) { + RING_DBG("[%s] Using CBR", encoderName); + encoderCtx_->rc_min_rate = \ + encoderCtx_->rc_max_rate = encoderCtx_->bit_rate; + } + + encoderCtx_->thread_count = std::thread::hardware_concurrency(); + RING_DBG("[%s] Using %d threads", encoderName, encoderCtx_->thread_count); + + if (is_video) { + // resolution must be a multiple of two + auto width_param = av_dict_get(options_, "width", NULL, 0); + char *width = width_param ? width_param->value : nullptr; + dstWidth_ = encoderCtx_->width = width ? atoi(width) : 0; + auto height_param = av_dict_get(options_, "height", NULL, 0); + char *height = height_param ? height_param->value : nullptr; + dstHeight_ = encoderCtx_->height = height ? atoi(height) : 0; + + auto framerate_param = av_dict_get(options_, "framerate", NULL, 0); + const char *framerate = framerate_param ? framerate_param->value : nullptr; + const int DEFAULT_FPS = 30; + const int fps = framerate ? atoi(framerate) : DEFAULT_FPS; + encoderCtx_->time_base = (AVRational) {1, fps}; + // emit one intra frame every gop_size frames + encoderCtx_->max_b_frames = 0; + encoderCtx_->pix_fmt = PIXEL_FORMAT(YUV420P); // TODO: option me ! + + // Fri Jul 22 11:37:59 EDT 2011:tmatth:XXX: DON'T set this, we want our + // pps and sps to be sent in-band for RTP + // This is to place global headers in extradata instead of every + // keyframe. + // encoderCtx_->flags |= CODEC_FLAG_GLOBAL_HEADER; + + } else { + encoderCtx_->sample_fmt = AV_SAMPLE_FMT_S16; + auto v = av_dict_get(options_, "sample_rate", NULL, 0); + if (v) { + encoderCtx_->sample_rate = atoi(v->value); + encoderCtx_->time_base = (AVRational) {1, encoderCtx_->sample_rate}; + } else { + RING_WARN("[%s] No sample rate set", encoderName); + encoderCtx_->sample_rate = 8000; + } + + v = av_dict_get(options_, "channels", NULL, 0); + if (v) { + auto c = std::atoi(v->value); + if (c > 2 or c < 1) { + RING_WARN("[%s] Clamping invalid channel value %d", encoderName, c); + c = 1; + } + encoderCtx_->channels = c; + } else { + RING_WARN("[%s] Channels not set", encoderName); + encoderCtx_->channels = 1; + } + + encoderCtx_->channel_layout = encoderCtx_->channels == 2 ? AV_CH_LAYOUT_STEREO : AV_CH_LAYOUT_MONO; + + v = av_dict_get(options_, "frame_size", NULL, 0); + if (v) { + encoderCtx_->frame_size = atoi(v->value); + RING_WARN("[%s] Frame size %d", encoderName, encoderCtx_->frame_size); + } else { + RING_WARN("[%s] Frame size not set", encoderName); + } + } +} + +void MediaEncoder::forcePresetX264() +{ + const char *speedPreset = "ultrafast"; + if (av_opt_set(encoderCtx_->priv_data, "preset", speedPreset, 0)) + RING_WARN("Failed to set x264 preset '%s'", speedPreset); + const char *tune = "zerolatency"; + if (av_opt_set(encoderCtx_->priv_data, "tune", tune, 0)) + RING_WARN("Failed to set x264 tune '%s'", tune); +} + +void MediaEncoder::extractProfileLevelID(const std::string ¶meters, + AVCodecContext *ctx) +{ + // From RFC3984: + // If no profile-level-id is present, the Baseline Profile without + // additional constraints at Level 1 MUST be implied. + ctx->profile = FF_PROFILE_H264_BASELINE; + ctx->level = 0x0d; + // ctx->level = 0x0d; // => 13 aka 1.3 + if (parameters.empty()) + return; + + const std::string target("profile-level-id="); + size_t needle = parameters.find(target); + if (needle == std::string::npos) + return; + + needle += target.length(); + const size_t id_length = 6; /* digits */ + const std::string profileLevelID(parameters.substr(needle, id_length)); + if (profileLevelID.length() != id_length) + return; + + int result; + std::stringstream ss; + ss << profileLevelID; + ss >> std::hex >> result; + // profile-level id consists of three bytes + const unsigned char profile_idc = result >> 16; // 42xxxx -> 42 + const unsigned char profile_iop = ((result >> 8) & 0xff); // xx80xx -> 80 + ctx->level = result & 0xff; // xxxx0d -> 0d + switch (profile_idc) { + case FF_PROFILE_H264_BASELINE: + // check constraint_set_1_flag + if ((profile_iop & 0x40) >> 6) + ctx->profile |= FF_PROFILE_H264_CONSTRAINED; + break; + case FF_PROFILE_H264_HIGH_10: + case FF_PROFILE_H264_HIGH_422: + case FF_PROFILE_H264_HIGH_444_PREDICTIVE: + // check constraint_set_3_flag + if ((profile_iop & 0x10) >> 4) + ctx->profile |= FF_PROFILE_H264_INTRA; + break; + } + RING_DBG("Using profile %x and level %d", ctx->profile, ctx->level); +} + +} // namespace ring diff --git a/src/media/media_encoder.h b/src/media/media_encoder.h new file mode 100644 index 0000000000..8b1951767e --- /dev/null +++ b/src/media/media_encoder.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2013-2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __MEDIA_ENCODER_H__ +#define __MEDIA_ENCODER_H__ + +#include "config.h" + +#ifdef RING_VIDEO +#include "video/video_base.h" +#include "video/video_scaler.h" +#endif + +#include "noncopyable.h" +#include "media_buffer.h" + +#include <map> +#include <memory> +#include <string> + +class AVCodecContext; +class AVStream; +class AVFormatContext; +class AVDictionary; +class AVCodec; + +namespace ring { + +class AudioBuffer; +class MediaIOHandle; +class DeviceParams; +class MediaDescription; + +class MediaEncoderException : public std::runtime_error { + public: + MediaEncoderException(const char *msg) : std::runtime_error(msg) {} +}; + +class MediaEncoder { +public: + MediaEncoder(); + ~MediaEncoder(); + + void setInterruptCallback(int (*cb)(void*), void *opaque); + + void setDeviceOptions(const DeviceParams& args); + void openOutput(const char *filename, const MediaDescription& args); + void startIO(); + void setIOContext(const std::unique_ptr<MediaIOHandle> &ioctx); + +#ifdef RING_VIDEO + int encode(VideoFrame &input, bool is_keyframe, int64_t frame_number); +#endif // RING_VIDEO + + int encode_audio(const AudioBuffer &input); + int flush(); + void print_sdp(std::string &sdp_); + + /* getWidth and getHeight return size of the encoded frame. + * Values have meaning only after openOutput call. + */ + int getWidth() const { return dstWidth_; } + int getHeight() const { return dstHeight_; } + +private: + NON_COPYABLE(MediaEncoder); + void setOptions(const MediaDescription& args); + void setScaleDest(void *data, int width, int height, int pix_fmt); + void prepareEncoderContext(bool is_video); + void forcePresetX264(); + void extractProfileLevelID(const std::string ¶meters, AVCodecContext *ctx); + + AVCodec *outputEncoder_ = nullptr; + AVCodecContext *encoderCtx_ = nullptr; + AVFormatContext *outputCtx_ = nullptr; + AVStream *stream_ = nullptr; + unsigned sent_samples = 0; + +#ifdef RING_VIDEO + video::VideoScaler scaler_; + VideoFrame scaledFrame_; +#endif // RING_VIDEO + + uint8_t *scaledFrameBuffer_ = nullptr; + int scaledFrameBufferSize_ = 0; + int streamIndex_ = -1; + int dstWidth_ = 0; + int dstHeight_ = 0; +#if (LIBAVCODEC_VERSION_MAJOR < 54) + uint8_t *encoderBuffer_ = nullptr; + int encoderBufferSize_ = 0; +#endif + +protected: + AVDictionary *options_ = nullptr; +}; + +} // namespace ring + +#endif // __MEDIA_ENCODER_H__ diff --git a/src/media/media_io_handle.cpp b/src/media/media_io_handle.cpp new file mode 100644 index 0000000000..1b0bd0c823 --- /dev/null +++ b/src/media/media_io_handle.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2013-2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "libav_deps.h" // MUST BE INCLUDED FIRST +#include "media_io_handle.h" + +namespace ring { + +MediaIOHandle::MediaIOHandle(std::size_t buffer_size, + bool writeable, + io_readcallback read_cb, + io_writecallback write_cb, + io_seekcallback seek_cb, + void *opaque) : ctx_(0), buf_(0) + +{ + buf_ = static_cast<unsigned char *>(av_malloc(buffer_size)); + ctx_ = avio_alloc_context(buf_, buffer_size, writeable, opaque, read_cb, + write_cb, seek_cb); + ctx_->max_packet_size = buffer_size; +} + +MediaIOHandle::~MediaIOHandle() { av_free(ctx_); av_free(buf_); } + +} // namespace ring diff --git a/src/media/media_io_handle.h b/src/media/media_io_handle.h new file mode 100644 index 0000000000..f2ccda44ce --- /dev/null +++ b/src/media/media_io_handle.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2013-2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __MEDIA_IO_HANDLE_H__ +#define __MEDIA_IO_HANDLE_H__ + +#include "noncopyable.h" + +#include <cstdlib> +#include <cstdint> + +#ifndef AVFORMAT_AVIO_H +class AVIOContext; +#endif + +typedef int(*io_readcallback)(void *opaque, uint8_t *buf, int buf_size); +typedef int(*io_writecallback)(void *opaque, uint8_t *buf, int buf_size); +typedef int64_t(*io_seekcallback)(void *opaque, int64_t offset, int whence); + +namespace ring { + +class MediaIOHandle { +public: + MediaIOHandle(std::size_t buffer_size, + bool writeable, + io_readcallback read_cb, + io_writecallback write_cb, + io_seekcallback seek_cb, + void *opaque); + ~MediaIOHandle(); + + AVIOContext* getContext() { return ctx_; } + +private: + NON_COPYABLE(MediaIOHandle); + AVIOContext *ctx_; + unsigned char *buf_; +}; + +} // namespace ring + +#endif // __MEDIA_DECODER_H__ diff --git a/src/media/rtp_session.h b/src/media/rtp_session.h new file mode 100644 index 0000000000..52ba6ac73d --- /dev/null +++ b/src/media/rtp_session.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __RTP_SESSION_H__ +#define __RTP_SESSION_H__ + +#include "socket_pair.h" +#include "sip/sip_utils.h" + +#include <string> +#include <memory> +#include <mutex> + +namespace ring { + +class RtpSession { +public: + RtpSession(const std::string &callID) : callID_(callID) {} + virtual ~RtpSession() {}; + + virtual void start() = 0; + virtual void start(std::unique_ptr<IceSocket> rtp_sock, + std::unique_ptr<IceSocket> rtcp_sock) = 0; + virtual void stop() = 0; + + virtual void updateMedia(const MediaDescription& send, + const MediaDescription& receive) { + send_ = send; + receive_ = receive; + } + +protected: + std::recursive_mutex mutex_; + std::unique_ptr<SocketPair> socketPair_; + const std::string callID_; + + MediaDescription send_; + MediaDescription receive_; + + std::string getRemoteRtpUri() const { + return "rtp://" + send_.addr.toString(true); + } +}; + +} // namespace ring + +#endif // __RTP_SESSION_H__ diff --git a/src/media/socket_pair.cpp b/src/media/socket_pair.cpp new file mode 100644 index 0000000000..b7f0ab1379 --- /dev/null +++ b/src/media/socket_pair.cpp @@ -0,0 +1,472 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Copyright (c) 2002 Fabrice Bellard + * + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "libav_deps.h" // MUST BE INCLUDED FIRST +#include "socket_pair.h" +#include "ice_socket.h" +#include "libav_utils.h" +#include "logger.h" + +extern "C" { +#include "srtp.h" +} + +#include <cstring> +#include <stdexcept> +#include <unistd.h> +#include <poll.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> + +#ifdef __ANDROID__ +#include <asm-generic/fcntl.h> +#define SOCK_NONBLOCK O_NONBLOCK +#endif + +#ifdef __APPLE__ +#include <fcntl.h> +#endif + +namespace ring { + +static constexpr int NET_POLL_TIMEOUT = 100; /* poll() timeout in ms */ +static constexpr int RTP_MAX_PACKET_LENGTH = 8192; + +class SRTPProtoContext { + public: + SRTPProtoContext(const char* out_suite, const char* out_key, + const char* in_suite, const char* in_key) { + if (out_suite && out_key) { + // XXX: see srtp_open from libavformat/srtpproto.c + if (ff_srtp_set_crypto(&srtp_out, out_suite, out_key) < 0) { + srtp_close(); + throw std::runtime_error("Could not set crypto on output"); + } + } + + if (in_suite && in_key) { + if (ff_srtp_set_crypto(&srtp_in, in_suite, in_key) < 0) { + srtp_close(); + throw std::runtime_error("Could not set crypto on input"); + } + } + } + + ~SRTPProtoContext() { + srtp_close(); + } + + SRTPContext srtp_out {}; + SRTPContext srtp_in {}; + uint8_t encryptbuf[RTP_MAX_PACKET_LENGTH]; + + private: + void srtp_close() noexcept { + ff_srtp_free(&srtp_out); + ff_srtp_free(&srtp_in); + } +}; + +static int +ff_network_wait_fd(int fd) +{ + struct pollfd p = { fd, POLLOUT, 0 }; + int ret; + ret = poll(&p, 1, NET_POLL_TIMEOUT); + return ret < 0 ? errno : p.revents & (POLLOUT | POLLERR | POLLHUP) ? 0 : -EAGAIN; +} + +static struct +addrinfo* udp_resolve_host(const char *node, int service) +{ + struct addrinfo hints, *res = 0; + int error; + char sport[16]; + + memset(&hints, 0, sizeof(hints)); + snprintf(sport, sizeof(sport), "%d", service); + + hints.ai_socktype = SOCK_DGRAM; + hints.ai_family = AF_UNSPEC; + hints.ai_flags = AI_PASSIVE; + if ((error = getaddrinfo(node, sport, &hints, &res))) { + res = NULL; + RING_ERR("%s\n", gai_strerror(error)); + } + + return res; +} + +static unsigned +udp_set_url(struct sockaddr_storage *addr, const char *hostname, int port) +{ + struct addrinfo *res0; + int addr_len; + + res0 = udp_resolve_host(hostname, port); + if (res0 == 0) return 0; + memcpy(addr, res0->ai_addr, res0->ai_addrlen); + addr_len = res0->ai_addrlen; + freeaddrinfo(res0); + + return addr_len; +} + +static int +udp_socket_create(sockaddr_storage *addr, socklen_t *addr_len, int local_port) +{ + int udp_fd = -1; + struct addrinfo *res0 = NULL, *res = NULL; + + res0 = udp_resolve_host(0, local_port); + if (res0 == 0) + return -1; + for (res = res0; res; res=res->ai_next) { +#ifdef __APPLE__ + udp_fd = socket(res->ai_family, SOCK_DGRAM, 0); + if (udp_fd != -1 && fcntl(udp_fd, F_SETFL, O_NONBLOCK) != -1) { +#else + udp_fd = socket(res->ai_family, SOCK_DGRAM | SOCK_NONBLOCK, 0); + if (udp_fd != -1) { +#endif + break; + } + RING_ERR("socket error"); + } + + if (udp_fd < 0) { + freeaddrinfo(res0); + return -1; + } + + memcpy(addr, res->ai_addr, res->ai_addrlen); + *addr_len = res->ai_addrlen; + + // bind socket so that we send from and receive + // on local port + if (bind(udp_fd, reinterpret_cast<sockaddr*>(addr), *addr_len) < 0) { + RING_ERR("Bind failed"); + strErr(); + close(udp_fd); + udp_fd = -1; + } + + freeaddrinfo(res0); + + return udp_fd; +} + +using std::string; +static const size_t RTP_BUFFER_SIZE = 1472; +static const size_t SRTP_BUFFER_SIZE = RTP_BUFFER_SIZE - 10; + +SocketPair::SocketPair(const char *uri, int localPort) + : rtp_sock_() + , rtcp_sock_() + , rtcpWriteMutex_() + , rtpDestAddr_() + , rtpDestAddrLen_() + , rtcpDestAddr_() + , rtcpDestAddrLen_() +{ + openSockets(uri, localPort); +} + +SocketPair::SocketPair(std::unique_ptr<IceSocket> rtp_sock, + std::unique_ptr<IceSocket> rtcp_sock) + : rtp_sock_(std::move(rtp_sock)) + , rtcp_sock_(std::move(rtcp_sock)) + , rtcpWriteMutex_() + , rtpDestAddr_() + , rtpDestAddrLen_() + , rtcpDestAddr_() + , rtcpDestAddrLen_() +{} + +SocketPair::~SocketPair() +{ + interrupted_ = true; + if (rtpHandle_ >= 0) + closeSockets(); +} + +void SocketPair::createSRTP(const char *out_suite, const char *out_key, + const char *in_suite, const char *in_key) +{ + srtpContext_.reset(new SRTPProtoContext(out_suite, out_key, + in_suite, in_key)); +} + +void SocketPair::interrupt() +{ + interrupted_ = true; +} + +void SocketPair::closeSockets() +{ + if (rtcpHandle_ > 0 and close(rtcpHandle_)) + strErr(); + if (rtpHandle_ > 0 and close(rtpHandle_)) + strErr(); +} + +void SocketPair::openSockets(const char *uri, int local_rtp_port) +{ + char hostname[256]; + char path[1024]; + int rtp_port; + + libav_utils::ring_url_split(uri, hostname, sizeof(hostname), &rtp_port, path, + sizeof(path)); + + const int rtcp_port = rtp_port + 1; + const int local_rtcp_port = local_rtp_port + 1; + + sockaddr_storage rtp_addr, rtcp_addr; + socklen_t rtp_len, rtcp_len; + + // Open sockets and store addresses for sending + if ((rtpHandle_ = udp_socket_create(&rtp_addr, &rtp_len, local_rtp_port)) == -1 or + (rtcpHandle_ = udp_socket_create(&rtcp_addr, &rtcp_len, local_rtcp_port)) == -1 or + (rtpDestAddrLen_ = udp_set_url(&rtpDestAddr_, hostname, rtp_port)) == 0 or + (rtcpDestAddrLen_ = udp_set_url(&rtcpDestAddr_, hostname, rtcp_port)) == 0) { + + // Handle failed socket creation + closeSockets(); + throw std::runtime_error("Socket creation failed"); + } + + RING_WARN("SocketPair: local{%d,%d}, remote{%d,%d}", + local_rtp_port, local_rtcp_port, rtp_port, rtcp_port); +} + +MediaIOHandle* SocketPair::createIOContext() +{ + return new MediaIOHandle(srtpContext_ ? SRTP_BUFFER_SIZE : RTP_BUFFER_SIZE, true, + &readCallback, &writeCallback, 0, + reinterpret_cast<void*>(this)); +} + +int +SocketPair::waitForData() +{ + if (rtpHandle_ >= 0) { + // work with system socket + struct pollfd p[2] = { {rtpHandle_, POLLIN, 0}, + {rtcpHandle_, POLLIN, 0} }; + return poll(p, 2, NET_POLL_TIMEOUT); + } + + // work with IceSocket + auto result = rtp_sock_->waitForData(NET_POLL_TIMEOUT); + if (result < 0) { + errno = EIO; + return -1; + } + + return result; +} + +int +SocketPair::readRtpData(void *buf, int buf_size) +{ + auto data = static_cast<uint8_t *>(buf); + + if (rtpHandle_ >= 0) { + // work with system socket + struct sockaddr_storage from; + socklen_t from_len = sizeof(from); + +start: + int result = recvfrom(rtpHandle_, buf, buf_size, 0, + (struct sockaddr *)&from, &from_len); + if (result > 0 and srtpContext_ and srtpContext_->srtp_in.aes) + if (ff_srtp_decrypt(&srtpContext_->srtp_in, data, &result) < 0) + goto start; // XXX: see libavformat/srtpproto.c + + return result; + } + + // work with IceSocket +start_ice: + int result = rtp_sock_->recv(static_cast<unsigned char*>(buf), buf_size); + if (result > 0 and srtpContext_ and srtpContext_->srtp_in.aes) { + if (ff_srtp_decrypt(&srtpContext_->srtp_in, data, &result) < 0) + goto start_ice; + } else if (result < 0) { + errno = EIO; + return -1; + } else if (result == 0) { + errno = EAGAIN; + return -1; + } + + return result; +} + +int +SocketPair::readRtcpData(void *buf, int buf_size) +{ + if (rtcpHandle_ >= 0) { + // work with system socket + struct sockaddr_storage from; + socklen_t from_len = sizeof(from); + return recvfrom(rtcpHandle_, buf, buf_size, 0, + (struct sockaddr *)&from, &from_len); + } + + // work with IceSocket + auto result = rtcp_sock_->recv(static_cast<unsigned char*>(buf), buf_size); + if (result < 0) { + errno = EIO; + return -1; + } + if (result == 0) { + errno = EAGAIN; + return -1; + } + return result; +} + +int +SocketPair::writeRtpData(void* buf, int buf_size) +{ + auto data = static_cast<const uint8_t *>(buf); + + if (rtpHandle_ >= 0) { + auto ret = ff_network_wait_fd(rtpHandle_); + if (ret < 0) + return ret; + + if (srtpContext_ and srtpContext_->srtp_out.aes) { + buf_size = ff_srtp_encrypt(&srtpContext_->srtp_out, data, + buf_size, srtpContext_->encryptbuf, + sizeof(srtpContext_->encryptbuf)); + if (buf_size < 0) + return buf_size; + + return sendto(rtpHandle_, srtpContext_->encryptbuf, buf_size, 0, + (sockaddr*) &rtpDestAddr_, rtpDestAddrLen_); + } + + return sendto(rtpHandle_, buf, buf_size, 0, + (sockaddr*) &rtpDestAddr_, rtpDestAddrLen_); + } + + // work with IceSocket + if (srtpContext_ and srtpContext_->srtp_out.aes) { + buf_size = ff_srtp_encrypt(&srtpContext_->srtp_out, data, + buf_size, srtpContext_->encryptbuf, + sizeof(srtpContext_->encryptbuf)); + if (buf_size < 0) + return buf_size; + + buf = srtpContext_->encryptbuf; + } + + return rtp_sock_->send(static_cast<unsigned char*>(buf), buf_size); +} + +int +SocketPair::writeRtcpData(void *buf, int buf_size) +{ + std::lock_guard<std::mutex> lock(rtcpWriteMutex_); + + if (rtcpHandle_ >= 0) { + auto ret = ff_network_wait_fd(rtcpHandle_); + if (ret < 0) + return ret; + return sendto(rtcpHandle_, buf, buf_size, 0, + (sockaddr*) &rtcpDestAddr_, rtcpDestAddrLen_); + } + + // work with IceSocket + return rtcp_sock_->send(static_cast<unsigned char*>(buf), buf_size); +} + +int SocketPair::readCallback(void *opaque, uint8_t *buf, int buf_size) +{ + SocketPair *context = static_cast<SocketPair*>(opaque); + +retry: + if (context->interrupted_) { + RING_ERR("interrupted"); + return -EINTR; + } + + if (context->waitForData() < 0) { + if (errno == EINTR) + goto retry; + return -EIO; + } + + /* RTP */ + int len = context->readRtpData(buf, buf_size); + if (len < 0) { + if (errno == EAGAIN or errno == EINTR) + goto retry; + return -EIO; + } + + return len; +} + +int SocketPair::writeCallback(void *opaque, uint8_t *buf, int buf_size) +{ + SocketPair *context = static_cast<SocketPair*>(opaque); + int ret; + +retry: + if (RTP_PT_IS_RTCP(buf[1])) { + return buf_size; + /* RTCP payload type */ + ret = context->writeRtcpData(buf, buf_size); + if (ret < 0) { + if (ret == -EAGAIN) + goto retry; + return ret; + } + } else { + /* RTP payload type */ + ret = context->writeRtpData(buf, buf_size); + if (ret < 0) { + if (ret == -EAGAIN) + goto retry; + return ret; + } + } + + return ret < 0 ? errno : ret; +} + +} // namespace ring diff --git a/src/media/socket_pair.h b/src/media/socket_pair.h new file mode 100644 index 0000000000..b1df9343ea --- /dev/null +++ b/src/media/socket_pair.h @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Copyright (C) 2012 VLC authors and VideoLAN + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef SOCKET_PAIR_H_ +#define SOCKET_PAIR_H_ + +#include "media_io_handle.h" + +#include <sys/socket.h> +#include <mutex> +#include <memory> +#include <atomic> + +#include <cstdint> + +namespace ring { + +class IceSocket; +class SRTPProtoContext; + +class SocketPair { + public: + SocketPair(const char *uri, int localPort); + SocketPair(std::unique_ptr<IceSocket> rtp_sock, + std::unique_ptr<IceSocket> rtcp_sock); + ~SocketPair(); + + void interrupt(); + + MediaIOHandle* createIOContext(); + + void openSockets(const char *uri, int localPort); + void closeSockets(); + + /* + Supported suites are: + + AES_CM_128_HMAC_SHA1_80 + SRTP_AES128_CM_HMAC_SHA1_80 + AES_CM_128_HMAC_SHA1_32 + SRTP_AES128_CM_HMAC_SHA1_3 + + Example (unsecure) usage: + createSRTP("AES_CM_128_HMAC_SHA1_80", + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn", + "AES_CM_128_HMAC_SHA1_80", + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmn"); + + Will throw an std::runtime_error on failure, should be handled at a higher level + */ + void createSRTP(const char *out_suite, const char *out_params, const char *in_suite, const char *in_params); + + private: + NON_COPYABLE(SocketPair); + + static int readCallback(void *opaque, uint8_t *buf, int buf_size); + static int writeCallback(void *opaque, uint8_t *buf, int buf_size); + + int waitForData(); + int readRtpData(void *buf, int buf_size); + int readRtcpData(void *buf, int buf_size); + int writeRtpData(void *buf, int buf_size); + int writeRtcpData(void *buf, int buf_size); + + std::unique_ptr<IceSocket> rtp_sock_; + std::unique_ptr<IceSocket> rtcp_sock_; + + std::mutex rtcpWriteMutex_; + + int rtpHandle_ {-1}; + int rtcpHandle_ {-1}; + sockaddr_storage rtpDestAddr_; + socklen_t rtpDestAddrLen_; + sockaddr_storage rtcpDestAddr_; + socklen_t rtcpDestAddrLen_; + std::atomic_bool interrupted_ {false}; + std::unique_ptr<SRTPProtoContext> srtpContext_; +}; + +} // namespace ring + +#endif // SOCKET_PAIR_H_ diff --git a/src/media/srtp.c b/src/media/srtp.c new file mode 100644 index 0000000000..1aa965f420 --- /dev/null +++ b/src/media/srtp.c @@ -0,0 +1,325 @@ +/* + * SRTP encryption/decryption + * Copyright (c) 2012 Martin Storsjo + * + * This file is part of Libav. + * + * Libav is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * Libav is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Libav; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include <stdlib.h> +#include <libavutil/common.h> +#include <libavutil/base64.h> +#include <libavutil/aes.h> +#include <libavutil/hmac.h> +#include <libavutil/intreadwrite.h> +#include <libavutil/log.h> +#include "srtp.h" + +void ff_srtp_free(struct SRTPContext *s) +{ + if (!s) + return; + av_freep(&s->aes); + if (s->hmac) + av_hmac_free(s->hmac); + s->hmac = NULL; +} + +static void encrypt_counter(struct AVAES *aes, uint8_t *iv, uint8_t *outbuf, + int outlen) +{ + int i, j, outpos; + for (i = 0, outpos = 0; outpos < outlen; i++) { + uint8_t keystream[16]; + AV_WB16(&iv[14], i); + av_aes_crypt(aes, keystream, iv, 1, NULL, 0); + for (j = 0; j < 16 && outpos < outlen; j++, outpos++) + outbuf[outpos] ^= keystream[j]; + } +} + +static void derive_key(struct AVAES *aes, const uint8_t *salt, int label, + uint8_t *out, int outlen) +{ + uint8_t input[16] = { 0 }; + memcpy(input, salt, 14); + // Key derivation rate assumed to be zero + input[14 - 7] ^= label; + memset(out, 0, outlen); + encrypt_counter(aes, input, out, outlen); +} + +int ff_srtp_set_crypto(struct SRTPContext *s, const char *suite, + const char *params) +{ + uint8_t buf[30]; + + ff_srtp_free(s); + + // RFC 4568 + if (!strcmp(suite, "AES_CM_128_HMAC_SHA1_80") || + !strcmp(suite, "SRTP_AES128_CM_HMAC_SHA1_80")) { + s->rtp_hmac_size = s->rtcp_hmac_size = 10; + } else if (!strcmp(suite, "AES_CM_128_HMAC_SHA1_32")) { + s->rtp_hmac_size = s->rtcp_hmac_size = 4; + } else if (!strcmp(suite, "SRTP_AES128_CM_HMAC_SHA1_32")) { + // RFC 5764 section 4.1.2 + s->rtp_hmac_size = 4; + s->rtcp_hmac_size = 10; + } else { + av_log(NULL, AV_LOG_WARNING, "SRTP Crypto suite %s not supported\n", + suite); + return AVERROR(EINVAL); + } + if (av_base64_decode(buf, params, sizeof(buf)) != sizeof(buf)) { + av_log(NULL, AV_LOG_WARNING, "Incorrect amount of SRTP params\n"); + return AVERROR(EINVAL); + } + // MKI and lifetime not handled yet + s->aes = av_aes_alloc(); + s->hmac = av_hmac_alloc(AV_HMAC_SHA1); + if (!s->aes || !s->hmac) + return AVERROR(ENOMEM); + memcpy(s->master_key, buf, 16); + memcpy(s->master_salt, buf + 16, 14); + + // RFC 3711 + av_aes_init(s->aes, s->master_key, 128, 0); + + derive_key(s->aes, s->master_salt, 0x00, s->rtp_key, sizeof(s->rtp_key)); + derive_key(s->aes, s->master_salt, 0x02, s->rtp_salt, sizeof(s->rtp_salt)); + derive_key(s->aes, s->master_salt, 0x01, s->rtp_auth, sizeof(s->rtp_auth)); + + derive_key(s->aes, s->master_salt, 0x03, s->rtcp_key, sizeof(s->rtcp_key)); + derive_key(s->aes, s->master_salt, 0x05, s->rtcp_salt, sizeof(s->rtcp_salt)); + derive_key(s->aes, s->master_salt, 0x04, s->rtcp_auth, sizeof(s->rtcp_auth)); + return 0; +} + +static void create_iv(uint8_t *iv, const uint8_t *salt, uint64_t index, + uint32_t ssrc) +{ + uint8_t indexbuf[8]; + int i; + memset(iv, 0, 16); + AV_WB32(&iv[4], ssrc); + AV_WB64(indexbuf, index); + for (i = 0; i < 8; i++) // index << 16 + iv[6 + i] ^= indexbuf[i]; + for (i = 0; i < 14; i++) + iv[i] ^= salt[i]; +} + +int ff_srtp_decrypt(struct SRTPContext *s, uint8_t *buf, int *lenptr) +{ + uint8_t iv[16] = { 0 }, hmac[20]; + int len = *lenptr; + int av_uninit(seq_largest); + uint32_t ssrc, av_uninit(roc); + uint64_t index; + int rtcp, hmac_size; + + // TODO: Missing replay protection + + if (len < 2) + return AVERROR_INVALIDDATA; + + rtcp = RTP_PT_IS_RTCP(buf[1]); + hmac_size = rtcp ? s->rtcp_hmac_size : s->rtp_hmac_size; + + if (len < hmac_size) + return AVERROR_INVALIDDATA; + + // Authentication HMAC + av_hmac_init(s->hmac, rtcp ? s->rtcp_auth : s->rtp_auth, sizeof(s->rtp_auth)); + // If MKI is used, this should exclude the MKI as well + av_hmac_update(s->hmac, buf, len - hmac_size); + + if (!rtcp) { + int seq = AV_RB16(buf + 2); + uint32_t v; + uint8_t rocbuf[4]; + + // RFC 3711 section 3.3.1, appendix A + seq_largest = s->seq_initialized ? s->seq_largest : seq; + v = roc = s->roc; + if (seq_largest < 32768) { + if (seq - seq_largest > 32768) + v = roc - 1; + } else { + if (seq_largest - 32768 > seq) + v = roc + 1; + } + if (v == roc) { + seq_largest = FFMAX(seq_largest, seq); + } else if (v == roc + 1) { + seq_largest = seq; + roc = v; + } + index = seq + (((uint64_t)v) << 16); + + AV_WB32(rocbuf, roc); + av_hmac_update(s->hmac, rocbuf, 4); + } + + av_hmac_final(s->hmac, hmac, sizeof(hmac)); + if (memcmp(hmac, buf + len - hmac_size, hmac_size)) { + av_log(NULL, AV_LOG_WARNING, "HMAC mismatch\n"); + return AVERROR_INVALIDDATA; + } + + len -= hmac_size; + *lenptr = len; + + if (len < 12) + return AVERROR_INVALIDDATA; + + if (rtcp) { + uint32_t srtcp_index = AV_RB32(buf + len - 4); + len -= 4; + *lenptr = len; + + ssrc = AV_RB32(buf + 4); + index = srtcp_index & 0x7fffffff; + + buf += 8; + len -= 8; + if (!(srtcp_index & 0x80000000)) + return 0; + } else { + int ext, csrc; + s->seq_initialized = 1; + s->seq_largest = seq_largest; + s->roc = roc; + + csrc = buf[0] & 0x0f; + ext = buf[0] & 0x10; + ssrc = AV_RB32(buf + 8); + + buf += 12; + len -= 12; + + buf += 4 * csrc; + len -= 4 * csrc; + if (len < 0) + return AVERROR_INVALIDDATA; + + if (ext) { + if (len < 4) + return AVERROR_INVALIDDATA; + ext = (AV_RB16(buf + 2) + 1) * 4; + if (len < ext) + return AVERROR_INVALIDDATA; + len -= ext; + buf += ext; + } + } + + create_iv(iv, rtcp ? s->rtcp_salt : s->rtp_salt, index, ssrc); + av_aes_init(s->aes, rtcp ? s->rtcp_key : s->rtp_key, 128, 0); + encrypt_counter(s->aes, iv, buf, len); + + return 0; +} + +int ff_srtp_encrypt(struct SRTPContext *s, const uint8_t *in, int len, + uint8_t *out, int outlen) +{ + uint8_t iv[16] = { 0 }, hmac[20]; + uint64_t index; + uint32_t ssrc; + int rtcp, hmac_size, padding; + uint8_t *buf; + + if (len < 8) + return AVERROR_INVALIDDATA; + + rtcp = RTP_PT_IS_RTCP(in[1]); + hmac_size = rtcp ? s->rtcp_hmac_size : s->rtp_hmac_size; + padding = hmac_size; + if (rtcp) + padding += 4; // For the RTCP index + + if (len + padding > outlen) + return 0; + + memcpy(out, in, len); + buf = out; + + if (rtcp) { + ssrc = AV_RB32(buf + 4); + index = s->rtcp_index++; + + buf += 8; + len -= 8; + } else { + int ext, csrc; + int seq = AV_RB16(buf + 2); + + if (len < 12) + return AVERROR_INVALIDDATA; + + ssrc = AV_RB32(buf + 8); + + if (seq < s->seq_largest) + s->roc++; + s->seq_largest = seq; + index = seq + (((uint64_t)s->roc) << 16); + + csrc = buf[0] & 0x0f; + ext = buf[0] & 0x10; + + buf += 12; + len -= 12; + + buf += 4 * csrc; + len -= 4 * csrc; + if (len < 0) + return AVERROR_INVALIDDATA; + + if (ext) { + if (len < 4) + return AVERROR_INVALIDDATA; + ext = (AV_RB16(buf + 2) + 1) * 4; + if (len < ext) + return AVERROR_INVALIDDATA; + len -= ext; + buf += ext; + } + } + + create_iv(iv, rtcp ? s->rtcp_salt : s->rtp_salt, index, ssrc); + av_aes_init(s->aes, rtcp ? s->rtcp_key : s->rtp_key, 128, 0); + encrypt_counter(s->aes, iv, buf, len); + + if (rtcp) { + AV_WB32(buf + len, 0x80000000 | index); + len += 4; + } + + av_hmac_init(s->hmac, rtcp ? s->rtcp_auth : s->rtp_auth, sizeof(s->rtp_auth)); + av_hmac_update(s->hmac, out, buf + len - out); + if (!rtcp) { + uint8_t rocbuf[4]; + AV_WB32(rocbuf, s->roc); + av_hmac_update(s->hmac, rocbuf, 4); + } + av_hmac_final(s->hmac, hmac, sizeof(hmac)); + + memcpy(buf + len, hmac, hmac_size); + len += hmac_size; + return buf + len - out; +} diff --git a/src/media/srtp.h b/src/media/srtp.h new file mode 100644 index 0000000000..2bfae60623 --- /dev/null +++ b/src/media/srtp.h @@ -0,0 +1,63 @@ +/* + * SRTP encryption/decryption + * Copyright (c) 2012 Martin Storsjo + * + * This file is part of Libav. + * + * Libav is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * Libav is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with Libav; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SRTP_H +#define SRTP_H + +#include <stdint.h> + +struct AVAES; +struct AVHMAC; + +struct SRTPContext { + struct AVAES *aes; + struct AVHMAC *hmac; + int rtp_hmac_size, rtcp_hmac_size; + uint8_t master_key[16]; + uint8_t master_salt[14]; + uint8_t rtp_key[16], rtcp_key[16]; + uint8_t rtp_salt[14], rtcp_salt[14]; + uint8_t rtp_auth[20], rtcp_auth[20]; + int seq_largest, seq_initialized; + uint32_t roc; + + uint32_t rtcp_index; +}; + +int ff_srtp_set_crypto(struct SRTPContext *s, const char *suite, + const char *params); +void ff_srtp_free(struct SRTPContext *s); +int ff_srtp_decrypt(struct SRTPContext *s, uint8_t *buf, int *lenptr); +int ff_srtp_encrypt(struct SRTPContext *s, const uint8_t *in, int len, + uint8_t *out, int outlen); + +/* RTCP packet types */ +enum RTCPType { + RTCP_FIR = 192, + RTCP_IJ = 195, + RTCP_SR = 200, + RTCP_TOKEN = 210 +}; + +#define RTP_PT_IS_RTCP(x) (((x) >= RTCP_FIR && (x) <= RTCP_IJ) || \ + ((x) >= RTCP_SR && (x) <= RTCP_TOKEN)) + +#endif /* SRTP_H */ diff --git a/src/media/system_codec_container.cpp b/src/media/system_codec_container.cpp new file mode 100644 index 0000000000..4ce6b09f44 --- /dev/null +++ b/src/media/system_codec_container.cpp @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2013-2015 Savoir-Faire Linux Inc. + * Author: Eloi BAIL <eloi.bail@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "libav_deps.h" // MUST BE INCLUDED FIRST +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "logger.h" +#include "system_codec_container.h" + +namespace ring { + +decltype(getGlobalInstance<SystemCodecContainer>)& getSystemCodecContainer = getGlobalInstance<SystemCodecContainer>; + +constexpr static auto DEFAULT_VIDEO_BITRATE = 800; // in Kbits/second + +SystemCodecContainer::SystemCodecContainer() +{ + initCodecConfig(); +} + +SystemCodecContainer::~SystemCodecContainer() +{ + //TODO +} + +void +SystemCodecContainer::initCodecConfig() +{ + availableCodecList_ = { +#ifdef RING_VIDEO + /* Define supported video codec*/ + std::make_shared<SystemVideoCodecInfo>(AV_CODEC_ID_H264, + "H264", "libx264", + CODEC_ENCODER_DECODER, + DEFAULT_VIDEO_BITRATE), + + std::make_shared<SystemVideoCodecInfo>(AV_CODEC_ID_H263, + "H263", "h263", + CODEC_ENCODER_DECODER, + DEFAULT_VIDEO_BITRATE), + + std::make_shared<SystemVideoCodecInfo>(AV_CODEC_ID_VP8, + "VP8", "libvpx", + CODEC_ENCODER_DECODER, + DEFAULT_VIDEO_BITRATE), + + std::make_shared<SystemVideoCodecInfo>(AV_CODEC_ID_MPEG4, + "MP4V-ES", "mpeg4", + CODEC_ENCODER_DECODER, + DEFAULT_VIDEO_BITRATE), +#endif + /* Define supported audio codec*/ + std::make_shared<SystemAudioCodecInfo>(AV_CODEC_ID_PCM_ALAW, + "PCMA", "pcm_alaw", + CODEC_ENCODER_DECODER, + 64, 8000, 1, 8), + + std::make_shared<SystemAudioCodecInfo>(AV_CODEC_ID_PCM_MULAW, + "PCMU" ,"pcm_mulaw", + CODEC_ENCODER_DECODER, + 64, 8000, 1, 0), + + std::make_shared<SystemAudioCodecInfo>(AV_CODEC_ID_OPUS, + "opus", "libopus", + CODEC_ENCODER_DECODER, + 0, 48000, 2, 104), + + std::make_shared<SystemAudioCodecInfo>(AV_CODEC_ID_ADPCM_G722, + "G722", "g722", + CODEC_ENCODER_DECODER, + 0, 16000, 1, 9), + + std::make_shared<SystemAudioCodecInfo>(AV_CODEC_ID_SPEEX, + "speex", "libspeex", + CODEC_ENCODER_DECODER, + 0, 8000, 1, 110), + + std::make_shared<SystemAudioCodecInfo>(AV_CODEC_ID_SPEEX, + "speex", "libspeex", + CODEC_ENCODER_DECODER, + 0, 16000, 1, 111), + + std::make_shared<SystemAudioCodecInfo>(AV_CODEC_ID_SPEEX, + "speex", "libspeex", + CODEC_ENCODER_DECODER, + 0, 32000, 1, 112), + }; + + checkInstalledCodecs(); +} + +void +SystemCodecContainer::checkInstalledCodecs() +{ + AVCodecID codecId; + std::string codecName; + CodecType codecType; + + for (const auto& codecIt: availableCodecList_) { + codecId = (AVCodecID)codecIt->avcodecId; + codecName = codecIt->name; + codecType = codecIt->codecType; + + RING_INFO("Checking codec %s", codecName.c_str()); + + if (codecType & CODEC_ENCODER) { + if (avcodec_find_encoder(codecId) != nullptr) { + RING_INFO("### Found encoder %s", codecIt->to_string().c_str()); + } else { + RING_ERR("Can not find encoder for codec %s", codecName.c_str()); + codecIt->codecType = (CodecType)((unsigned)codecType & ~CODEC_ENCODER); + } + } + + if (codecType & CODEC_DECODER) { + if (avcodec_find_decoder(codecId) != nullptr) { + RING_INFO("### Found decoder %s", codecIt->to_string().c_str()); + } else { + RING_ERR("Can not find decoder for codec %s", codecName.c_str()); + codecIt->codecType = (CodecType)((unsigned)codecType & ~CODEC_DECODER); + } + } + } +} + +std::vector<std::shared_ptr<SystemCodecInfo>> +SystemCodecContainer::getSystemCodecInfoList(MediaType mediaType) +{ + if (mediaType & MEDIA_ALL) + return availableCodecList_; + + //otherwise we have to instantiate a new list containing filtered objects + // must be destroyed by the caller... + std::vector<std::shared_ptr<SystemCodecInfo>> systemCodecList; + for (const auto& codecIt: availableCodecList_) { + if (codecIt->mediaType & mediaType) + systemCodecList.push_back(codecIt); + } + return systemCodecList; +} + +std::vector<unsigned> +SystemCodecContainer::getSystemCodecInfoIdList(MediaType mediaType) +{ + std::vector<unsigned> idList; + for (const auto& codecIt: availableCodecList_) { + if (codecIt->mediaType & mediaType) + idList.push_back(codecIt->id); + } + return idList; +} + +std::shared_ptr<SystemCodecInfo> +SystemCodecContainer::searchCodecById(unsigned codecId, MediaType mediaType) +{ + for (const auto& codecIt: availableCodecList_) { + if ((codecIt->id == codecId) && + (codecIt->mediaType & mediaType )) + return codecIt; + } + return {}; +} +std::shared_ptr<SystemCodecInfo> +SystemCodecContainer::searchCodecByName(std::string name, MediaType mediaType) +{ + for (const auto& codecIt: availableCodecList_) { + if ((codecIt->name.compare(name) == 0) && + (codecIt->mediaType & mediaType )) + return codecIt; + } + return {}; +} +std::shared_ptr<SystemCodecInfo> +SystemCodecContainer::searchCodecByPayload(unsigned payload, MediaType mediaType) +{ + for (const auto& codecIt: availableCodecList_) { + if ((codecIt->payloadType == payload ) && + (codecIt->mediaType & mediaType)) + return codecIt; + } + return {}; +} + +} // namespace ring diff --git a/src/media/system_codec_container.h b/src/media/system_codec_container.h new file mode 100644 index 0000000000..c3d7477dd6 --- /dev/null +++ b/src/media/system_codec_container.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2015 Savoir-Faire Linux Inc. + * Author: Eloi BAIL <eloi.bail@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __SYSTEM_CODEC_CONTAINER_H__ +#define __SYSTEM_CODEC_CONTAINER_H__ + +#include "media_codec.h" +#include "ring_types.h" + +#include <string> +#include <vector> +#include <memory> + +namespace ring { + +class SystemCodecContainer; + +extern decltype(getGlobalInstance<SystemCodecContainer>)& getSystemCodecContainer; + +class SystemCodecContainer +{ + public: + SystemCodecContainer(); + ~SystemCodecContainer(); + + std::vector<std::shared_ptr<SystemCodecInfo>> + getSystemCodecInfoList(MediaType mediaType = MEDIA_ALL); + + std::vector<unsigned> + getSystemCodecInfoIdList(MediaType type = MEDIA_ALL); + + std::shared_ptr<SystemCodecInfo> + searchCodecById(unsigned codecId, MediaType type = MEDIA_ALL); + + std::shared_ptr<SystemCodecInfo> + searchCodecByName(std::string name, MediaType type = MEDIA_ALL); + + std::shared_ptr<SystemCodecInfo> + searchCodecByPayload(unsigned payload, MediaType type = MEDIA_ALL); + + private: + /* available audio & video codec */ + std::vector<std::shared_ptr<SystemCodecInfo>> availableCodecList_; + + void initCodecConfig(); + void checkInstalledCodecs(); +}; + +} // namespace ring + +#endif //SYSTEM_CODEC_CONTAINER diff --git a/src/media/video/.gitignore b/src/media/video/.gitignore new file mode 100644 index 0000000000..6dbbe09ae6 --- /dev/null +++ b/src/media/video/.gitignore @@ -0,0 +1,2 @@ +ffmpeg2rtp +ffmpeg2shm diff --git a/src/media/video/Makefile.am b/src/media/video/Makefile.am new file mode 100644 index 0000000000..73876d36a3 --- /dev/null +++ b/src/media/video/Makefile.am @@ -0,0 +1,32 @@ +include $(top_srcdir)/globals.mak + +SUBDIRS= test + +if HAVE_LINUX +SUBDIRS+= \ + v4l2 +endif + +if HAVE_OSX +SUBDIRS+= \ + osxvideo +endif + +noinst_LTLIBRARIES = libvideo.la +libvideo_la_SOURCES = \ + video_device.h \ + video_device_monitor.cpp video_device_monitor.h \ + video_base.cpp video_base.h \ + video_scaler.cpp video_scaler.h \ + video_mixer.cpp video_mixer.h \ + video_input.cpp video_input.h \ + video_receive_thread.cpp video_receive_thread.h \ + video_sender.cpp video_sender.h \ + video_rtp_session.cpp video_rtp_session.h \ + sinkclient.cpp sinkclient.h \ + video_provider.h + +libvideo_la_LIBADD = @LIBAVCODEC_LIBS@ @LIBAVFORMAT_LIBS@ @LIBAVDEVICE_LIBS@ @LIBSWSCALE_LIBS@ @LIBAVUTIL_LIBS@ + +AM_CXXFLAGS=@LIBAVCODEC_CFLAGS@ @LIBAVFORMAT_CFLAGS@ @LIBAVDEVICE_CFLAGS@ @LIBSWSCALE_CFLAGS@ +AM_CFLAGS=@LIBAVCODEC_CFLAGS@ @LIBAVFORMAT_CFLAGS@ @LIBAVDEVICE_CFLAGS@ @LIBSWSCALE_CFLAGS@ diff --git a/src/media/video/TODO b/src/media/video/TODO new file mode 100644 index 0000000000..6e825c3b6e --- /dev/null +++ b/src/media/video/TODO @@ -0,0 +1,8 @@ +Video TODO +---- + +* refactor VideoRtpThread, while loop should be the only thing in the thread +* camera configuration +* test on network, with netem +* figure out when streaming files, playback doesn't happend at correct + framerate (it goes as fast as possible) diff --git a/src/media/video/osxvideo/Makefile.am b/src/media/video/osxvideo/Makefile.am new file mode 100644 index 0000000000..5da22cf4f7 --- /dev/null +++ b/src/media/video/osxvideo/Makefile.am @@ -0,0 +1,9 @@ +include $(top_srcdir)/globals.mak + +noinst_LTLIBRARIES = libosxvideo.la + +libosxvideo_la_SOURCES = \ + video_device_impl.mm \ + video_device_monitor_impl.mm + +AM_OBJCXXFLAGS = -std=c++11 diff --git a/src/media/video/osxvideo/video_device_impl.mm b/src/media/video/osxvideo/video_device_impl.mm new file mode 100644 index 0000000000..ffb26eb544 --- /dev/null +++ b/src/media/video/osxvideo/video_device_impl.mm @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2015 Savoir-Faire Linux Inc. + * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <algorithm> +#include <cassert> +#include <climits> +#include <map> +#include <string> +#include <sstream> +#include <stdexcept> +#include <string> +#include <vector> + +#include "logger.h" +#include "../video_device.h" + +#import <AVFoundation/AVFoundation.h> + +namespace ring { namespace video { + +class VideoDeviceImpl { + public: + /** + * @throw std::runtime_error + */ + VideoDeviceImpl(const std::string& path); + + std::string device; + std::string name; + + std::vector<std::string> getChannelList() const; + std::vector<std::string> getSizeList(const std::string& channel) const; + std::vector<std::string> getSizeList() const; + std::vector<std::string> getRateList(const std::string& channel, const std::string& size) const; + float getRate(unsigned rate) const; + + VideoSettings getSettings() const; + void applySettings(VideoSettings settings); + + DeviceParams getDeviceParams() const; + + private: + AVCaptureDevice* avDevice_; +}; + +VideoDeviceImpl::VideoDeviceImpl(const std::string& uniqueID) + : device(uniqueID) + , avDevice_([AVCaptureDevice deviceWithUniqueID: + [NSString stringWithCString:uniqueID.c_str() encoding:[NSString defaultCStringEncoding]]]) +{ + name = [[avDevice_ localizedName] UTF8String]; + // Set default settings + applySettings(VideoSettings()); +} + +void +VideoDeviceImpl::applySettings(VideoSettings settings) +{ +//TODO: not supported for now on OSX +// Set preferences or fallback to defaults. +// channel_ = getChannel(settings["channel"]); +// size_ = channel_.getSize(settings["size"]); +// rate_ = size_.getRate(settings["rate"]); +} + +DeviceParams +VideoDeviceImpl::getDeviceParams() const +{ + DeviceParams params; + params.input = "[" + device + "]"; + params.format = "avfoundation"; +// No channel support for now +// params.channel = channel_.idx; + auto format = [avDevice_ activeFormat]; + CMVideoDimensions dimensions = + CMVideoFormatDescriptionGetDimensions(format.formatDescription); + params.width = dimensions.width; + params.height = dimensions.height; + auto frameRate = (AVFrameRateRange*) + [format.videoSupportedFrameRateRanges objectAtIndex:0]; + params.framerate = frameRate.maxFrameRate; + return params; +} + +VideoSettings +VideoDeviceImpl::getSettings() const +{ + VideoSettings settings; + + settings.name = [[avDevice_ localizedName] UTF8String]; + + return settings; +} + +VideoDevice::VideoDevice(const std::string& path) : + deviceImpl_(new VideoDeviceImpl(path)) +{ + node_ = path; + name = deviceImpl_->name; +} + +DeviceParams +VideoDevice::getDeviceParams() const +{ + return deviceImpl_->getDeviceParams(); +} + +void +VideoDevice::applySettings(VideoSettings settings) +{ + deviceImpl_->applySettings(settings); +} + +VideoSettings +VideoDevice::getSettings() const +{ + return deviceImpl_->getSettings(); +} + +std::vector<std::string> +VideoDeviceImpl::getSizeList() const +{ + return getSizeList("default"); +} + +std::vector<std::string> +VideoDeviceImpl::getRateList(const std::string& channel, const std::string& size) const +{ + auto format = [avDevice_ activeFormat]; + std::vector<std::string> v; + + for (AVFrameRateRange* frameRateRange in format.videoSupportedFrameRateRanges) { + std::stringstream ss; + ss << frameRateRange.maxFrameRate; + v.push_back(ss.str()); + } + return v; +} + +std::vector<std::string> +VideoDeviceImpl::getSizeList(const std::string& channel) const +{ + std::vector<std::string> v; + + for (AVCaptureDeviceFormat* format in avDevice_.formats) { + std::stringstream ss; + auto dimensions = CMVideoFormatDescriptionGetDimensions(format.formatDescription); + ss << dimensions.width << "x" << dimensions.height; + v.push_back(ss.str()); + } + return v; +} + +std::vector<std::string> VideoDeviceImpl::getChannelList() const +{ + return {"default"}; +} + +DRing::VideoCapabilities +VideoDevice::getCapabilities() const +{ + DRing::VideoCapabilities cap; + + for (const auto& chan : deviceImpl_->getChannelList()) + for (const auto& size : deviceImpl_->getSizeList(chan)) + cap[chan][size] = deviceImpl_->getRateList(chan, size); + + return cap; +} + +VideoDevice::~VideoDevice() +{} + +}} // namespace ring::video diff --git a/src/media/video/osxvideo/video_device_monitor_impl.mm b/src/media/video/osxvideo/video_device_monitor_impl.mm new file mode 100644 index 0000000000..2eeee8e0f4 --- /dev/null +++ b/src/media/video/osxvideo/video_device_monitor_impl.mm @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2015 Savoir-Faire Linux Inc. + * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <algorithm> +#include <cerrno> +#include <cstdio> +#include <mutex> +#include <sstream> +#include <stdexcept> // for std::runtime_error +#include <string> +#include <thread> +#include <unistd.h> +#include <vector> + +#include "../video_device_monitor.h" +#include "logger.h" +#include "noncopyable.h" + +#import <AVFoundation/AVFoundation.h> + +namespace ring { namespace video { + +class VideoDeviceMonitorImpl { + public: + /* + * This is the only restriction to the pImpl design: + * as the Linux implementation has a thread, it needs a way to notify + * devices addition and deletion. + * + * This class should maybe inherit from VideoDeviceMonitor instead of + * being its pImpl. + */ + VideoDeviceMonitorImpl(VideoDeviceMonitor* monitor); + ~VideoDeviceMonitorImpl(); + + void start(); + + private: + NON_COPYABLE(VideoDeviceMonitorImpl); + + VideoDeviceMonitor* monitor_; + NSArray* observers; +}; + + +VideoDeviceMonitorImpl::VideoDeviceMonitorImpl(VideoDeviceMonitor* monitor) : + monitor_(monitor) +{ + /* Enumerate existing devices */ + auto myVideoDevices = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo] + arrayByAddingObjectsFromArray: + [AVCaptureDevice devicesWithMediaType:AVMediaTypeMuxed]]; + if ( [myVideoDevices count] == 0 ) + { + RING_ERR("Can't find any suitable video device"); + return; + } + + int deviceCount = [myVideoDevices count]; + int ivideo; + for ( ivideo = 0; ivideo < deviceCount; ++ivideo ) + { + AVCaptureDevice* avf_device = [myVideoDevices objectAtIndex:ivideo]; + printf("avcapture %d/%d %s %s\n", ivideo + 1, + deviceCount, + [[avf_device modelID] UTF8String], + [[avf_device uniqueID] UTF8String]); + try { + monitor_->addDevice([[avf_device uniqueID] UTF8String]); + } catch (const std::runtime_error &e) { + RING_ERR("%s", e.what()); + } + } + return; +} + +void VideoDeviceMonitorImpl::start() +{ + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + id deviceWasConnectedObserver = [notificationCenter addObserverForName:AVCaptureDeviceWasConnectedNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification *note) { + AVCaptureDevice* dev = (AVCaptureDevice*)note.object; + monitor_->addDevice([[dev uniqueID] UTF8String]); + }]; + id deviceWasDisconnectedObserver = [notificationCenter addObserverForName:AVCaptureDeviceWasDisconnectedNotification + object:nil + queue:[NSOperationQueue mainQueue] + usingBlock:^(NSNotification *note) { + AVCaptureDevice* dev = (AVCaptureDevice*)note.object; + monitor_->removeDevice([[dev uniqueID] UTF8String]); + }]; + observers = [[NSArray alloc] initWithObjects:deviceWasConnectedObserver, deviceWasDisconnectedObserver, nil]; +} + +VideoDeviceMonitorImpl::~VideoDeviceMonitorImpl() +{ + NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; + for (id observer in observers) + [notificationCenter removeObserver:observer]; + + [observers release]; +} + +VideoDeviceMonitor::VideoDeviceMonitor() : + preferences_(), devices_(), + monitorImpl_(new VideoDeviceMonitorImpl(this)) +{ + monitorImpl_->start(); +} + +VideoDeviceMonitor::~VideoDeviceMonitor() +{} + +}} // namespace ring::video diff --git a/src/media/video/shm_header.h b/src/media/video/shm_header.h new file mode 100644 index 0000000000..6c6a615961 --- /dev/null +++ b/src/media/video/shm_header.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2012-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * Portions derived from GStreamer: + * Copyright (C) <2009> Collabora Ltd + * @author: Olivier Crete <olivier.crete@collabora.co.uk + * Copyright (C) <2009> Nokia Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef SHM_HEADER_H_ +#define SHM_HEADER_H_ + +#include <semaphore.h> + +struct SHMHeader { + sem_t notification; + sem_t mutex; + + unsigned buffer_gen; + int buffer_size; + /* The header will be aligned on 16-byte boundaries */ + char padding[8]; + + char data[]; +}; + +#endif diff --git a/src/media/video/sinkclient.cpp b/src/media/video/sinkclient.cpp new file mode 100644 index 0000000000..994d4d1802 --- /dev/null +++ b/src/media/video/sinkclient.cpp @@ -0,0 +1,331 @@ +/* + * Copyright (C) 2012-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * Portions derived from GStreamer: + * Copyright (C) <2009> Collabora Ltd + * @author: Olivier Crete <olivier.crete@collabora.co.uk + * Copyright (C) <2009> Nokia Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sinkclient.h" + +#if HAVE_SHM +#include "shm_header.h" +#endif // HAVE_SHM + +#include "video_scaler.h" +#include "media_buffer.h" +#include "logger.h" +#include "noncopyable.h" + +#include <sys/mman.h> +#include <fcntl.h> +#include <cstdio> +#include <sstream> +#include <unistd.h> +#include <cerrno> +#include <cstring> +#include <stdexcept> + +namespace ring { namespace video { + +#if HAVE_SHM +// RAII class helper on sem_wait/sem_post sempahore operations +class SemGuardLock { + public: + explicit SemGuardLock(sem_t& mutex) : m_(mutex) { + auto ret = ::sem_wait(&m_); + if (ret < 0) { + std::ostringstream msg; + msg << "SHM mutex@" << &m_ + << " lock failed (" << ret << ")"; + throw std::logic_error {msg.str()}; + } + } + + ~SemGuardLock() { + ::sem_post(&m_); + } + + private: + sem_t& m_; +}; + +class ShmHolder +{ + public: + ShmHolder(const std::string& shm_name={}); + ~ShmHolder(); + + std::string openedName() const noexcept { + return opened_name_; + } + + void render_frame(VideoFrame& src); + + private: + bool resize_area(std::size_t desired_length); + void alloc_area(std::size_t desired_length) noexcept; + + std::string shm_name_; + std::string opened_name_; + std::size_t shm_area_len_ {0}; + SHMHeader* shm_area_ {static_cast<SHMHeader*>(MAP_FAILED)}; + int fd_ {-1}; +}; + +void +ShmHolder::alloc_area(std::size_t desired_length) noexcept +{ + shm_area_ = static_cast<SHMHeader*>(::mmap(nullptr, desired_length, + PROT_READ | PROT_WRITE, + MAP_SHARED, fd_, 0)); +} + +ShmHolder::ShmHolder(const std::string& shm_name) : shm_name_ {shm_name} +{ + static constexpr int flags = O_RDWR | O_CREAT | O_TRUNC | O_EXCL; + static constexpr int perms = S_IRUSR | S_IWUSR; + + if (not shm_name_.empty()) { + fd_ = ::shm_open(shm_name_.c_str(), flags, perms); + if (fd_ < 0) { + std::ostringstream msg; + msg << "could not open shm area \"" + << shm_name_.c_str() + << "\""; + throw std::runtime_error {msg.str()}; + } + } else { + for (int i = 0; fd_ < 0; ++i) { + std::ostringstream name; + name << PACKAGE_NAME << "_shm_" << getpid() << "_" << i; + shm_name_ = name.str(); + fd_ = ::shm_open(shm_name_.c_str(), flags, perms); + if (fd_ < 0 and errno != EEXIST) + throw std::runtime_error {"shm_open() failed"}; + } + } + + RING_DBG("Using name %s", shm_name_.c_str()); + opened_name_ = shm_name_; + + shm_area_len_ = sizeof(SHMHeader); + + if (::ftruncate(fd_, shm_area_len_)) { + RING_ERR("Could not make shm area large enough for header"); + strErr(); + throw std::runtime_error {"could not make shm area large enough for header"}; + } + + alloc_area(shm_area_len_); + + if (shm_area_ == MAP_FAILED) + throw std::runtime_error {"could not map shm area, mmap failed"}; + + std::memset(shm_area_, 0, shm_area_len_); + + if (::sem_init(&shm_area_->notification, 1, 0) != 0) + throw std::runtime_error {"sem_init: notification initialization failed"}; + + if (::sem_init(&shm_area_->mutex, 1, 1) != 0) + throw std::runtime_error {"sem_init: mutex initialization failed"}; +} + +ShmHolder::~ShmHolder() +{ + if (fd_ >= 0 and ::close(fd_) == -1) + strErr(); + + if (not opened_name_.empty()) + ::shm_unlink(opened_name_.c_str()); + + if (shm_area_ != MAP_FAILED) { + ::sem_post(&shm_area_->notification); + if (::munmap(shm_area_, shm_area_len_) < 0) + strErr(); + } +} + +bool +ShmHolder::resize_area(size_t desired_length) +{ + if (desired_length <= shm_area_len_) + return true; + + if (::munmap(shm_area_, shm_area_len_)) { + RING_ERR("Could not unmap shared area"); + strErr(); + return false; + } + + if (::ftruncate(fd_, desired_length)) { + RING_ERR("Could not resize shared area"); + strErr(); + return false; + } + + alloc_area(desired_length); + + if (shm_area_ == MAP_FAILED) { + shm_area_len_ = 0; + RING_ERR("Could not remap shared area"); + return false; + } + + shm_area_len_ = desired_length; + return true; +} + +void +ShmHolder::render_frame(VideoFrame& src) +{ + VideoFrame dst; + VideoScaler scaler; + + const int width = src.width(); + const int height = src.height(); + const int format = VIDEO_PIXFMT_BGRA; + const auto bytes = videoFrameSize(format, width, height); + + if (!resize_area(sizeof(SHMHeader) + bytes)) { + RING_ERR("Could not resize area"); + return; + } + + SemGuardLock lk{shm_area_->mutex}; + + dst.setFromMemory(shm_area_->data, format, width, height); + scaler.scale(src, dst); + + shm_area_->buffer_size = bytes; + ++shm_area_->buffer_gen; + sem_post(&shm_area_->notification); +} + +std::string +SinkClient::openedName() const noexcept +{ + return shm_->openedName(); +} + +bool +SinkClient::start() noexcept +{ + if (not shm_) { + try { + shm_ = std::make_shared<ShmHolder>(); + } catch (const std::runtime_error& e) { + strErr(); + RING_ERR("SHMHolder ctor failure: %s", e.what()); + } + } + return static_cast<bool>(shm_); +} + +bool +SinkClient::stop() noexcept +{ + shm_.reset(); + return true; +} + +#else // HAVE_SHM + +std::string +SinkClient::openedName() const noexcept +{ + return {}; +} + +bool +SinkClient::start() noexcept +{ + return true; +} + +bool +SinkClient::stop() noexcept +{ + return true; +} + +#endif // !HAVE_SHM + +SinkClient::SinkClient(const std::string& id) : id_ {id} +#ifdef DEBUG_FPS + , frameCount_(0u) + , lastFrameDebug_(std::chrono::system_clock::now()) +#endif +{} + +void +SinkClient::update(Observable<std::shared_ptr<VideoFrame>>* /*obs*/, + std::shared_ptr<VideoFrame>& frame_p) +{ + auto f = frame_p; // keep a local reference during rendering + +#ifdef DEBUG_FPS + auto currentTime = std::chrono::system_clock::now(); + const std::chrono::duration<double> seconds = currentTime - lastFrameDebug_; + ++frameCount_; + if (seconds.count() > 1) { + RING_DBG("%s: FPS %f", id_.c_str(), frameCount_ / seconds.count()); + frameCount_ = 0; + lastFrameDebug_ = currentTime; + } +#endif + +#if HAVE_SHM + shm_->render_frame(*f.get()); +#endif + + if (target_) { + VideoFrame dst; + VideoScaler scaler; + const int width = f->width(); + const int height = f->height(); + const int format = VIDEO_PIXFMT_RGBA; + const auto bytes = videoFrameSize(format, width, height); + + targetData_.resize(bytes); + auto data = targetData_.data(); + + dst.setFromMemory(data, format, width, height); + scaler.scale(*f, dst); + target_(data); + } +} + +}} // namespace ring::video diff --git a/src/media/video/sinkclient.h b/src/media/video/sinkclient.h new file mode 100644 index 0000000000..048945346b --- /dev/null +++ b/src/media/video/sinkclient.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2012-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * Portions derived from GStreamer: + * Copyright (C) <2009> Collabora Ltd + * @author: Olivier Crete <olivier.crete@collabora.co.uk + * Copyright (C) <2009> Nokia Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#pragma once + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "video_provider.h" +#include "video_base.h" + +#include <string> +#include <vector> +#include <memory> + +namespace ring { namespace video { + +#if HAVE_SHM +class ShmHolder; +#endif // HAVE_SHM + +class SinkClient : public VideoFramePassiveReader +{ + public: + SinkClient(const std::string& id=""); + + const std::string& getId() const noexcept { + return id_; + } + + std::string openedName() const noexcept; + + // as VideoFramePassiveReader + void update(Observable<std::shared_ptr<ring::VideoFrame>>*, + std::shared_ptr<ring::VideoFrame>&); + + bool start() noexcept; + bool stop() noexcept; + + template <class T> + void registerTarget(T&& cb) noexcept { + target_ = std::forward<T>(cb); + } + + private: + const std::string id_; + std::function<void(unsigned char*)> target_; + std::vector<unsigned char> targetData_; + +#ifdef DEBUG_FPS + unsigned frameCount_; + std::chrono::time_point<std::chrono::system_clock> lastFrameDebug_; +#endif + +#if HAVE_SHM + // using shared_ptr and not unique_ptr as ShmHolder is forwared only + std::shared_ptr<ShmHolder> shm_; +#endif // HAVE_SHM +}; + +}} // namespace ring::video diff --git a/src/media/video/test/.gitignore b/src/media/video/test/.gitignore new file mode 100644 index 0000000000..90574ca50a --- /dev/null +++ b/src/media/video/test/.gitignore @@ -0,0 +1,5 @@ +test_thread +test_v4l2 +test_video_endpoint +test_video_rtp +test_video_preview diff --git a/src/media/video/test/Makefile.am b/src/media/video/test/Makefile.am new file mode 100644 index 0000000000..96be9b522d --- /dev/null +++ b/src/media/video/test/Makefile.am @@ -0,0 +1,18 @@ +include $(top_srcdir)/globals.mak + +TESTS=test_shm test_video_input test_video_rtp +check_PROGRAMS=test_video_rtp test_video_input test_shm + +test_video_rtp_SOURCES=test_video_rtp.cpp +test_video_rtp_LDADD=$(top_builddir)/src/libring.la $(top_builddir)/src/media/video/libvideo.la $(YAML_LIBS) + +test_video_input_SOURCES=test_video_input.cpp test_video_input.h +test_video_input_LDADD=$(top_builddir)/src/libring.la $(top_builddir)/src/media/video/libvideo.la $(YAML_LIBS) + +if HAVE_LINUX +test_shm_SOURCES=test_shm.cpp shm_src.cpp shm_src.h +test_shm_LDADD=$(top_builddir)/src/libring.la $(top_builddir)/src/media/video/libvideo.la $(YAML_LIBS) -lrt +test_shm_CXXFLAGS=$(AM_CXXFLAGS) +endif + +AM_CXXFLAGS=-I$(top_srcdir)/src/media/video -I$(top_srcdir)/src diff --git a/src/media/video/test/README b/src/media/video/test/README new file mode 100644 index 0000000000..9ef0c4f1e8 --- /dev/null +++ b/src/media/video/test/README @@ -0,0 +1 @@ +c++ shm_src.cpp shmclient.cpp -o shmclient `pkg-config --cflags --libs clutter-1.0` -lrt -pthread -O2 diff --git a/src/media/video/test/make_rtp_stream.sh b/src/media/video/test/make_rtp_stream.sh new file mode 100755 index 0000000000..dfb4759b5b --- /dev/null +++ b/src/media/video/test/make_rtp_stream.sh @@ -0,0 +1,2 @@ +# disables audio +ffmpeg -f video4linux2 -i /dev/video0 -srcw 320 -srch 240 -an -r 30 -vprofile baseline -level 13 -vb 400000 -vcodec libx264 -payload_type 109 -preset veryfast -tune zerolatency -f rtp rtp://192.168.50.116:2228 diff --git a/src/media/video/test/shm_src.cpp b/src/media/video/test/shm_src.cpp new file mode 100644 index 0000000000..6fbcb80fe5 --- /dev/null +++ b/src/media/video/test/shm_src.cpp @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2012-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * Portions derived from GStreamer: + * Copyright (C) <2009> Collabora Ltd + * @author: Olivier Crete <olivier.crete@collabora.co.uk + * Copyright (C) <2009> Nokia Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "shm_src.h" +#include "../shm_header.h" +#include <sys/mman.h> +#include <fcntl.h> +#include <cstdio> +#include <iostream> +#include <unistd.h> +#include <cerrno> +#include <cstring> +#include <cassert> + +SHMSrc::SHMSrc(const std::string &shm_name) : + shm_name_(shm_name), + fd_(-1), + shm_area_(static_cast<SHMHeader*>(MAP_FAILED)), + shm_area_len_(0), + buffer_gen_(0) + {} + +bool +SHMSrc::start() +{ + if (fd_ != -1) { + std::cerr << "fd must be -1" << std::endl; + return false; + } + + fd_ = shm_open(shm_name_.c_str(), O_RDWR, 0); + if (fd_ < 0) { + std::cerr << "could not open shm area \"" << shm_name_ << "\", shm_open failed" << std::endl; + perror(strerror(errno)); + return false; + } + shm_area_len_ = sizeof(SHMHeader); + + shm_area_ = static_cast<SHMHeader*>(mmap(NULL, shm_area_len_, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0)); + + if (shm_area_ == MAP_FAILED) { + std::cerr << "Could not map shm area, mmap failed" << std::endl; + return false; + } + + return true; +} + +bool +SHMSrc::stop() +{ + if (fd_ >= 0) + close(fd_); + fd_ = -1; + + if (shm_area_ != MAP_FAILED) + munmap(shm_area_, shm_area_len_); + shm_area_len_ = 0; + shm_area_ = static_cast<SHMHeader*>(MAP_FAILED); + + return true; +} + +bool +SHMSrc::resize_area() +{ + while ((sizeof(SHMHeader) + shm_area_->buffer_size) > shm_area_len_) { + size_t new_size = sizeof(SHMHeader) + shm_area_->buffer_size; + + shm_unlock(); + if (munmap(shm_area_, shm_area_len_)) { + std::cerr << "Could not unmap shared area" << std::endl; + perror(strerror(errno)); + return false; + } + + shm_area_ = static_cast<SHMHeader*>(mmap(NULL, new_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd_, 0)); + shm_area_len_ = new_size; + + if (!shm_area_) { + shm_area_ = 0; + std::cerr << "Could not remap shared area" << std::endl; + return false; + } + + shm_area_len_ = new_size; + shm_lock(); + } + return true; +} + +void SHMSrc::render(char *dest, size_t len) +{ + shm_lock(); + + while (buffer_gen_ == shm_area_->buffer_gen) { + shm_unlock(); + std::cerr << "Waiting for next buffer" << std::endl;; + sem_wait(&shm_area_->notification); + + shm_lock(); + } + + if (!resize_area()) + return; + + std::cerr << "Reading from buffer!" << std::endl; + memcpy(dest, shm_area_->data, len); + buffer_gen_ = shm_area_->buffer_gen; + shm_unlock(); +} + +void SHMSrc::shm_lock() +{ + sem_wait(&shm_area_->mutex); +} + +void SHMSrc::shm_unlock() +{ + sem_post(&shm_area_->mutex); +} diff --git a/src/media/video/test/shm_src.h b/src/media/video/test/shm_src.h new file mode 100644 index 0000000000..b81e6bbbc8 --- /dev/null +++ b/src/media/video/test/shm_src.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2012-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * Portions derived from GStreamer: + * Copyright (C) <2009> Collabora Ltd + * @author: Olivier Crete <olivier.crete@collabora.co.uk + * Copyright (C) <2009> Nokia Inc + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef SHM_SRC_H_ +#define SHM_SRC_H_ + +#include <string> +#include "../../noncopyable.h" + +class SHMHeader; +// Example Shared memory source, only useful for testing +// as far as the daemon is concerned + +class SHMSrc { + public: + SHMSrc(const std::string &shm_name); + virtual ~SHMSrc() {}; + + bool start(); + bool stop(); + + bool resize_area(); + + void render(char *data, size_t len); + + protected: + void shm_lock(); + void shm_unlock(); + std::string shm_name_; + int fd_; + SHMHeader *shm_area_; + size_t shm_area_len_; + unsigned buffer_gen_; + + private: + NON_COPYABLE(SHMSrc); +}; + +#endif // SHM_SRC_H_ diff --git a/src/media/video/test/shmclient.cpp b/src/media/video/test/shmclient.cpp new file mode 100644 index 0000000000..e994d5c6e4 --- /dev/null +++ b/src/media/video/test/shmclient.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2011-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <cstdio> +#include <cstdlib> +#include "shm_src.h" +#include "../shm_header.h" +#include "../../noncopyable.h" +#include <sys/mman.h> +#include <iostream> +#include <clutter/clutter.h> + +class ClutterSHMSrc : public SHMSrc { + public: + ClutterSHMSrc(const std::string &name, + unsigned width, + unsigned height, + ClutterActor *texture) : + SHMSrc(name), + width_(width), + height_(height), + texture_(texture) + { + printf("Creating source with name:%s width:%d height:%d texture:%p\n", name.c_str(), width, height, texture); + } + + void render_to_texture() + { + if (shm_area_ == MAP_FAILED) { + g_print("shm_area is MAP FAILED!\n"); + return; + } + + shm_lock(); + + while (buffer_gen_ == shm_area_->buffer_gen) { + shm_unlock(); + sem_wait(&shm_area_->notification); + + shm_lock(); + } + + if (!resize_area()) { + g_print("could not resize area\n"); + return; + } + + clutter_actor_set_size(texture_, width_, height_); + const int BPP = 4; + const int ROW_STRIDE = BPP * width_; + /* update the clutter texture */ + clutter_texture_set_from_rgb_data(CLUTTER_TEXTURE(texture_), + reinterpret_cast<const unsigned char *>(shm_area_->data), + TRUE, + width_, + height_, + ROW_STRIDE, + BPP, + CLUTTER_TEXTURE_RGB_FLAG_BGR, + NULL); + buffer_gen_ = shm_area_->buffer_gen; + shm_unlock(); + } + + private: + NON_COPYABLE(ClutterSHMSrc); + unsigned width_; + unsigned height_; + ClutterActor *texture_; +}; + +gboolean updateTexture(gpointer data) +{ + ClutterSHMSrc *src = static_cast<ClutterSHMSrc *>(data); + src->render_to_texture(); + return TRUE; +} + +int main(int argc, char *argv[]) +{ + if (argc < 4) { + printf("Usage: ./shmclient <shm_filename> <width> <height>\n"); + return 1; + } + + /* Initialize Clutter */ + if (clutter_init(NULL, NULL) != CLUTTER_INIT_SUCCESS) + return 1; + + /* Get the default stage */ + ClutterActor *stage = clutter_stage_get_default(); + + const int width = atoi(argv[2]); + const int height = atoi(argv[3]); + + clutter_actor_set_size(stage, width, height); + + ClutterActor *texture = clutter_texture_new(); + + clutter_stage_set_title(CLUTTER_STAGE(stage), "Client"); + /* Add ClutterTexture to the stage */ + clutter_container_add(CLUTTER_CONTAINER(stage), texture, NULL); + + ClutterSHMSrc src(argv[1], width, height, texture); + if (not src.start()) { + printf("Could not start SHM source\n"); + return 1; + } + /* frames are read and saved here */ + g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE, 30, updateTexture, &src, NULL); + + clutter_actor_show_all(stage); + + /* main loop */ + clutter_main(); + src.stop(); + + return 0; +} diff --git a/src/media/video/test/test_shm.cpp b/src/media/video/test/test_shm.cpp new file mode 100644 index 0000000000..4ab0b172ab --- /dev/null +++ b/src/media/video/test/test_shm.cpp @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2012-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "shm_sink.h" +#include "shm_src.h" +#include <thread> +#include <signal.h> +#include <iostream> +#include <unistd.h> +#include <sys/wait.h> +#include <atomic> +#include <cstring> +#include <cassert> + +static std::atomic<bool> done(false); + +static void +signal_handler(int /*sig*/) +{ + done = true; +} + +static const char test_data[] = "abcdefghijklmnopqrstuvwxyz"; + +static void +sink_thread() +{ + ring::video::SHMSink sink("bob");; + if (!sink.start()) + return; + std::vector<unsigned char> test_vec(test_data, test_data + sizeof(test_data) / sizeof(test_data[0])); + + while (!done) { + sink.render(test_vec); + usleep(1000); + } + sink.stop(); + std::cerr << std::endl; + std::cerr << "Exitting sink thread" << std::endl; +} + +static void +run_client() +{ + SHMSrc src("bob");; + bool started = false; + while (not done and not started) { + sleep(1); + if (src.start()) + started = true; + } + // we get here if the above loop was interupted by our signal handler + if (!started) + return; + + // initialize destination string to 0's + std::vector<char> dest(sizeof(test_data), 0); + const std::vector<char> test_data_str(test_data, test_data + sizeof(test_data)); + assert(test_data_str.size() == 27); + assert(dest.size() == test_data_str.size()); + while (not done and dest != test_data_str) { + src.render(dest.data(), dest.size()); + usleep(1000); + } + src.stop(); + std::cerr << "Got characters, exitting client process" << std::endl; +} + +static void +run_daemon() +{ + std::thread bob(sink_thread); + /* Wait for child process. */ + int status; + int pid; + if ((pid = wait(&status)) == -1) { + perror("wait error"); + } else { + // Check status. + if (WIFSIGNALED(status) != 0) + std::cout << "Child process ended because of signal " << + WTERMSIG(status) << std::endl; + else if (WIFEXITED(status) != 0) + std::cout << "Child process ended normally; status = " << + WEXITSTATUS(status) << std::endl; + else + std::cout << "Child process did not end normally" << std::endl; + } + std::cout << "Finished waiting for child" << std::endl; + done = true; + // wait for thread + bob.join(); +} +int main() +{ + signal(SIGINT, signal_handler); + pid_t pid = fork(); + if (pid < 0) { + std::cerr << "Failed to fork" << std::endl; + return 1; + } else if (pid == 0) { + // child code only + run_client(); + } else { + // parent code only + run_daemon(); + } + return 0; +} diff --git a/src/media/video/test/test_video_input.cpp b/src/media/video/test/test_video_input.cpp new file mode 100644 index 0000000000..c7f932f796 --- /dev/null +++ b/src/media/video/test/test_video_input.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2011-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <unistd.h> // for sleep +#include "test_video_input.h" +#include "video_input.h" +#include <map> +#include <string> + +namespace ring { namespace video { namespace test { + +void VideoInputTest::testInput() +{ + std::string resource = "display://" + std::string(getenv("DISPLAY") ? : ":0.0"); + VideoInput video; + video.switchInput(resource); + usleep(10000); +} + +}}} // namespace ring::video::test + +int main () +{ + for (int i = 0; i < 20; ++i) { + ring::video::test::VideoInputTest test; + test.testInput(); + } + return 0; +} diff --git a/src/media/video/test/test_video_input.h b/src/media/video/test/test_video_input.h new file mode 100644 index 0000000000..64889e616b --- /dev/null +++ b/src/media/video/test/test_video_input.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2011-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef _VIDEO_INPUT_TEST_ +#define _VIDEO_INPUT_TEST_ + +namespace ring { namespace video { namespace test { + +class VideoInputTest { +public: + void testInput(); +}; + +}}} // namespace ring::video::test + +#endif // _VIDEO_INPUT_TEST_ diff --git a/src/media/video/test/test_video_rtp.cpp b/src/media/video/test/test_video_rtp.cpp new file mode 100644 index 0000000000..011d781f41 --- /dev/null +++ b/src/media/video/test/test_video_rtp.cpp @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2011-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "video_rtp_session.h" +#include "video_device_monitor.h" + +#include "ip_utils.h" + +#include <unistd.h> // for sleep + +int main () +{ + ring::video::VideoDeviceMonitor monitor; + ring::video::VideoRtpSession session("test", monitor.getDeviceParams(monitor.getDefaultDevice())); + + ring::MediaDescription local {}; + local.addr = {AF_INET}; + local.addr.setPort(12345); + session.updateMedia(local, ring::MediaDescription{}); + + session.start(); + sleep(5); + session.stop(); + + return 0; +} diff --git a/src/media/video/v4l2/Makefile.am b/src/media/video/v4l2/Makefile.am new file mode 100644 index 0000000000..99339d3050 --- /dev/null +++ b/src/media/video/v4l2/Makefile.am @@ -0,0 +1,10 @@ +include $(top_srcdir)/globals.mak + +noinst_LTLIBRARIES = libv4l2.la + +libv4l2_la_SOURCES = \ + video_device_impl.cpp \ + video_device_monitor_impl.cpp + +AM_CXXFLAGS = @UDEV_CFLAGS@ +libv4l2_la_LIBADD = @UDEV_LIBS@ diff --git a/src/media/video/v4l2/video_device_impl.cpp b/src/media/video/v4l2/video_device_impl.cpp new file mode 100644 index 0000000000..f6e1996b30 --- /dev/null +++ b/src/media/video/v4l2/video_device_impl.cpp @@ -0,0 +1,572 @@ +/* + * Copyright (C) 2011-2015 Savoir-Faire Linux Inc. + * Author: Rafaël Carré <rafael.carre@savoirfairelinux.com> + * Author: Vivien Didelot <vivien.didelot@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <algorithm> +#include <cassert> +#include <climits> +#include <cstring> +#include <map> +#include <sstream> +#include <stdexcept> +#include <string> +#include <vector> + +extern "C" { +#include <linux/videodev2.h> +#if !defined(VIDIOC_ENUM_FRAMESIZES) || !defined(VIDIOC_ENUM_FRAMEINTERVALS) +# error You need at least Linux 2.6.19 +#endif + +#include <fcntl.h> +#include <unistd.h> +#include <sys/ioctl.h> +} + +#include "logger.h" +#include "../video_device.h" + +#define ZEROVAR(x) memset(&(x), 0, sizeof(x)) + +namespace ring { namespace video { + +class VideoV4l2Size { + public: + VideoV4l2Size(const unsigned width, const unsigned height); + + /** + * @throw std::runtime_error + */ + void getFrameRates(int fd, unsigned int pixel_format); + std::vector<std::string> getRateList() const; + float getRate(unsigned rate) const; + + unsigned width; + unsigned height; + + private: + std::vector<float> rates_; +}; + +class VideoV4l2Channel { + public: + VideoV4l2Channel(unsigned idx, const char *s); + + /** + * @throw std::runtime_error + */ + void getFormat(int fd); + /** + * @throw std::runtime_error + */ + unsigned int getSizes(int fd, unsigned int pixel_format); + + void setFourcc(unsigned code); + const char * getFourcc() const; + + std::vector<std::string> getSizeList() const; + const VideoV4l2Size& getSize(const std::string &name) const; + + unsigned idx; + std::string name; + + private: + void putCIFFirst(); + std::vector<VideoV4l2Size> sizes_; + char fourcc_[5]; +}; + +class VideoDeviceImpl { + public: + /** + * @throw std::runtime_error + */ + VideoDeviceImpl(const std::string& path); + + std::string device; + std::string name; + + std::vector<std::string> getChannelList() const; + std::vector<std::string> getSizeList(const std::string& channel) const; + std::vector<std::string> getRateList(const std::string& channel, const std::string& size) const; + + VideoSettings getSettings() const; + void applySettings(VideoSettings settings); + + DeviceParams getDeviceParams() const; + + private: + std::vector<VideoV4l2Channel> channels_; + const VideoV4l2Channel& getChannel(const std::string& name) const; + + /* Preferences */ + VideoV4l2Channel channel_; + VideoV4l2Size size_; + float rate_; +}; + +static const unsigned pixelformats_supported[] = { + /* pixel format depth description */ + + /* preferred formats, they can be fed directly to the video encoder */ + V4L2_PIX_FMT_YUV420, /* 12 YUV 4:2:0 */ + V4L2_PIX_FMT_YUV422P, /* 16 YVU422 planar */ + V4L2_PIX_FMT_YUV444, /* 16 xxxxyyyy uuuuvvvv */ + + /* Luminance+Chrominance formats */ + V4L2_PIX_FMT_YVU410, /* 9 YVU 4:1:0 */ + V4L2_PIX_FMT_YVU420, /* 12 YVU 4:2:0 */ + V4L2_PIX_FMT_YUYV, /* 16 YUV 4:2:2 */ + V4L2_PIX_FMT_YYUV, /* 16 YUV 4:2:2 */ + V4L2_PIX_FMT_YVYU, /* 16 YVU 4:2:2 */ + V4L2_PIX_FMT_UYVY, /* 16 YUV 4:2:2 */ + V4L2_PIX_FMT_VYUY, /* 16 YUV 4:2:2 */ + V4L2_PIX_FMT_YUV411P, /* 16 YVU411 planar */ + V4L2_PIX_FMT_Y41P, /* 12 YUV 4:1:1 */ + V4L2_PIX_FMT_YUV555, /* 16 YUV-5-5-5 */ + V4L2_PIX_FMT_YUV565, /* 16 YUV-5-6-5 */ + V4L2_PIX_FMT_YUV32, /* 32 YUV-8-8-8-8 */ + V4L2_PIX_FMT_YUV410, /* 9 YUV 4:1:0 */ + V4L2_PIX_FMT_HI240, /* 8 8-bit color */ + V4L2_PIX_FMT_HM12, /* 8 YUV 4:2:0 16x16 macroblocks */ + + /* two planes -- one Y, one Cr + Cb interleaved */ + V4L2_PIX_FMT_NV12, /* 12 Y/CbCr 4:2:0 */ + V4L2_PIX_FMT_NV21, /* 12 Y/CrCb 4:2:0 */ + V4L2_PIX_FMT_NV16, /* 16 Y/CbCr 4:2:2 */ + V4L2_PIX_FMT_NV61, /* 16 Y/CrCb 4:2:2 */ + +#if 0 + /* RGB formats */ + V4L2_PIX_FMT_RGB332, /* 8 RGB-3-3-2 */ + V4L2_PIX_FMT_RGB444, /* 16 xxxxrrrr ggggbbbb */ + V4L2_PIX_FMT_RGB555, /* 16 RGB-5-5-5 */ + V4L2_PIX_FMT_RGB565, /* 16 RGB-5-6-5 */ + V4L2_PIX_FMT_RGB555X, /* 16 RGB-5-5-5 BE */ + V4L2_PIX_FMT_RGB565X, /* 16 RGB-5-6-5 BE */ + V4L2_PIX_FMT_BGR666, /* 18 BGR-6-6-6 */ + V4L2_PIX_FMT_BGR24, /* 24 BGR-8-8-8 */ + V4L2_PIX_FMT_RGB24, /* 24 RGB-8-8-8 */ + V4L2_PIX_FMT_BGR32, /* 32 BGR-8-8-8-8 */ + V4L2_PIX_FMT_RGB32, /* 32 RGB-8-8-8-8 */ + + /* Grey formats */ + V4L2_PIX_FMT_GREY, /* 8 Greyscale */ + V4L2_PIX_FMT_Y4, /* 4 Greyscale */ + V4L2_PIX_FMT_Y6, /* 6 Greyscale */ + V4L2_PIX_FMT_Y10, /* 10 Greyscale */ + V4L2_PIX_FMT_Y16, /* 16 Greyscale */ + + /* Palette formats */ + V4L2_PIX_FMT_PAL8, /* 8 8-bit palette */ +#endif +}; + +/* Returns a score for the given pixelformat + * + * Lowest score is the best, the first entries in the array are the formats + * supported as an input for the video encoders. + * + * Other entries in the array are YUV formats + * + * RGB / grey / palette formats are not supported because most cameras support + * YUV input + * + */ + +static unsigned int pixelformat_score(unsigned pixelformat) +{ + for (const auto &item : pixelformats_supported) + if (item == pixelformat) + return item; + + return UINT_MAX - 1; +} + +using std::vector; +using std::string; +using std::stringstream; + +VideoV4l2Size::VideoV4l2Size(const unsigned width, const unsigned height) : + width(width), height(height), rates_() {} + +vector<string> VideoV4l2Size::getRateList() const +{ + vector<string> v; + + for (const auto &item : rates_) { + stringstream ss; + ss << item; + v.push_back(ss.str()); + } + + return v; +} + +void VideoV4l2Size::getFrameRates(int fd, unsigned int pixel_format) +{ + v4l2_frmivalenum frmival; + ZEROVAR(frmival); + frmival.pixel_format = pixel_format; + frmival.width = width; + frmival.height = height; + + if (ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival)) { + rates_.push_back(25); + RING_ERR("could not query frame interval for size"); + return; + } + + switch(frmival.type) { + case V4L2_FRMIVAL_TYPE_DISCRETE: + do { + const float rate = static_cast<float>(frmival.discrete.denominator) + / frmival.discrete.numerator; + rates_.push_back(rate); + ++frmival.index; + } while (!ioctl(fd, VIDIOC_ENUM_FRAMEINTERVALS, &frmival)); + break; + case V4L2_FRMIVAL_TYPE_CONTINUOUS: + rates_.push_back(25); + // TODO + RING_ERR("Continuous Frame Intervals not supported"); + break; + case V4L2_FRMIVAL_TYPE_STEPWISE: + rates_.push_back(25); + // TODO + RING_ERR("Stepwise Frame Intervals not supported"); + break; + } +} + +float +VideoV4l2Size::getRate(unsigned rate) const +{ + for (const auto& item : rates_) { + if (item == rate) + return item; + } + + // fallback to last size + assert(not rates_.empty()); + return rates_.back(); +} + +VideoV4l2Channel::VideoV4l2Channel(unsigned idx, const char *s) : + idx(idx), name(s), sizes_(), fourcc_() {} + +void VideoV4l2Channel::setFourcc(unsigned code) +{ + fourcc_[0] = code; + fourcc_[1] = code >> 8; + fourcc_[2] = code >> 16; + fourcc_[3] = code >> 24; + fourcc_[4] = '\0'; +} + +const char * +VideoV4l2Channel::getFourcc() const +{ + return fourcc_; +} + +vector<string> VideoV4l2Channel::getSizeList() const +{ + vector<string> v; + + for (const auto &item : sizes_) { + stringstream ss; + ss << item.width << "x" << item.height; + v.push_back(ss.str()); + } + + return v; +} + +unsigned int +VideoV4l2Channel::getSizes(int fd, unsigned int pixelformat) +{ + v4l2_frmsizeenum frmsize; + ZEROVAR(frmsize); + frmsize.index = 0; + frmsize.pixel_format = pixelformat; + if (!ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize)) { + + switch (frmsize.type) { + case V4L2_FRMSIZE_TYPE_DISCRETE: + do { + VideoV4l2Size size(frmsize.discrete.width, frmsize.discrete.height); + size.getFrameRates(fd, frmsize.pixel_format); + sizes_.push_back(size); + ++frmsize.index; + } while (!ioctl(fd, VIDIOC_ENUM_FRAMESIZES, &frmsize)); + return pixelformat; + + // TODO, we dont want to display a list of 2000x2000 + // resolutions if the camera supports continuous framesizes + // from 1x1 to 2000x2000 + // We should limit to a list of known standard sizes + case V4L2_FRMSIZE_TYPE_CONTINUOUS: + RING_ERR("Continuous Frame sizes not supported"); + break; + case V4L2_FRMSIZE_TYPE_STEPWISE: + RING_ERR("Stepwise Frame sizes not supported"); + break; + } + } + + v4l2_format fmt; + ZEROVAR(fmt); + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + if (ioctl(fd, VIDIOC_G_FMT, &fmt) < 0) + throw std::runtime_error("Could not get format"); + + VideoV4l2Size size(fmt.fmt.pix.width, fmt.fmt.pix.height); + size.getFrameRates(fd, fmt.fmt.pix.pixelformat); + sizes_.push_back(size); + + return fmt.fmt.pix.pixelformat; +} + +// Put CIF resolution (352x288) first in the list since it is more prevalent in +// VoIP +void VideoV4l2Channel::putCIFFirst() +{ + const auto iter = std::find_if(sizes_.begin(), sizes_.end(), + [] (const VideoV4l2Size &size) + { + return size.width == 352 and size.height == 258; + }); + + if (iter != sizes_.end() and iter != sizes_.begin()) + std::swap(*iter, *sizes_.begin()); +} + +void VideoV4l2Channel::getFormat(int fd) +{ + if (ioctl(fd, VIDIOC_S_INPUT, &idx)) + throw std::runtime_error("VIDIOC_S_INPUT failed"); + + v4l2_fmtdesc fmt; + ZEROVAR(fmt); + unsigned fmt_index; + fmt.index = fmt_index = 0; + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + unsigned int best_score = UINT_MAX; + unsigned int best_idx = 0; + unsigned int pixelformat = 0; + while (!ioctl(fd, VIDIOC_ENUM_FMT, &fmt)) { + if (fmt_index != fmt.index) + break; + + unsigned int score = pixelformat_score(fmt.pixelformat); + if (score < best_score) { + pixelformat = fmt.pixelformat; + best_idx = fmt_index; + best_score = score; + } + + fmt.index = ++fmt_index; + } + if (fmt_index == 0) + throw std::runtime_error("Could not enumerate formats"); + + fmt.index = best_idx; + pixelformat = getSizes(fd, pixelformat); + putCIFFirst(); + + setFourcc(pixelformat); +} + +const VideoV4l2Size& +VideoV4l2Channel::getSize(const string &name) const +{ + for (const auto &item : sizes_) { + stringstream ss; + ss << item.width << "x" << item.height; + if (ss.str() == name) + return item; + } + + // fallback to last size + assert(not sizes_.empty()); + return sizes_.back(); +} + +VideoDeviceImpl::VideoDeviceImpl(const string& path) : + device(path), name(), channels_(), + channel_(-1, ""), size_(-1, -1), rate_(-1) +{ + int fd = open(device.c_str(), O_RDWR); + if (fd == -1) + throw std::runtime_error("could not open device"); + + v4l2_capability cap; + if (ioctl(fd, VIDIOC_QUERYCAP, &cap)) + throw std::runtime_error("could not query capabilities"); + + if (!(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)) + throw std::runtime_error("not a capture device"); + + name = string(reinterpret_cast<const char*>(cap.card)); + + v4l2_input input; + ZEROVAR(input); + unsigned idx; + input.index = idx = 0; + while (!ioctl(fd, VIDIOC_ENUMINPUT, &input)) { + if (idx != input.index) + break; + + if (input.type & V4L2_INPUT_TYPE_CAMERA) { + VideoV4l2Channel channel(idx, (const char*) input.name); + channel.getFormat(fd); + channels_.push_back(channel); + } + + input.index = ++idx; + } + + ::close(fd); + + // Set default settings + applySettings(VideoSettings()); +} + +vector<string> VideoDeviceImpl::getChannelList() const +{ + vector<string> v; + + for (const auto &itr : channels_) + v.push_back(itr.name); + + return v; +} + +vector<string> +VideoDeviceImpl::getSizeList(const string& channel) const +{ + return getChannel(channel).getSizeList(); +} + +vector<string> +VideoDeviceImpl::getRateList(const string& channel, const string& size) const +{ + return getChannel(channel).getSize(size).getRateList(); +} + +const VideoV4l2Channel& +VideoDeviceImpl::getChannel(const string &name) const +{ + for (const auto &item : channels_) + if (item.name == name) + return item; + + assert(not channels_.empty()); + return channels_.back(); +} + +void +VideoDeviceImpl::applySettings(VideoSettings settings) +{ + // Set preferences or fallback to defaults. + channel_ = getChannel(settings.channel); + size_ = channel_.getSize(settings.video_size); + rate_ = size_.getRate(settings.framerate); +} + +VideoSettings +VideoDeviceImpl::getSettings() const +{ + VideoSettings settings; + settings.name = name; + settings.channel = channel_.name; + stringstream video_size; + video_size << size_.width << "x" << size_.height; + settings.video_size = video_size.str(); + settings.framerate = rate_; + return settings; +} + +DeviceParams +VideoDeviceImpl::getDeviceParams() const +{ + DeviceParams params; + params.input = device; + params.format = "video4linux2"; + params.channel = channel_.idx; + params.width = size_.width; + params.height = size_.height; + params.framerate = rate_; + return params; +} + +VideoDevice::VideoDevice(const string& path) : + deviceImpl_(new VideoDeviceImpl(path)) +{ + node_ = path; + name = deviceImpl_->name; +} + +void +VideoDevice::applySettings(VideoSettings settings) +{ + deviceImpl_->applySettings(settings); +} + +VideoSettings +VideoDevice::getSettings() const +{ + return deviceImpl_->getSettings(); +} + +DeviceParams +VideoDevice::getDeviceParams() const +{ + return deviceImpl_->getDeviceParams(); +} + +DRing::VideoCapabilities +VideoDevice::getCapabilities() const +{ + DRing::VideoCapabilities cap; + + for (const auto& chan : deviceImpl_->getChannelList()) + for (const auto& size : deviceImpl_->getSizeList(chan)) + cap[chan][size] = deviceImpl_->getRateList(chan, size); + + return cap; +} + +VideoDevice::~VideoDevice() +{} + +}} // namespace ring::video diff --git a/src/media/video/v4l2/video_device_monitor_impl.cpp b/src/media/video/v4l2/video_device_monitor_impl.cpp new file mode 100644 index 0000000000..8d867f647b --- /dev/null +++ b/src/media/video/v4l2/video_device_monitor_impl.cpp @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2009 Rémi Denis-Courmont + * + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Rafaël Carré <rafael.carre@savoirfairelinux.com> + * Author: Vivien Didelot <vivien.didelot@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <algorithm> +#include <cerrno> +#include <cstdio> +#include <cstring> +#include <libudev.h> +#include <mutex> +#include <sstream> +#include <stdexcept> // for std::runtime_error +#include <string> +#include <thread> +#include <unistd.h> +#include <vector> + +#include "../video_device_monitor.h" +#include "logger.h" +#include "noncopyable.h" + +extern "C" { +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +} + +namespace ring { namespace video { + +using std::vector; +using std::string; + +class VideoDeviceMonitorImpl { + public: + /* + * This is the only restriction to the pImpl design: + * as the Linux implementation has a thread, it needs a way to notify + * devices addition and deletion. + * + * This class should maybe inherit from VideoDeviceMonitor instead of + * being its pImpl. + */ + VideoDeviceMonitorImpl(VideoDeviceMonitor* monitor); + ~VideoDeviceMonitorImpl(); + + void start(); + + private: + NON_COPYABLE(VideoDeviceMonitorImpl); + + VideoDeviceMonitor* monitor_; + + void run(); + std::thread thread_; + mutable std::mutex mutex_; + + udev *udev_; + udev_monitor *udev_mon_; + bool probing_; +}; + +static int is_v4l2(struct udev_device *dev) +{ + const char *version = udev_device_get_property_value(dev, "ID_V4L_VERSION"); + /* we do not support video4linux 1 */ + return version and strcmp(version, "1"); +} + +VideoDeviceMonitorImpl::VideoDeviceMonitorImpl(VideoDeviceMonitor* monitor) : + monitor_(monitor), + thread_(), mutex_(), + udev_(0), udev_mon_(0), + probing_(false) +{ + udev_list_entry *devlist; + udev_enumerate *devenum; + + udev_ = udev_new(); + if (!udev_) + goto udev_failed; + + udev_mon_ = udev_monitor_new_from_netlink(udev_, "udev"); + if (!udev_mon_) + goto udev_failed; + if (udev_monitor_filter_add_match_subsystem_devtype(udev_mon_, "video4linux", NULL)) + goto udev_failed; + + /* Enumerate existing devices */ + devenum = udev_enumerate_new(udev_); + if (devenum == NULL) + goto udev_failed; + if (udev_enumerate_add_match_subsystem(devenum, "video4linux")) { + udev_enumerate_unref(devenum); + goto udev_failed; + } + + udev_monitor_enable_receiving(udev_mon_); + /* Note that we enumerate _after_ monitoring is enabled so that we do not + * loose device events occuring while we are enumerating. We could still + * loose events if the Netlink socket receive buffer overflows. */ + udev_enumerate_scan_devices(devenum); + devlist = udev_enumerate_get_list_entry(devenum); + struct udev_list_entry *deventry; + udev_list_entry_foreach(deventry, devlist) { + const char *path = udev_list_entry_get_name(deventry); + struct udev_device *dev = udev_device_new_from_syspath(udev_, path); + + if (is_v4l2(dev)) { + const char *devpath = udev_device_get_devnode(dev); + if (devpath) { + try { + monitor_->addDevice(string(devpath)); + } catch (const std::runtime_error &e) { + RING_ERR("%s", e.what()); + } + } + } + udev_device_unref(dev); + } + udev_enumerate_unref(devenum); + + return; + +udev_failed: + + RING_ERR("udev enumeration failed"); + + if (udev_mon_) + udev_monitor_unref(udev_mon_); + if (udev_) + udev_unref(udev_); + udev_mon_ = NULL; + udev_ = NULL; + + /* fallback : go through /dev/video* */ + for (int idx = 0;; ++idx) { + std::stringstream ss; + ss << "/dev/video" << idx; + try { + monitor_->addDevice(ss.str()); + } catch (const std::runtime_error &e) { + RING_ERR("%s", e.what()); + return; + } + } +} + +void VideoDeviceMonitorImpl::start() +{ + probing_ = true; + thread_ = std::thread(&VideoDeviceMonitorImpl::run, this); +} + +VideoDeviceMonitorImpl::~VideoDeviceMonitorImpl() +{ + probing_ = false; + if (thread_.joinable()) + thread_.join(); + if (udev_mon_) + udev_monitor_unref(udev_mon_); + if (udev_) + udev_unref(udev_); +} + +void VideoDeviceMonitorImpl::run() +{ + if (!udev_mon_) { + probing_ = false; + return; + } + + const int udev_fd = udev_monitor_get_fd(udev_mon_); + while (probing_) { + timeval timeout = {0 /* sec */, 500000 /* usec */}; + fd_set set; + FD_ZERO(&set); + FD_SET(udev_fd, &set); + + int ret = select(udev_fd + 1, &set, NULL, NULL, &timeout); + switch (ret) { + case 0: + break; + case 1: + { + udev_device *dev = udev_monitor_receive_device(udev_mon_); + if (!is_v4l2(dev)) { + udev_device_unref(dev); + break; + } + + const char *node = udev_device_get_devnode(dev); + const char *action = udev_device_get_action(dev); + if (!strcmp(action, "add")) { + RING_DBG("udev: adding %s", node); + try { + monitor_->addDevice(node); + } catch (const std::runtime_error &e) { + RING_ERR("%s", e.what()); + } + } else if (!strcmp(action, "remove")) { + RING_DBG("udev: removing %s", node); + monitor_->removeDevice(string(node)); + } + udev_device_unref(dev); + break; + } + + case -1: + if (errno == EAGAIN) + continue; + RING_ERR("udev monitoring thread: select failed (%m)"); + probing_ = false; + return; + + default: + RING_ERR("select() returned %d (%m)", ret); + probing_ = false; + return; + } + } +} + +VideoDeviceMonitor::VideoDeviceMonitor() : + preferences_(), devices_(), + monitorImpl_(new VideoDeviceMonitorImpl(this)) +{ + monitorImpl_->start(); +} + +VideoDeviceMonitor::~VideoDeviceMonitor() +{} + +}} // namespace ring::video diff --git a/src/media/video/video_base.cpp b/src/media/video/video_base.cpp new file mode 100644 index 0000000000..19744d2b52 --- /dev/null +++ b/src/media/video/video_base.cpp @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2013-2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "libav_deps.h" // MUST BE INCLUDED FIRST +#include "video_base.h" +#include "media_buffer.h" +#include "string_utils.h" +#include "logger.h" + +#include <cassert> + +namespace ring { namespace video { + +/*=== VideoPacket ===========================================================*/ + +VideoPacket::VideoPacket() : packet_(static_cast<AVPacket *>(av_mallocz(sizeof(AVPacket)))) +{ + av_init_packet(packet_); +} + +VideoPacket::~VideoPacket() { av_free_packet(packet_); av_free(packet_); } + +/*=== VideoGenerator =========================================================*/ + +VideoFrame& +VideoGenerator::getNewFrame() +{ + std::lock_guard<std::mutex> lk(mutex_); + if (writableFrame_) + writableFrame_->reset(); + else + writableFrame_.reset(new VideoFrame()); + return *writableFrame_.get(); +} + +void +VideoGenerator::publishFrame() +{ + std::lock_guard<std::mutex> lk(mutex_); + lastFrame_ = std::move(writableFrame_); + notify(lastFrame_); +} + +void +VideoGenerator::flushFrames() +{ + std::lock_guard<std::mutex> lk(mutex_); + writableFrame_.reset(); + lastFrame_.reset(); +} + +std::shared_ptr<VideoFrame> +VideoGenerator::obtainLastFrame() +{ + std::lock_guard<std::mutex> lk(mutex_); + return lastFrame_; +} + +/*=== VideoSettings =========================================================*/ + +static std::string +extractString(const std::map<std::string, std::string>& settings, const std::string& key) { + auto i = settings.find(key); + if (i != settings.cend()) + return i->second; + return {}; +} + +static unsigned +extractInt(const std::map<std::string, std::string>& settings, const std::string& key) { + auto i = settings.find(key); + if (i != settings.cend()) + return std::stoi(i->second); + return 0; +} + +VideoSettings::VideoSettings(const std::map<std::string, std::string>& settings) +{ + name = extractString(settings, "name"); + channel = extractString(settings, "channel"); + video_size = extractString(settings, "size"); + framerate = extractInt(settings, "rate"); +} + +std::map<std::string, std::string> +VideoSettings::to_map() const +{ + return { + {"name", name}, + {"size", video_size}, + {"channel", channel}, + {"rate", ring::to_string(framerate)} + }; +} + +}} // namespace ring::video + +namespace YAML { + +Node +convert<ring::video::VideoSettings>::encode(const ring::video::VideoSettings& rhs) { + Node node; + node["name"] = rhs.name; + node["video_size"] = rhs.video_size; + node["channel"] = rhs.channel; + node["framerate"] = ring::to_string(rhs.framerate); + return node; +} + +bool +convert<ring::video::VideoSettings>::decode(const Node& node, ring::video::VideoSettings& rhs) { + if (not node.IsMap()) { + RING_WARN("Can't decode VideoSettings YAML node"); + return false; + } + rhs.name = node["name"].as<std::string>(); + rhs.video_size = node["video_size"].as<std::string>(); + rhs.channel = node["channel"].as<std::string>(); + rhs.framerate = node["framerate"].as<unsigned>(); + return true; +} + +Emitter& operator << (Emitter& out, const ring::video::VideoSettings& v) { + out << convert<ring::video::VideoSettings>::encode(v); + return out; +} + +} // namespace YAML diff --git a/src/media/video/video_base.h b/src/media/video/video_base.h new file mode 100644 index 0000000000..e7299db060 --- /dev/null +++ b/src/media/video/video_base.h @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2013-2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#pragma once + +#include "noncopyable.h" + +#include <yaml-cpp/yaml.h> + +#include <cstdlib> +#include <cstdint> +#include <memory> +#include <set> +#include <mutex> + +class AVPacket; +class AVDictionary; + +#ifndef AVFORMAT_AVIO_H +class AVIOContext; +#endif + +namespace ring { +class VideoFrame; +} + +namespace ring { namespace video { + +enum VideoPixelFormat { + VIDEO_PIXFMT_BGRA = -1, + VIDEO_PIXFMT_YUV420P = -2, + VIDEO_PIXFMT_RGBA = -3, +}; + +template <typename T> class Observer; +template <typename T> class Observable; + +/*=== Observable =============================================================*/ + +template <typename T> +class Observable +{ + public: + Observable() : observers_(), mutex_() {} + virtual ~Observable() { + std::lock_guard<std::mutex> lk(mutex_); + for (auto &o : observers_) + o->detached(this); + }; + + bool attach(Observer<T>* o) { + std::lock_guard<std::mutex> lk(mutex_); + if (o and observers_.insert(o).second) { + o->attached(this); + return true; + } + return false; + } + + bool detach(Observer<T>* o) { + std::lock_guard<std::mutex> lk(mutex_); + if (o and observers_.erase(o)) { + o->detached(this); + return true; + } + return false; + } + + void notify(T& data) { + std::lock_guard<std::mutex> lk(mutex_); + for (auto observer : observers_) + observer->update(this, data); + } + + int getObserversCount() { + std::lock_guard<std::mutex> lk(mutex_); + return observers_.size(); + } + + private: + NON_COPYABLE(Observable<T>); + + std::set<Observer<T>*> observers_; + std::mutex mutex_; // lock observers_ +}; + +/*=== Observer =============================================================*/ + +template <typename T> +class Observer +{ +public: + virtual ~Observer() {}; + virtual void update(Observable<T>*, T&) = 0; + virtual void attached(Observable<T>*) {}; + virtual void detached(Observable<T>*) {}; +}; + +struct VideoFrameActiveWriter: Observable<std::shared_ptr<VideoFrame>> {}; +struct VideoFramePassiveReader: Observer<std::shared_ptr<VideoFrame>> {}; + +/*=== VideoPacket ===========================================================*/ + +class VideoPacket { + public: + VideoPacket(); + ~VideoPacket(); + AVPacket* get() { return packet_; }; + + private: + NON_COPYABLE(VideoPacket); + AVPacket *packet_; +}; + +/*=== VideoGenerator =========================================================*/ + +class VideoGenerator : public VideoFrameActiveWriter +{ +public: + VideoGenerator() { } + + virtual int getWidth() const = 0; + virtual int getHeight() const = 0; + virtual int getPixelFormat() const = 0; + + std::shared_ptr<VideoFrame> obtainLastFrame(); + +protected: + // getNewFrame and publishFrame must be called by the same thread only + VideoFrame& getNewFrame(); + void publishFrame(); + void flushFrames(); + +private: + std::shared_ptr<VideoFrame> writableFrame_ = nullptr; + std::shared_ptr<VideoFrame> lastFrame_ = nullptr; + std::mutex mutex_ = {}; // lock writableFrame_/lastFrame_ access +}; + +struct VideoSettings +{ + VideoSettings() {} + VideoSettings(const std::map<std::string, std::string>& settings); + + std::map<std::string, std::string> to_map() const; + + std::string name {}; + std::string video_size {}; + std::string channel {}; + unsigned framerate {}; +}; + +}} // namespace ring::video + +namespace YAML { +template<> +struct convert<ring::video::VideoSettings> { + static Node encode(const ring::video::VideoSettings& rhs); + static bool decode(const Node& node, ring::video::VideoSettings& rhs); +}; + +Emitter& operator << (Emitter& out, const ring::video::VideoSettings& v); + +} // namespace YAML diff --git a/src/media/video/video_device.h b/src/media/video/video_device.h new file mode 100644 index 0000000000..92a3123d19 --- /dev/null +++ b/src/media/video/video_device.h @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2014-2015 Savoir-Faire Linux Inc. + * Author: Vivien Didelot <vivien.didelot@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __VIDEO_DEVICE_H__ +#define __VIDEO_DEVICE_H__ + +#include "media/media_device.h" +#include "video_base.h" + +#include <map> +#include <memory> +#include <string> +#include <vector> +#include "videomanager_interface.h" + +namespace ring { namespace video { + +class VideoDeviceImpl; + +class VideoDevice { +public: + + VideoDevice(const std::string& path); + ~VideoDevice(); + + /* + * The device name, e.g. "Integrated Camera", + * actually used as the identifier. + */ + std::string name = ""; + + const std::string& getNode() const { return node_; } + + /* + * Get the 3 level deep tree of possible settings for the device. + * The levels are channels, sizes, and rates. + * + * The result map for the "Integrated Camera" looks like this: + * + * {'Camera 1': {'1280x720': ['10'], + * '320x240': ['30', '15'], + * '352x288': ['30', '15'], + * '424x240': ['30', '15'], + * '640x360': ['30', '15'], + * '640x480': ['30', '15'], + * '800x448': ['15'], + * '960x540': ['10']}} + */ + DRing::VideoCapabilities getCapabilities() const; + + /* + * Get the settings for the device. + */ + VideoSettings getSettings() const; + + /* + * Setup the device with the preferences listed in the "settings" map. + * The expected map should be similar to the result of getSettings(). + * + * If a key is missing, a valid default value is choosen. Thus, calling + * this function with an empty map will reset the device to default. + */ + void applySettings(VideoSettings settings); + + /** + * Returns the parameters needed for actual use of the device + */ + DeviceParams getDeviceParams() const; + +private: + + /* + * The device node, e.g. "/dev/video0". + */ + std::string node_ = ""; + + /* + * Device specific implementation. + * On Linux, V4L2 stuffs go there. + * + * Note: since a VideoDevice is copyable, + * deviceImpl_ cannot be an unique_ptr. + */ + std::shared_ptr<VideoDeviceImpl> deviceImpl_; +}; + +}} // namespace ring::video + +#endif // __VIDEO_DEVICE_H__ diff --git a/src/media/video/video_device_monitor.cpp b/src/media/video/video_device_monitor.cpp new file mode 100644 index 0000000000..f8fdc07d39 --- /dev/null +++ b/src/media/video/video_device_monitor.cpp @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author: Vivien Didelot <vivien.didelot@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <algorithm> +#include <cassert> +#include <sstream> + +#include <yaml-cpp/yaml.h> + +#include "manager.h" +#include "client/videomanager.h" +#include "client/signal.h" +#include "config/yamlparser.h" +#include "logger.h" +#include "video_device_monitor.h" + +namespace ring { namespace video { + +constexpr const char * const VideoDeviceMonitor::CONFIG_LABEL; + +using std::map; +using std::string; +using std::stringstream;; +using std::vector; + +vector<string> +VideoDeviceMonitor::getDeviceList() const +{ + vector<string> names; + + for (const auto& dev : devices_) + names.push_back(dev.name); + + return names; +} + +DRing::VideoCapabilities +VideoDeviceMonitor::getCapabilities(const string& name) const +{ + const auto iter = findDeviceByName(name); + + if (iter == devices_.end()) + return DRing::VideoCapabilities(); + + return iter->getCapabilities(); +} + +VideoSettings +VideoDeviceMonitor::getSettings(const string& name) +{ + const auto itd = findDeviceByName(name); + + if (itd == devices_.end()) + return VideoSettings(); + + return itd->getSettings(); +} + +void +VideoDeviceMonitor::applySettings(const string& name, VideoSettings settings) +{ + const auto iter = findDeviceByName(name); + + if (iter == devices_.end()) + return; + + iter->applySettings(settings); + overwritePreferences(iter->getSettings()); +} + +string +VideoDeviceMonitor::getDefaultDevice() const +{ + return defaultDevice_; +} + +std::string +VideoDeviceMonitor::getMRLForDefaultDevice() const +{ + const auto it = findDeviceByName(defaultDevice_); + if(it == std::end(devices_)) + return {}; + return "camera://" + it->getSettings().name; +} + +void +VideoDeviceMonitor::setDefaultDevice(const std::string& name) +{ + const auto it = findDeviceByName(name); + if (it != devices_.end()) + defaultDevice_ = it->name; +} + +DeviceParams +VideoDeviceMonitor::getDeviceParams(const std::string& name) const +{ + const auto itd = findDeviceByName(name); + if (itd == devices_.cend()) + return DeviceParams(); + return itd->getDeviceParams(); +} + +static int +getNumber(const string &name, size_t *sharp) +{ + size_t len = name.length(); + // name is too short to be numbered + if (len < 3) + return -1; + + for (size_t c = len; c; --c) { + if (name[c] == '#') { + unsigned i; + if (sscanf(name.substr(c).c_str(), "#%u", &i) != 1) + return -1; + *sharp = c; + return i; + } + } + + return -1; +} + +static void +giveUniqueName(VideoDevice &dev, const vector<VideoDevice> &devices) +{ +start: + for (auto &item : devices) { + if (dev.name == item.name) { + size_t sharp; + int num = getNumber(dev.name, &sharp); + if (num < 0) // not numbered + dev.name += " #0"; + else { + stringstream ss; + ss << num + 1; + dev.name.replace(sharp + 1, ss.str().length(), ss.str()); + } + goto start; // we changed the name, let's look again if it is unique + } + } +} + +static void +notify() +{ + if (!ManagerImpl::initialized) { + RING_WARN("Manager not initialized yet"); + return; + } + emitSignal<DRing::VideoSignal::DeviceEvent>(); +} + +void +VideoDeviceMonitor::addDevice(const string& node) +{ + if (findDeviceByNode(node) != devices_.end()) + return; + + // instantiate a new unique device + VideoDevice dev(node); + giveUniqueName(dev, devices_); + + // restore its preferences if any, or store the defaults + auto it = findPreferencesByName(dev.name); + if (it != preferences_.end()) + dev.applySettings(*it); + else + preferences_.push_back(dev.getSettings()); + + // in case there is no default device on a fresh run + if (defaultDevice_.empty()) + defaultDevice_ = dev.name; + + devices_.push_back(dev); + notify(); +} + +void +VideoDeviceMonitor::removeDevice(const string& node) +{ + const auto it = findDeviceByNode(node); + + if (it == devices_.end()) + return; + + if (defaultDevice_ == it->name) + defaultDevice_.clear(); + + devices_.erase(it); + notify(); +} + +vector<VideoDevice>::iterator +VideoDeviceMonitor::findDeviceByName(const string& name) +{ + vector<VideoDevice>::iterator it; + + for (it = devices_.begin(); it != devices_.end(); ++it) + if (it->name == name) + break; + + return it; +} + +vector<VideoDevice>::const_iterator +VideoDeviceMonitor::findDeviceByName(const string& name) const +{ + vector<VideoDevice>::const_iterator it; + + for (it = devices_.cbegin(); it != devices_.cend(); ++it) + if (it->name == name) + break; + + return it; +} + +vector<VideoDevice>::iterator +VideoDeviceMonitor::findDeviceByNode(const string& node) +{ + vector<VideoDevice>::iterator it; + + for (it = devices_.begin(); it != devices_.end(); ++it) + if (it->getNode() == node) + break; + + return it; +} + +vector<VideoDevice>::const_iterator +VideoDeviceMonitor::findDeviceByNode(const string& node) const +{ + vector<VideoDevice>::const_iterator it; + + for (it = devices_.cbegin(); it != devices_.cend(); ++it) + if (it->getNode() == node) + break; + + return it; +} + +vector<VideoSettings>::iterator +VideoDeviceMonitor::findPreferencesByName(const string& name) +{ + for (auto it = preferences_.begin(); it != preferences_.end(); ++it) + if (it->name == name) return it; + return preferences_.end(); +} + +void +VideoDeviceMonitor::overwritePreferences(VideoSettings settings) +{ + auto it = findPreferencesByName(settings.name); + if (it != preferences_.end()) + preferences_.erase(it); + preferences_.push_back(settings); +} + +void +VideoDeviceMonitor::serialize(YAML::Emitter &out) +{ + out << YAML::Key << CONFIG_LABEL << YAML::Value << YAML::BeginMap; + out << YAML::Key << "devices" << YAML::Value << preferences_; + out << YAML::EndMap; +} + +void +VideoDeviceMonitor::unserialize(const YAML::Node &in) +{ + const auto &node = in[CONFIG_LABEL]; + + /* load the device list from the "video" YAML section */ + const auto& devices = node["devices"]; + for (const auto& dev : devices) { + VideoSettings pref = dev.as<VideoSettings>(); + if (pref.name.empty()) + continue; // discard malformed section + overwritePreferences(pref); + auto itd = findDeviceByName(pref.name); + if (itd != devices_.end()) + itd->applySettings(pref); + } + + // Restore the default device if present, or select the first one + const string pref = preferences_.empty() ? "" : preferences_[0].name; + const string first = devices_.empty() ? "" : devices_[0].name; + if (findDeviceByName(pref) != devices_.end()) + defaultDevice_ = pref; + else + defaultDevice_ = first; +} + +}} // namespace ring::video diff --git a/src/media/video/video_device_monitor.h b/src/media/video/video_device_monitor.h new file mode 100644 index 0000000000..8219faa126 --- /dev/null +++ b/src/media/video/video_device_monitor.h @@ -0,0 +1,114 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author: Vivien Didelot <vivien.didelot@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef VIDEO_DEVICE_MONITOR_H__ +#define VIDEO_DEVICE_MONITOR_H__ + +#include "config/serializable.h" +#include "noncopyable.h" + +#include <map> +#include <string> +#include <memory> + +#include "video_device.h" + +namespace YAML { +class Emitter; +class Node; +} + +namespace ring { namespace video { + +class VideoDeviceMonitorImpl; + +class VideoDeviceMonitor : public Serializable +{ + public: + VideoDeviceMonitor(); + ~VideoDeviceMonitor(); + + std::vector<std::string> getDeviceList() const; + + DRing::VideoCapabilities getCapabilities(const std::string& name) const; + VideoSettings getSettings(const std::string& name); + void applySettings(const std::string& name, VideoSettings settings); + + std::string getDefaultDevice() const; + std::string getMRLForDefaultDevice() const; + void setDefaultDevice(const std::string& name); + + void addDevice(const std::string &node); + void removeDevice(const std::string &node); + + /** + * Params for libav + */ + DeviceParams getDeviceParams(const std::string& name) const; + + /* + * Interface to load from/store to the (YAML) configuration file. + */ + void serialize(YAML::Emitter &out); + virtual void unserialize(const YAML::Node &in); + + private: + NON_COPYABLE(VideoDeviceMonitor); + + /* + * User preferred settings for a device, + * as loaded from (and stored to) the configuration file. + */ + std::vector<VideoSettings> preferences_; + + void overwritePreferences(VideoSettings settings); + std::vector<VideoSettings>::iterator findPreferencesByName(const std::string& name); + + /* + * Vector containing the video devices. + */ + std::vector<VideoDevice> devices_; + std::string defaultDevice_ = ""; + + std::vector<VideoDevice>::iterator findDeviceByName(const std::string& name); + std::vector<VideoDevice>::const_iterator findDeviceByName(const std::string& name) const; + std::vector<VideoDevice>::iterator findDeviceByNode(const std::string& node); + std::vector<VideoDevice>::const_iterator findDeviceByNode(const std::string& node) const; + + std::unique_ptr<VideoDeviceMonitorImpl> monitorImpl_; + + constexpr static const char *CONFIG_LABEL = "video"; +}; + +}} // namespace ring::video + +#endif /* VIDEO_DEVICE_MONITOR_H__ */ diff --git a/src/media/video/video_input.cpp b/src/media/video/video_input.cpp new file mode 100644 index 0000000000..c11f19524c --- /dev/null +++ b/src/media/video/video_input.cpp @@ -0,0 +1,368 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * Author: Vivien Didelot <vivien.didelot@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifdef RING_VIDEO +#include "video_input.h" +#endif // RING_VIDEO + +#include "media_decoder.h" +#include "manager.h" +#include "client/videomanager.h" +#include "client/signal.h" +#include "sinkclient.h" +#include "logger.h" + +#include <map> +#include <string> +#include <sstream> +#include <cassert> +#include <unistd.h> + +namespace ring { namespace video { + +VideoInput::VideoInput() + : VideoGenerator::VideoGenerator() + , sink_ {Manager::instance().createSinkClient("local")} + , loop_(std::bind(&VideoInput::setup, this), + std::bind(&VideoInput::process, this), + std::bind(&VideoInput::cleanup, this)) +{} + +VideoInput::~VideoInput() +{ + loop_.join(); +} + +bool VideoInput::setup() +{ + /* Sink setup */ + if (!sink_->start()) { + RING_ERR("Cannot start shared memory sink"); + return false; + } + if (not attach(sink_.get())) + RING_WARN("Failed to attach sink"); + return true; +} + +void VideoInput::process() +{ + bool newDecoderCreated = false; + + if (switchPending_.exchange(false)) { + deleteDecoder(); + createDecoder(); + newDecoderCreated = true; + } + + if (not decoder_) { + loop_.stop(); + return; + } + + captureFrame(); + + if (newDecoderCreated) { + /* Signal the client about the new sink */ + emitSignal<DRing::VideoSignal::DecodingStarted>(sink_->getId(), + sink_->openedName(), + decoder_->getWidth(), + decoder_->getHeight(), + false); + RING_DBG("LOCAL: shm sink <%s> started: size = %dx%d", + sink_->openedName().c_str(), decoder_->getWidth(), + decoder_->getHeight()); + } +} + +void VideoInput::cleanup() +{ + deleteDecoder(); + + if (detach(sink_.get())) + sink_->stop(); +} + +void VideoInput::clearOptions() +{ + decOpts_ = {}; + emulateRate_ = false; +} + +int VideoInput::interruptCb(void *data) +{ + VideoInput *context = static_cast<VideoInput*>(data); + return not context->loop_.isRunning(); +} + +bool VideoInput::captureFrame() +{ + VideoPacket pkt; + const auto ret = decoder_->decode(getNewFrame(), pkt); + + switch (ret) { + case MediaDecoder::Status::FrameFinished: + break; + + case MediaDecoder::Status::ReadError: + case MediaDecoder::Status::DecodeError: + loop_.stop(); + // fallthrough + case MediaDecoder::Status::Success: + return false; + + // Play in loop + case MediaDecoder::Status::EOFError: + deleteDecoder(); + createDecoder(); + return false; + } + + publishFrame(); + return true; +} + +void +VideoInput::createDecoder() +{ + if (decOpts_.input.empty()) { + foundDecOpts(decOpts_); + return; + } + + decoder_ = new MediaDecoder(); + + if (emulateRate_) + decoder_->emulateRate(); + + decoder_->setInterruptCallback(interruptCb, this); + + if (decoder_->openInput(decOpts_) < 0) { + RING_ERR("Could not open input \"%s\"", decOpts_.input.c_str()); + delete decoder_; + decoder_ = nullptr; + //foundDecOpts_.set_exception(std::runtime_error("Could not open input")); + foundDecOpts(decOpts_); + return; + } + + /* Data available, finish the decoding */ + if (decoder_->setupFromVideoData() < 0) { + RING_ERR("decoder IO startup failed"); + delete decoder_; + decoder_ = nullptr; + //foundDecOpts_.set_exception(std::runtime_error("Could not read data")); + foundDecOpts(decOpts_); + return; + } + decOpts_.width = decoder_->getWidth(); + decOpts_.height = decoder_->getHeight(); + decOpts_.framerate = decoder_->getFps(); + RING_INFO("create decoder with video params : size=%dX%d, fps=%d", + decOpts_.width, + decOpts_.height, + decOpts_.framerate); + foundDecOpts(decOpts_); +} + +void +VideoInput::deleteDecoder() +{ + if (not decoder_) + return; + + emitSignal<DRing::VideoSignal::DecodingStopped>(sink_->getId(), + sink_->openedName(), + false); + flushFrames(); + delete decoder_; + decoder_ = nullptr; +} + +bool +VideoInput::initCamera(const std::string& device) +{ + decOpts_ = ring::getVideoDeviceMonitor().getDeviceParams(device); + return true; +} + +static constexpr unsigned +round2pow(unsigned i, unsigned n) +{ + return (i >> n) << n; +} + +bool +VideoInput::initX11(std::string display) +{ + size_t space = display.find(' '); + + clearOptions(); + decOpts_.format = "x11grab"; + decOpts_.framerate = 25; + + if (space != std::string::npos) { + std::istringstream iss(display.substr(space + 1)); + char sep; + unsigned w, h; + iss >> w >> sep >> h; + // round to 8 pixel block + decOpts_.width = round2pow(w, 3); + decOpts_.height = round2pow(h, 3); + decOpts_.input = display.erase(space); + } else { + decOpts_.input = display; + //decOpts_.video_size = "vga"; + decOpts_.width = 640; + decOpts_.height = 480; + } + + return true; +} + +bool +VideoInput::initFile(std::string path) +{ + size_t dot = path.find_last_of('.'); + std::string ext = dot == std::string::npos ? "" : path.substr(dot + 1); + + /* File exists? */ + if (access(path.c_str(), R_OK) != 0) { + RING_ERR("file '%s' unavailable\n", path.c_str()); + return false; + } + + clearOptions(); + emulateRate_ = true; + decOpts_.input = path; + decOpts_.loop = "1"; + + // Force 1fps for static image + if (ext == "jpeg" || ext == "jpg" || ext == "png") { + decOpts_.format = "image2"; + decOpts_.framerate = 1; + } else { + RING_WARN("Guessing file type for %s", path.c_str()); + } + + return true; +} + +std::shared_future<DeviceParams> +VideoInput::switchInput(const std::string& resource) +{ + if (resource == currentResource_) + return futureDecOpts_; + + RING_DBG("MRL: '%s'", resource.c_str()); + + if (switchPending_) { + RING_ERR("Video switch already requested"); + return {}; + } + + currentResource_ = resource; + decOptsFound_ = false; + + std::promise<DeviceParams> p; + foundDecOpts_.swap(p); + + // Switch off video input? + if (resource.empty()) { + clearOptions(); + switchPending_ = true; + if (!loop_.isRunning()) + loop_.start(); + futureDecOpts_ = foundDecOpts_.get_future(); + return futureDecOpts_; + } + + // Supported MRL schemes + static const std::string sep = "://"; + + const auto pos = resource.find(sep); + if (pos == std::string::npos) + return {}; + + const auto prefix = resource.substr(0, pos); + if ((pos + sep.size()) >= resource.size()) + return {}; + + const auto suffix = resource.substr(pos + sep.size()); + + bool valid = false; + + if (prefix == "camera") { + /* Video4Linux2 */ + valid = initCamera(suffix); + } else if (prefix == "display") { + /* X11 display name */ + valid = initX11(suffix); + } else if (prefix == "file") { + /* Pathname */ + valid = initFile(suffix); + } + + // Unsupported MRL or failed initialization + if (not valid) { + RING_ERR("Failed to init input for MRL '%s'\n", resource.c_str()); + return {}; + } + + switchPending_ = true; + if (!loop_.isRunning()) + loop_.start(); + futureDecOpts_ = foundDecOpts_.get_future().share(); + return futureDecOpts_; +} + +int VideoInput::getWidth() const +{ return decoder_->getWidth(); } + +int VideoInput::getHeight() const +{ return decoder_->getHeight(); } + +int VideoInput::getPixelFormat() const +{ return decoder_->getPixelFormat(); } + +DeviceParams VideoInput::getParams() const +{ return decOpts_; } + +void +VideoInput::foundDecOpts(const DeviceParams& params) +{ + if (not decOptsFound_) { + decOptsFound_ = true; + foundDecOpts_.set_value(params); + } +} + +}} // namespace ring::video diff --git a/src/media/video/video_input.h b/src/media/video/video_input.h new file mode 100644 index 0000000000..6e9130f9f1 --- /dev/null +++ b/src/media/video/video_input.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2011-2015 Savoir-Faire Linux Inc. + * + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * Author: Vivien Didelot <vivien.didelot@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __VIDEO_INPUT_H__ +#define __VIDEO_INPUT_H__ + +#include "noncopyable.h" +#include "threadloop.h" +#include "media/media_device.h" // DeviceParams + +#include <map> +#include <atomic> +#include <future> +#include <string> + +namespace ring { +class MediaDecoder; +} + +namespace ring { namespace video { + +class SinkClient; + +class VideoInput : public VideoGenerator +{ +public: + VideoInput(); + ~VideoInput(); + + // as VideoGenerator + int getWidth() const; + int getHeight() const; + int getPixelFormat() const; + DeviceParams getParams() const; + + std::shared_future<DeviceParams> switchInput(const std::string& resource); + +private: + NON_COPYABLE(VideoInput); + + std::string currentResource_; + + MediaDecoder *decoder_ = nullptr; + std::shared_ptr<SinkClient> sink_; + std::atomic<bool> switchPending_ = {false}; + + DeviceParams decOpts_; + std::promise<DeviceParams> foundDecOpts_; + std::shared_future<DeviceParams> futureDecOpts_; + + std::atomic_bool decOptsFound_ {false}; + void foundDecOpts(const DeviceParams& params); + + bool emulateRate_ = false; + ThreadLoop loop_; + + void clearOptions(); + + void createDecoder(); + void deleteDecoder(); + + bool initCamera(const std::string& device); + bool initX11(std::string display); + bool initFile(std::string path); + + // for ThreadLoop + bool setup(); + void process(); + void cleanup(); + + static int interruptCb(void *ctx); + bool captureFrame(); +}; + +}} // namespace ring::video + +#endif // __VIDEO_INPUT_H__ diff --git a/src/media/video/video_mixer.cpp b/src/media/video/video_mixer.cpp new file mode 100644 index 0000000000..f699550a89 --- /dev/null +++ b/src/media/video/video_mixer.cpp @@ -0,0 +1,228 @@ +/* + * Copyright (C) 2013-2015 Savoir-Faire Linux Inc. + * + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "libav_deps.h" // MUST BE INCLUDED FIRST + +#include "video_mixer.h" +#include "media_buffer.h" +#include "logger.h" +#include "client/videomanager.h" +#include "client/signal.h" +#include "manager.h" +#include "sinkclient.h" +#include "logger.h" + +#include <cmath> +#include <unistd.h> + +namespace ring { namespace video { + +static const double FRAME_DURATION = 1/30.; + +VideoMixer::VideoMixer(const std::string &id) + : VideoGenerator::VideoGenerator() + , id_(id) + , sink_ {Manager::instance().createSinkClient(id)} + , loop_([]{return true;}, + std::bind(&VideoMixer::process, this), + []{}) +{ + // Local video camera is the main participant + videoLocal_ = getVideoCamera(); + if (videoLocal_) { + DRing::switchToCamera(); + videoLocal_->attach(this); + } + loop_.start(); +} + +VideoMixer::~VideoMixer() +{ + stop_sink(); + + if (videoLocal_) { + videoLocal_->detach(this); + // prefer to release it now than after the next join + videoLocal_.reset(); + } + + loop_.join(); +} + +void VideoMixer::attached(Observable<std::shared_ptr<VideoFrame> >* ob) +{ + auto lock(rwMutex_.write()); + + VideoMixerSource* src = new VideoMixerSource; + src->source = ob; + sources_.push_back(src); +} + +void VideoMixer::detached(Observable<std::shared_ptr<VideoFrame> >* ob) +{ + auto lock(rwMutex_.write()); + + for (auto x : sources_) { + if (x->source == ob) { + sources_.remove(x); + delete x; + break; + } + } +} + +void VideoMixer::update(Observable<std::shared_ptr<VideoFrame> >* ob, + std::shared_ptr<VideoFrame>& frame_p) +{ + auto lock(rwMutex_.read()); + + for (const auto& x : sources_) { + if (x->source == ob) { + if (!x->update_frame) + x->update_frame.reset(new VideoFrame); + *x->update_frame = *frame_p; + x->atomic_swap_render(x->update_frame); + return; + } + } +} + +void VideoMixer::process() +{ + const auto now = std::chrono::system_clock::now(); + const std::chrono::duration<double> diff = now - lastProcess_; + const double delay = FRAME_DURATION - diff.count(); + if (delay > 0) + usleep(delay * 1e6); + lastProcess_ = now; + + VideoFrame& output = getNewFrame(); + try { + output.reserve(VIDEO_PIXFMT_YUV420P, width_, height_); + } catch (const std::bad_alloc& e) { + RING_ERR("VideoFrame::allocBuffer() failed"); + return; + } + + yuv422_clear_to_black(output); + + { + auto lock(rwMutex_.read()); + + int i = 0; + for (const auto& x : sources_) { + /* thread stop pending? */ + if (!loop_.isRunning()) + return; + + // make rendered frame temporarily unavailable for update() + // to avoid concurrent access. + std::unique_ptr<VideoFrame> input; + x->atomic_swap_render(input); + + if (input) + render_frame(output, *input, i); + + x->atomic_swap_render(input); + ++i; + } + } + + publishFrame(); +} + +void +VideoMixer::render_frame(VideoFrame& output, const VideoFrame& input, int index) +{ + if (!width_ or !height_ or !input.pointer()) + return; + + const int n = sources_.size(); + const int zoom = ceil(sqrt(n)); + int cell_width = width_ / zoom; + int cell_height = height_ / zoom; + int xoff = (index % zoom) * cell_width; + int yoff = (index / zoom) * cell_height; + + scaler_.scale_and_pad(input, output, xoff, yoff, cell_width, cell_height, true); +} + +void VideoMixer::setDimensions(int width, int height) +{ + auto lock(rwMutex_.write()); + + width_ = width; + height_ = height; + + // cleanup the previous frame to have a nice copy in rendering method + std::shared_ptr<VideoFrame> previous_p(obtainLastFrame()); + if (previous_p) + yuv422_clear_to_black(*previous_p); + + stop_sink(); + start_sink(); +} + +void VideoMixer::start_sink() +{ + if (sink_->start()) { + if (this->attach(sink_.get())) { + emitSignal<DRing::VideoSignal::DecodingStarted>(id_, + sink_->openedName(), + width_, height_, + true); + RING_DBG("MX: shm sink <%s> started: size = %dx%d", + sink_->openedName().c_str(), width_, height_); + } + } else + RING_WARN("MX: sink startup failed"); +} + +void VideoMixer::stop_sink() +{ + if (this->detach(sink_.get())) { + emitSignal<DRing::VideoSignal::DecodingStopped>(id_, + sink_->openedName(), + true); + sink_->stop(); + } +} + +int VideoMixer::getWidth() const +{ return width_; } + +int VideoMixer::getHeight() const +{ return height_; } + +int VideoMixer::getPixelFormat() const +{ return VIDEO_PIXFMT_YUV420P; } + +}} // namespace ring::video diff --git a/src/media/video/video_mixer.h b/src/media/video/video_mixer.h new file mode 100644 index 0000000000..6333cb52a8 --- /dev/null +++ b/src/media/video/video_mixer.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2013-2015 Savoir-Faire Linux Inc. + * + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __VIDEO_MIXER_H__ +#define __VIDEO_MIXER_H__ + +#include "noncopyable.h" +#include "video_base.h" +#include "video_scaler.h" +#include "threadloop.h" +#include "rw_mutex.h" + +#include <list> +#include <chrono> +#include <memory> + +namespace ring { namespace video { + +class SinkClient; + + struct VideoMixerSource { + Observable<std::shared_ptr<VideoFrame>>* source = nullptr; + std::unique_ptr<VideoFrame> update_frame; + std::unique_ptr<VideoFrame> render_frame; + void atomic_swap_render(std::unique_ptr<VideoFrame>& other) { + std::lock_guard<std::mutex> lock(mutex_); + render_frame.swap(other); + } + private: + std::mutex mutex_ = {}; + }; + +class VideoMixer : + public VideoGenerator, + public VideoFramePassiveReader +{ +public: + VideoMixer(const std::string &id); + virtual ~VideoMixer(); + + void setDimensions(int width, int height); + + int getWidth() const; + int getHeight() const; + int getPixelFormat() const; + + // as VideoFramePassiveReader + void update(Observable<std::shared_ptr<VideoFrame> >* ob, + std::shared_ptr<VideoFrame>& v); + void attached(Observable<std::shared_ptr<VideoFrame> >* ob); + void detached(Observable<std::shared_ptr<VideoFrame> >* ob); + +private: + NON_COPYABLE(VideoMixer); + + void render_frame(VideoFrame& output, const VideoFrame& input, int index); + + void start_sink(); + void stop_sink(); + + void process(); + + const std::string id_; + int width_ = 0; + int height_ = 0; + std::list<VideoMixerSource *> sources_ = {}; + rw_mutex rwMutex_ = {}; + + std::shared_ptr<SinkClient> sink_; + + ThreadLoop loop_; + std::chrono::time_point<std::chrono::system_clock> lastProcess_ = {}; + std::shared_ptr<VideoFrameActiveWriter> videoLocal_ = nullptr; + VideoScaler scaler_ = {}; +}; + +}} // namespace ring::video + +#endif // __VIDEO_MIXER_H__ diff --git a/src/media/video/video_provider.h b/src/media/video/video_provider.h new file mode 100644 index 0000000000..c7f189fb75 --- /dev/null +++ b/src/media/video/video_provider.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2012-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef VIDEO_PROVIDER_H_ +#define VIDEO_PROVIDER_H_ + +namespace ring { namespace video { + +class VideoProvider { + public: + virtual void fillBuffer(void *data) = 0; + virtual ~VideoProvider() {} +}; + +}} // namespace ring::video + +#endif // VIDEO_PROVIDER_H_ diff --git a/src/media/video/video_receive_thread.cpp b/src/media/video/video_receive_thread.cpp new file mode 100644 index 0000000000..3500d3c4f8 --- /dev/null +++ b/src/media/video/video_receive_thread.cpp @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "libav_deps.h" // MUST BE INCLUDED FIRST +#include "video_receive_thread.h" +#include "media/media_decoder.h" +#include "socket_pair.h" +#include "manager.h" +#include "client/videomanager.h" +#include "client/signal.h" +#include "sinkclient.h" +#include "logger.h" + +#include <unistd.h> +#include <map> + +namespace ring { namespace video { + +using std::string; + +VideoReceiveThread::VideoReceiveThread(const std::string& id, + const std::string &sdp) : + VideoGenerator::VideoGenerator() + , args_() + , dstWidth_(0) + , dstHeight_(0) + , id_(id) + , stream_(sdp) + , sdpContext_(stream_.str().size(), false, &readFunction, 0, 0, this) + , sink_ {Manager::instance().createSinkClient(id)} + , requestKeyFrameCallback_(0) + , loop_(std::bind(&VideoReceiveThread::setup, this), + std::bind(&VideoReceiveThread::process, this), + std::bind(&VideoReceiveThread::cleanup, this)) +{} + +VideoReceiveThread::~VideoReceiveThread() +{ + loop_.join(); +} + +void +VideoReceiveThread::startLoop() +{ + loop_.start(); +} + +// We do this setup here instead of the constructor because we don't want the +// main thread to block while this executes, so it happens in the video thread. +bool VideoReceiveThread::setup() +{ + videoDecoder_.reset(new MediaDecoder()); + + dstWidth_ = args_.width; + dstHeight_ = args_.height; + + const std::string SDP_FILENAME = "dummyFilename"; + if (args_.input.empty()) { + args_.format = "sdp"; + args_.input = SDP_FILENAME; + } else if (args_.input.substr(0, strlen("/dev/video")) == "/dev/video") { + // it's a v4l device if starting with /dev/video + // FIXME: This is not a robust way of checking if we mean to use a + // v4l2 device + args_.format = "video4linux2"; + } + + videoDecoder_->setInterruptCallback(interruptCb, this); + + if (args_.input == SDP_FILENAME) { + // Force custom_io so the SDP demuxer will not open any UDP connections + // We need it to use ICE transport. + args_.sdp_flags = "custom_io"; + + EXIT_IF_FAIL(not stream_.str().empty(), "No SDP loaded"); + videoDecoder_->setIOContext(&sdpContext_); + } + + EXIT_IF_FAIL(!videoDecoder_->openInput(args_), + "Could not open input \"%s\"", args_.input.c_str()); + + if (args_.input == SDP_FILENAME) { + // Now replace our custom AVIOContext with one that will read packets + videoDecoder_->setIOContext(demuxContext_.get()); + } + + // FIXME: this is a hack because our peer sends us RTP before + // we're ready for it, and we miss the SPS/PPS. We should be + // ready earlier. + sleep(1); + if (requestKeyFrameCallback_) + requestKeyFrameCallback_(id_); + + EXIT_IF_FAIL(!videoDecoder_->setupFromVideoData(), + "decoder IO startup failed"); + + // Default size from input video + if (dstWidth_ == 0 and dstHeight_ == 0) { + dstWidth_ = videoDecoder_->getWidth(); + dstHeight_ = videoDecoder_->getHeight(); + } + + EXIT_IF_FAIL(sink_->start(), "RX: sink startup failed"); + + auto conf = Manager::instance().getConferenceFromCallID(id_); + if (!conf) + exitConference(); + + return true; +} + +void VideoReceiveThread::process() +{ decodeFrame(); } + +void VideoReceiveThread::cleanup() +{ + if (detach(sink_.get())) + emitSignal<DRing::VideoSignal::DecodingStopped>(id_, + sink_->openedName(), + false); + sink_->stop(); + + videoDecoder_.reset(); + demuxContext_.reset(); +} + +// This callback is used by libav internally to break out of blocking calls +int VideoReceiveThread::interruptCb(void *data) +{ + const auto context = static_cast<VideoReceiveThread*>(data); + return not context->loop_.isRunning(); +} + +int VideoReceiveThread::readFunction(void *opaque, uint8_t *buf, int buf_size) +{ + std::istream &is = static_cast<VideoReceiveThread*>(opaque)->stream_; + is.read(reinterpret_cast<char*>(buf), buf_size); + return is.gcount(); +} + +void VideoReceiveThread::addIOContext(SocketPair &socketPair) +{ + demuxContext_.reset(socketPair.createIOContext()); +} + +bool VideoReceiveThread::decodeFrame() +{ + VideoPacket pkt; + const auto ret = videoDecoder_->decode(getNewFrame(), pkt); + + switch (ret) { + case MediaDecoder::Status::FrameFinished: + publishFrame(); + return true; + + case MediaDecoder::Status::DecodeError: + RING_WARN("video decoding failure"); + if (requestKeyFrameCallback_) + requestKeyFrameCallback_(id_); + break; + + case MediaDecoder::Status::ReadError: + RING_ERR("fatal error, read failed"); + loop_.stop(); + + default: + break; + } + + return false; +} + + +void VideoReceiveThread::enterConference() +{ + if (!loop_.isRunning()) + return; + + if (detach(sink_.get())) { + emitSignal<DRing::VideoSignal::DecodingStopped>(id_, + sink_->openedName(), + false); + RING_DBG("RX: shm sink <%s> detached", sink_->openedName().c_str()); + } +} + +void VideoReceiveThread::exitConference() +{ + if (!loop_.isRunning()) + return; + + if (dstWidth_ > 0 && dstHeight_ > 0) { + if (attach(sink_.get())) { + emitSignal<DRing::VideoSignal::DecodingStarted>(id_, + sink_->openedName(), + dstWidth_, + dstHeight_, false); + RING_DBG("RX: shm sink <%s> started: size = %dx%d", + sink_->openedName().c_str(), dstWidth_, dstHeight_); + } + } +} + +void VideoReceiveThread::setRequestKeyFrameCallback( + void (*cb)(const std::string &)) +{ requestKeyFrameCallback_ = cb; } + +int VideoReceiveThread::getWidth() const +{ return dstWidth_; } + +int VideoReceiveThread::getHeight() const +{ return dstHeight_; } + +int VideoReceiveThread::getPixelFormat() const +{ return videoDecoder_->getPixelFormat(); } + +}} // namespace ring::video diff --git a/src/media/video/video_receive_thread.h b/src/media/video/video_receive_thread.h new file mode 100644 index 0000000000..e93c15d341 --- /dev/null +++ b/src/media/video/video_receive_thread.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2011-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef _VIDEO_RECEIVE_THREAD_H_ +#define _VIDEO_RECEIVE_THREAD_H_ + +#include "video_base.h" +#include "media_codec.h" +#include "media_io_handle.h" +#include "media_device.h" +#include "threadloop.h" +#include "noncopyable.h" + +#include <map> +#include <string> +#include <climits> +#include <sstream> +#include <memory> + +namespace ring { +class SocketPair; +class MediaDecoder; +} // namespace ring + +namespace ring { namespace video { + +class SinkClient; + +class VideoReceiveThread : public VideoGenerator { +public: + VideoReceiveThread(const std::string &id, const std::string &sdp); + ~VideoReceiveThread(); + void startLoop(); + + void addIOContext(SocketPair &socketPair); + void setRequestKeyFrameCallback(void (*)(const std::string &)); + void enterConference(); + void exitConference(); + + // as VideoGenerator + int getWidth() const; + int getHeight() const; + int getPixelFormat() const; + +private: + NON_COPYABLE(VideoReceiveThread); + + DeviceParams args_; + + /*-------------------------------------------------------------*/ + /* These variables should be used in thread (i.e. run()) only! */ + /*-------------------------------------------------------------*/ + std::unique_ptr<MediaDecoder> videoDecoder_; + int dstWidth_; + int dstHeight_; + const std::string id_; + std::istringstream stream_; + MediaIOHandle sdpContext_; + std::unique_ptr<MediaIOHandle> demuxContext_; + std::shared_ptr<SinkClient> sink_; + void (*requestKeyFrameCallback_)(const std::string &); + void openDecoder(); + bool decodeFrame(); + static int interruptCb(void *ctx); + static int readFunction(void *opaque, uint8_t *buf, int buf_size); + + ThreadLoop loop_; + + // used by ThreadLoop + bool setup(); + void process(); + void cleanup(); +}; + +}} // namespace ring::video + +#endif // _VIDEO_RECEIVE_THREAD_H_ diff --git a/src/media/video/video_rtp_session.cpp b/src/media/video/video_rtp_session.cpp new file mode 100644 index 0000000000..97d831fa25 --- /dev/null +++ b/src/media/video/video_rtp_session.cpp @@ -0,0 +1,284 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "client/videomanager.h" +#include "video_rtp_session.h" +#include "video_sender.h" +#include "video_receive_thread.h" +#include "video_mixer.h" +#include "ice_socket.h" +#include "socket_pair.h" +#include "sip/sipvoiplink.h" // for enqueueKeyframeRequest +#include "manager.h" +#include "logger.h" + +#include <sstream> +#include <map> +#include <string> + +namespace ring { namespace video { + +using std::map; +using std::string; + +constexpr static auto NEWPARAMS_TIMEOUT = std::chrono::milliseconds(1000); + +VideoRtpSession::VideoRtpSession(const string &callID, + const DeviceParams& localVideoParams) : + RtpSession(callID), localVideoParams_(localVideoParams) +{} + +VideoRtpSession::~VideoRtpSession() +{ stop(); } + +void VideoRtpSession::startSender() +{ + if (send_.enabled and not send_.holding) { + if (sender_) { + if (videoLocal_) + videoLocal_->detach(sender_.get()); + if (videoMixer_) + videoMixer_->detach(sender_.get()); + RING_WARN("Restarting video sender"); + } + + if (not conference_) { + videoLocal_ = getVideoCamera(); + if (auto input = videoManager.videoInput.lock()) { + auto newParams = input->switchInput(input_); + try { + if (newParams.valid() && + newParams.wait_for(NEWPARAMS_TIMEOUT) == std::future_status::ready) + localVideoParams_ = newParams.get(); + else + RING_ERR("No valid new video parameters."); + } catch (const std::exception& e) { + RING_ERR("Exception during retriving video parameters: %s", + e.what()); + } + } else { + RING_WARN("Can't lock video input"); + } + } + + try { + sender_.reset( + new VideoSender(getRemoteRtpUri(), localVideoParams_, send_, *socketPair_) + ); + } catch (const MediaEncoderException &e) { + RING_ERR("%s", e.what()); + send_.enabled = false; + } + } +} + +void VideoRtpSession::startReceiver() +{ + if (receive_.enabled and not receive_.holding) { + if (receiveThread_) + RING_WARN("restarting video receiver"); + receiveThread_.reset( + new VideoReceiveThread(callID_, receive_.receiving_sdp) + ); + receiveThread_->setRequestKeyFrameCallback(&SIPVoIPLink::enqueueKeyframeRequest); + receiveThread_->addIOContext(*socketPair_); + receiveThread_->startLoop(); + } else { + RING_DBG("Video receiving disabled"); + if (receiveThread_) + receiveThread_->detach(videoMixer_.get()); + receiveThread_.reset(); + } +} + +void VideoRtpSession::start() +{ + std::lock_guard<std::recursive_mutex> lock(mutex_); + + if (not send_.enabled and not receive_.enabled) { + stop(); + return; + } + + try { + socketPair_.reset( + new SocketPair(getRemoteRtpUri().c_str(), receive_.addr.getPort()) + ); + if (send_.crypto and receive_.crypto) { + socketPair_->createSRTP(receive_.crypto.getCryptoSuite().c_str(), + receive_.crypto.getSrtpKeyInfo().c_str(), + send_.crypto.getCryptoSuite().c_str(), + send_.crypto.getSrtpKeyInfo().c_str()); + } + } catch (const std::runtime_error &e) { + RING_ERR("Socket creation failed on port %d: %s", receive_.addr.getPort(), e.what()); + return; + } + + startSender(); + startReceiver(); + + // Setup video pipeline + if (conference_) + setupConferenceVideoPipeline(conference_); + else if (sender_) { + if (videoLocal_) + videoLocal_->attach(sender_.get()); + } else { + videoLocal_.reset(); + } +} + +void VideoRtpSession::start(std::unique_ptr<IceSocket> rtp_sock, + std::unique_ptr<IceSocket> rtcp_sock) +{ + std::lock_guard<std::recursive_mutex> lock(mutex_); + + if (not send_.enabled and not receive_.enabled) { + stop(); + return; + } + + try { + socketPair_.reset(new SocketPair(std::move(rtp_sock), + std::move(rtcp_sock))); + if (send_.crypto and receive_.crypto) { + socketPair_->createSRTP(receive_.crypto.getCryptoSuite().c_str(), + receive_.crypto.getSrtpKeyInfo().c_str(), + send_.crypto.getCryptoSuite().c_str(), + send_.crypto.getSrtpKeyInfo().c_str()); + } + } catch (const std::runtime_error &e) { + RING_ERR("Socket creation failed"); + return; + } + + startSender(); + startReceiver(); + + // Setup video pipeline + if (conference_) + setupConferenceVideoPipeline(conference_); + else if (sender_) { + if (videoLocal_) + videoLocal_->attach(sender_.get()); + } else { + videoLocal_.reset(); + } +} + +void VideoRtpSession::stop() +{ + std::lock_guard<std::recursive_mutex> lock(mutex_); + + if (videoLocal_) + videoLocal_->detach(sender_.get()); + + if (videoMixer_) { + videoMixer_->detach(sender_.get()); + if (receiveThread_) + receiveThread_->detach(videoMixer_.get()); + } + + if (socketPair_) + socketPair_->interrupt(); + + receiveThread_.reset(); + sender_.reset(); + socketPair_.reset(); + videoLocal_.reset(); + conference_ = nullptr; +} + +void VideoRtpSession::forceKeyFrame() +{ + std::lock_guard<std::recursive_mutex> lock(mutex_); + if (sender_) + sender_->forceKeyFrame(); +} + +void VideoRtpSession::setupConferenceVideoPipeline(Conference* conference) +{ + assert(conference); + + videoMixer_ = std::move(conference->getVideoMixer()); + assert(videoMixer_.get()); + videoMixer_->setDimensions(localVideoParams_.width, localVideoParams_.height); + + if (sender_) { + // Swap sender from local video to conference video mixer + if (videoLocal_) + videoLocal_->detach(sender_.get()); + videoMixer_->attach(sender_.get()); + } + + if (receiveThread_) { + receiveThread_->enterConference(); + receiveThread_->attach(videoMixer_.get()); + } +} + +void VideoRtpSession::enterConference(Conference* conference) +{ + std::lock_guard<std::recursive_mutex> lock(mutex_); + + exitConference(); + + conference_ = conference; + + if (send_.enabled or receiveThread_) + setupConferenceVideoPipeline(conference); +} + +void VideoRtpSession::exitConference() +{ + std::lock_guard<std::recursive_mutex> lock(mutex_); + + + if (videoMixer_) { + if (sender_) + videoMixer_->detach(sender_.get()); + + if (receiveThread_) { + receiveThread_->detach(videoMixer_.get()); + receiveThread_->exitConference(); + } + + videoMixer_.reset(); + } + + if (videoLocal_) + videoLocal_->attach(sender_.get()); + + conference_ = nullptr; +} + +}} // namespace ring::video diff --git a/src/media/video/video_rtp_session.h b/src/media/video/video_rtp_session.h new file mode 100644 index 0000000000..e6a57ee9f5 --- /dev/null +++ b/src/media/video/video_rtp_session.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __VIDEO_RTP_SESSION_H__ +#define __VIDEO_RTP_SESSION_H__ + +#include "media/rtp_session.h" +#include "media/media_device.h" + +#include "video_base.h" + +#include <string> +#include <memory> + +namespace ring { +class Conference; +} // namespace ring + +namespace ring { namespace video { + +class VideoMixer; +class VideoSender; +class VideoReceiveThread; + +class VideoRtpSession : public RtpSession { +public: + VideoRtpSession(const std::string& callID, + const DeviceParams& localVideoParams); + ~VideoRtpSession(); + + void start(); + void start(std::unique_ptr<IceSocket> rtp_sock, + std::unique_ptr<IceSocket> rtcp_sock); + void stop(); + + void forceKeyFrame(); + void bindMixer(VideoMixer* mixer); + void unbindMixer(); + void enterConference(Conference* conference); + void exitConference(); + void switchInput(const std::string& input) { + input_ = input; + } + +private: + void setupConferenceVideoPipeline(Conference *conference); + void startSender(); + void startReceiver(); + + std::string input_; + DeviceParams localVideoParams_; + + std::unique_ptr<VideoSender> sender_; + std::unique_ptr<VideoReceiveThread> receiveThread_; + Conference* conference_ {nullptr}; + std::shared_ptr<VideoMixer> videoMixer_; + std::shared_ptr<VideoFrameActiveWriter> videoLocal_; +}; + +}} // namespace ring::video + +#endif // __VIDEO_RTP_SESSION_H__ diff --git a/src/media/video/video_scaler.cpp b/src/media/video/video_scaler.cpp new file mode 100644 index 0000000000..6525d132e9 --- /dev/null +++ b/src/media/video/video_scaler.cpp @@ -0,0 +1,171 @@ + +/* + * Copyright (C) 2013-2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "libav_deps.h" // MUST BE INCLUDED FIRST +#include "video_scaler.h" +#include "media_buffer.h" +#include "logger.h" + +#include <cassert> + +namespace ring { namespace video { + +VideoScaler::VideoScaler() + : ctx_(0), mode_(SWS_FAST_BILINEAR), tmp_data_() +{} + +VideoScaler::~VideoScaler() +{ + sws_freeContext(ctx_); +} + +void +VideoScaler::scale(const VideoFrame& input, VideoFrame& output) +{ + const auto input_frame = input.pointer(); + auto output_frame = output.pointer(); + + ctx_ = sws_getCachedContext(ctx_, + input_frame->width, + input_frame->height, + (AVPixelFormat) input_frame->format, + output_frame->width, + output_frame->height, + (AVPixelFormat) output_frame->format, + mode_, + NULL, NULL, NULL); + if (!ctx_) { + RING_ERR("Unable to create a scaler context"); + return; + } + + sws_scale(ctx_, input_frame->data, input_frame->linesize, 0, + input_frame->height, output_frame->data, + output_frame->linesize); +} + +void +VideoScaler::scale_with_aspect(const VideoFrame& input, VideoFrame& output) +{ + auto output_frame = output.pointer(); + scale_and_pad(input, output, 0, 0, output_frame->width, + output_frame->height, true); +} + +static inline bool is_yuv_planar(const AVPixFmtDescriptor *desc) +{ + unsigned used_bit_mask = (1u << desc->nb_components) - 1; + + if (not (desc->flags & PIX_FMT_PLANAR) + or desc->flags & PIX_FMT_RGB) + return false; + + /* handle formats that do not use all planes */ + for (unsigned i = 0; i < desc->nb_components; ++i) + used_bit_mask &= ~(1u << desc->comp[i].plane); + + return not used_bit_mask; +} + +void +VideoScaler::scale_and_pad(const VideoFrame& input, VideoFrame& output, + unsigned xoff, unsigned yoff, + unsigned dest_width, unsigned dest_height, + bool keep_aspect) +{ + const auto input_frame = input.pointer(); + auto output_frame = output.pointer(); + + /* Correct destination width/height and offset if we need to keep input + * frame aspect. + */ + if (keep_aspect) { + const float local_ratio = (float)dest_width / dest_height; + const float input_ratio = (float)input_frame->width / input_frame->height; + + if (local_ratio > input_ratio) { + auto old_dest_width = dest_width; + dest_width = dest_height * input_ratio; + xoff += (old_dest_width - dest_width) / 2; + } else { + auto old_dest_heigth = dest_height; + dest_height = dest_width / input_ratio; + yoff += (old_dest_heigth - dest_height) / 2; + } + } + + // buffer overflow checks + assert(xoff + dest_width <= (unsigned)output_frame->width); + assert(yoff + dest_height <= (unsigned)output_frame->height); + + ctx_ = sws_getCachedContext(ctx_, + input_frame->width, + input_frame->height, + (AVPixelFormat) input_frame->format, + dest_width, + dest_height, + (AVPixelFormat) output_frame->format, + mode_, + NULL, NULL, NULL); + if (!ctx_) { + RING_ERR("Unable to create a scaler context"); + return; + } + + // Make an offset'ed copy of output data from xoff and yoff + const AVPixFmtDescriptor *out_desc = av_pix_fmt_desc_get((AVPixelFormat)output_frame->format); + if (is_yuv_planar(out_desc)) { + unsigned x_shift = out_desc->log2_chroma_w; + unsigned y_shift = out_desc->log2_chroma_h; + + tmp_data_[0] = output_frame->data[0] + yoff * output_frame->linesize[0] + xoff; + tmp_data_[1] = output_frame->data[1] + (yoff >> y_shift) * output_frame->linesize[1] + (xoff >> x_shift); + tmp_data_[2] = output_frame->data[2] + (yoff >> y_shift) * output_frame->linesize[2] + (xoff >> x_shift); + tmp_data_[3] = nullptr; + } else { + memcpy(tmp_data_, output_frame->data, sizeof(tmp_data_)); + tmp_data_[0] += yoff * output_frame->linesize[0] + xoff; + } + + sws_scale(ctx_, input_frame->data, input_frame->linesize, 0, + input_frame->height, tmp_data_, output_frame->linesize); +} + +void VideoScaler::reset() +{ + if (ctx_) { + sws_freeContext(ctx_); + ctx_ = nullptr; + } +} + +}} // namespace ring::video diff --git a/src/media/video/video_scaler.h b/src/media/video/video_scaler.h new file mode 100644 index 0000000000..6e16a665a1 --- /dev/null +++ b/src/media/video/video_scaler.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011-2015 Savoir-Faire Linux Inc. + * + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __VIDEO_SCALER_H__ +#define __VIDEO_SCALER_H__ + +#include "video_base.h" +#include "noncopyable.h" + + +class SwsContext; + +namespace ring { namespace video { + +class VideoScaler { +public: + VideoScaler(); + ~VideoScaler(); + void reset(); + void scale(const VideoFrame &input, VideoFrame &output); + void scale_with_aspect(const VideoFrame &input, VideoFrame &output); + void scale_and_pad(const VideoFrame &input, VideoFrame &output, + unsigned xoff, unsigned yoff, + unsigned dest_width, unsigned dest_height, + bool keep_aspect); + +private: + NON_COPYABLE(VideoScaler); + SwsContext *ctx_; + int mode_; + uint8_t *tmp_data_[4]; // used by scale_and_pad +}; + +}} // namespace ring::video + +#endif // __VIDEO_SCALER_H__ diff --git a/src/media/video/video_sender.cpp b/src/media/video/video_sender.cpp new file mode 100644 index 0000000000..483f38ae26 --- /dev/null +++ b/src/media/video/video_sender.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "video_sender.h" +#include "video_mixer.h" +#include "socket_pair.h" +#include "client/videomanager.h" +#include "logger.h" +#include "manager.h" + +#include <map> +#include <unistd.h> + +namespace ring { namespace video { + +using std::string; + +VideoSender::VideoSender(const std::string& dest, const DeviceParams& dev, + const MediaDescription& args, SocketPair& socketPair) + : muxContext_(socketPair.createIOContext()) + , videoEncoder_(new MediaEncoder) +{ + videoEncoder_->setDeviceOptions(dev); + videoEncoder_->openOutput(dest.c_str(), args); + videoEncoder_->setIOContext(muxContext_); + videoEncoder_->startIO(); + + videoEncoder_->print_sdp(sdp_); +} + +void VideoSender::encodeAndSendVideo(VideoFrame& input_frame) +{ + bool is_keyframe = forceKeyFrame_ > 0; + + if (is_keyframe) + --forceKeyFrame_; + + if (videoEncoder_->encode(input_frame, is_keyframe, frameNumber_++) < 0) + RING_ERR("encoding failed"); +} + +void VideoSender::update(Observable<std::shared_ptr<VideoFrame> >* /*obs*/, + std::shared_ptr<VideoFrame> & frame_p) +{ + encodeAndSendVideo(*frame_p); +} + +void VideoSender::forceKeyFrame() +{ + ++forceKeyFrame_; +} + +}} // namespace ring::video diff --git a/src/media/video/video_sender.h b/src/media/video/video_sender.h new file mode 100644 index 0000000000..a8edc79cb6 --- /dev/null +++ b/src/media/video/video_sender.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2011-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __VIDEO_SENDER_H__ +#define __VIDEO_SENDER_H__ + +#include "noncopyable.h" +#include "media_encoder.h" +#include "media_io_handle.h" +#include "video_mixer.h" + +#include <map> +#include <string> +#include <memory> +#include <atomic> + +namespace ring { + class SocketPair; +} + +namespace ring { namespace video { + +class VideoSender : public VideoFramePassiveReader +{ +public: + VideoSender(const std::string& dest, + const DeviceParams& dev, + const MediaDescription& args, + SocketPair& socketPair); + + std::string getSDP() const { return sdp_; } + void forceKeyFrame(); + + // as VideoFramePassiveReader + void update(Observable<std::shared_ptr<VideoFrame> >* obs, + std::shared_ptr<VideoFrame> &); + +private: + NON_COPYABLE(VideoSender); + + void encodeAndSendVideo(VideoFrame&); + + // encoder MUST be deleted before muxContext + std::unique_ptr<MediaIOHandle> muxContext_ = nullptr; + std::unique_ptr<MediaEncoder> videoEncoder_ = nullptr; + + std::atomic<int> forceKeyFrame_ = { 0 }; + int64_t frameNumber_ = 0; + std::string sdp_ = ""; +}; + +}} // namespace ring::video + +#endif // __VIDEO_SENDER_H__ diff --git a/src/noncopyable.h b/src/noncopyable.h new file mode 100644 index 0000000000..e9eea4b610 --- /dev/null +++ b/src/noncopyable.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2011-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef NON_COPYABLE_H_ +#define NON_COPYABLE_H_ + +/** + * @file noncopyable.h + * @brief Simple macro to hide class' copy constructor and assignment operator. + * Useful to avoid shallow copying (i.e. classes with pointer members) + * Usage: For a class named MyClass, the macro call + * NON_COPYABLE(MyClass) should go in the private section of MyClass + */ + +#define NON_COPYABLE(ClassName) \ + ClassName(const ClassName&) = delete; \ + ClassName& operator=(const ClassName&) = delete + +#endif // NON_COPYABLE_H_ diff --git a/src/numbercleaner.cpp b/src/numbercleaner.cpp new file mode 100644 index 0000000000..fdafb3791a --- /dev/null +++ b/src/numbercleaner.cpp @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "numbercleaner.h" + +#include "string_utils.h" + +#include <algorithm> + +#define INVALID_CHAR " -()" + +namespace ring { namespace NumberCleaner { + +static void +strip_chars(const std::string &to_strip, std::string &num) +{ + for (const auto &item : to_strip) + num.erase(std::remove(num.begin(), num.end(), item), num.end()); +} + +std::string +clean(std::string to_clean, const std::string &prefix) +{ + to_clean = trim(to_clean); + size_t pos; + //Hostname and DNS can have '-' + if ((pos = to_clean.find("@")) == std::string::npos) { + strip_chars(INVALID_CHAR, to_clean); + return to_clean.insert(0, prefix); + } + else { + std::string high = to_clean.substr(0,pos+1); + strip_chars(INVALID_CHAR, high); + return high+to_clean.substr(pos+1); + } +} + +}} // namespace ring::NumberCleaner diff --git a/src/numbercleaner.h b/src/numbercleaner.h new file mode 100644 index 0000000000..22c557fe73 --- /dev/null +++ b/src/numbercleaner.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef _NUMBER_CLEANER_H_ +#define _NUMBER_CLEANER_H_ + +#include <string> + +namespace ring { namespace NumberCleaner { + +std::string clean(std::string to_clean, const std::string &prefix = ""); + +}} // namespace ring::NumberCleaner + +#endif // _NUMBER_CLEANER_H_ diff --git a/src/plugin_loader.h b/src/plugin_loader.h new file mode 100644 index 0000000000..4455cd8c7d --- /dev/null +++ b/src/plugin_loader.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#ifndef PLUGIN_LOADER_H +#define PLUGIN_LOADER_H + +#include "ring_plugin.h" + +#include <string> + +namespace ring { + +class Plugin +{ + public: + virtual ~Plugin() = default; + + static Plugin* load(const std::string& path, std::string& error); + + virtual void* getSymbol(const char* name) const = 0; + virtual RING_PluginInitFunc getInitFunction() const { + return reinterpret_cast<RING_PluginInitFunc>(getSymbol(RING_DYN_INIT_FUNC_NAME)); + }; + + protected: + Plugin() = default; +}; + +} // namespace ring + +#endif /* PLUGIN_LOADER_H */ diff --git a/src/plugin_loader_dl.cpp b/src/plugin_loader_dl.cpp new file mode 100644 index 0000000000..bf358200f6 --- /dev/null +++ b/src/plugin_loader_dl.cpp @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "plugin_loader.h" + +#include <dlfcn.h> +#include <memory> + +namespace ring { + +class DLPlugin : public Plugin +{ + public: + DLPlugin(void* handle) : handle_(handle, ::dlclose) {}; + void* getSymbol(const char* name) const; + + private: + std::unique_ptr<void, int(*)(void*)> handle_; +}; + +void* +DLPlugin::getSymbol(const char* name) const +{ + if (!handle_) + return nullptr; + + return ::dlsym(handle_.get(), name); +} + +Plugin* +Plugin::load(const std::string& path, std::string& error) +{ + if (path.empty()) { + error = "Empty path"; + return nullptr; + } + + // Clear any existing error + ::dlerror(); + + void* handle = ::dlopen(path.c_str(), RTLD_NOW); + if (!handle) { + error += "Failed to load \"" + path + '"'; + + std::string dlError = ::dlerror(); + if(dlError.size()) + error += " (" + dlError + ")"; + return nullptr; + } + + return new DLPlugin(handle); +} + +} // namespace ring diff --git a/src/plugin_manager.cpp b/src/plugin_manager.cpp new file mode 100644 index 0000000000..41e74c3113 --- /dev/null +++ b/src/plugin_manager.cpp @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "plugin_manager.h" +#include "plugin_loader.h" +#include "logger.h" + +#include <utility> + +namespace ring { + +PluginManager::PluginManager() +{ + pluginApi_.context = reinterpret_cast<void*>(this); +} + +PluginManager::~PluginManager() +{ + for (auto func : exitFuncVec_) { + try { + (*func)(); + } catch (...) { + RING_WARN("Exception caught during plugin exit"); + } + } + + dynPluginMap_.clear(); + exactMatchMap_.clear(); + wildCardVec_.clear(); + exitFuncVec_.clear(); +} + +bool +PluginManager::load(const std::string& path) +{ + // TODO: Resolve symbolic links and make path absolute + + // Don't load the same dynamic library twice + if (dynPluginMap_.find(path) != dynPluginMap_.end()) { + RING_WARN("plugin: already loaded"); + return true; + } + + std::string error; + std::unique_ptr<Plugin> plugin(Plugin::load(path, error)); + if (!plugin) { + RING_ERR("plugin: %s", error.c_str()); + return false; + } + + const auto& init_func = plugin->getInitFunction(); + if (!init_func) { + RING_ERR("plugin: no init symbol"); + return false; + } + + if (!registerPlugin(init_func)) + return false; + + dynPluginMap_[path] = std::move(plugin); + return true; +} + +bool +PluginManager::registerPlugin(RING_PluginInitFunc initFunc) +{ + RING_PluginExitFunc exitFunc = nullptr; + + try { + exitFunc = initFunc(&pluginApi_); + } catch (const std::runtime_error& e) { + RING_ERR("%s", e.what()); + } + + if (!exitFunc) { + tempExactMatchMap_.clear(); + tempWildCardVec_.clear(); + RING_ERR("plugin: init failed"); + return false; + } + + exitFuncVec_.push_back(exitFunc); + exactMatchMap_.insert(tempExactMatchMap_.begin(), + tempExactMatchMap_.end()); + wildCardVec_.insert(wildCardVec_.end(), + tempWildCardVec_.begin(), + tempWildCardVec_.end()); + return true; +} + +bool +PluginManager::registerService(const std::string& name, + ServiceFunction&& func) +{ + services_[name] = std::forward<ServiceFunction>(func); + return true; +} + +void +PluginManager::unRegisterService(const std::string& name) +{ + services_.erase(name); +} + +int32_t +PluginManager::invokeService(const std::string& name, void* data) +{ + const auto& iterFunc = services_.find(name); + if (iterFunc == services_.cend()) { + RING_ERR("Services not found: %s", name.c_str()); + return -1; + } + + const auto& func = iterFunc->second; + + try { + return func(data); + } catch (const std::runtime_error &e) { + RING_ERR("%s", e.what()); + return -1; + } +} + +/* WARNING: exposed to plugins through RING_PluginAPI */ +bool +PluginManager::registerObjectFactory(const char* type, + const RING_PluginObjectFactory& factoryData) +{ + if (!type) + return false; + + if (!factoryData.create || !factoryData.destroy) + return false; + + // Strict compatibility on ABI + if (factoryData.version.abi != pluginApi_.version.abi) + return false; + + // Backward compatibility on API + if (factoryData.version.api < pluginApi_.version.api) + return false; + + const std::string key(type); + auto deleter = [factoryData](void* o) { + factoryData.destroy(o, factoryData.closure); + }; + ObjectFactory factory = {factoryData, deleter}; + + // wildcard registration? + if (key == "*") { + wildCardVec_.push_back(factory); + return true; + } + + // fails on duplicate for exactMatch map + if (exactMatchMap_.find(key) != exactMatchMap_.end()) + return false; + + exactMatchMap_[key] = factory; + return true; +} + +std::unique_ptr<void, PluginManager::ObjectDeleter> +PluginManager::createObject(const std::string& type) +{ + if (type == "*") + return {nullptr, nullptr}; + + RING_PluginObjectParams op = { + .pluginApi = &pluginApi_, + .type = type.c_str(), + }; + + // Try to find an exact match + const auto& factoryIter = exactMatchMap_.find(type); + if (factoryIter != exactMatchMap_.end()) { + const auto& factory = factoryIter->second; + auto object = factory.data.create(&op, factory.data.closure); + if (object) + return {object, factory.deleter}; + } + + // Try to find a wildcard match + for (const auto& factory : wildCardVec_) + { + auto object = factory.data.create(&op, factory.data.closure); + if (object) { + // promote registration to exactMatch_ + // (but keep also wildcard registration for other object types) + int32_t res = registerObjectFactory(op.type, factory.data); + if (res < 0) { + RING_ERR("failed to register object %s", op.type); + return {nullptr, nullptr}; + } + + return {object, factory.deleter}; + } + } + + return {nullptr, nullptr}; +} + +/* WARNING: exposed to plugins through RING_PluginAPI */ +int32_t +PluginManager::registerObjectFactory_(const RING_PluginAPI* api, + const char* type, void* data) +{ + auto manager = reinterpret_cast<PluginManager*>(api->context); + if (!manager) { + RING_ERR("registerObjectFactory called with null plugin API"); + return -1; + } + + if (!data) { + RING_ERR("registerObjectFactory called with null factory data"); + return -1; + } + + const auto factory = reinterpret_cast<RING_PluginObjectFactory*>(data); + return manager->registerObjectFactory(type, *factory) ? 0 : -1; +} + +/* WARNING: exposed to plugins through RING_PluginAPI */ +int32_t +PluginManager::invokeService_(const RING_PluginAPI* api, const char* name, + void* data) +{ + auto manager = reinterpret_cast<PluginManager*>(api->context); + if (!manager) { + RING_ERR("invokeService called with null plugin API"); + return -1; + } + + return manager->invokeService(name, data); +} + +} // namespace ring diff --git a/src/plugin_manager.h b/src/plugin_manager.h new file mode 100644 index 0000000000..1c04f16057 --- /dev/null +++ b/src/plugin_manager.h @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef PLUGIN_MANAGER_H +#define PLUGIN_MANAGER_H + +#include "ring_plugin.h" +#include "noncopyable.h" + +#include <map> +#include <vector> +#include <memory> +#include <mutex> +#include <functional> +#include <string> + +#include <inttypes.h> + +namespace ring { + +class Plugin; + +class PluginManager +{ + public: + using ObjectDeleter = std::function<void(void*)>; + using ServiceFunction = std::function<int32_t(void*)>; + + private: + struct ObjectFactory { + RING_PluginObjectFactory data; + ObjectDeleter deleter; + }; + + using PluginMap = std::map<std::string, std::shared_ptr<Plugin>>; + using ExitFuncVec = std::vector<RING_PluginExitFunc>; + using ObjectFactoryVec = std::vector<ObjectFactory>; + using ObjectFactoryMap = std::map<std::string, ObjectFactory>; + + public: + PluginManager(); + ~PluginManager(); + + /** + * Load a dynamic plugin by filename. + * + * @param path fully qualified pathname on a loadable plugin binary + * @return true if success + */ + bool load(const std::string& path); + + /** + * Register a plugin. + * + * @param initFunc plugin init function + * @return true if success + */ + bool registerPlugin(RING_PluginInitFunc initFunc); + + /** + * Register a new service for plugin. + * + * @param name The service name + * @param func The function called by Ring_PluginAPI.invokeService + * @return true if success + */ + bool registerService(const std::string& name, ServiceFunction&& func); + + void unRegisterService(const std::string& name); + + /** + * Register a new public objects factory. + * + * @param type unique identifier of the object + * @param params object factory details + * @return true if success + * + * Note: type can be the string "*" meaning that the factory + * will be called if no exact match factories are found for a given type. + */ + bool registerObjectFactory(const char* type, + const RING_PluginObjectFactory& factory); + + /** + * Create a new plugin's exported object. + * + * @param type unique identifier of the object to create. + * @return unique pointer on created object. + */ + std::unique_ptr<void, ObjectDeleter> createObject(const std::string& type); + + const RING_PluginAPI& getPluginAPI() const { + return pluginApi_; + } + + private: + NON_COPYABLE(PluginManager); + + /** + * Implements RING_PluginAPI.registerObjectFactory(). + * Must be C accessible. + */ + static int32_t registerObjectFactory_(const RING_PluginAPI* api, + const char* type, + void* data); + + /** + * Implements RING_PluginAPI.invokeService(). + * Must be C accessible. + */ + static int32_t invokeService_(const RING_PluginAPI* api, + const char* name, + void* data); + + int32_t invokeService(const std::string& name, void* data); + + std::mutex mutex_ = {}; + RING_PluginAPI pluginApi_ = { + { RING_PLUGIN_ABI_VERSION, RING_PLUGIN_API_VERSION }, + nullptr, // set by PluginManager constructor + registerObjectFactory_, invokeService_, + }; + PluginMap dynPluginMap_ = {{}}; // Only dynamic loaded plugins + ExitFuncVec exitFuncVec_ = {}; + ObjectFactoryMap exactMatchMap_ = {{}}; + ObjectFactoryVec wildCardVec_ = {}; + + // Storage used during plugin initialisation. + // Will be copied into previous ones only if the initialisation success. + ObjectFactoryMap tempExactMatchMap_ = {{}}; + ObjectFactoryVec tempWildCardVec_ = {}; + + // registered services + std::map<std::string, ServiceFunction> services_ {{}}; +}; + +} // namespace ring + +#endif /* PLUGIN_MANAGER_H */ diff --git a/src/preferences.cpp b/src/preferences.cpp new file mode 100644 index 0000000000..473b252dcc --- /dev/null +++ b/src/preferences.cpp @@ -0,0 +1,531 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "preferences.h" +#include "logger.h" +#include "audio/audiolayer.h" +#if HAVE_OPENSL +#include "audio/opensl/opensllayer.h" +#else +#if HAVE_ALSA +#include "audio/alsa/alsalayer.h" +#endif +#if HAVE_JACK +#include "audio/jack/jacklayer.h" +#endif +#if HAVE_PULSE +#include "audio/pulseaudio/pulselayer.h" +#endif +#if HAVE_COREAUDIO +#include "audio/coreaudio/corelayer.h" +#endif +#endif /* HAVE_OPENSL */ + +#include <yaml-cpp/yaml.h> +#include "config/yamlparser.h" +#include "hooks/urlhook.h" +#include "sip/sip_utils.h" +#include <sstream> +#include <algorithm> +#include "fileutils.h" + +namespace ring { + +using yaml_utils::parseValue; + +constexpr const char * const Preferences::CONFIG_LABEL; +const char * const Preferences::DFT_ZONE = "North America"; +const char * const Preferences::REGISTRATION_EXPIRE_KEY = "registrationexpire"; + +// general preferences +static const char * const ORDER_KEY = "order"; +static const char * const AUDIO_API_KEY = "audioApi"; +static const char * const HISTORY_LIMIT_KEY = "historyLimit"; +static const char * const HISTORY_MAX_CALLS_KEY = "historyMaxCalls"; +static const char * const ZONE_TONE_CHOICE_KEY = "zoneToneChoice"; +static const char * const PORT_NUM_KEY = "portNum"; +static const char * const SEARCH_BAR_DISPLAY_KEY = "searchBarDisplay"; +static const char * const MD5_HASH_KEY = "md5Hash"; + +// voip preferences +constexpr const char * const VoipPreference::CONFIG_LABEL; +static const char * const PLAY_DTMF_KEY = "playDtmf"; +static const char * const PLAY_TONES_KEY = "playTones"; +static const char * const PULSE_LENGTH_KEY = "pulseLength"; +static const char * const SYMMETRIC_RTP_KEY = "symmetric"; +static const char * const ZID_FILE_KEY = "zidFile"; + +// hooks preferences +constexpr const char * const HookPreference::CONFIG_LABEL; +static const char * const IAX2_ENABLED_KEY = "iax2Enabled"; +static const char * const NUMBER_ADD_PREFIX_KEY = "numberAddPrefix"; +static const char * const NUMBER_ENABLED_KEY = "numberEnabled"; +static const char * const SIP_ENABLED_KEY = "sipEnabled"; +static const char * const URL_COMMAND_KEY = "urlCommand"; +static const char * const URL_SIP_FIELD_KEY = "urlSipField"; + +// audio preferences +constexpr const char * const AudioPreference::CONFIG_LABEL; +static const char * const ALSAMAP_KEY = "alsa"; +static const char * const PULSEMAP_KEY = "pulse"; +static const char * const CARDIN_KEY = "cardIn"; +static const char * const CARDOUT_KEY = "cardOut"; +static const char * const CARDRING_KEY = "cardRing"; +static const char * const PLUGIN_KEY = "plugin"; +static const char * const SMPLRATE_KEY = "smplRate"; +static const char * const DEVICE_PLAYBACK_KEY = "devicePlayback"; +static const char * const DEVICE_RECORD_KEY = "deviceRecord"; +static const char * const DEVICE_RINGTONE_KEY = "deviceRingtone"; +static const char * const RECORDPATH_KEY = "recordPath"; +static const char * const ALWAYS_RECORDING_KEY = "alwaysRecording"; +static const char * const VOLUMEMIC_KEY = "volumeMic"; +static const char * const VOLUMESPKR_KEY = "volumeSpkr"; +static const char * const NOISE_REDUCE_KEY = "noiseReduce"; +static const char * const AGC_KEY = "automaticGainControl"; +static const char * const CAPTURE_MUTED_KEY = "captureMuted"; +static const char * const PLAYBACK_MUTED_KEY = "playbackMuted"; + +// shortcut preferences +constexpr const char * const ShortcutPreferences::CONFIG_LABEL; +static const char * const HANGUP_SHORT_KEY = "hangUp"; +static const char * const PICKUP_SHORT_KEY = "pickUp"; +static const char * const POPUP_SHORT_KEY = "popupWindow"; +static const char * const TOGGLE_HOLD_SHORT_KEY = "toggleHold"; +static const char * const TOGGLE_PICKUP_HANGUP_SHORT_KEY = "togglePickupHangup"; + +static const char * const DFT_PULSE_LENGTH_STR = "250"; /** Default DTMF lenght */ +static const char * const ZRTP_ZIDFILE = "zidFile"; /** The filename used for storing ZIDs */ +static const char * const ALSA_DFT_CARD = "0"; /** Default sound card index */ + +Preferences::Preferences() : + accountOrder_("") + , historyLimit_(30) + , historyMaxCalls_(20) + , zoneToneChoice_(DFT_ZONE) // DFT_ZONE + , registrationExpire_(180) + , portNum_(sip_utils::DEFAULT_SIP_PORT) + , searchBarDisplay_(true) + , md5Hash_(false) +{} + +void Preferences::verifyAccountOrder(const std::vector<std::string> &accountIDs) +{ + std::vector<std::string> tokens; + std::string token; + bool drop = false; + + for (const auto c : accountOrder_) { + if (c != '/') { + token += c; + } else { + if (find(accountIDs.begin(), accountIDs.end(), token) != accountIDs.end()) + tokens.push_back(token); + else { + RING_DBG("Dropping nonexistent account %s", token.c_str()); + drop = true; + } + token.clear(); + } + } + + if (drop) { + accountOrder_.clear(); + for (const auto &t : tokens) + accountOrder_ += t + "/"; + } +} + +void Preferences::addAccount(const std::string &newAccountID) +{ + // Add the newly created account in the account order list + if (not accountOrder_.empty()) + accountOrder_.insert(0, newAccountID + "/"); + else + accountOrder_ = newAccountID + "/"; +} + +void Preferences::removeAccount(const std::string &oldAccountID) +{ + // include the slash since we don't want to remove a partial match + const size_t start = accountOrder_.find(oldAccountID + "/"); + if (start != std::string::npos) + accountOrder_.erase(start, oldAccountID.length() + 1); +} + +void Preferences::serialize(YAML::Emitter &out) +{ + out << YAML::Key << CONFIG_LABEL << YAML::Value << YAML::BeginMap; + + out << YAML::Key << HISTORY_LIMIT_KEY << YAML::Value << historyLimit_; + out << YAML::Key << HISTORY_MAX_CALLS_KEY << YAML::Value << historyMaxCalls_; + out << YAML::Key << MD5_HASH_KEY << YAML::Value << md5Hash_; + out << YAML::Key << ORDER_KEY << YAML::Value << accountOrder_; + out << YAML::Key << PORT_NUM_KEY << YAML::Value << portNum_; + out << YAML::Key << REGISTRATION_EXPIRE_KEY << YAML::Value << registrationExpire_; + out << YAML::Key << SEARCH_BAR_DISPLAY_KEY << YAML::Value << searchBarDisplay_; + out << YAML::Key << ZONE_TONE_CHOICE_KEY << YAML::Value << zoneToneChoice_; + out << YAML::EndMap; +} + +void Preferences::unserialize(const YAML::Node &in) +{ + const auto &node = in[CONFIG_LABEL]; + + parseValue(node, ORDER_KEY, accountOrder_); + parseValue(node, HISTORY_LIMIT_KEY, historyLimit_); + parseValue(node, HISTORY_MAX_CALLS_KEY, historyMaxCalls_); + parseValue(node, ZONE_TONE_CHOICE_KEY, zoneToneChoice_); + parseValue(node, REGISTRATION_EXPIRE_KEY, registrationExpire_); + parseValue(node, PORT_NUM_KEY, portNum_); + parseValue(node, SEARCH_BAR_DISPLAY_KEY, searchBarDisplay_); + parseValue(node, MD5_HASH_KEY, md5Hash_); +} + +VoipPreference::VoipPreference() : + playDtmf_(true) + , playTones_(true) + , pulseLength_(atoi(DFT_PULSE_LENGTH_STR)) + , symmetricRtp_(true) + , zidFile_(ZRTP_ZIDFILE) +{} + +void VoipPreference::serialize(YAML::Emitter &out) +{ + out << YAML::Key << CONFIG_LABEL << YAML::Value << YAML::BeginMap; + out << YAML::Key << PLAY_DTMF_KEY << YAML::Value << playDtmf_; + out << YAML::Key << PLAY_TONES_KEY << YAML::Value << playTones_; + out << YAML::Key << PULSE_LENGTH_KEY << YAML::Value << pulseLength_; + out << YAML::Key << SYMMETRIC_RTP_KEY << YAML::Value << symmetricRtp_; + out << YAML::Key << ZID_FILE_KEY << YAML::Value << zidFile_; + out << YAML::EndMap; +} + +void VoipPreference::unserialize(const YAML::Node &in) +{ + const auto &node = in[CONFIG_LABEL]; + parseValue(node, PLAY_DTMF_KEY, playDtmf_); + parseValue(node, PLAY_TONES_KEY, playTones_); + parseValue(node, PULSE_LENGTH_KEY, pulseLength_); + parseValue(node, SYMMETRIC_RTP_KEY, symmetricRtp_); + parseValue(node, ZID_FILE_KEY, zidFile_); +} + +HookPreference::HookPreference() : + iax2Enabled_(false) + , numberAddPrefix_("") + , numberEnabled_(false) + , sipEnabled_(false) + , urlCommand_("x-www-browser") + , urlSipField_("X-ring-url") +{} + +HookPreference::HookPreference(const std::map<std::string, std::string> &settings) : + iax2Enabled_(settings.find("URLHOOK_IAX2_ENABLED")->second == "true") + , numberAddPrefix_(settings.find("PHONE_NUMBER_HOOK_ADD_PREFIX")->second) + , numberEnabled_(settings.find("PHONE_NUMBER_HOOK_ENABLED")->second == "true") + , sipEnabled_(settings.find("URLHOOK_SIP_ENABLED")->second == "true") + , urlCommand_(settings.find("URLHOOK_COMMAND")->second) + , urlSipField_(settings.find("URLHOOK_SIP_FIELD")->second) +{} + +std::map<std::string, std::string> HookPreference::toMap() const +{ + std::map<std::string, std::string> settings; + settings["URLHOOK_IAX2_ENABLED"] = iax2Enabled_ ? "true" : "false"; + settings["PHONE_NUMBER_HOOK_ADD_PREFIX"] = numberAddPrefix_; + settings["PHONE_NUMBER_HOOK_ENABLED"] = numberEnabled_ ? "true" : "false"; + settings["URLHOOK_SIP_ENABLED"] = sipEnabled_ ? "true" : "false"; + settings["URLHOOK_COMMAND"] = urlCommand_; + settings["URLHOOK_SIP_FIELD"] = urlSipField_; + + return settings; +} + +void HookPreference::serialize(YAML::Emitter &out) +{ + out << YAML::Key << CONFIG_LABEL << YAML::Value << YAML::BeginMap; + out << YAML::Key << IAX2_ENABLED_KEY << YAML::Value << iax2Enabled_; + out << YAML::Key << NUMBER_ADD_PREFIX_KEY << YAML::Value << numberAddPrefix_; + out << YAML::Key << SIP_ENABLED_KEY << YAML::Value << sipEnabled_; + out << YAML::Key << URL_COMMAND_KEY << YAML::Value << urlCommand_; + out << YAML::Key << URL_SIP_FIELD_KEY << YAML::Value << urlSipField_; + out << YAML::EndMap; +} + +void HookPreference::unserialize(const YAML::Node &in) +{ + const auto &node = in[CONFIG_LABEL]; + + parseValue(node, IAX2_ENABLED_KEY, iax2Enabled_); + parseValue(node, NUMBER_ADD_PREFIX_KEY, numberAddPrefix_); + parseValue(node, SIP_ENABLED_KEY, sipEnabled_); + parseValue(node, URL_COMMAND_KEY, urlCommand_); + parseValue(node, URL_SIP_FIELD_KEY, urlSipField_); +} + +void HookPreference::runHook(pjsip_msg *msg) +{ + if (sipEnabled_) { + const std::string header(sip_utils::fetchHeaderValue(msg, urlSipField_)); + UrlHook::runAction(urlCommand_, header); + } +} + +AudioPreference::AudioPreference() : + audioApi_(PULSEAUDIO_API_STR) + , alsaCardin_(atoi(ALSA_DFT_CARD)) + , alsaCardout_(atoi(ALSA_DFT_CARD)) + , alsaCardring_(atoi(ALSA_DFT_CARD)) + , alsaPlugin_("default") + , alsaSmplrate_(44100) + , pulseDevicePlayback_("") + , pulseDeviceRecord_("") + , pulseDeviceRingtone_("") + , recordpath_("") + , alwaysRecording_(false) + , volumemic_(1.0) + , volumespkr_(1.0) + , denoise_(false) + , agcEnabled_(false) + , captureMuted_(false) + , playbackMuted_(false) +{} + +#if HAVE_ALSA + +static const int ALSA_DFT_CARD_ID = 0; // Index of the default soundcard + +static void +checkSoundCard(int &card, DeviceType type) +{ + if (not AlsaLayer::soundCardIndexExists(card, type)) { + RING_WARN(" Card with index %d doesn't exist or is unusable.", card); + card = ALSA_DFT_CARD_ID; + } +} +#endif + +AudioLayer* AudioPreference::createAudioLayer() +{ +#if HAVE_OPENSL + return new OpenSLLayer(*this); +#else + +#if HAVE_JACK + if (audioApi_ == JACK_API_STR) { + if (system("jack_lsp > /dev/null") == 0) { + try { + return new JackLayer(*this); + } catch (const std::runtime_error &e) { + RING_ERR("%s", e.what()); +#if HAVE_PULSE + RING_WARN("falling back to pulseaudio"); + audioApi_ = PULSEAUDIO_API_STR; +#elif HAVE_ALSA + audioApi_ = ALSA_API_STR; +#elif HAVE_COREAUDIO + audioApi_ = COREAUDIO_API_STR; +#else + throw; +#endif + } + } + } +#endif + +#if HAVE_PULSE + + if (audioApi_ == PULSEAUDIO_API_STR) { + try { + return new PulseLayer(*this); + } catch (const std::runtime_error &e) { + RING_WARN("Could not create pulseaudio layer, falling back to ALSA"); + } + } + +#endif + +#if HAVE_ALSA + + audioApi_ = ALSA_API_STR; + checkSoundCard(alsaCardin_, DeviceType::CAPTURE); + checkSoundCard(alsaCardout_, DeviceType::PLAYBACK); + checkSoundCard(alsaCardring_, DeviceType::RINGTONE); + + return new AlsaLayer(*this); +#endif + +#if HAVE_COREAUDIO + audioApi_ = COREAUDIO_API_STR; + try { + return new CoreLayer(*this); + } catch (const std::runtime_error &e) { + RING_WARN("Could not create coreaudio layer. There will be no sound."); + } + return NULL; +#endif + +#endif // __ANDROID__ +} + +void AudioPreference::serialize(YAML::Emitter &out) +{ + out << YAML::Key << CONFIG_LABEL << YAML::Value << YAML::BeginMap; + // alsa submap + out << YAML::Key << ALSAMAP_KEY << YAML::Value << YAML::BeginMap; + out << YAML::Key << CARDIN_KEY << YAML::Value << alsaCardin_; + out << YAML::Key << CARDOUT_KEY << YAML::Value << alsaCardout_; + out << YAML::Key << CARDRING_KEY << YAML::Value << alsaCardring_; + out << YAML::Key << PLUGIN_KEY << YAML::Value << alsaPlugin_; + out << YAML::Key << SMPLRATE_KEY << YAML::Value << alsaSmplrate_; + out << YAML::EndMap; + + // common options + out << YAML::Key << ALWAYS_RECORDING_KEY << YAML::Value << alwaysRecording_; + out << YAML::Key << AUDIO_API_KEY << YAML::Value << audioApi_; + out << YAML::Key << AGC_KEY << YAML::Value << agcEnabled_; + out << YAML::Key << CAPTURE_MUTED_KEY << YAML::Value << captureMuted_; + out << YAML::Key << NOISE_REDUCE_KEY << YAML::Value << denoise_; + out << YAML::Key << PLAYBACK_MUTED_KEY << YAML::Value << playbackMuted_; + + // pulse submap + out << YAML::Key << PULSEMAP_KEY << YAML::Value << YAML::BeginMap; + out << YAML::Key << DEVICE_PLAYBACK_KEY << YAML::Value << pulseDevicePlayback_; + out << YAML::Key << DEVICE_RECORD_KEY << YAML::Value << pulseDeviceRecord_; + out << YAML::Key << DEVICE_RINGTONE_KEY << YAML::Value << pulseDeviceRingtone_; + out << YAML::EndMap; + + // more common options! + out << YAML::Key << RECORDPATH_KEY << YAML::Value << recordpath_; + out << YAML::Key << VOLUMEMIC_KEY << YAML::Value << volumemic_; + out << YAML::Key << VOLUMESPKR_KEY << YAML::Value << volumespkr_; + + out << YAML::EndMap; +} + +bool +AudioPreference::setRecordPath(const std::string &r) +{ + std::string path = fileutils::expand_path(r); + if (fileutils::isDirectoryWritable(path)) { + recordpath_ = path; + return true; + } else { + RING_ERR("%s is not writable, cannot be the recording path", path.c_str()); + return false; + } +} + +void AudioPreference::unserialize(const YAML::Node &in) +{ + const auto &node = in[CONFIG_LABEL]; + + // alsa submap + const auto &alsa = node[ALSAMAP_KEY]; + + parseValue(alsa, CARDIN_KEY, alsaCardin_); + parseValue(alsa, CARDOUT_KEY, alsaCardout_); + parseValue(alsa, CARDRING_KEY, alsaCardring_); + parseValue(alsa, PLUGIN_KEY, alsaPlugin_); + parseValue(alsa, SMPLRATE_KEY, alsaSmplrate_); + + // common options + parseValue(node, ALWAYS_RECORDING_KEY, alwaysRecording_); + parseValue(node, AUDIO_API_KEY, audioApi_); + parseValue(node, AGC_KEY, agcEnabled_); + parseValue(node, CAPTURE_MUTED_KEY, captureMuted_); + parseValue(node, NOISE_REDUCE_KEY, denoise_); + parseValue(node, PLAYBACK_MUTED_KEY, playbackMuted_); + + // pulse submap + const auto &pulse = node[PULSEMAP_KEY]; + parseValue(pulse, DEVICE_PLAYBACK_KEY, pulseDevicePlayback_); + parseValue(pulse, DEVICE_RECORD_KEY, pulseDeviceRecord_); + parseValue(pulse, DEVICE_RINGTONE_KEY, pulseDeviceRingtone_); + + // more common options! + parseValue(node, RECORDPATH_KEY, recordpath_); + parseValue(node, VOLUMEMIC_KEY, volumemic_); + parseValue(node, VOLUMESPKR_KEY, volumespkr_); +} + +ShortcutPreferences::ShortcutPreferences() : hangup_(), pickup_(), popup_(), + toggleHold_(), togglePickupHangup_() {} + +std::map<std::string, std::string> ShortcutPreferences::getShortcuts() const +{ + std::map<std::string, std::string> shortcutsMap; + + shortcutsMap[HANGUP_SHORT_KEY] = hangup_; + shortcutsMap[PICKUP_SHORT_KEY] = pickup_; + shortcutsMap[POPUP_SHORT_KEY] = popup_; + shortcutsMap[TOGGLE_HOLD_SHORT_KEY] = toggleHold_; + shortcutsMap[TOGGLE_PICKUP_HANGUP_SHORT_KEY] = togglePickupHangup_; + + return shortcutsMap; +} + +void ShortcutPreferences::setShortcuts(std::map<std::string, std::string> map) +{ + hangup_ = map[HANGUP_SHORT_KEY]; + pickup_ = map[PICKUP_SHORT_KEY]; + popup_ = map[POPUP_SHORT_KEY]; + toggleHold_ = map[TOGGLE_HOLD_SHORT_KEY]; + togglePickupHangup_ = map[TOGGLE_PICKUP_HANGUP_SHORT_KEY]; +} + + +void ShortcutPreferences::serialize(YAML::Emitter &out) +{ + out << YAML::Key << CONFIG_LABEL << YAML::Value << YAML::BeginMap; + out << YAML::Key << HANGUP_SHORT_KEY << YAML::Value << hangup_; + out << YAML::Key << PICKUP_SHORT_KEY << YAML::Value << pickup_; + out << YAML::Key << POPUP_SHORT_KEY << YAML::Value << popup_; + out << YAML::Key << TOGGLE_HOLD_SHORT_KEY << YAML::Value << toggleHold_; + out << YAML::Key << TOGGLE_PICKUP_HANGUP_SHORT_KEY << YAML::Value << togglePickupHangup_; + out << YAML::EndMap; +} + +void ShortcutPreferences::unserialize(const YAML::Node &in) +{ + const auto &node = in[CONFIG_LABEL]; + + parseValue(node, HANGUP_SHORT_KEY, hangup_); + parseValue(node, PICKUP_SHORT_KEY, pickup_); + parseValue(node, POPUP_SHORT_KEY, popup_); + parseValue(node, TOGGLE_HOLD_SHORT_KEY, toggleHold_); + parseValue(node, TOGGLE_PICKUP_HANGUP_SHORT_KEY, togglePickupHangup_); +} + +} // namespace ring diff --git a/src/preferences.h b/src/preferences.h new file mode 100644 index 0000000000..54887d3265 --- /dev/null +++ b/src/preferences.h @@ -0,0 +1,456 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __PREFERENCE_H__ +#define __PREFERENCE_H__ + +#include "config/serializable.h" +#include <string> +#include <map> +#include <vector> + +namespace YAML { + class Emitter; + class Node; +} + +struct pjsip_msg; + +namespace ring { + +class AudioLayer; + +class Preferences : public Serializable { + public: + static const char * const DFT_ZONE; + static const char * const REGISTRATION_EXPIRE_KEY; + + Preferences(); + + void serialize(YAML::Emitter &out); + void unserialize(const YAML::Node &in); + + std::string getAccountOrder() const { + return accountOrder_; + } + + // flush invalid accountIDs from account order + void verifyAccountOrder(const std::vector<std::string> &accounts); + + void addAccount(const std::string &acc); + void removeAccount(const std::string &acc); + + void setAccountOrder(const std::string &ord) { + accountOrder_ = ord; + } + + int getHistoryLimit() const { + return historyLimit_; + } + + void setHistoryLimit(int lim) { + historyLimit_ = lim; + } + + int getHistoryMaxCalls() const { + return historyMaxCalls_; + } + + void setHistoryMaxCalls(int max) { + historyMaxCalls_ = max; + } + + std::string getZoneToneChoice() const { + return zoneToneChoice_; + } + + void setZoneToneChoice(const std::string &str) { + zoneToneChoice_ = str; + } + + int getRegistrationExpire() const { + return registrationExpire_; + } + + void setRegistrationExpire(int exp) { + registrationExpire_ = exp; + } + + int getPortNum() const { + return portNum_; + } + + void setPortNum(int port) { + portNum_ = port; + } + + bool getSearchBarDisplay() const { + return searchBarDisplay_; + } + + void setSearchBarDisplay(bool search) { + searchBarDisplay_ = search; + } + + bool getMd5Hash() const { + return md5Hash_; + } + void setMd5Hash(bool md5) { + md5Hash_ = md5; + } + + private: + std::string accountOrder_; + int historyLimit_; + int historyMaxCalls_; + std::string zoneToneChoice_; + int registrationExpire_; + int portNum_; + bool searchBarDisplay_; + bool md5Hash_; + constexpr static const char * const CONFIG_LABEL = "preferences"; +}; + +class VoipPreference : public Serializable { + public: + VoipPreference(); + + void serialize(YAML::Emitter &out); + void unserialize(const YAML::Node &in); + + bool getPlayDtmf() const { + return playDtmf_; + } + + void setPlayDtmf(bool dtmf) { + playDtmf_ = dtmf; + } + + bool getPlayTones() const { + return playTones_; + } + + void setPlayTones(bool tone) { + playTones_ = tone; + } + + int getPulseLength() const { + return pulseLength_; + } + + void setPulseLength(int length) { + pulseLength_ = length; + } + + bool getSymmetricRtp() const { + return symmetricRtp_; + } + void setSymmetricRtp(bool sym) { + symmetricRtp_ = sym; + } + + std::string getZidFile() const { + return zidFile_; + } + void setZidFile(const std::string &file) { + zidFile_ = file; + } + + private: + bool playDtmf_; + bool playTones_; + int pulseLength_; + bool symmetricRtp_; + std::string zidFile_; + constexpr static const char * const CONFIG_LABEL = "voipPreferences"; +}; + +class HookPreference : public Serializable { + public: + HookPreference(); + HookPreference(const std::map<std::string, std::string> &settings); + + void serialize(YAML::Emitter &out); + void unserialize(const YAML::Node &in); + + std::string getNumberAddPrefix() const { + if (numberEnabled_) + return numberAddPrefix_; + else + return ""; + } + + bool getIax2Enabled() const { return iax2Enabled_; } + const std::string & getUrlCommand() const { return urlCommand_; } + + std::map<std::string, std::string> toMap() const; + void runHook(pjsip_msg *msg); + + private: + bool iax2Enabled_; + std::string numberAddPrefix_; + bool numberEnabled_; + bool sipEnabled_; + std::string urlCommand_; + std::string urlSipField_; + constexpr static const char * const CONFIG_LABEL = "hooks"; +}; + +class AudioPreference : public Serializable { + public: + AudioPreference(); + AudioLayer *createAudioLayer(); + AudioLayer *switchAndCreateAudioLayer(); + + std::string getAudioApi() const { + return audioApi_; + } + + void setAudioApi(const std::string &api) { + audioApi_ = api; + } + + void serialize(YAML::Emitter &out); + void unserialize(const YAML::Node &in); + + // alsa preference + int getAlsaCardin() const { + return alsaCardin_; + } + void setAlsaCardin(int c) { + alsaCardin_ = c; + } + + int getAlsaCardout() const { + return alsaCardout_; + } + + void setAlsaCardout(int c) { + alsaCardout_ = c; + } + + int getAlsaCardring() const { + return alsaCardring_; + } + + void setAlsaCardring(int c) { + alsaCardring_ = c; + } + + std::string getAlsaPlugin() const { + return alsaPlugin_; + } + + void setAlsaPlugin(const std::string &p) { + alsaPlugin_ = p; + } + + int getAlsaSmplrate() const { + return alsaSmplrate_; + } + void setAlsaSmplrate(int r) { + alsaSmplrate_ = r; + } + + //pulseaudio preference + std::string getPulseDevicePlayback() const { + return pulseDevicePlayback_; + } + + void setPulseDevicePlayback(const std::string &p) { + pulseDevicePlayback_ = p; + } + + std::string getPulseDeviceRecord() const { + return pulseDeviceRecord_; + } + void setPulseDeviceRecord(const std::string &r) { + pulseDeviceRecord_ = r; + } + + std::string getPulseDeviceRingtone() const { + return pulseDeviceRingtone_; + } + + void setPulseDeviceRingtone(const std::string &r) { + pulseDeviceRingtone_ = r; + } + + // general preference + std::string getRecordPath() const { + return recordpath_; + } + + // Returns true if directory is writeable + bool setRecordPath(const std::string &r); + + bool getIsAlwaysRecording() const { + return alwaysRecording_; + } + + void setIsAlwaysRecording(bool rec) { + alwaysRecording_ = rec; + } + + double getVolumemic() const { + return volumemic_; + } + void setVolumemic(double m) { + volumemic_ = m; + } + + double getVolumespkr() const { + return volumespkr_; + } + void setVolumespkr(double s) { + volumespkr_ = s; + } + + bool isAGCEnabled() const { + return agcEnabled_; + } + + void setAGCState(bool enabled) { + agcEnabled_ = enabled; + } + + bool getNoiseReduce() const { + return denoise_; + } + + void setNoiseReduce(bool enabled) { + denoise_ = enabled; + } + + bool getCaptureMuted() const { + return captureMuted_; + } + + void setCaptureMuted(bool muted) { + captureMuted_ = muted; + } + + bool getPlaybackMuted() const { + return playbackMuted_; + } + + void setPlaybackMuted(bool muted) { + playbackMuted_= muted; + } + + private: + std::string audioApi_; + + // alsa preference + int alsaCardin_; + int alsaCardout_; + int alsaCardring_; + std::string alsaPlugin_; + int alsaSmplrate_; + + //pulseaudio preference + std::string pulseDevicePlayback_; + std::string pulseDeviceRecord_; + std::string pulseDeviceRingtone_; + + // general preference + std::string recordpath_; //: /home/msavard/Bureau + bool alwaysRecording_; + double volumemic_; + double volumespkr_; + + bool denoise_; + bool agcEnabled_; + bool captureMuted_; + bool playbackMuted_; + constexpr static const char * const CONFIG_LABEL = "audio"; +}; + +class ShortcutPreferences : public Serializable { + public: + ShortcutPreferences(); + void serialize(YAML::Emitter &out); + void unserialize(const YAML::Node &in); + + void setShortcuts(std::map<std::string, std::string> shortcuts); + std::map<std::string, std::string> getShortcuts() const; + + std::string getHangup() const { + return hangup_; + } + + void setHangup(const std::string &hangup) { + hangup_ = hangup; + } + + std::string getPickup() const { + return pickup_; + } + + void setPickup(const std::string &pickup) { + pickup_ = pickup; + } + + std::string getPopup() const { + return popup_; + } + + void setPopup(const std::string &popup) { + popup_ = popup; + } + + std::string getToggleHold() const { + return toggleHold_; + } + + void setToggleHold(const std::string &hold) { + toggleHold_ = hold; + } + + std::string getTogglePickupHangup() const { + return togglePickupHangup_; + } + + void setTogglePickupHangup(const std::string &toggle) { + togglePickupHangup_ = toggle; + } + + private: + std::string hangup_; + std::string pickup_; + std::string popup_; + std::string toggleHold_; + std::string togglePickupHangup_; + constexpr static const char * const CONFIG_LABEL = "shortcuts"; +}; + +} // namespace ring + +#endif diff --git a/src/registration_states.h b/src/registration_states.h new file mode 100644 index 0000000000..5b72325b44 --- /dev/null +++ b/src/registration_states.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef REGISTRATION_STATES_H_ +#define REGISTRATION_STATES_H_ + +namespace ring { + +/** Contains all the Registration states for an account can be in */ +enum class RegistrationState { + UNREGISTERED, + TRYING, + REGISTERED, + ERROR_GENERIC, + ERROR_AUTH, + ERROR_NETWORK, + ERROR_HOST, + ERROR_SERVICE_UNAVAILABLE, + ERROR_EXIST_STUN, + ERROR_NOT_ACCEPTABLE +}; + +} // namespace ring + +#endif // REGISTRATION_STATES_H_ diff --git a/src/ring_api.cpp b/src/ring_api.cpp new file mode 100644 index 0000000000..0cb592d99e --- /dev/null +++ b/src/ring_api.cpp @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2014-2015 Savoir-Faire Linux Inc. + * Author: Philippe Proulx <philippe.proulx@savoirfairelinux.com> + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#include <string> +#include <vector> +#include <map> +#include <cstdlib> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "manager.h" +#include "managerimpl.h" +#include "logger.h" +#include "dring.h" +#include "callmanager_interface.h" +#include "configurationmanager_interface.h" +#include "presencemanager_interface.h" + +#ifdef RING_VIDEO +#include "client/videomanager.h" +#endif // RING_VIDEO + +namespace DRing { + +const char* +version() noexcept +{ + return PACKAGE_VERSION; +} + +bool +init(enum InitFlag flags) noexcept +{ + ::setDebugMode(flags & DRING_FLAG_DEBUG); + ::setConsoleLog(flags & DRING_FLAG_CONSOLE_LOG); + + try { + // current implementation use static variable + return &ring::Manager::instance() != nullptr; + } catch (...) { + return nullptr; + } +} + +bool +start(const std::string& config_file) noexcept +{ + try { + ring::Manager::instance().init(config_file); + } catch (...) { + return false; + } + return true; +} + +void +fini() noexcept +{ + ring::Manager::instance().finish(); +} + +void +pollEvents() noexcept +{ + ring::Manager::instance().pollEvents(); +} + +} // namespace DRing diff --git a/src/ring_plugin.h b/src/ring_plugin.h new file mode 100644 index 0000000000..c3a011d835 --- /dev/null +++ b/src/ring_plugin.h @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef RING_PLUGIN_H +#define RING_PLUGIN_H + +#include <inttypes.h> + +#ifdef __cplusplus +# define EXTERNAL_C_LINKAGE extern "C" +# define C_INTERFACE_START EXTERNAL_C_LINKAGE { +# define C_INTERFACE_END } +#else +# define C_LINKAGE +# define C_INTERFACE_START +# define C_INTERFACE_END +#endif + +#define RING_PLUGIN_ABI_VERSION 1 /* 0 doesn't exist, considered as error */ +#define RING_PLUGIN_API_VERSION 1 /* 0 doesn't exist, considered as error */ + +C_INTERFACE_START; + +typedef struct RING_PluginVersion { + /* plugin is not loadable if this number differs from one + * stored in the plugin loader */ + uint32_t abi; + + /* a difference on api number may be acceptable, see the loader code */ + uint32_t api; +} RING_PluginVersion; + +struct RING_PluginAPI; + +/* RING_PluginCreateFunc parameters */ +typedef struct RING_PluginObjectParams { + const RING_PluginAPI* pluginApi; /* this API */ + const char* type; +} RING_PluginObjectParams; + +typedef void* (*RING_PluginCreateFunc)(RING_PluginObjectParams* params, void* closure); + +typedef void (*RING_PluginDestroyFunc)(void *object, void* closure); + +/* RING_PluginAPI.registerObjectFactory data */ +typedef struct RING_PluginObjectFactory { + RING_PluginVersion version; + void* closure; /* closure for create */ + RING_PluginCreateFunc create; + RING_PluginDestroyFunc destroy; +} RING_PluginObjectFactory; + +/* Plugins exposed API prototype */ +typedef int32_t (*RING_PluginFunc)(const RING_PluginAPI* api, + const char* name, + void* data); + +/* RING_PluginInitFunc parameters. + * This structure is filled by the Plugin manager. + * For backware compatibility, never c + */ +typedef struct RING_PluginAPI { + RING_PluginVersion version; /* structure version, always the first data */ + void* context; /* opaque structure used by next functions */ + + /* API usable by plugin implementors */ + RING_PluginFunc registerObjectFactory; + RING_PluginFunc invokeService; +} RING_PluginAPI; + +typedef void (*RING_PluginExitFunc)(void); + +typedef RING_PluginExitFunc (*RING_PluginInitFunc)(const RING_PluginAPI *api); + +C_INTERFACE_END; + +#define RING_DYN_INIT_FUNC_NAME "RING_dynPluginInit" +#define RING_PLUGIN_INIT_STATIC(fname, pname) RING_PLUGIN_INIT(fname, pname) +#define RING_PLUGIN_INIT_DYNAMIC(pname) RING_PLUGIN_INIT(RING_dynPluginInit, pname) + +/* Define here platform dependent way to export a declaration x to the dynamic + * loading system. + */ + +/* Default case (like POSIX/.so) */ + +#define RING_PLUGIN_INIT(fname, pname) \ + EXTERNAL_C_LINKAGE RING_PluginExitFunc fname(const RING_PluginAPI *pname) +#define RING_PLUGIN_EXIT(fname) \ + EXTERNAL_C_LINKAGE void fname(void) + +#endif /* RING_PLUGIN_H */ diff --git a/src/ring_types.h b/src/ring_types.h new file mode 100644 index 0000000000..9bf25d5b59 --- /dev/null +++ b/src/ring_types.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef RING_TYPES_H_ +#define RING_TYPES_H_ + +#include <type_traits> +#include <memory> +#include <mutex> +#include <cstddef> // for size_t + +namespace ring { + +typedef int16_t AudioSample; + +static constexpr size_t SIZEBUF = 32000; /** About 1s of buffering at 48kHz */ + +/** + * This meta-function is used to enable a template overload + * only if given class T is a base of class U + */ +template <class T, class U> +using enable_if_base_of = typename std::enable_if<std::is_base_of<T, U>::value, T>::type; + +/** + * Return a shared pointer on an auto-generated global instance of class T. + * This instance is created only at usage and destroyed when not, + * as we keep only a weak reference on it. + * But when created it's always the same object until all holders release + * their sharing. + * An optional MaxRespawn positive integer can be given to limit the number + * of time the object can be created (i.e. different instance). + * Any negative values (default) block this effect (unlimited respawn). + * This function is thread-safe. + */ +template <class T, signed MaxRespawn=-1> +std::shared_ptr<T> +getGlobalInstance() +{ + static std::recursive_mutex mutex; // recursive as instance calls recursively + static std::weak_ptr<T> wlink; + + std::unique_lock<std::recursive_mutex> lock(mutex); + + if (wlink.expired()) { + static signed counter {MaxRespawn}; + if (not counter) + return nullptr; + auto link = std::make_shared<T>(); + wlink = link; + if (counter > 0) + --counter; + return link; + } + + return wlink.lock(); +} + +} // namespace ring + +#endif // RING_TYPES_H_ diff --git a/src/ringdht/Makefile.am b/src/ringdht/Makefile.am new file mode 100644 index 0000000000..1ee66dc804 --- /dev/null +++ b/src/ringdht/Makefile.am @@ -0,0 +1,18 @@ +include $(top_srcdir)/globals.mak + +if USE_DHT + +noinst_LTLIBRARIES = libringacc.la +libringacc_la_CXXFLAGS = @CXXFLAGS@ + +libringacc_la_LIBADD = $(DHT_LIBS) + +libringacc_la_SOURCES = \ + ringaccount.cpp \ + ringaccount.h \ + sip_transport_ice.cpp \ + sip_transport_ice.h \ + sips_transport_ice.cpp \ + sips_transport_ice.h + +endif diff --git a/src/ringdht/ringaccount.cpp b/src/ringdht/ringaccount.cpp new file mode 100644 index 0000000000..415bc5c6ce --- /dev/null +++ b/src/ringdht/ringaccount.cpp @@ -0,0 +1,1125 @@ +/* + * Copyright (C) 2014-2015 Savoir-Faire Linux Inc. + * + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "ringaccount.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sip/sdp.h" +#include "sip/sipvoiplink.h" +#include "sip/sipcall.h" +#include "sip/siptransport.h" + +#include "sips_transport_ice.h" +#include "ice_transport.h" + +#include "client/signal.h" + +#include "upnp/upnp_control.h" +#include "system_codec_container.h" + +#include "account_schema.h" +#include "logger.h" +#include "manager.h" + +#ifdef RING_VIDEO +#include "libav_utils.h" +#endif +#include "fileutils.h" +#include "string_utils.h" +#include "array_size.h" + +#include "config/yamlparser.h" + +#include "gnutls_support.h" + +#include <opendht/securedht.h> +#include <yaml-cpp/yaml.h> + +#include <algorithm> +#include <array> +#include <memory> +#include <sstream> +#include <cctype> + +namespace ring { + +static constexpr int ICE_COMPONENTS {1}; +static constexpr int ICE_COMP_SIP_TRANSPORT {0}; +static constexpr int ICE_INIT_TIMEOUT {5}; +static constexpr int ICE_NEGOTIATION_TIMEOUT {60}; + +constexpr const char * const RingAccount::ACCOUNT_TYPE; + +RingAccount::RingAccount(const std::string& accountID, bool /* presenceEnabled */) + : SIPAccountBase(accountID), via_addr_() + , gtlsGIG_ {tls::GnuTlsGlobalInit::make_guard()} +{ + fileutils::check_dir(fileutils::get_cache_dir().c_str()); + cachePath_ = fileutils::get_cache_dir()+DIR_SEPARATOR_STR+getAccountID(); + dataPath_ = cachePath_ + DIR_SEPARATOR_STR "values"; + + /* ~/.local/{appname} */ + fileutils::check_dir(fileutils::get_data_dir().c_str()); + + /* ~/.local/{appname}/{accountID} */ + idPath_ = fileutils::get_data_dir()+DIR_SEPARATOR_STR+getAccountID(); + fileutils::check_dir(idPath_.c_str()); + caPath_ = idPath_ + DIR_SEPARATOR_STR "certs"; + caListPath_ = idPath_ + DIR_SEPARATOR_STR "ca_list.pem"; +} + +RingAccount::~RingAccount() +{ + Manager::instance().unregisterEventHandler((uintptr_t)this); + dht_.join(); + gnutls_global_deinit(); +} + +std::shared_ptr<SIPCall> +RingAccount::newIncomingCall(const std::string& from) +{ + std::lock_guard<std::mutex> lock(callsMutex_); + auto call_it = pendingSipCalls_.begin(); + while (call_it != pendingSipCalls_.end()) { + auto call = call_it->call.lock(); + if (not call) { + RING_WARN("newIncomingCall: discarding deleted call"); + call_it = pendingSipCalls_.erase(call_it); + } else if (call->getPeerNumber() == from) { + pendingSipCalls_.erase(call_it); + RING_DBG("newIncomingCall: found matching call for %s", from.c_str()); + return call; + } else { + ++call_it; + } + } + RING_ERR("newIncomingCall: can't find matching call for %s", from.c_str()); + return nullptr; +} + +template <> +std::shared_ptr<SIPCall> +RingAccount::newOutgoingCall(const std::string& toUrl) +{ + auto dhtf = toUrl.find("ring:"); + if (dhtf != std::string::npos) { + dhtf = dhtf+5; + } else { + dhtf = toUrl.find("sips:"); + dhtf = (dhtf == std::string::npos) ? 0 : dhtf+5; + } + if (toUrl.length() - dhtf < 40) + throw std::invalid_argument("id must be a ring infohash"); + + const std::string toUri = toUrl.substr(dhtf, 40); + if (std::find_if_not(toUri.cbegin(), toUri.cend(), ::isxdigit) != toUri.cend()) + throw std::invalid_argument("id must be a ring infohash"); + + RING_DBG("Calling DHT peer %s", toUrl.c_str()); + + auto& manager = Manager::instance(); + auto call = manager.callFactory.newCall<SIPCall, RingAccount>(*this, manager.getNewCallID(), + Call::OUTGOING); + call->setIPToIP(true); + call->setSecure(isTlsEnabled()); + + // Create an ICE transport for SIP channel + auto& tfactory = manager.getIceTransportFactory(); + auto ice = tfactory.createTransport(("sip:" + call->getCallId()).c_str(), + ICE_COMPONENTS, true, getUPnPActive()); + if (not ice) { + call->removeCall(); + return nullptr; + } + + auto shared_this = std::static_pointer_cast<RingAccount>(shared_from_this()); + auto iceInitTimeout = std::chrono::steady_clock::now() + std::chrono::seconds {ICE_INIT_TIMEOUT}; + + manager.addTask([=] { + static std::uniform_int_distribution<dht::Value::Id> udist; + + /* First step: wait for an initialized ICE transport for SIP channel */ + if (ice->isFailed() or std::chrono::steady_clock::now() >= iceInitTimeout) { + RING_DBG("ice init failed (or timeout)"); + call->setConnectionState(Call::DISCONNECTED); + call->setState(Call::MERROR); + Manager::instance().callFailure(*call); // signal client + call->removeCall(); + return false; + } + + if (not ice->isInitialized()) + return true; + + /* Next step: sent the ICE data to peer through DHT */ + const dht::Value::Id callvid = udist(shared_this->rand_); + const dht::Value::Id replyvid = callvid + 1; + const auto toH = dht::InfoHash(toUri); + const auto callkey = dht::InfoHash::get("callto:" + toUri); + + std::weak_ptr<SIPCall> weak_call = call; + + call->setConnectionState(Call::TRYING); + shared_this->dht_.putEncrypted( + callkey, toH, + dht::Value { + dht::DhtMessage::TYPE, + dht::DhtMessage( + dht::DhtMessage::Service::ICE_CANDIDATES, + ice->getLocalAttributesAndCandidates() + ), + callvid + }, + [=](bool ok) { // Put complete callback + if (!ok) { + RING_WARN("Can't put ICE descriptor on DHT"); + if (auto call = weak_call.lock()) { + call->setConnectionState(Call::DISCONNECTED); + Manager::instance().callFailure(*call); // signal client + call->removeCall(); + } + } + shared_this->dht_.cancelPut(callkey, callvid); + }); + + auto listenKey = shared_this->dht_.listen( + callkey, + [=] (const std::vector<std::shared_ptr<dht::Value>>& vals) { + RING_DBG("Outcall listen callback (%d values)", vals.size()); + for (const auto& v : vals) { + if (v->recipient != shared_this->dht_.getId()) { + RING_DBG("Ignoring non encrypted or bad type value %s", + v->toString().c_str()); + continue; + } + if (v->id != replyvid) + continue; + dht::DhtMessage msg {v->data}; + RING_WARN("ICE request replied from DHT peer %s", + toH.toString().c_str()); + ice->start(msg.getMessage()); + return false; + } + return true; + }, + dht::DhtMessage::ServiceFilter(dht::DhtMessage::Service::ICE_CANDIDATES) + ); + + shared_this->pendingCalls_.emplace_back(PendingCall{ + std::chrono::steady_clock::now(), + ice, weak_call, + std::move(listenKey), + callkey, toH + }); + return false; + }); + + return call; +} + +void +RingAccount::createOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::string& to_id, IpAddr target) +{ + RING_WARN("RingAccount::createOutgoingCall to: %s target: %s", + to_id.c_str(), target.toString(true).c_str()); + call->initIceTransport(true); + call->setIPToIP(true); + call->setPeerNumber(getToUri(to_id+"@"+target.toString(true).c_str())); + call->initRecFilename(to_id); + + const auto localAddress = ip_utils::getInterfaceAddr(getLocalInterface()); + call->setCallMediaLocal(call->getIceTransport()->getDefaultLocalAddress()); + + IpAddr addrSdp; + if (getUPnPActive()) { + /* use UPnP addr, or published addr if its set */ + addrSdp = getPublishedSameasLocal() ? + getUPnPIpAddress() : getPublishedIpAddress(); + } else { + addrSdp = isStunEnabled() or (not getPublishedSameasLocal()) ? + getPublishedIpAddress() : localAddress; + } + + /* fallback on local address */ + if (not addrSdp) addrSdp = localAddress; + + // Initialize the session using ULAW as default codec in case of early media + // The session should be ready to receive media once the first INVITE is sent, before + // the session initialization is completed + if (!getSystemCodecContainer()->searchCodecByName("PCMA", ring::MEDIA_AUDIO)) + throw VoipLinkException("Could not instantiate codec for early media"); + + // Building the local SDP offer + auto& sdp = call->getSDP(); + + sdp.setPublishedIP(addrSdp); + const bool created = sdp.createOffer( + getActiveAccountCodecInfoList(MEDIA_AUDIO), + getActiveAccountCodecInfoList(videoEnabled_ ? MEDIA_VIDEO : MEDIA_NONE), + getSrtpKeyExchange() + ); + + if (not created or not SIPStartCall(call, target)) + throw VoipLinkException("Could not send outgoing INVITE request for new call"); +} + +std::shared_ptr<Call> +RingAccount::newOutgoingCall(const std::string& toUrl) +{ + return newOutgoingCall<SIPCall>(toUrl); +} + +bool +RingAccount::SIPStartCall(const std::shared_ptr<SIPCall>& call, IpAddr target) +{ + call->setupLocalSDPFromIce(); + std::string toUri(call->getPeerNumber()); // expecting a fully well formed sip uri + + pj_str_t pjTo = pj_str((char*) toUri.c_str()); + + // Create the from header + std::string from(getFromUri()); + pj_str_t pjFrom = pj_str((char*) from.c_str()); + + std::string targetStr = getToUri(target.toString(true)/*+";transport=ICE"*/); + pj_str_t pjTarget = pj_str((char*) targetStr.c_str()); + + pj_str_t pjContact; + { + auto transport = call->getTransport(); + pjContact = getContactHeader(transport ? transport->get() : nullptr); + } + + RING_DBG("contact header: %.*s / %s -> %s / %.*s", + pjContact.slen, pjContact.ptr, from.c_str(), toUri.c_str(), pjTarget.slen, pjTarget.ptr); + + pjsip_dialog *dialog = NULL; + if (pjsip_dlg_create_uac(pjsip_ua_instance(), &pjFrom, &pjContact, &pjTo, &pjTarget, &dialog) != PJ_SUCCESS) { + RING_ERR("Unable to create SIP dialogs for user agent client when " + "calling %s", toUri.c_str()); + return false; + } + + pj_str_t subj_hdr_name = CONST_PJ_STR("Subject"); + pjsip_hdr* subj_hdr = (pjsip_hdr*) pjsip_parse_hdr(dialog->pool, &subj_hdr_name, (char *) "Phone call", 10, NULL); + + pj_list_push_back(&dialog->inv_hdr, subj_hdr); + + pjsip_inv_session* inv = nullptr; + if (pjsip_inv_create_uac(dialog, call->getSDP().getLocalSdpSession(), 0, &inv) != PJ_SUCCESS) { + RING_ERR("Unable to create invite session for user agent client"); + return false; + } + + if (!inv) { + RING_ERR("Call invite is not initialized"); + return PJ_FALSE; + } + + call->inv.reset(inv); + +/* + updateDialogViaSentBy(dialog); + if (hasServiceRoute()) + pjsip_dlg_set_route_set(dialog, sip_utils::createRouteSet(getServiceRoute(), call->inv->pool)); +*/ + call->inv->mod_data[link_->getModId()] = (void*)call.get(); + + pjsip_tx_data *tdata; + + if (pjsip_inv_invite(call->inv.get(), &tdata) != PJ_SUCCESS) { + RING_ERR("Could not initialize invite messager for this call"); + return false; + } + + //const pjsip_tpselector tp_sel = getTransportSelector(); + const pjsip_tpselector tp_sel = {PJSIP_TPSELECTOR_TRANSPORT, {call->getTransport()->get()}}; + if (pjsip_dlg_set_transport(dialog, &tp_sel) != PJ_SUCCESS) { + RING_ERR("Unable to associate transport for invite session dialog"); + return false; + } + + if (pjsip_inv_send_msg(call->inv.get(), tdata) != PJ_SUCCESS) { + call->inv.reset(); + RING_ERR("Unable to send invite message for this call"); + return false; + } + + call->setConnectionState(Call::PROGRESSING); + call->setState(Call::ACTIVE); + + return true; +} + +void RingAccount::serialize(YAML::Emitter &out) +{ + out << YAML::BeginMap; + SIPAccountBase::serialize(out); + out << YAML::Key << Conf::DHT_PORT_KEY << YAML::Value << dhtPort_; + + // tls submap + out << YAML::Key << Conf::TLS_KEY << YAML::Value << YAML::BeginMap; + SIPAccountBase::serializeTls(out); + out << YAML::EndMap; + + out << YAML::EndMap; +} + +void RingAccount::unserialize(const YAML::Node &node) +{ + using yaml_utils::parseValue; + + SIPAccountBase::unserialize(node); + in_port_t port {DHT_DEFAULT_PORT}; + parseValue(node, Conf::DHT_PORT_KEY, port); + dhtPort_ = port ? port : DHT_DEFAULT_PORT; + dhtPortUsed_ = dhtPort_; + checkIdentityPath(); +} + +void +RingAccount::checkIdentityPath() +{ + if (not tlsPrivateKeyFile_.empty() and not tlsCertificateFile_.empty()) { + loadIdentity(); + return; + } + + const auto idPath = fileutils::get_data_dir()+DIR_SEPARATOR_STR+getAccountID(); + tlsPrivateKeyFile_ = idPath + DIR_SEPARATOR_STR "dht.key"; + tlsCertificateFile_ = idPath + DIR_SEPARATOR_STR "dht.crt"; + tlsCaListFile_ = idPath + DIR_SEPARATOR_STR "ca.crt"; + loadIdentity(); +} + +std::pair<std::shared_ptr<dht::crypto::Certificate>, dht::crypto::Identity> +RingAccount::loadIdentity() +{ + dht::crypto::Certificate ca_cert; + + dht::crypto::Certificate dht_cert; + dht::crypto::PrivateKey dht_key; + + try { + ca_cert = dht::crypto::Certificate(fileutils::loadFile(tlsCaListFile_)); + dht_cert = dht::crypto::Certificate(fileutils::loadFile(tlsCertificateFile_)); + dht_key = dht::crypto::PrivateKey(fileutils::loadFile(tlsPrivateKeyFile_)); + } + catch (const std::exception& e) { + RING_ERR("Error loading identity: %s", e.what()); + auto ca = dht::crypto::generateIdentity("Ring CA"); + if (!ca.first || !ca.second) { + throw VoipLinkException("Can't generate CA for this account."); + } + auto id = dht::crypto::generateIdentity("Ring", ca); + if (!id.first || !id.second) { + throw VoipLinkException("Can't generate identity for this account."); + } + idPath_ = fileutils::get_data_dir() + DIR_SEPARATOR_STR + getAccountID(); + fileutils::check_dir(idPath_.c_str()); + + saveIdentity(ca, idPath_ + DIR_SEPARATOR_STR "ca"); + tlsCaListFile_ = idPath_ + DIR_SEPARATOR_STR "ca.crt"; + + saveIdentity(id, idPath_ + DIR_SEPARATOR_STR "dht"); + tlsCertificateFile_ = idPath_ + DIR_SEPARATOR_STR "dht.crt"; + tlsPrivateKeyFile_ = idPath_ + DIR_SEPARATOR_STR "dht.key"; + + username_ = id.second->getId().toString(); + return {ca.second, id}; + } + + username_ = dht_cert.getId().toString(); + return { + std::make_shared<dht::crypto::Certificate>(std::move(ca_cert)), + { + std::make_shared<dht::crypto::PrivateKey>(std::move(dht_key)), + std::make_shared<dht::crypto::Certificate>(std::move(dht_cert)) + } + }; +} + +void +RingAccount::saveIdentity(const dht::crypto::Identity id, const std::string& path) const +{ + if (id.first) + fileutils::saveFile(path + ".key", id.first->serialize()); + if (id.second) + fileutils::saveFile(path + ".crt", id.second->getPacked()); +} + +template <typename T> +static void +parseInt(const std::map<std::string, std::string> &details, const char *key, T &i) +{ + const auto iter = details.find(key); + if (iter == details.end()) { + RING_ERR("Couldn't find key %s", key); + return; + } + i = atoi(iter->second.c_str()); +} + +void RingAccount::setAccountDetails(const std::map<std::string, std::string> &details) +{ + SIPAccountBase::setAccountDetails(details); + if (hostname_ == "") + hostname_ = DHT_DEFAULT_BOOTSTRAP; + parseInt(details, Conf::CONFIG_DHT_PORT, dhtPort_); + if (dhtPort_ == 0) + dhtPort_ = DHT_DEFAULT_PORT; + dhtPortUsed_ = dhtPort_; + checkIdentityPath(); +} + +std::map<std::string, std::string> RingAccount::getAccountDetails() const +{ + std::map<std::string, std::string> a = SIPAccountBase::getAccountDetails(); + a.emplace(Conf::CONFIG_DHT_PORT, ring::to_string(dhtPort_)); + return a; +} + +void +RingAccount::handleEvents() +{ + dht_.loop(); + + std::lock_guard<std::mutex> lock(callsMutex_); + auto now = std::chrono::steady_clock::now(); + auto c = pendingCalls_.begin(); + while (c != pendingCalls_.end()) { + auto call = c->call.lock(); + if (not call) { + RING_WARN("Removing deleted call from pending calls"); + if (c->call_key != dht::InfoHash()) + dht_.cancelListen(c->call_key, c->listen_key.get()); + c = pendingCalls_.erase(c); + continue; + } + auto ice = c->ice.get(); + if (ice->isRunning()) { + regenerateCAList(); + auto id = loadIdentity(); + auto remote_h = c->id; + tls::TlsParams tlsParams { + .ca_list = caListPath_, + .id = id.second, + .dh_params = dhParams_, + .timeout = std::chrono::seconds(30), + .cert_check = [remote_h](unsigned status, + const gnutls_datum_t* cert_list, + unsigned cert_num) -> pj_status_t { + RING_WARN("TLS certificate check for %s", + remote_h.toString().c_str()); + + if (status & GNUTLS_CERT_EXPIRED || + status & GNUTLS_CERT_NOT_ACTIVATED) + return PJ_SSL_CERT_EVALIDITY_PERIOD; + else if (status & GNUTLS_CERT_INSECURE_ALGORITHM) + return PJ_SSL_CERT_EUNTRUSTED; + + if (cert_num == 0) + return PJ_SSL_CERT_EUNKNOWN; + + try { + std::vector<uint8_t> crt_blob(cert_list[0].data, + cert_list[0].data + cert_list[0].size); + dht::crypto::Certificate crt(crt_blob); + const auto tls_id = crt.getId(); + if (crt.getUID() != tls_id.toString()) { + RING_ERR("Certificate UID must be the public key ID"); + return PJ_SSL_CERT_EUNTRUSTED; + } + + if (tls_id != remote_h) { + RING_ERR("Certificate public key (ID %s) doesn't match expectation (%s)", + tls_id.toString().c_str(), + remote_h.toString().c_str()); + return PJ_SSL_CERT_EUNTRUSTED; + } + } catch (const std::exception& e) { + return PJ_SSL_CERT_EUNKNOWN; + } + return PJ_SUCCESS; + } + }; + auto tr = link_->sipTransportBroker->getTlsIceTransport(c->ice, ICE_COMP_SIP_TRANSPORT, tlsParams); + call->setTransport(tr); + call->setConnectionState(Call::PROGRESSING); + if (c->call_key == dht::InfoHash()) { + RING_DBG("ICE succeeded : moving incomming call to pending sip call"); + auto in = c; + ++c; + pendingSipCalls_.splice(pendingSipCalls_.begin(), pendingCalls_, in, c); + } else { + RING_DBG("ICE succeeded : removing pending outgoing call"); + createOutgoingCall(call, c->id.toString(), ice->getRemoteAddress(ICE_COMP_SIP_TRANSPORT)); + dht_.cancelListen(c->call_key, c->listen_key.get()); + c = pendingCalls_.erase(c); + } + } else if (ice->isFailed() || now - c->start > std::chrono::seconds(ICE_NEGOTIATION_TIMEOUT)) { + RING_WARN("ICE timeout : removing pending call"); + if (c->call_key != dht::InfoHash()) + dht_.cancelListen(c->call_key, c->listen_key.get()); + call->setConnectionState(Call::DISCONNECTED); + Manager::instance().callFailure(*call); + c = pendingCalls_.erase(c); + call->removeCall(); + } else + ++c; + } +} + +bool RingAccount::mapPortUPnP() +{ + if (getUPnPActive()) { + /* create port mapping from published port to local port to the local IP + * note that since different RING accounts can use the same port, + * it may already be open, thats OK + * + * if the desired port is taken by another client, then it will try to map + * a different port, if succesfull, then we have to use that port for DHT + */ + uint16_t port_used; + std::lock_guard<std::mutex> lock(upnp_mtx); + if (upnp_->addAnyMapping(dhtPort_, ring::upnp::PortType::UDP, false, &port_used)) { + if (port_used != dhtPort_) + RING_DBG("UPnP could not map port %u for DHT, using %u instead", dhtPort_, port_used); + dhtPortUsed_ = port_used; + return true; + } else { + /* failed to map any port */ + return false; + } + } else { + /* not using UPnP, so return true */ + return true; + } +} + +void RingAccount::doRegister() +{ + if (not isEnabled()) { + RING_WARN("Account must be enabled to register, ignoring"); + return; + } + + if (not dhParams_.valid()) { + generateDhParams(); + } + + /* if UPnP is enabled, then wait for IGD to complete registration */ + if ( upnpEnabled_ ) { + auto shared = shared_from_this(); + RING_DBG("UPnP: waiting for IGD to register RING account"); + setRegistrationState(RegistrationState::TRYING); + std::thread{ [shared] { + auto this_ = std::static_pointer_cast<RingAccount>(shared).get(); + if ( not this_->mapPortUPnP()) + RING_WARN("UPnP: Could not successfully map DHT port with UPnP, continuing with account registration anyways."); + this_->doRegister_(); + }}.detach(); + } else + doRegister_(); + +} + +static constexpr const char* +dhtStatusStr(dht::Dht::Status status) { + return status == dht::Dht::Status::Connected ? "connected" : ( + status == dht::Dht::Status::Connecting ? "connecting" : + "disconnected"); +} + +void RingAccount::doRegister_() +{ + try { + loadTreatedCalls(); + if (dht_.isRunning()) { + RING_ERR("DHT already running (stopping it first)."); + dht_.join(); + } + auto identity = loadIdentity(); + dht_.run(dhtPortUsed_, identity.second, false, [=](dht::Dht::Status s4, dht::Dht::Status s6) { + RING_WARN("Dht status : IPv4 %s; IPv6 %s", dhtStatusStr(s4), dhtStatusStr(s6)); + auto status = std::max(s4, s6); + switch(status) { + case dht::Dht::Status::Connecting: + case dht::Dht::Status::Connected: + setRegistrationState(status == dht::Dht::Status::Connected ? RegistrationState::REGISTERED : RegistrationState::TRYING); + break; + case dht::Dht::Status::Disconnected: + default: + setRegistrationState(status == dht::Dht::Status::Disconnected ? RegistrationState::UNREGISTERED : RegistrationState::ERROR_GENERIC); + break; + } + }); + +#if 0 // enable if dht_ logging is needed + dht_.setLoggers( + [](char const* m, va_list args){ vlogger(LOG_ERR, m, args); }, + [](char const* m, va_list args){ vlogger(LOG_WARNING, m, args); }, + [](char const* m, va_list args){ vlogger(LOG_DEBUG, m, args); } + ); +#endif + + dht_.importValues(loadValues()); + + // Publish our own CA + dht_.put(identity.first->getPublicKey().getId(), dht::Value { + dht::CERTIFICATE_TYPE, + *identity.first, + 1 + }); + + username_ = dht_.getId().toString(); + + Manager::instance().registerEventHandler((uintptr_t)this, [this]{ handleEvents(); }); + setRegistrationState(RegistrationState::TRYING); + + dht_.bootstrap(loadNodes()); + if (!hostname_.empty()) { + std::stringstream ss(hostname_); + std::vector<std::pair<sockaddr_storage, socklen_t>> bootstrap; + std::string node_addr; + while (std::getline(ss, node_addr, ';')) { + auto ips = ip_utils::getAddrList(node_addr); + if (ips.empty()) { + IpAddr resolved(node_addr); + if (resolved) { + if (resolved.getPort() == 0) + resolved.setPort(DHT_DEFAULT_PORT); + bootstrap.emplace_back(resolved, resolved.getLength()); + } + } else { + for (auto& ip : ips) { + if (ip.getPort() == 0) + ip.setPort(DHT_DEFAULT_PORT); + bootstrap.emplace_back(ip, ip.getLength()); + } + } + } + for (auto ip : bootstrap) + RING_DBG("Bootstrap node: %s", IpAddr(ip.first).toString(true).c_str()); + dht_.bootstrap(bootstrap); + } + + // Listen for incoming calls + auto shared = std::static_pointer_cast<RingAccount>(shared_from_this()); + auto listenKey = "callto:"+dht_.getId().toString(); + RING_WARN("Listening on %s : %s", listenKey.c_str(), dht::InfoHash::get(listenKey).toString().c_str()); + dht_.listen ( + listenKey, + [shared,listenKey] (const std::vector<std::shared_ptr<dht::Value>>& vals) { + auto& this_ = *shared.get(); + for (const auto& v : vals) { + std::shared_ptr<SIPCall> call; + try { + if (v->recipient != this_.dht_.getId()) { + RING_DBG("Ignoring non encrypted value %s.", v->toString().c_str()); + continue; + } + auto remote_id = v->owner.getId(); + if (remote_id == this_.dht_.getId()) + continue; + dht::DhtMessage msg {v->data}; + auto res = this_.treatedCalls_.insert(v->id); + this_.saveTreatedCalls(); + if (!res.second) + continue; + auto from = remote_id.toString(); + auto from_vid = v->id; + auto reply_vid = from_vid+1; + RING_WARN("Received incoming DHT call request from %s", from.c_str()); + call = Manager::instance().callFactory.newCall<SIPCall, RingAccount>(this_, Manager::instance().getNewCallID(), Call::INCOMING); + auto& iceTransportFactory = Manager::instance().getIceTransportFactory(); + auto ice = iceTransportFactory.createTransport( + ("sip:"+call->getCallId()).c_str(), + ICE_COMPONENTS, + false, + this_.getUPnPActive() + ); + if (ice->waitForInitialization(ICE_INIT_TIMEOUT) <= 0) + throw std::runtime_error("Can't initialize ICE.."); + + std::weak_ptr<SIPCall> weak_call = call; + + this_.dht_.putEncrypted( + listenKey, + remote_id, + dht::Value { + dht::DhtMessage::TYPE, + dht::DhtMessage( + dht::DhtMessage::Service::ICE_CANDIDATES, + ice->getLocalAttributesAndCandidates() + ), + reply_vid + }, + [weak_call,ice,shared,listenKey,reply_vid](bool ok) { + auto& this_ = *std::static_pointer_cast<RingAccount>(shared).get(); + if (!ok) { + RING_WARN("Can't put ICE descriptor on DHT"); + if (auto call = weak_call.lock()) { + call->setConnectionState(Call::DISCONNECTED); + Manager::instance().callFailure(*call); + call->removeCall(); + } + } + this_.dht_.cancelPut(listenKey, reply_vid); + } + ); + ice->start(msg.getMessage()); + call->setPeerNumber(from); + call->initRecFilename(from); + { + std::lock_guard<std::mutex> lock(this_.callsMutex_); + this_.pendingCalls_.emplace_back(PendingCall{std::chrono::steady_clock::now(), ice, weak_call, {}, {}, remote_id}); + } + return true; + } catch (const std::exception& e) { + RING_ERR("ICE/DHT error: %s", e.what()); + if (call) { + call->setConnectionState(Call::DISCONNECTED); + Manager::instance().callFailure(*call); + } + } + } + return true; + }, + dht::DhtMessage::ServiceFilter(dht::DhtMessage::Service::ICE_CANDIDATES) + ); + } + catch (const std::exception& e) { + RING_ERR("Error registering DHT account: %s", e.what()); + setRegistrationState(RegistrationState::ERROR_GENERIC); + } +} + + +void RingAccount::doUnregister(std::function<void(bool)> released_cb) +{ + { + std::lock_guard<std::mutex> lock(callsMutex_); + pendingCalls_.clear(); + pendingSipCalls_.clear(); + } + + /* RING_DBG("UPnP: removing port mapping for DHT account."); */ + upnp_->removeMappings(); + + Manager::instance().unregisterEventHandler((uintptr_t)this); + saveNodes(dht_.exportNodes()); + saveValues(dht_.exportValues()); + dht_.join(); + setRegistrationState(RegistrationState::UNREGISTERED); + if (released_cb) + released_cb(false); +} + +void +RingAccount::registerCA(const dht::crypto::Certificate& crt) +{ + fileutils::saveFile(caPath_ + DIR_SEPARATOR_STR + crt.getPublicKey().getId().toString(), crt.getPacked()); +} + +bool +RingAccount::unregisterCA(const dht::InfoHash& crt_id) +{ + auto cas = getRegistredCAs(); + bool deleted = false; + for (const auto& ca_path : cas) { + try { + dht::crypto::Certificate tmp_crt(fileutils::loadFile(ca_path)); + if (tmp_crt.getPublicKey().getId() == crt_id) + deleted &= remove(ca_path.c_str()) == 0; + } catch (const std::exception&) {} + } + return deleted; +} + +void +RingAccount::loadTreatedCalls() +{ + std::string treatedcallPath = cachePath_+DIR_SEPARATOR_STR "treatedCalls"; + { + std::ifstream file(treatedcallPath); + if (!file.is_open()) { + RING_WARN("Could not load treated calls from %s", treatedcallPath.c_str()); + return; + } + std::string line; + while (std::getline(file, line)) { + std::istringstream iss(line); + dht::Value::Id vid; + if (!(iss >> std::hex >> vid)) { break; } + treatedCalls_.insert(vid); + } + } +} + +void +RingAccount::saveTreatedCalls() const +{ + fileutils::check_dir(cachePath_.c_str()); + std::string treatedcallPath = cachePath_+DIR_SEPARATOR_STR "treatedCalls"; + { + std::ofstream file(treatedcallPath, std::ios::trunc); + if (!file.is_open()) { + RING_ERR("Could not save treated calls to %s", treatedcallPath.c_str()); + return; + } + for (auto& c : treatedCalls_) + file << std::hex << c << "\n"; + } +} + +std::vector<std::string> +RingAccount::getRegistredCAs() +{ + return fileutils::readDirectory(caPath_); +} + +void +RingAccount::regenerateCAList() +{ + std::ofstream list(caListPath_, std::ios::trunc | std::ios::binary); + if (!list.is_open()) { + RING_ERR("Could write CA list"); + return; + } + auto cas = getRegistredCAs(); + { + std::ifstream file(tlsCaListFile_, std::ios::binary); + list << file.rdbuf(); + } + for (const auto& ca : cas) { + std::ifstream file(ca, std::ios::binary); + if (!file) + continue; + list << file.rdbuf(); + } +} + +void RingAccount::saveNodes(const std::vector<dht::Dht::NodeExport>& nodes) const +{ + if (nodes.empty()) + return; + fileutils::check_dir(cachePath_.c_str()); + std::string nodesPath = cachePath_+DIR_SEPARATOR_STR "nodes"; + { + std::ofstream file(nodesPath, std::ios::trunc); + if (!file.is_open()) { + RING_ERR("Could not save nodes to %s", nodesPath.c_str()); + return; + } + for (auto& n : nodes) + file << n.id << " " << IpAddr(n.ss).toString(true) << "\n"; + } +} + +void RingAccount::saveValues(const std::vector<dht::Dht::ValuesExport>& values) const +{ + fileutils::check_dir(dataPath_.c_str()); + for (const auto& v : values) { + const std::string fname = dataPath_ + DIR_SEPARATOR_STR + v.first.toString(); + std::ofstream file(fname, std::ios::trunc | std::ios::out | std::ios::binary); + file.write((const char*)v.second.data(), v.second.size()); + } +} + +std::vector<dht::Dht::NodeExport> +RingAccount::loadNodes() const +{ + std::vector<dht::Dht::NodeExport> nodes; + std::string nodesPath = cachePath_+DIR_SEPARATOR_STR "nodes"; + { + std::ifstream file(nodesPath); + if (!file.is_open()) { + RING_ERR("Could not load nodes from %s", nodesPath.c_str()); + return nodes; + } + std::string line; + while (std::getline(file, line)) + { + std::istringstream iss(line); + std::string id, ipstr; + if (!(iss >> id >> ipstr)) { break; } + IpAddr ip {ipstr}; + dht::Dht::NodeExport e {{id}, ip, ip.getLength()}; + nodes.push_back(e); + } + } + return nodes; +} + +std::vector<dht::Dht::ValuesExport> +RingAccount::loadValues() const +{ + std::vector<dht::Dht::ValuesExport> values; + const auto dircontent(fileutils::readDirectory(dataPath_)); + for (const auto& fname : dircontent) { + const auto file = dataPath_+DIR_SEPARATOR_STR+fname; + try { + std::ifstream ifs(file, std::ifstream::in | std::ifstream::binary); + std::istreambuf_iterator<char> begin(ifs), end; + values.emplace_back(dht::Dht::ValuesExport{{fname}, std::vector<uint8_t>{begin, end}}); + } catch (const std::exception& e) { + RING_ERR("Error reading value: %s", e.what()); + } + remove(file.c_str()); + } + RING_WARN("Loaded %lu values", values.size()); + return values; +} + +void +RingAccount::initTlsConfiguration() +{ + regenerateCAList(); + +} + +static std::unique_ptr<gnutls_dh_params_int, decltype(gnutls_dh_params_deinit)&> +getNewDhParams() +{ + using namespace std::chrono; + auto bits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, /* GNUTLS_SEC_PARAM_HIGH */ GNUTLS_SEC_PARAM_NORMAL); + RING_DBG("Generating DH params with %u bits", bits); + auto t1 = high_resolution_clock::now(); + + gnutls_dh_params_t new_params_; + int ret = gnutls_dh_params_init(&new_params_); + if (ret != GNUTLS_E_SUCCESS) { + RING_ERR("Error initializing DH params: %s", gnutls_strerror(ret)); + return {nullptr, gnutls_dh_params_deinit}; + } + + ret = gnutls_dh_params_generate2(new_params_, bits); + if (ret != GNUTLS_E_SUCCESS) { + RING_ERR("Error generating DH params: %s", gnutls_strerror(ret)); + return {nullptr, gnutls_dh_params_deinit}; + } + + auto time_span = duration_cast<duration<double>>(high_resolution_clock::now() - t1); + RING_WARN("Generated DH params with %u bits in %lfs", bits, time_span.count()); + return {new_params_, gnutls_dh_params_deinit}; +} + +void +RingAccount::generateDhParams() +{ + std::packaged_task<decltype(getNewDhParams())()> task(&getNewDhParams); + dhParams_ = task.get_future(); + std::thread task_td(std::move(task)); + task_td.detach(); +} + +void RingAccount::loadConfig() +{ + RING_WARN("RingAccount::loadConfig()"); + initTlsConfiguration(); +} + +MatchRank +RingAccount::matches(const std::string &userName, const std::string &server) const +{ + auto dhtId = dht_.getId().toString(); + if (userName == dhtId || server == dhtId) { + RING_DBG("Matching account id in request with username %s", userName.c_str()); + return MatchRank::FULL; + } else { + RING_DBG("No match for account %s in request with username %s", dht_.getId().toString().c_str(), userName.c_str()); + return MatchRank::NONE; + } +} + +std::string RingAccount::getFromUri() const +{ + return "<sip:" + dht_.getId().toString() + "@ring.dht>"; +} + +std::string RingAccount::getToUri(const std::string& to) const +{ + return "<sips:" + to + ";transport=tls>"; +} + +pj_str_t +RingAccount::getContactHeader(pjsip_transport* t) +{ + if (!t) { + RING_ERR("getContactHeader: no SIP transport provided"); + contact_.slen = pj_ansi_snprintf(contact_.ptr, PJSIP_MAX_URL_SIZE, + "<sips:%s@ring.dht>", + username_.c_str()); + return contact_; + } + + // FIXME: be sure that given transport is from SipIceTransport + auto tlsTr = reinterpret_cast<tls::SipsIceTransport::TransportData*>(t)->self; + auto address = tlsTr->getLocalAddress(); + contact_.slen = pj_ansi_snprintf(contact_.ptr, PJSIP_MAX_URL_SIZE, + "<sips:%s%s%s;transport=tls>", + username_.c_str(), + (username_.empty() ? "" : "@"), + address.toString(true).c_str()); + return contact_; +} + +/** + * Enable the presence module + */ +void +RingAccount::enablePresence(const bool& /* enabled */) +{ +} + +/** + * Set the presence (PUBLISH/SUBSCRIBE) support flags + * and process the change. + */ +void +RingAccount::supportPresence(int /* function */, bool /* enabled*/) +{ +} + +/* +void RingAccount::updateDialogViaSentBy(pjsip_dialog *dlg) +{ + if (allowViaRewrite_ && via_addr_.host.slen > 0) + pjsip_dlg_set_via_sent_by(dlg, &via_addr_, via_tp_); +} +*/ + +} // namespace ring diff --git a/src/ringdht/ringaccount.h b/src/ringdht/ringaccount.h new file mode 100644 index 0000000000..84cb1411a5 --- /dev/null +++ b/src/ringdht/ringaccount.h @@ -0,0 +1,378 @@ +/* + * Copyright (C) 2014-2015 Savoir-Faire Linux Inc. + * + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef RINGACCOUNT_H +#define RINGACCOUNT_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sip/sipaccountbase.h" + +#include "noncopyable.h" +#include "ip_utils.h" +#include "ring_types.h" // enable_if_base_of + +#include <opendht/dhtrunner.h> + +#include <pjsip/sip_types.h> + +#include <vector> +#include <map> +#include <chrono> +#include <list> + +/** + * @file sipaccount.h + * @brief A SIP Account specify SIP specific functions and object = SIPCall/SIPVoIPLink) + */ + +namespace YAML { +class Node; +class Emitter; +} + +namespace ring { + +namespace Conf { + const char *const DHT_PORT_KEY = "dhtPort"; + const char *const DHT_VALUES_PATH_KEY = "dhtValuesPath"; +} + +namespace tls { +class GnuTlsGlobalInit; +} // namespace tls + +class IceTransport; + +class RingAccount : public SIPAccountBase { + public: + constexpr static const char * const ACCOUNT_TYPE = "RING"; + constexpr static const in_port_t DHT_DEFAULT_PORT = 4222; + constexpr static const char * const DHT_DEFAULT_BOOTSTRAP = "bootstrap.ring.cx"; + + const char* getAccountType() const { + return ACCOUNT_TYPE; + } + + /** + * Constructor + * @param accountID The account identifier + */ + RingAccount(const std::string& accountID, bool presenceEnabled); + + ~RingAccount(); + + /** + * Serialize internal state of this account for configuration + * @param YamlEmitter the configuration engine which generate the configuration file + */ + virtual void serialize(YAML::Emitter &out); + + /** + * Populate the internal state for this account based on info stored in the configuration file + * @param The configuration node for this account + */ + virtual void unserialize(const YAML::Node &node); + + /** + * Return an map containing the internal state of this account. Client application can use this method to manage + * account info. + * @return A map containing the account information. + */ + virtual std::map<std::string, std::string> getAccountDetails() const; + + /** + * Actually useless, since config loading is done in init() + */ + void loadConfig(); + + /** + * Connect to the DHT. + */ + void doRegister(); + + /** + * Disconnect from the DHT. + */ + void doUnregister(std::function<void(bool)> cb = std::function<void(bool)>()); + + /** + * @return pj_str_t "From" uri based on account information. + * From RFC3261: "The To header field first and foremost specifies the desired + * logical" recipient of the request, or the address-of-record of the + * user or resource that is the target of this request. [...] As such, it is + * very important that the From URI not contain IP addresses or the FQDN + * of the host on which the UA is running, since these are not logical + * names." + */ + std::string getFromUri() const; + + /** + * This method adds the correct scheme, hostname and append + * the ;transport= parameter at the end of the uri, in accordance with RFC3261. + * It is expected that "port" is present in the internal hostname_. + * + * @return pj_str_t "To" uri based on @param username + * @param username A string formatted as : "username" + */ + std::string getToUri(const std::string& username) const; + + /** + * In the current version of Ring, "srv" uri is obtained in the preformated + * way: hostname:port. This method adds the correct scheme and append + * the ;transport= parameter at the end of the uri, in accordance with RFC3261. + * + * @return pj_str_t "server" uri based on @param hostPort + * @param hostPort A string formatted as : "hostname:port" + */ + std::string getServerUri() const { return ""; }; + + /** + * Get the contact header for + * @return pj_str_t The contact header based on account information + */ + pj_str_t getContactHeader(pjsip_transport* = nullptr); + + void setReceivedParameter(const std::string &received) { + receivedParameter_ = received; + via_addr_.host.ptr = (char *) receivedParameter_.c_str(); + via_addr_.host.slen = receivedParameter_.size(); + } + + std::string getReceivedParameter() const { + return receivedParameter_; + } + + pjsip_host_port * + getViaAddr() { + return &via_addr_; + } + + /* Returns true if the username and/or hostname match this account */ + MatchRank matches(const std::string &username, const std::string &hostname) const; + + /** + * Presence management + */ + //SIPPresence * getPresence() const; + + /** + * Activate the module. + * @param function Publish or subscribe to enable + * @param enable Flag + */ + void enablePresence(const bool& enable); + /** + * Activate the publish/subscribe. + * @param enable Flag + */ + void supportPresence(int function, bool enable); + + /** + * Implementation of Account::newOutgoingCall() + * Note: keep declaration before newOutgoingCall template. + */ + std::shared_ptr<Call> newOutgoingCall(const std::string& toUrl); + + /** + * Create outgoing SIPCall. + * @param[in] toUrl The address to call + * @return std::shared_ptr<T> A shared pointer on the created call. + * The type of this instance is given in template argument. + * This type can be any base class of SIPCall class (included). + */ + template <class T=SIPCall> + std::shared_ptr<enable_if_base_of<T, SIPCall> > + newOutgoingCall(const std::string& toUrl); + + /** + * Create incoming SIPCall. + * @param[in] from The origin of the call + * @return std::shared_ptr<T> A shared pointer on the created call. + * The type of this instance is given in template argument. + * This type can be any base class of SIPCall class (included). + */ + virtual std::shared_ptr<SIPCall> + newIncomingCall(const std::string& from = {}); + + virtual bool isTlsEnabled() const { + return true; + } + + virtual sip_utils::KeyExchangeProtocol getSrtpKeyExchange() const { + return sip_utils::KeyExchangeProtocol::SDES; + } + + virtual bool getSrtpFallback() const { + return false; + } + + void registerCA(const dht::crypto::Certificate&); + bool unregisterCA(const dht::InfoHash&); + std::vector<std::string> getRegistredCAs(); + + private: + + void doRegister_(); + + const dht::ValueType USER_PROFILE_TYPE = {9, "User profile", std::chrono::hours(24 * 7)}; + //const dht::ValueType ICE_ANNOUCEMENT_TYPE = {10, "ICE descriptors", std::chrono::minutes(3)}; + + NON_COPYABLE(RingAccount); + + void handleEvents(); + + void createOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::string& to_id, IpAddr target); + + /** + * Set the internal state for this account, mainly used to manage account details from the client application. + * @param The map containing the account information. + */ + virtual void setAccountDetails(const std::map<std::string, std::string> &details); + + /** + * Start a SIP Call + * @param call The current call + * @return true if all is correct + */ + bool SIPStartCall(const std::shared_ptr<SIPCall>& call, IpAddr target); + + void regenerateCAList(); + + /** + * Maps require port via UPnP + */ + bool mapPortUPnP(); + + dht::DhtRunner dht_ {}; + + struct PendingCall { + std::chrono::steady_clock::time_point start; + std::shared_ptr<IceTransport> ice; + std::weak_ptr<SIPCall> call; + std::future<size_t> listen_key; + dht::InfoHash call_key; + dht::InfoHash id; + }; + + /** + * DHT calls waiting for ICE negotiation + */ + std::list<PendingCall> pendingCalls_ {}; + /** + * Incoming DHT calls that are not yet actual SIP calls. + */ + std::list<PendingCall> pendingSipCalls_ {}; + std::set<dht::Value::Id> treatedCalls_ {}; + mutable std::mutex callsMutex_ {}; + + std::string idPath_ {}; + std::string cachePath_ {}; + std::string dataPath_ {}; + std::string caPath_ {}; + std::string caListPath_ {}; + + /** + * Validate the values for privkeyPath_ and certPath_. + * If one of these fields is empty, reset them to the default values. + */ + void checkIdentityPath(); + + void saveIdentity(const dht::crypto::Identity id, const std::string& path) const; + void saveNodes(const std::vector<dht::Dht::NodeExport>&) const; + void saveValues(const std::vector<dht::Dht::ValuesExport>&) const; + + void loadTreatedCalls(); + void saveTreatedCalls() const; + + /** + * If privkeyPath_ is a valid private key file (PEM or DER), + * and certPath_ a valid certificate file, load and returns them. + * Otherwise, generate a new identity and returns it. + */ + std::pair<std::shared_ptr<dht::crypto::Certificate>, dht::crypto::Identity> loadIdentity(); + std::vector<dht::Dht::NodeExport> loadNodes() const; + std::vector<dht::Dht::ValuesExport> loadValues() const; + + /** + * Initializes tls settings from configuration file. + */ + void initTlsConfiguration(); + + /** + * DHT port preference + */ + in_port_t dhtPort_ {DHT_DEFAULT_PORT}; + + /** + * DHT port actually used, + * this holds the actual port used for DHT, which may not be the port + * selected in the configuration in the case that UPnP is used and the + * configured port is already used by another client + */ + in_port_t dhtPortUsed_ {DHT_DEFAULT_PORT}; + + /** + * The TLS settings, used only if tls is chosen as a sip transport. + */ + void generateDhParams(); + + std::shared_future<std::unique_ptr<gnutls_dh_params_int, decltype(gnutls_dh_params_deinit)&>> dhParams_; + std::mutex dhParamsMtx_; + std::condition_variable dhParamsCv_; + + /** + * Optional: "received" parameter from VIA header + */ + std::string receivedParameter_ {}; + + /** + * Optional: "rport" parameter from VIA header + */ + int rPort_ {-1}; + + /** + * Optional: via_addr construct from received parameters + */ + pjsip_host_port via_addr_; + + char contactBuffer_[PJSIP_MAX_URL_SIZE] {}; + pj_str_t contact_ {contactBuffer_, 0}; + pjsip_transport *via_tp_ {nullptr}; + + std::unique_ptr<tls::GnuTlsGlobalInit> gtlsGIG_; +}; + +} // namespace ring + +#endif diff --git a/src/ringdht/sip_transport_ice.cpp b/src/ringdht/sip_transport_ice.cpp new file mode 100644 index 0000000000..26acf33d64 --- /dev/null +++ b/src/ringdht/sip_transport_ice.cpp @@ -0,0 +1,255 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "sip_transport_ice.h" +#include "ice_transport.h" +#include "manager.h" +#include "logger.h" + +#include <pjsip/sip_transport.h> +#include <pjsip/sip_endpoint.h> +#include <pj/lock.h> + +#include <algorithm> + +namespace ring { + +static constexpr int POOL_TP_INIT {512}; +static constexpr int POOL_TP_INC {512}; +static constexpr int TRANSPORT_INFO_LENGTH {64}; + +static void +sockaddr_to_host_port(pj_pool_t* pool, + pjsip_host_port* host_port, + const pj_sockaddr* addr) +{ + host_port->host.ptr = (char*) pj_pool_alloc(pool, PJ_INET6_ADDRSTRLEN+4); + pj_sockaddr_print(addr, host_port->host.ptr, PJ_INET6_ADDRSTRLEN+4, 0); + host_port->host.slen = pj_ansi_strlen(host_port->host.ptr); + host_port->port = pj_sockaddr_get_port(addr); +} + +SipIceTransport::SipIceTransport(pjsip_endpoint* endpt, pj_pool_t& /* pool */, + long /* t_type */, + const std::shared_ptr<IceTransport>& ice, + int comp_id) + : pool_(nullptr, pj_pool_release) + , rxPool_(nullptr, pj_pool_release) + , trData_() + , rdata_() + , ice_(ice) + , comp_id_(comp_id) +{ + trData_.self = this; + + if (not ice or not ice->isRunning()) + throw std::logic_error("ice transport must exist and negotiation completed"); + + RING_DBG("SipIceTransport@%p {tr=%p}", this, &trData_.base); + auto& base = trData_.base; + + pool_.reset(pjsip_endpt_create_pool(endpt, "SipIceTransport.pool", POOL_TP_INIT, POOL_TP_INC)); + if (not pool_) + throw std::bad_alloc(); + auto pool = pool_.get(); + + pj_ansi_snprintf(base.obj_name, PJ_MAX_OBJ_NAME, "SipIceTransport"); + base.endpt = endpt; + base.tpmgr = pjsip_endpt_get_tpmgr(endpt); + base.pool = pool; + + rdata_.tp_info.pool = pool; + + // FIXME: not destroyed in case of exception + if (pj_atomic_create(pool, 0, &base.ref_cnt) != PJ_SUCCESS) + throw std::runtime_error("Can't create PJSIP atomic."); + + // FIXME: not destroyed in case of exception + if (pj_lock_create_recursive_mutex(pool, "SipIceTransport.mutex", &base.lock) != PJ_SUCCESS) + throw std::runtime_error("Can't create PJSIP mutex."); + + auto remote = ice->getRemoteAddress(comp_id); + RING_DBG("SipIceTransport: remote is %s", remote.toString(true).c_str()); + pj_sockaddr_cp(&base.key.rem_addr, remote.pjPtr()); + base.key.type = PJSIP_TRANSPORT_UDP;//t_type; + base.type_name = (char*)pjsip_transport_get_type_name((pjsip_transport_type_e)base.key.type); + base.flag = pjsip_transport_get_flag_from_type((pjsip_transport_type_e)base.key.type); + base.info = (char*) pj_pool_alloc(pool, TRANSPORT_INFO_LENGTH); + + char print_addr[PJ_INET6_ADDRSTRLEN+10]; + pj_ansi_snprintf(base.info, TRANSPORT_INFO_LENGTH, "%s to %s", + base.type_name, + pj_sockaddr_print(remote.pjPtr(), print_addr, + sizeof(print_addr), 3)); + base.addr_len = remote.getLength(); + base.dir = PJSIP_TP_DIR_NONE;//is_server? PJSIP_TP_DIR_INCOMING : PJSIP_TP_DIR_OUTGOING; + base.data = nullptr; + + /* Set initial local address */ + auto local = ice->getDefaultLocalAddress(); + pj_sockaddr_cp(&base.local_addr, local.pjPtr()); + + sockaddr_to_host_port(pool, &base.local_name, &base.local_addr); + sockaddr_to_host_port(pool, &base.remote_name, remote.pjPtr()); + + base.send_msg = [](pjsip_transport *transport, + pjsip_tx_data *tdata, + const pj_sockaddr_t *rem_addr, int addr_len, + void *token, pjsip_transport_callback callback) { + auto& this_ = reinterpret_cast<TransportData*>(transport)->self; + return this_->send(tdata, rem_addr, addr_len, token, callback); + }; + base.do_shutdown = [](pjsip_transport *transport) -> pj_status_t { + auto& this_ = reinterpret_cast<TransportData*>(transport)->self; + RING_WARN("SipIceTransport@%p: shutdown", this_); + return PJ_SUCCESS; + }; + base.destroy = [](pjsip_transport *transport) -> pj_status_t { + auto& this_ = reinterpret_cast<TransportData*>(transport)->self; + RING_WARN("SipIceTransport@%p: destroy", this_); + delete this_; + return PJ_SUCCESS; + }; + + /* Init rdata */ + rxPool_.reset(pjsip_endpt_create_pool(base.endpt, + "SipIceTransport.rtd%p", + PJSIP_POOL_RDATA_LEN, + PJSIP_POOL_RDATA_INC)); + if (not rxPool_) + throw std::bad_alloc(); + auto rx_pool = rxPool_.get(); + + rdata_.tp_info.pool = rx_pool; + rdata_.tp_info.transport = &base; + rdata_.tp_info.tp_data = this; + rdata_.tp_info.op_key.rdata = &rdata_; + pj_ioqueue_op_key_init(&rdata_.tp_info.op_key.op_key, sizeof(pj_ioqueue_op_key_t)); + rdata_.pkt_info.src_addr = base.key.rem_addr; + rdata_.pkt_info.src_addr_len = sizeof(rdata_.pkt_info.src_addr); + auto rem_addr = &base.key.rem_addr; + pj_sockaddr_print(rem_addr, rdata_.pkt_info.src_name, sizeof(rdata_.pkt_info.src_name), 0); + rdata_.pkt_info.src_port = pj_sockaddr_get_port(rem_addr); + rdata_.pkt_info.len = 0; + rdata_.pkt_info.zero = 0; + + if (pjsip_transport_register(base.tpmgr, &base) != PJ_SUCCESS) + throw std::runtime_error("Can't register PJSIP transport."); + is_registered_ = true; + + Manager::instance().registerEventHandler((uintptr_t)this, + [this]{ loop(); }); +} + +SipIceTransport::~SipIceTransport() +{ + RING_DBG("~SipIceTransport@%p", this); + Manager::instance().unregisterEventHandler((uintptr_t)this); + pj_lock_destroy(trData_.base.lock); + pj_atomic_destroy(trData_.base.ref_cnt); + RING_DBG("destroying SipIceTransport@%p", this); +} + +void +SipIceTransport::loop() +{ + while (ice_->getNextPacketSize(comp_id_) > 0) + onRecv(); +} + +IpAddr +SipIceTransport::getLocalAddress() const +{ + return ice_->getLocalAddress(comp_id_); +} + +pj_status_t +SipIceTransport::send(pjsip_tx_data *tdata, const pj_sockaddr_t *rem_addr, + int addr_len, void *token, + pjsip_transport_callback callback) +{ + /* Sanity check */ + PJ_ASSERT_RETURN(tdata, PJ_EINVAL); + + /* Check that there's no pending operation associated with the tdata */ + PJ_ASSERT_RETURN(tdata->op_key.tdata == nullptr, PJSIP_EPENDINGTX); + + /* Check the address is supported */ + PJ_ASSERT_RETURN(rem_addr && (addr_len==sizeof(pj_sockaddr_in) || + addr_len==sizeof(pj_sockaddr_in6)), + PJ_EINVAL); + + /* Init op key. */ + tdata->op_key.tdata = tdata; + tdata->op_key.token = token; + tdata->op_key.callback = callback; + + auto buf_sz = tdata->buf.cur - tdata->buf.start; + auto size = ice_->send(comp_id_, (uint8_t*)tdata->buf.start, buf_sz); + if (size > 0) { + if (size < buf_sz) { + std::move(tdata->buf.start + size, + tdata->buf.start + buf_sz, + tdata->buf.start); + tdata->buf.cur -= size; + } + tdata->op_key.tdata = nullptr; + } else + return PJ_EUNKNOWN; + + return PJ_SUCCESS; +} + +ssize_t +SipIceTransport::onRecv() +{ + rdata_.pkt_info.len += ice_->recv(comp_id_, + (uint8_t*)rdata_.pkt_info.packet+rdata_.pkt_info.len, + sizeof(rdata_.pkt_info.packet)-rdata_.pkt_info.len); + rdata_.pkt_info.zero = 0; + pj_gettimeofday(&rdata_.pkt_info.timestamp); + + auto eaten = pjsip_tpmgr_receive_packet(rdata_.tp_info.transport->tpmgr, &rdata_); + + /* Move unprocessed data to the front of the buffer */ + auto rem = rdata_.pkt_info.len - eaten; + if (rem > 0 && rem != rdata_.pkt_info.len) { + std::move(rdata_.pkt_info.packet + eaten, + rdata_.pkt_info.packet + eaten + rem, + rdata_.pkt_info.packet); + } + rdata_.pkt_info.len = rem; + + /* Reset pool */ + pj_pool_reset(rdata_.tp_info.pool); +} + +} // namespace ring diff --git a/src/ringdht/sip_transport_ice.h b/src/ringdht/sip_transport_ice.h new file mode 100644 index 0000000000..26d39c75bd --- /dev/null +++ b/src/ringdht/sip_transport_ice.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#pragma once + +#include "ip_utils.h" + +#include <pjsip.h> +#include <pj/pool.h> + +#include <functional> +#include <memory> +#include <type_traits> // std::is_standard_layout + +namespace ring { + +class IceTransport; + +struct SipIceTransport +{ + // This structure SHOULD be standard-layout, + // implies std::is_standard_layout<TransportData>::value + // SHOULD return true! + using TransportData = struct { + pjsip_transport base; // do not move, SHOULD be the fist member + SipIceTransport* self {nullptr}; + }; + static_assert(std::is_standard_layout<TransportData>::value, + "TranportData requires standard-layout"); + + + SipIceTransport(pjsip_endpoint* endpt, pj_pool_t& pool, long t_type, + const std::shared_ptr<IceTransport>& ice, + int comp_id); + ~SipIceTransport(); + + /** + * To be called on a regular basis to receive packets + */ + void loop(); + + IpAddr getLocalAddress() const; + + std::shared_ptr<IceTransport> getIceTransport() const { + return ice_; + } + + pjsip_transport* getTransportBase() { + return &trData_.base; + } + + private: + std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&> pool_; + std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&> rxPool_; + + TransportData trData_; + + pjsip_rx_data rdata_; + bool is_registered_ {false}; + const std::shared_ptr<IceTransport> ice_; + const int comp_id_; + + pj_status_t send(pjsip_tx_data *tdata, const pj_sockaddr_t *rem_addr, + int addr_len, void *token, + pjsip_transport_callback callback); + + ssize_t onRecv(); +}; + +} // namespace ring diff --git a/src/ringdht/sips_transport_ice.cpp b/src/ringdht/sips_transport_ice.cpp new file mode 100644 index 0000000000..c9de602720 --- /dev/null +++ b/src/ringdht/sips_transport_ice.cpp @@ -0,0 +1,1178 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "sips_transport_ice.h" +#include "ice_transport.h" +#include "manager.h" +#include "logger.h" +#include "gnutls_support.h" + +#include <gnutls/dtls.h> +#include <gnutls/abstract.h> + +#include <pjsip/sip_transport.h> +#include <pjsip/sip_endpoint.h> +#include <pj/compat/socket.h> +#include <pj/lock.h> + +#include <algorithm> +#include <cstring> // std::memset + +namespace ring { namespace tls { + +static constexpr int POOL_TP_INIT {512}; +static constexpr int POOL_TP_INC {512}; +static constexpr int TRANSPORT_INFO_LENGTH {64}; +static constexpr int DTLS_MTU {6400}; + +static void +sockaddr_to_host_port(pj_pool_t* pool, + pjsip_host_port* host_port, + const pj_sockaddr* addr) +{ + host_port->host.ptr = (char*) pj_pool_alloc(pool, PJ_INET6_ADDRSTRLEN+4); + pj_sockaddr_print(addr, host_port->host.ptr, PJ_INET6_ADDRSTRLEN+4, 0); + host_port->host.slen = pj_ansi_strlen(host_port->host.ptr); + host_port->port = pj_sockaddr_get_port(addr); +} + +SipsIceTransport::SipsIceTransport(pjsip_endpoint* endpt, + const TlsParams& param, + const std::shared_ptr<ring::IceTransport>& ice, + int comp_id) + : gtlsGIG_ {GnuTlsGlobalInit::make_guard()} + , pool_ {nullptr, pj_pool_release} + , rxPool_ (nullptr, pj_pool_release) + , trData_ () + , ice_ (ice) + , comp_id_ (comp_id) + , param_ (param) + , tlsThread_( + std::bind(&SipsIceTransport::setup, this), + std::bind(&SipsIceTransport::loop, this), + std::bind(&SipsIceTransport::clean, this)) + , xcred_ {nullptr, gnutls_certificate_free_credentials} +{ + RING_DBG("SipIceTransport@%p {tr=%p}", this, &trData_.base); + + trData_.self = this; + + if (not ice or not ice->isRunning()) + throw std::logic_error("ICE transport must exist and negotiation completed"); + + pool_.reset(pjsip_endpt_create_pool(endpt, "SipsIceTransport.pool", + POOL_TP_INIT, POOL_TP_INC)); + if (not pool_) { + RING_ERR("Can't create PJSIP pool"); + throw std::bad_alloc(); + } + auto pool = pool_.get(); + + auto& base = trData_.base; + pj_ansi_snprintf(base.obj_name, PJ_MAX_OBJ_NAME, "SipsIceTransport"); + base.endpt = endpt; + base.tpmgr = pjsip_endpt_get_tpmgr(endpt); + base.pool = pool; + + if (pj_atomic_create(pool, 0, &base.ref_cnt) != PJ_SUCCESS) + throw std::runtime_error("Can't create PJSIP atomic."); + + if (pj_lock_create_recursive_mutex(pool, "SipsIceTransport.mutex", + &base.lock) != PJ_SUCCESS) + throw std::runtime_error("Can't create PJSIP mutex."); + + is_server_ = not ice->isInitiator(); + local_ = ice->getLocalAddress(comp_id); + remote_ = ice->getRemoteAddress(comp_id); + pj_sockaddr_cp(&base.key.rem_addr, remote_.pjPtr()); + base.key.type = PJSIP_TRANSPORT_TLS; + base.type_name = (char*)pjsip_transport_get_type_name((pjsip_transport_type_e)base.key.type); + base.flag = pjsip_transport_get_flag_from_type((pjsip_transport_type_e)base.key.type); + base.info = (char*) pj_pool_alloc(pool, TRANSPORT_INFO_LENGTH); + + char print_addr[PJ_INET6_ADDRSTRLEN+10]; + pj_ansi_snprintf(base.info, TRANSPORT_INFO_LENGTH, "%s to %s", + base.type_name, + pj_sockaddr_print(remote_.pjPtr(), print_addr, + sizeof(print_addr), 3)); + base.addr_len = remote_.getLength(); + base.dir = PJSIP_TP_DIR_NONE; ///is_server? PJSIP_TP_DIR_INCOMING : PJSIP_TP_DIR_OUTGOING; + base.data = nullptr; + + /* Set initial local address */ + auto local = ice->getDefaultLocalAddress(); + pj_sockaddr_cp(&base.local_addr, local.pjPtr()); + + sockaddr_to_host_port(pool, &base.local_name, &base.local_addr); + sockaddr_to_host_port(pool, &base.remote_name, remote_.pjPtr()); + + base.send_msg = [](pjsip_transport *transport, + pjsip_tx_data *tdata, + const pj_sockaddr_t *rem_addr, int addr_len, + void *token, pjsip_transport_callback callback) -> pj_status_t { + auto& this_ = reinterpret_cast<TransportData*>(transport)->self; + return this_->send(tdata, rem_addr, addr_len, token, callback); + }; + base.do_shutdown = [](pjsip_transport *transport) -> pj_status_t { + auto& this_ = reinterpret_cast<TransportData*>(transport)->self; + RING_DBG("SipsIceTransport@%p: shutdown", this_); + this_->shutdown(); + return PJ_SUCCESS; + }; + base.destroy = [](pjsip_transport *transport) -> pj_status_t { + auto& this_ = reinterpret_cast<TransportData*>(transport)->self; + RING_WARN("SipsIceTransport@%p: destroy", this_); + delete this_; + return PJ_SUCCESS; + }; + + /* Init rdata_ */ + std::memset(&rdata_, 0, sizeof(pjsip_rx_data)); + rxPool_.reset(pjsip_endpt_create_pool(base.endpt, + "SipsIceTransport.rxPool", + PJSIP_POOL_RDATA_LEN, + PJSIP_POOL_RDATA_LEN)); + if (not rxPool_) { + RING_ERR("Can't create PJSIP rx pool"); + throw std::bad_alloc(); + } + rdata_.tp_info.pool = rxPool_.get(); + rdata_.tp_info.transport = &base; + rdata_.tp_info.tp_data = this; + rdata_.tp_info.op_key.rdata = &rdata_; + pj_ioqueue_op_key_init(&rdata_.tp_info.op_key.op_key, + sizeof(pj_ioqueue_op_key_t)); + rdata_.pkt_info.src_addr = base.key.rem_addr; + rdata_.pkt_info.src_addr_len = sizeof(rdata_.pkt_info.src_addr); + auto rem_addr = &base.key.rem_addr; + pj_sockaddr_print(rem_addr, rdata_.pkt_info.src_name, + sizeof(rdata_.pkt_info.src_name), 0); + rdata_.pkt_info.src_port = pj_sockaddr_get_port(rem_addr); + + std::memset(&localCertInfo_, 0, sizeof(pj_ssl_cert_info)); + std::memset(&remoteCertInfo_, 0, sizeof(pj_ssl_cert_info)); + + /* Register error subsystem */ + /*pj_status_t status = pj_register_strerror(PJ_ERRNO_START_USER + + PJ_ERRNO_SPACE_SIZE * 6, + PJ_ERRNO_SPACE_SIZE, + &tls_strerror); + pj_assert(status == PJ_SUCCESS);*/ + + if (pjsip_transport_register(base.tpmgr, &base) != PJ_SUCCESS) + throw std::runtime_error("Can't register PJSIP transport."); + + gnutls_priority_init(&priority_cache, + "SECURE192:-VERS-TLS-ALL:+VERS-DTLS1.0:%SERVER_PRECEDENCE", + nullptr); + + ice_->setOnRecv(comp_id_, [this](uint8_t* buf, size_t len) { + { + std::lock_guard<std::mutex> l(inputBuffMtx_); + tlsInputBuff_.emplace_back(buf, buf+len); + canRead_ = true; + RING_DBG("Ice: got data at %lu", + clock::now().time_since_epoch().count()); + } + cv_.notify_all(); + return len; + }); + + tlsThread_.start(); + Manager::instance().registerEventHandler((uintptr_t)this, [this]{ handleEvents(); }); +} + +SipsIceTransport::~SipsIceTransport() +{ + RING_DBG("~SipsIceTransport"); + shutdown(); + ice_->setOnRecv(comp_id_, nullptr); + tlsThread_.join(); + Manager::instance().unregisterEventHandler((uintptr_t)this); + + pj_lock_destroy(trData_.base.lock); + pj_atomic_destroy(trData_.base.ref_cnt); +} + +pj_status_t +SipsIceTransport::startTlsSession() +{ + RING_DBG("SipsIceTransport::startTlsSession as %s", + (is_server_ ? "server" : "client")); + int ret; + + ret = gnutls_init(&session_, (is_server_ ? GNUTLS_SERVER : GNUTLS_CLIENT) | GNUTLS_DATAGRAM); + if (ret != GNUTLS_E_SUCCESS) { + shutdown(); + return tls_status_from_err(ret); + } + + gnutls_session_set_ptr(session_, this); + gnutls_transport_set_ptr(session_, this); + + gnutls_priority_set(session_, priority_cache); + + /* Allocate credentials for handshaking and transmission */ + gnutls_certificate_credentials_t certCred; + ret = gnutls_certificate_allocate_credentials(&certCred); + if (ret < 0 or not certCred) { + RING_ERR("Can't allocate credentials"); + shutdown(); + return PJ_ENOMEM; + } + + xcred_.reset(certCred); + + if (is_server_) { + auto& dh_params = param_.dh_params.get(); + if (dh_params) + gnutls_certificate_set_dh_params(certCred, dh_params.get()); + else + RING_ERR("DH params unavaliable"); + } + + gnutls_certificate_set_verify_function(certCred, [](gnutls_session_t session) { + auto this_ = reinterpret_cast<SipsIceTransport*>(gnutls_session_get_ptr(session)); + return this_->verifyCertificate(); + }); + + if (not param_.ca_list.empty()) { + /* Load CA if one is specified. */ + ret = gnutls_certificate_set_x509_trust_file(certCred, + param_.ca_list.c_str(), + GNUTLS_X509_FMT_PEM); + if (ret < 0) + ret = gnutls_certificate_set_x509_trust_file(certCred, + param_.ca_list.c_str(), + GNUTLS_X509_FMT_DER); + if (ret < 0) + throw std::runtime_error("Can't load CA."); + RING_WARN("Loaded %s", param_.ca_list.c_str()); + + if (param_.id.first) { + /* Load certificate, key and pass */ + ret = gnutls_certificate_set_x509_key(certCred, + ¶m_.id.second->cert, 1, + param_.id.first->x509_key); + if (ret < 0) + throw std::runtime_error("Can't load certificate : " + + std::string(gnutls_strerror(ret))); + } + } + + /* Finally set credentials for this session */ + ret = gnutls_credentials_set(session_, GNUTLS_CRD_CERTIFICATE, certCred); + if (ret != GNUTLS_E_SUCCESS) { + shutdown(); + return tls_status_from_err(ret); + } + + if (is_server_) { + /* Require client certificate and valid cookie */ + gnutls_certificate_server_set_request(session_, GNUTLS_CERT_REQUIRE); + gnutls_dtls_prestate_set(session_, &prestate_); + } + + gnutls_dtls_set_mtu(session_, DTLS_MTU); + + gnutls_transport_set_push_function(session_, [](gnutls_transport_ptr_t t, + const void* d , + size_t s) -> ssize_t { + auto this_ = reinterpret_cast<SipsIceTransport*>(t); + return this_->tlsSend(d, s); + }); + gnutls_transport_set_pull_function(session_, [](gnutls_transport_ptr_t t, + void* d, + size_t s) -> ssize_t { + auto this_ = reinterpret_cast<SipsIceTransport*>(t); + return this_->tlsRecv(d, s); + }); + gnutls_transport_set_pull_timeout_function(session_, [](gnutls_transport_ptr_t t, + unsigned ms) -> int { + auto this_ = reinterpret_cast<SipsIceTransport*>(t); + return this_->waitForTlsData(ms); + }); + + // start handshake + handshakeStart_ = clock::now(); + state_ = TlsConnectionState::HANDSHAKING; + + return PJ_SUCCESS; +} + +void +SipsIceTransport::closeTlsSession() +{ + state_ = TlsConnectionState::DISCONNECTED; + if (session_) { + gnutls_bye(session_, GNUTLS_SHUT_RDWR); + gnutls_deinit(session_); + session_ = nullptr; + } + + xcred_.reset(); +} + +void +SipsIceTransport::certGetCn(const pj_str_t* gen_name, pj_str_t* cn) +{ + pj_str_t CN_sign = {(char*)"CN=", 3}; + char *p, *q; + + std::memset(cn, 0, sizeof(*cn)); + + p = pj_strstr(gen_name, &CN_sign); + if (!p) + return; + + p += 3; /* shift pointer to value part */ + pj_strset(cn, p, gen_name->slen - (p - gen_name->ptr)); + q = pj_strchr(cn, ','); + if (q) + cn->slen = q - p; +} + +/* Get certificate info; in case the certificate info is already populated, + * this function will check if the contents need updating by inspecting the + * issuer and the serial number. */ +void +SipsIceTransport::certGetInfo(pj_pool_t* pool, pj_ssl_cert_info* ci, + const gnutls_datum_t& crt_raw) +{ + char buf[512] = { 0 }; + size_t bufsize = sizeof(buf); + std::array<uint8_t, sizeof(ci->serial_no)> serial_no; /* should be >= sizeof(ci->serial_no) */ + size_t serialsize = serial_no.size(); + size_t len = sizeof(buf); + int i, ret, seq = 0; + pj_ssl_cert_name_type type; + + pj_assert(pool && ci && crt_raw.data); + + dht::crypto::Certificate crt(Blob(crt_raw.data, crt_raw.data + crt_raw.size)); + + /* Get issuer */ + gnutls_x509_crt_get_issuer_dn(crt.cert, buf, &bufsize); + + /* Get serial no */ + gnutls_x509_crt_get_serial(crt.cert, serial_no.data(), &serialsize); + + /* Check if the contents need to be updated */ + if (not pj_strcmp2(&ci->issuer.info, buf) and + not std::memcmp(ci->serial_no, serial_no.data(), serialsize)) + return; + + /* Update cert info */ + std::memset(ci, 0, sizeof(pj_ssl_cert_info)); + + /* Full raw certificate */ + const pj_str_t raw_crt_pjstr {(char*)crt_raw.data, crt_raw.size}; + pj_strdup(pool, &ci->cert_raw, &raw_crt_pjstr); + + /* Version */ + ci->version = gnutls_x509_crt_get_version(crt.cert); + + /* Issuer */ + pj_strdup2(pool, &ci->issuer.info, buf); + certGetCn(&ci->issuer.info, &ci->issuer.cn); + + /* Serial number */ + std::copy(serial_no.cbegin(), serial_no.cend(), (uint8_t*)ci->serial_no); + + /* Subject */ + bufsize = sizeof(buf); + gnutls_x509_crt_get_dn(crt.cert, buf, &bufsize); + pj_strdup2(pool, &ci->subject.info, buf); + certGetCn(&ci->subject.info, &ci->subject.cn); + + /* Validity */ + ci->validity.end.sec = gnutls_x509_crt_get_expiration_time(crt.cert); + ci->validity.start.sec = gnutls_x509_crt_get_activation_time(crt.cert); + ci->validity.gmt = 0; + + /* Subject Alternative Name extension */ + if (ci->version >= 3) { + char out[256] = { 0 }; + /* Get the number of all alternate names so that we can allocate + * the correct number of bytes in subj_alt_name */ + while (gnutls_x509_crt_get_subject_alt_name(crt.cert, seq, out, &len, nullptr) != GNUTLS_E_REQUESTED_DATA_NOT_AVAILABLE) + seq++; + + ci->subj_alt_name.entry = \ + (decltype(ci->subj_alt_name.entry))pj_pool_calloc(pool, seq, sizeof(*ci->subj_alt_name.entry)); + if (!ci->subj_alt_name.entry) { + last_err_ = GNUTLS_E_MEMORY_ERROR; + return; + } + + /* Now populate the alternative names */ + for (i = 0; i < seq; i++) { + len = sizeof(out) - 1; + ret = gnutls_x509_crt_get_subject_alt_name(crt.cert, i, out, &len, nullptr); + switch (ret) { + case GNUTLS_SAN_IPADDRESS: + type = PJ_SSL_CERT_NAME_IP; + pj_inet_ntop2(len == sizeof(pj_in6_addr) ? pj_AF_INET6() : pj_AF_INET(), + out, buf, sizeof(buf)); + break; + case GNUTLS_SAN_URI: + type = PJ_SSL_CERT_NAME_URI; + break; + case GNUTLS_SAN_RFC822NAME: + type = PJ_SSL_CERT_NAME_RFC822; + break; + case GNUTLS_SAN_DNSNAME: + type = PJ_SSL_CERT_NAME_DNS; + break; + default: + type = PJ_SSL_CERT_NAME_UNKNOWN; + break; + } + + if (len && type != PJ_SSL_CERT_NAME_UNKNOWN) { + ci->subj_alt_name.entry[ci->subj_alt_name.cnt].type = type; + pj_strdup2(pool, + &ci->subj_alt_name.entry[ci->subj_alt_name.cnt].name, + type == PJ_SSL_CERT_NAME_IP ? buf : out); + ci->subj_alt_name.cnt++; + } + } + /* TODO: if no DNS alt. names were found, we could check against + * the commonName as per RFC3280. */ + } +} + + +/* Update local & remote certificates info. This function should be + * called after handshake or renegotiation successfully completed. */ +void +SipsIceTransport::certUpdate() +{ + /* Get active local certificate */ + if(const auto local_raw = gnutls_certificate_get_ours(session_)) + certGetInfo(pool_.get(), &localCertInfo_, *local_raw); + else + std::memset(&localCertInfo_, 0, sizeof(pj_ssl_cert_info)); + + unsigned int certslen = 0; + if (const auto remote_raw = gnutls_certificate_get_peers(session_, &certslen)) + certGetInfo(pool_.get(), &remoteCertInfo_, *remote_raw); + else + std::memset(&remoteCertInfo_, 0, sizeof(pj_ssl_cert_info)); +} + +void +SipsIceTransport::getInfo(pj_ssl_sock_info* info) +{ + std::memset(info, 0, sizeof(*info)); + + /* Established flag */ + info->established = (state_ == TlsConnectionState::ESTABLISHED); + + /* Protocol */ + info->proto = PJ_SSL_SOCK_PROTO_DTLS1; + + /* Local address */ + pj_sockaddr_cp(&info->local_addr, local_.pjPtr()); + + if (info->established) { + unsigned char id[2]; + gnutls_cipher_algorithm_t cipher; + gnutls_cipher_algorithm_t lookup; + + /* Current cipher */ + cipher = gnutls_cipher_get(session_); + for (size_t i=0; ; ++i) { + const auto suite = gnutls_cipher_suite_info(i, id, nullptr, &lookup, + nullptr, nullptr); + if (not suite) { + RING_ERR("Can't find info for cipher %s (%d)", gnutls_cipher_get_name(cipher), cipher); + break; + } + + if (lookup == cipher) { + info->cipher = (pj_ssl_cipher) ((id[0] << 8) | id[1]); + break; + } + } + + /* Remote address */ + pj_sockaddr_cp(&info->remote_addr, remote_.pjPtr()); + + /* Certificates info */ + info->local_cert_info = &localCertInfo_; + info->remote_cert_info = &remoteCertInfo_; + + /* Verification status */ + info->verify_status = verifyStatus_; + } + + /* Last known GnuTLS error code */ + info->last_native_err = last_err_; +} + +pj_status_t +SipsIceTransport::tryHandshake() +{ + RING_DBG("SipsIceTransport::tryHandshake as %s", + (is_server_ ? "server" : "client")); + pj_status_t status; + int ret = gnutls_handshake(session_); + if (ret == GNUTLS_E_SUCCESS) { + /* System are GO */ + RING_DBG("SipsIceTransport::tryHandshake : ESTABLISHED"); + state_ = TlsConnectionState::ESTABLISHED; + status = PJ_SUCCESS; + } else if (!gnutls_error_is_fatal(ret)) { + /* Non fatal error, retry later (busy or again) */ + status = PJ_EPENDING; + } else { + /* Fatal error invalidates session, no fallback */ + status = PJ_EINVAL; + } + last_err_ = ret; + return status; +} + +/* When handshake completed: + * - notify application + * - if handshake failed, reset SSL state + * - return false when SSL socket instance is destroyed by application. */ +bool +SipsIceTransport::onHandshakeComplete(pj_status_t status) +{ + /* Update certificates info on successful handshake */ + if (status == PJ_SUCCESS) { + RING_WARN("Handshake success on remote %s", + remote_.toString(true).c_str()); + certUpdate(); + } else { + /* Handshake failed destroy ourself silently. */ + char errmsg[PJ_ERR_MSG_SIZE]; + RING_WARN("Handshake failed on remote %s: %s", + remote_.toString(true).c_str(), + pj_strerror(status, errmsg, sizeof(errmsg)).ptr); + } + + pj_ssl_sock_info ssl_info; + getInfo(&ssl_info); + + if (status != PJ_SUCCESS) + shutdown(); // to be done after getInfo() call + + auto state_cb = pjsip_tpmgr_get_state_cb(trData_.base.tpmgr); + pjsip_transport_state_info state_info; + pjsip_tls_state_info tls_info; + + /* Init transport state info */ + std::memset(&state_info, 0, sizeof(state_info)); + std::memset(&tls_info, 0, sizeof(tls_info)); + tls_info.ssl_sock_info = &ssl_info; + state_info.ext_info = &tls_info; + state_info.status = ssl_info.verify_status ? PJSIP_TLS_ECERTVERIF : PJ_SUCCESS; + + /* Notify application */ + if (state_cb) { + const auto state = (status != PJ_SUCCESS) ? + PJSIP_TP_STATE_DISCONNECTED : PJSIP_TP_STATE_CONNECTED; + (*state_cb)(getTransportBase(), state, &state_info); + } + + return status == PJ_SUCCESS; +} + +int +SipsIceTransport::verifyCertificate() +{ + RING_DBG("SipsIceTransport::verifyCertificate"); + + /* Support only x509 format */ + if (gnutls_certificate_type_get(session_) != GNUTLS_CRT_X509) { + verifyStatus_ = PJ_SSL_CERT_EINVALID_FORMAT; + return GNUTLS_E_CERTIFICATE_ERROR; + } + + /* Store verification status */ + unsigned int status = 0; + auto ret = gnutls_certificate_verify_peers2(session_, &status); + if (ret < 0 or (status & GNUTLS_CERT_SIGNATURE_FAILURE) != 0) { + verifyStatus_ = PJ_SSL_CERT_EUNTRUSTED; + return GNUTLS_E_CERTIFICATE_ERROR; + } + + unsigned int cert_list_size = 0; + auto cert_list = gnutls_certificate_get_peers(session_, &cert_list_size); + if (cert_list == nullptr) { + verifyStatus_ = PJ_SSL_CERT_EISSUER_NOT_FOUND; + return GNUTLS_E_CERTIFICATE_ERROR; + } + + if (param_.cert_check) { + verifyStatus_ = param_.cert_check(status, cert_list, cert_list_size); + if (verifyStatus_ != PJ_SUCCESS) + return GNUTLS_E_CERTIFICATE_ERROR; + } + + /* notify GnuTLS to continue handshake normally */ + return GNUTLS_E_SUCCESS; +} + +bool +SipsIceTransport::setup() +{ + RING_WARN("Starting GnuTLS thread"); + if (is_server_) { + gnutls_key_generate(&cookie_key_, GNUTLS_COOKIE_KEY_SIZE); + state_ = TlsConnectionState::COOKIE; + return true; + } + return startTlsSession() == PJ_SUCCESS; +} + +void +SipsIceTransport::handleEvents() +{ + decltype(rxPending_) rx; + { + std::lock_guard<std::mutex> l(rxMtx_); + rx = std::move(rxPending_); + } + + for (auto it = rx.begin(); it != rx.end(); ++it) { + auto& pck = *it; + pj_pool_reset(rdata_.tp_info.pool); + pj_gettimeofday(&rdata_.pkt_info.timestamp); + rdata_.pkt_info.len = pck.size(); + std::copy_n(pck.data(), pck.size(), rdata_.pkt_info.packet); + auto eaten = pjsip_tpmgr_receive_packet(trData_.base.tpmgr, &rdata_); + if (eaten != rdata_.pkt_info.len) { + // partial sip packet received + auto npck_it = std::next(it); + if (npck_it != rx.end()) { + // drop current packet, merge reminder with next one + auto& npck = *npck_it; + npck.insert(npck.begin(), pck.begin()+eaten, pck.end()); + } else { + // erase eaten part, keep reminder + pck.erase(pck.begin(), pck.begin()+eaten); + { + std::lock_guard<std::mutex> l(rxMtx_); + rxPending_.splice(rxPending_.begin(), rx, it); + } + break; + } + } + } + putBuff(std::move(rx)); + rxCv_.notify_all(); +} + +void +SipsIceTransport::loop() +{ + if (not ice_->isRunning()) { + shutdown(); + return; + } + + if (state_ == TlsConnectionState::COOKIE) { + { + std::unique_lock<std::mutex> l(inputBuffMtx_); + if (tlsInputBuff_.empty()) { + cv_.wait(l, [&](){ + return state_ != TlsConnectionState::COOKIE or not tlsInputBuff_.empty(); + }); + } + if (state_ != TlsConnectionState::COOKIE) + return; + const auto& pck = tlsInputBuff_.front(); + std::memset(&prestate_, 0, sizeof(prestate_)); + int ret = gnutls_dtls_cookie_verify(&cookie_key_, + &trData_.base.key.rem_addr, + trData_.base.addr_len, + (char*)pck.data(), pck.size(), + &prestate_); + if (ret < 0) { + gnutls_dtls_cookie_send(&cookie_key_, + &trData_.base.key.rem_addr, + trData_.base.addr_len, &prestate_, this, + [](gnutls_transport_ptr_t t, + const void* d , + size_t s) -> ssize_t { + auto this_ = reinterpret_cast<SipsIceTransport*>(t); + return this_->tlsSend(d, s); + }); + tlsInputBuff_.pop_front(); + if (tlsInputBuff_.empty()) + canRead_ = false; + return; + } + } + startTlsSession(); + } + + if (state_ == TlsConnectionState::HANDSHAKING) { + if (clock::now() - handshakeStart_ > param_.timeout) { + onHandshakeComplete(PJ_ETIMEDOUT); + return; + } + auto status = tryHandshake(); + if (status != PJ_EPENDING) + onHandshakeComplete(status); + } + + if (state_ == TlsConnectionState::ESTABLISHED) { + { + std::mutex flagsMtx_ {}; + std::unique_lock<std::mutex> l(flagsMtx_); + cv_.wait(l, [&](){ + return state_ != TlsConnectionState::ESTABLISHED or canRead_ or canWrite_; + }); + } + + if (state_ != TlsConnectionState::ESTABLISHED and not getTransportBase()->is_shutdown) + return; + + decltype(rxPending_) rx; + while (canRead_ or gnutls_record_check_pending(session_)) { + if (rx.empty()) + getBuff(rx, PJSIP_MAX_PKT_LEN); + auto& buf = rx.front(); + const auto decrypted_size = gnutls_record_recv(session_, buf.data(), buf.size()); + + if (decrypted_size > 0/* || transport error */) { + buf.resize(decrypted_size); + { + std::lock_guard<std::mutex> l(rxMtx_); + rxPending_.splice(rxPending_.end(), rx, rx.begin()); + } + } else if (decrypted_size == 0) { + /* EOF */ + tlsThread_.stop(); + break; + } else if (decrypted_size == GNUTLS_E_AGAIN or + decrypted_size == GNUTLS_E_INTERRUPTED) { + break; + } else if (decrypted_size == GNUTLS_E_REHANDSHAKE) { + /* Seems like we are renegotiating */ + + RING_DBG("rehandshake"); + auto try_handshake_status = tryHandshake(); + + /* Not pending is either success or failed */ + if (try_handshake_status != PJ_EPENDING) { + if (!onHandshakeComplete(try_handshake_status)) { + break; + } + } + + if (try_handshake_status != PJ_SUCCESS and + try_handshake_status != PJ_EPENDING) { + break; + } + } else if (!gnutls_error_is_fatal(decrypted_size)) { + /* non-fatal error, let's just continue */ + } else { + shutdown(); + break; + } + } + putBuff(std::move(rx)); + flushOutputBuff(); + } +} + +void +SipsIceTransport::clean() +{ + RING_WARN("Ending GnuTLS thread"); + tlsInputBuff_.clear(); + canRead_ = false; + + while (not outputBuff_.empty()) { + auto& f = outputBuff_.front(); + f.tdata_op_key->tdata = nullptr; + if (f.tdata_op_key->callback) + f.tdata_op_key->callback(getTransportBase(), + f.tdata_op_key->token, + -PJ_RETURN_OS_ERROR(OSERR_ENOTCONN)); + outputBuff_.pop_front(); + } + canWrite_ = false; + if (cookie_key_.data) { + gnutls_free(cookie_key_.data); + cookie_key_.data = nullptr; + cookie_key_.size = 0; + } + { + // make sure all incoming packets are reported before closing + std::unique_lock<std::mutex> l(rxMtx_); + rxCv_.wait(l, [&](){ + return rxPending_.empty(); + }); + } + { + std::lock_guard<std::mutex> lk(buffPoolMtx_); + buffPool_.clear(); + } + + bool event = state_ == TlsConnectionState::ESTABLISHED; + closeTlsSession(); + + pjsip_transport_add_ref(getTransportBase()); + auto state_cb = pjsip_tpmgr_get_state_cb(trData_.base.tpmgr); + if (state_cb && event) { + pjsip_transport_state_info state_info; + pjsip_tls_state_info tls_info; + + /* Init transport state info */ + std::memset(&state_info, 0, sizeof(state_info)); + std::memset(&tls_info, 0, sizeof(tls_info)); + pj_ssl_sock_info ssl_info; + getInfo(&ssl_info); + tls_info.ssl_sock_info = &ssl_info; + state_info.ext_info = &tls_info; + state_info.status = PJ_SUCCESS; + + (*state_cb)(getTransportBase(), PJSIP_TP_STATE_DISCONNECTED, &state_info); + } + + if (not trData_.base.is_shutdown and not trData_.base.is_destroying) + pjsip_transport_shutdown(getTransportBase()); + + pjsip_transport_dec_ref(getTransportBase()); +} + +IpAddr +SipsIceTransport::getLocalAddress() const +{ + return ice_->getLocalAddress(comp_id_); +} + +IpAddr +SipsIceTransport::getRemoteAddress() const +{ + return ice_->getRemoteAddress(comp_id_); +} + +ssize_t +SipsIceTransport::tlsSend(const void* d, size_t s) +{ + RING_DBG("SipsIceTransport::tlsSend %lu", s); + return ice_->send(comp_id_, (const uint8_t*)d, s); +} + +ssize_t +SipsIceTransport::tlsRecv(void* d , size_t s) +{ + std::lock_guard<std::mutex> l(inputBuffMtx_); + if (tlsInputBuff_.empty()) { + errno = EAGAIN; + return -1; + } + const auto& front = tlsInputBuff_.front(); + const auto n = std::min(front.size(), s); + RING_DBG("SipsIceTransport::tlsRecv %lu at %lu", + n, clock::now().time_since_epoch().count()); + std::copy_n(front.begin(), n, (uint8_t*)d); + tlsInputBuff_.pop_front(); + if (tlsInputBuff_.empty()) + canRead_ = false; + return n; +} + +int +SipsIceTransport::waitForTlsData(unsigned ms) +{ + RING_DBG("SipsIceTransport::waitForTlsData %u", ms); + + std::unique_lock<std::mutex> l(inputBuffMtx_); + if (tlsInputBuff_.empty()) { + cv_.wait_for(l, std::chrono::milliseconds(ms), [&]() { + return state_ == TlsConnectionState::DISCONNECTED or not tlsInputBuff_.empty(); + }); + } + if (state_ == TlsConnectionState::DISCONNECTED) { + errno = EINTR; + return -1; + } + return tlsInputBuff_.empty() ? 0 : tlsInputBuff_.front().size(); +} + +pj_status_t +SipsIceTransport::send(pjsip_tx_data *tdata, const pj_sockaddr_t *rem_addr, + int addr_len, void *token, + pjsip_transport_callback callback) +{ + // Sanity check + PJ_ASSERT_RETURN(tdata, PJ_EINVAL); + + // Check that there's no pending operation associated with the tdata + PJ_ASSERT_RETURN(tdata->op_key.tdata == nullptr, PJSIP_EPENDINGTX); + + // Check the address is supported + PJ_ASSERT_RETURN(rem_addr and + (addr_len==sizeof(pj_sockaddr_in) or + addr_len==sizeof(pj_sockaddr_in6)), + PJ_EINVAL); + + tdata->op_key.token = token; + tdata->op_key.callback = callback; + if (state_ == TlsConnectionState::ESTABLISHED) { + decltype(txBuff_) tx; + size_t size = tdata->buf.cur - tdata->buf.start; + getBuff(tx, (uint8_t*)tdata->buf.start, (uint8_t*)tdata->buf.cur); + { + std::lock_guard<std::mutex> l(outputBuffMtx_); + txBuff_.splice(txBuff_.end(), std::move(tx)); + } + tdata->op_key.tdata = nullptr; + if (tdata->op_key.callback) + tdata->op_key.callback(getTransportBase(), token, size); + } else { + std::lock_guard<std::mutex> l(outputBuffMtx_); + tdata->op_key.tdata = tdata; + outputBuff_.emplace_back(DelayedTxData{&tdata->op_key, {}}); + if (tdata->msg && tdata->msg->type == PJSIP_REQUEST_MSG) { + auto& dtd = outputBuff_.back(); + dtd.timeout = clock::now(); + dtd.timeout += std::chrono::milliseconds(pjsip_cfg()->tsx.td); + } + } + canWrite_ = true; + cv_.notify_all(); + return PJ_EPENDING; +} + +pj_status_t +SipsIceTransport::flushOutputBuff() +{ + ssize_t status = PJ_SUCCESS; + + // send delayed data first + while (true) { + DelayedTxData f; + { + std::lock_guard<std::mutex> l(outputBuffMtx_); + if (outputBuff_.empty()) + break; + else { + f = outputBuff_.front(); + outputBuff_.pop_front(); + } + } + if (f.timeout != clock::time_point() && f.timeout < clock::now()) + continue; + status = trySend(f.tdata_op_key); + f.tdata_op_key->tdata = nullptr; + if (f.tdata_op_key->callback) + f.tdata_op_key->callback(getTransportBase(), + f.tdata_op_key->token, status); + if (status < 0) + break; + } + if (status < 0) + return status; + + decltype(txBuff_) tx; + { + std::lock_guard<std::mutex> l(outputBuffMtx_); + tx = std::move(txBuff_); + canWrite_ = false; + } + for (auto it = tx.begin(); it != tx.end(); ++it) { + const auto& msg = *it; + const auto nwritten = gnutls_record_send(session_, msg.data(), msg.size()); + if (nwritten <= 0) { + RING_ERR("gnutls_record_send: %s", gnutls_strerror(nwritten)); + status = tls_status_from_err(nwritten); + { + std::lock_guard<std::mutex> l(outputBuffMtx_); + txBuff_.splice(txBuff_.begin(), tx, it, tx.end()); + canWrite_ = true; + } + break; + } + } + putBuff(std::move(tx)); + return status > 0 ? PJ_SUCCESS : (pj_status_t)status; +} + +ssize_t +SipsIceTransport::trySend(pjsip_tx_data_op_key *pck) +{ + const auto tdata = pck->tdata; + const size_t size = tdata->buf.cur - tdata->buf.start; + const size_t max_tx_sz = gnutls_dtls_get_data_mtu(session_); + + size_t total_written = 0; + while (total_written < size) { + /* Ask GnuTLS to encrypt our plaintext now. GnuTLS will use the push + * callback to actually send the encrypted bytes. */ + const auto tx_size = std::min(max_tx_sz, size - total_written); + const auto nwritten = gnutls_record_send(session_, + tdata->buf.start + total_written, + tx_size); + if (nwritten <= 0) { + /* Normally we would have to retry record_send but our internal + * state has not changed, so we have to ask for more data first. + * We will just try again later, although this should never happen. + */ + RING_ERR("gnutls_record_send: %s", gnutls_strerror(nwritten)); + return tls_status_from_err(nwritten); + } + + /* Good, some data was encrypted and written */ + total_written += nwritten; + } + return total_written; +} + +void +SipsIceTransport::shutdown() +{ + RING_WARN("%s", __PRETTY_FUNCTION__); + state_ = TlsConnectionState::DISCONNECTED; + tlsThread_.stop(); + cv_.notify_all(); +} + +void +SipsIceTransport::getBuff(decltype(buffPool_)& l, const uint8_t* b, const uint8_t* e) +{ + std::lock_guard<std::mutex> lk(buffPoolMtx_); + if (buffPool_.empty()) + l.emplace_back(b, e); + else { + l.splice(l.end(), buffPool_, buffPool_.begin()); + auto& buf = l.back(); + buf.resize(std::distance(b, e)); + std::copy(b, e, buf.begin()); + } +} + +void +SipsIceTransport::getBuff(decltype(buffPool_)& l, const size_t s) +{ + std::lock_guard<std::mutex> lk(buffPoolMtx_); + if (buffPool_.empty()) + l.emplace_back(s); + else { + l.splice(l.end(), buffPool_, buffPool_.begin()); + auto& buf = l.back(); + buf.resize(s); + } +} + +void +SipsIceTransport::putBuff(decltype(buffPool_)&& l) +{ + std::lock_guard<std::mutex> lk(buffPoolMtx_); + buffPool_.splice(buffPool_.end(), l); +} + +pj_status_t +SipsIceTransport::tls_status_from_err(int err) +{ + pj_status_t status; + + switch (err) { + case GNUTLS_E_SUCCESS: + status = PJ_SUCCESS; + break; + case GNUTLS_E_MEMORY_ERROR: + status = PJ_ENOMEM; + break; + case GNUTLS_E_LARGE_PACKET: + status = PJ_ETOOBIG; + break; + case GNUTLS_E_NO_CERTIFICATE_FOUND: + status = PJ_ENOTFOUND; + break; + case GNUTLS_E_SESSION_EOF: + status = PJ_EEOF; + break; + case GNUTLS_E_HANDSHAKE_TOO_LARGE: + status = PJ_ETOOBIG; + break; + case GNUTLS_E_EXPIRED: + status = PJ_EGONE; + break; + case GNUTLS_E_TIMEDOUT: + status = PJ_ETIMEDOUT; + break; + case GNUTLS_E_PREMATURE_TERMINATION: + status = PJ_ECANCELLED; + break; + case GNUTLS_E_INTERNAL_ERROR: + case GNUTLS_E_UNIMPLEMENTED_FEATURE: + status = PJ_EBUG; + break; + case GNUTLS_E_AGAIN: + case GNUTLS_E_INTERRUPTED: + case GNUTLS_E_REHANDSHAKE: + status = PJ_EPENDING; + break; + case GNUTLS_E_TOO_MANY_EMPTY_PACKETS: + case GNUTLS_E_TOO_MANY_HANDSHAKE_PACKETS: + case GNUTLS_E_RECORD_LIMIT_REACHED: + status = PJ_ETOOMANY; + break; + case GNUTLS_E_UNSUPPORTED_VERSION_PACKET: + case GNUTLS_E_UNSUPPORTED_SIGNATURE_ALGORITHM: + case GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE: + case GNUTLS_E_X509_UNSUPPORTED_ATTRIBUTE: + case GNUTLS_E_X509_UNSUPPORTED_EXTENSION: + case GNUTLS_E_X509_UNSUPPORTED_CRITICAL_EXTENSION: + status = PJ_ENOTSUP; + break; + case GNUTLS_E_INVALID_SESSION: + case GNUTLS_E_INVALID_REQUEST: + case GNUTLS_E_INVALID_PASSWORD: + case GNUTLS_E_ILLEGAL_PARAMETER: + case GNUTLS_E_RECEIVED_ILLEGAL_EXTENSION: + case GNUTLS_E_UNEXPECTED_PACKET: + case GNUTLS_E_UNEXPECTED_PACKET_LENGTH: + case GNUTLS_E_UNEXPECTED_HANDSHAKE_PACKET: + case GNUTLS_E_UNWANTED_ALGORITHM: + case GNUTLS_E_USER_ERROR: + status = PJ_EINVAL; + break; + default: + status = PJ_EUNKNOWN; + break; + } + + /* Not thread safe */ + // tls_last_error = err; + return status; +} + +}} // namespace ring::tls diff --git a/src/ringdht/sips_transport_ice.h b/src/ringdht/sips_transport_ice.h new file mode 100644 index 0000000000..f6d5af410d --- /dev/null +++ b/src/ringdht/sips_transport_ice.h @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#pragma once + +#include "ip_utils.h" +#include "threadloop.h" + +#include <opendht/crypto.h> + +#include <pjsip.h> +#include <pj/pool.h> + +#include <gnutls/gnutls.h> +#include <gnutls/dtls.h> +#include <gnutls/abstract.h> + +#include <list> +#include <functional> +#include <memory> +#include <mutex> +#include <condition_variable> +#include <chrono> +#include <future> + +namespace ring { +class IceTransport; +} // namespace ring + +namespace ring { namespace tls { + +class GnuTlsGlobalInit; + +enum class TlsConnectionState { + DISCONNECTED, + COOKIE, + HANDSHAKING, + ESTABLISHED +}; + +struct TlsParams { + std::string ca_list; + dht::crypto::Identity id; + std::shared_future<std::unique_ptr<gnutls_dh_params_int, decltype(gnutls_dh_params_deinit)&>> dh_params; + std::chrono::steady_clock::duration timeout; + std::function<pj_status_t(unsigned status, + const gnutls_datum_t* cert_list, + unsigned cert_list_size)> cert_check; +}; + +struct SipsIceTransport +{ + using clock = std::chrono::steady_clock; + using TransportData = struct { + pjsip_transport base; // do not move, SHOULD be the fist member + SipsIceTransport* self {nullptr}; + }; + static_assert(std::is_standard_layout<TransportData>::value, + "TranportData requires standard-layout"); + + SipsIceTransport(pjsip_endpoint* endpt, const TlsParams& param, + const std::shared_ptr<IceTransport>& ice, int comp_id); + ~SipsIceTransport(); + + void shutdown(); + + IpAddr getLocalAddress() const; + IpAddr getRemoteAddress() const; + + std::shared_ptr<IceTransport> getIceTransport() const { + return ice_; + } + + pjsip_transport* getTransportBase() { + return &trData_.base; + } + +private: + std::unique_ptr<GnuTlsGlobalInit> gtlsGIG_; + std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&> pool_; + std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&> rxPool_; + + using DelayedTxData = struct { + pjsip_tx_data_op_key *tdata_op_key; + clock::time_point timeout; + }; + + TransportData trData_; + const std::shared_ptr<IceTransport> ice_; + const int comp_id_; + + TlsParams param_; + + bool is_server_ {false}; + //bool has_pending_connect_ {false}; + IpAddr local_ {}; + IpAddr remote_ {}; + + ThreadLoop tlsThread_; + std::condition_variable_any cv_; + std::atomic<bool> canRead_ {false}; + std::atomic<bool> canWrite_ {false}; + + // TODO + pj_status_t verifyStatus_ {}; + int last_err_; + + pj_ssl_cert_info localCertInfo_; + pj_ssl_cert_info remoteCertInfo_; + + std::atomic<TlsConnectionState> state_ {TlsConnectionState::DISCONNECTED}; + clock::time_point handshakeStart_; + + gnutls_session_t session_ {nullptr}; + std::unique_ptr<gnutls_certificate_credentials_st, decltype(gnutls_certificate_free_credentials)&> xcred_; + gnutls_priority_t priority_cache; + gnutls_datum_t cookie_key_ {nullptr, 0}; + gnutls_dtls_prestate_st prestate_; + + /** + * To be called on a regular basis to receive packets + */ + void handleEvents(); + + // ThreadLoop + bool setup(); + void loop(); + void clean(); + + // SIP transport <-> GnuTLS + pj_status_t send(pjsip_tx_data* tdata, const pj_sockaddr_t* rem_addr, + int addr_len, void* token, + pjsip_transport_callback callback); + ssize_t trySend(pjsip_tx_data_op_key* tdata); + pj_status_t flushOutputBuff(); + std::list<DelayedTxData> outputBuff_; + std::list<std::vector<uint8_t>> txBuff_; + std::mutex outputBuffMtx_; + + std::mutex rxMtx_; + std::condition_variable_any rxCv_; + std::list<std::vector<uint8_t>> rxPending_; + pjsip_rx_data rdata_; + + // data buffer pool + std::list<std::vector<uint8_t>> buffPool_; + std::mutex buffPoolMtx_; + void getBuff(decltype(buffPool_)& l, const uint8_t* b, const uint8_t* e); + void getBuff(decltype(buffPool_)& l, const size_t s); + void putBuff(decltype(buffPool_)&& l); + + // GnuTLS <-> ICE + ssize_t tlsSend(const void*, size_t); + ssize_t tlsRecv(void* d , size_t s); + int waitForTlsData(unsigned ms); + std::mutex inputBuffMtx_; + std::list<std::vector<uint8_t>> tlsInputBuff_; + + pj_status_t startTlsSession(); + void closeTlsSession(); + + pj_status_t tryHandshake(); + void certGetCn(const pj_str_t* gen_name, pj_str_t* cn); + void certGetInfo(pj_pool_t* pool, pj_ssl_cert_info* ci, const gnutls_datum_t& cert); + void certUpdate(); + bool onHandshakeComplete(pj_status_t status); + int verifyCertificate(); + void getInfo(pj_ssl_sock_info* info); + static pj_status_t tls_status_from_err(int err); +}; + +}} // namespace ring::tls diff --git a/src/rw_mutex.h b/src/rw_mutex.h new file mode 100644 index 0000000000..466465a04c --- /dev/null +++ b/src/rw_mutex.h @@ -0,0 +1,126 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef RW_MUTEX_H_ +#define RW_MUTEX_H_ + +#include "noncopyable.h" + +#include <mutex> +#include <atomic> +#include <condition_variable> +#include <string> +#include <sstream> + +namespace ring { + +/** + * rw_mutex is a shared mutex meant to protect + * rarely-modified, often-read data structures. + * + * Its goal is to optimize read throughput and latency. + * Multiple threads can concurrently read data while + * a writer thread gets exclusive access when needed. + */ +class rw_mutex { + public: + rw_mutex() : mutex(), canRead(), canWrite(), readers(0), writing(false) {} + void read_enter() { + std::unique_lock<std::mutex> lck(mutex); + canRead.wait(lck, [this]() { return !writing; }); + readers++; + } + void read_exit() { + //std::lock_guard<std::mutex> lck(mutex); + readers--; + canWrite.notify_one(); + } + void write_enter() { + std::unique_lock<std::mutex> lck(mutex); + canWrite.wait(lck, [this]() { return !writing && readers==0; }); + writing = true; + } + void write_exit() { + std::lock_guard<std::mutex> lck(mutex); + writing = false; + canWrite.notify_one(); + canRead.notify_all(); + } + + struct read_lock { + public: + read_lock(rw_mutex& m) : sem(m) { + sem.read_enter(); + } + ~read_lock() { + sem.read_exit(); + } + private: + rw_mutex& sem; + }; + + struct write_lock { + public: + write_lock(rw_mutex& m) : sem(m) { + sem.write_enter(); + } + ~write_lock() { + sem.write_exit(); + } + private: + rw_mutex& sem; + }; + + read_lock read() { + return read_lock(*this); + } + + write_lock write() { + return write_lock(*this); + } + + std::string toString() { + std::stringstream ss; + ss << "[rw_mutex write:" << (writing?"LOCKED":"unlocked") << " read:" << readers << "]"; + return ss.str(); + } + + private: + NON_COPYABLE(rw_mutex); + std::mutex mutex; + std::condition_variable canRead, canWrite; + std::atomic<unsigned> readers; + bool writing; +}; + +} // namespace ring + +#endif diff --git a/src/sip/Makefile.am b/src/sip/Makefile.am new file mode 100644 index 0000000000..f69ed0b8a1 --- /dev/null +++ b/src/sip/Makefile.am @@ -0,0 +1,47 @@ +include $(top_srcdir)/globals.mak + +noinst_LTLIBRARIES = libsiplink.la +libsiplink_la_CXXFLAGS = @CXXFLAGS@ -I$(top_srcdir)/src + +libsiplink_la_SOURCES = \ + sipaccountbase.cpp \ + sdp.cpp \ + sipaccount.cpp \ + sipcall.cpp \ + sipvoiplink.cpp \ + siptransport.cpp \ + sipaccountbase.h \ + sdp.h \ + sipaccount.h \ + sipcall.h \ + sipvoiplink.h \ + siptransport.h \ + sip_utils.cpp \ + sip_utils.h \ + base64.h \ + base64.c + +if BUILD_TLS +# These files depend on opendht +if USE_DHT +libsiplink_la_SOURCES += tlsvalidator.cpp \ + tlsvalidator.h +endif +endif + +libsiplink_la_SOURCES+=sippresence.cpp \ + sippresence.h \ + pres_sub_server.cpp\ + pres_sub_server.h\ + pres_sub_client.cpp\ + pres_sub_client.h + +if BUILD_SDES +libsiplink_la_SOURCES+= sdes_negotiator.cpp \ + sdes_negotiator.h \ + pattern.cpp \ + pattern.h + + +libsiplink_la_CXXFLAGS += @PCRE_CFLAGS@ +endif diff --git a/src/sip/base64.c b/src/sip/base64.c new file mode 100644 index 0000000000..b4affa9444 --- /dev/null +++ b/src/sip/base64.c @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <stdint.h> +#include <stdlib.h> + +/* Mainly based on the following stackoverflow question: + * http://stackoverflow.com/questions/342409/how-do-i-base64-encode-decode-in-c + */ +static const char encoding_table[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', + 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', + 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', + '3', '4', '5', '6', '7', '8', '9', '+', '/' +}; + +static const int mod_table[] = { 0, 2, 1 }; + +char *ring_base64_encode(const uint8_t *input, size_t input_length, + char *output, size_t *output_length) +{ + size_t i, j; + size_t out_sz = *output_length; + *output_length = 4 * ((input_length + 2) / 3); + if (out_sz < *output_length || output == NULL) + return NULL; + + for (i = 0, j = 0; i < input_length; ) { + uint8_t octet_a = i < input_length ? input[i++] : 0; + uint8_t octet_b = i < input_length ? input[i++] : 0; + uint8_t octet_c = i < input_length ? input[i++] : 0; + + uint32_t triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; + + output[j++] = encoding_table[(triple >> 3 * 6) & 0x3F]; + output[j++] = encoding_table[(triple >> 2 * 6) & 0x3F]; + output[j++] = encoding_table[(triple >> 1 * 6) & 0x3F]; + output[j++] = encoding_table[(triple >> 0 * 6) & 0x3F]; + } + + for (i = 0; i < mod_table[input_length % 3]; i++) + output[*output_length - 1 - i] = '='; + + return output; +} + +uint8_t *ring_base64_decode(const char *input, size_t input_length, + uint8_t *output, size_t *output_length) +{ + size_t i, j; + uint8_t decoding_table[256]; + + uint8_t c; + for (c = 0; c < 64; c++) + decoding_table[encoding_table[c]] = c; + + if (input_length % 4 != 0) + return NULL; + + size_t out_sz = *output_length; + *output_length = input_length / 4 * 3; + if (input[input_length - 1] == '=') + (*output_length)--; + if (input[input_length - 2] == '=') + (*output_length)--; + + if (out_sz < *output_length || output == NULL) + return NULL; + + for (i = 0, j = 0; i < input_length;) { + uint8_t sextet_a = input[i] == '=' ? 0 & i++ + : decoding_table[input[i++]]; + uint8_t sextet_b = input[i] == '=' ? 0 & i++ + : decoding_table[input[i++]]; + uint8_t sextet_c = input[i] == '=' ? 0 & i++ + : decoding_table[input[i++]]; + uint8_t sextet_d = input[i] == '=' ? 0 & i++ + : decoding_table[input[i++]]; + + uint32_t triple = (sextet_a << 3 * 6) + + (sextet_b << 2 * 6) + + (sextet_c << 1 * 6) + + (sextet_d << 0 * 6); + + if (j < *output_length) + output[j++] = (triple >> 2 * 8) & 0xFF; + if (j < *output_length) + output[j++] = (triple >> 1 * 8) & 0xFF; + if (j < *output_length) + output[j++] = (triple >> 0 * 8) & 0xFF; + } + + return output; +} diff --git a/src/sip/base64.h b/src/sip/base64.h new file mode 100644 index 0000000000..3f6c41949b --- /dev/null +++ b/src/sip/base64.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef H_BASE64 +#define H_BASE64 + +#ifdef __cplusplus +extern "C" { +#endif + +#include "stdint.h" + +/** + * Encode a buffer in base64. + * + * @param data the input buffer + * @param input_length the input length + * @param output_length the resulting output length + * @return a base64-encoded buffer + * + * @note callers should free the returned memory + */ +char *ring_base64_encode(const uint8_t *input, size_t input_length, + char *output, size_t *output_length); + +/** + * Decode a base64 buffer. + * + * @param data the input buffer + * @param input_length the input length + * @param output_length the resulting output length + * @return a buffer + * + * @note callers should free the returned memory + */ +uint8_t *ring_base64_decode(const char *input, size_t input_length, + uint8_t *output, size_t *output_length); + +#ifdef __cplusplus +} +#endif + +#include <string> +#include <vector> + +namespace ring { +namespace base64 { + +std::string +encode(const std::vector<uint8_t>::const_iterator begin, + const std::vector<uint8_t>::const_iterator end) +{ + size_t output_length = 4 * ((std::distance(begin, end) + 2) / 3); + std::string out; + out.resize(output_length); + ring_base64_encode(&(*begin), std::distance(begin, end), + &(*out.begin()), &output_length); + out.resize(output_length); + return out; +} + +std::string +encode(const std::vector<uint8_t>& dat) +{ + return encode(dat.cbegin(), dat.cend()); +} + +std::vector<uint8_t> +decode(const std::string& str) +{ + size_t output_length = str.length() / 4 * 3 + 2; + std::vector<uint8_t> output; + output.resize(output_length); + ring_base64_decode(str.data(), str.size(), output.data(), &output_length); + output.resize(output_length); + return output; +} + +}} // namespace ring::base64 + +#endif // H_BASE64 diff --git a/src/sip/pattern.cpp b/src/sip/pattern.cpp new file mode 100644 index 0000000000..340e47d654 --- /dev/null +++ b/src/sip/pattern.cpp @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Pierre-Luc Bacon <pierre-luc.bacon@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "pattern.h" +#include <sstream> +#include <cstdio> + +namespace ring { + +Pattern::Pattern(const std::string& pattern, bool matchGlobally) : + pattern_(pattern), + subject_(), + re_(NULL), + ovector_(), + offset_{0, 0}, + count_(0), + matchGlobally_(matchGlobally) +{ + // Compile the pattern + int offset; + const char * error; + + re_ = pcre_compile(pattern_.c_str(), 0, &error, &offset, NULL); + + if (re_ == NULL) { + std::string offsetStr; + std::stringstream ss; + ss << offset; + offsetStr = ss.str(); + + std::string msg("PCRE compiling failed at offset " + offsetStr); + + throw CompileError(msg); + } + + // Allocate an appropriate amount + // of memory for the output vector. + int captureCount; + pcre_fullinfo(re_, NULL, PCRE_INFO_CAPTURECOUNT, &captureCount); + + ovector_.clear(); + ovector_.resize((captureCount + 1) * 3); +} + +Pattern::~Pattern() +{ + if (re_ != NULL) + pcre_free(re_); +} + + +std::string Pattern::group(const char *groupName) +{ + const char * stringPtr = NULL; + int rc = pcre_get_named_substring(re_, subject_.substr(offset_[0]).c_str(), + &ovector_[0], count_, groupName, + &stringPtr); + + if (rc < 0) { + switch (rc) { + case PCRE_ERROR_NOSUBSTRING: + break; + + case PCRE_ERROR_NOMEMORY: + throw MatchError("Memory exhausted."); + + default: + throw MatchError("Failed to get named substring."); + } + } + std::string matchedStr; + if (stringPtr) { + matchedStr = stringPtr; + pcre_free_substring(stringPtr); + } + return matchedStr; +} + +size_t Pattern::start() const +{ + return ovector_[0] + offset_[0]; +} + +size_t Pattern::end() const +{ + return (ovector_[1] - 1) + offset_[0]; +} + +bool Pattern::matches() +{ + // Try to find a match for this pattern + int rc = pcre_exec(re_, NULL, subject_.substr(offset_[1]).c_str(), + subject_.length() - offset_[1], 0, 0, &ovector_[0], + ovector_.size()); + + // Matching failed. + if (rc < 0) { + offset_[0] = offset_[1] = 0; + return false; + } + + // Handle the case if matching should be done globally + if (matchGlobally_) { + offset_[0] = offset_[1]; + // New offset is old offset + end of relative offset + offset_[1] = ovector_[1] + offset_[0]; + } + + // Matching succeded but not enough space. + // @TODO figure out something more clever to do in this case. + if (rc == 0) + throw MatchError("No space to store all substrings."); + + // Matching succeeded. Keep the number of substrings for + // subsequent calls to group(). + count_ = rc; + return true; +} + +std::vector<std::string> Pattern::split() +{ + size_t tokenEnd = -1; + size_t tokenStart = 0; + std::vector<std::string> substringSplitted; + while (matches()) { + tokenStart = start(); + substringSplitted.push_back(subject_.substr(tokenEnd + 1, + tokenStart - tokenEnd - 1)); + tokenEnd = end(); + } + + substringSplitted.push_back(subject_.substr(tokenEnd + 1, + tokenStart - tokenEnd - 1)); + return substringSplitted; +} + +} // namespace ring diff --git a/src/sip/pattern.h b/src/sip/pattern.h new file mode 100644 index 0000000000..935eb9d141 --- /dev/null +++ b/src/sip/pattern.h @@ -0,0 +1,184 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Pierre-Luc Bacon <pierre-luc.bacon@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#ifndef __PATTERN_H__ +#define __PATTERN_H__ + +#include <stdexcept> +#include <string> +#include <vector> +#include <pcre.h> +#include "noncopyable.h" + +namespace ring { + +/** + * Exception object that is thrown when + * an error occured while compiling the + * regular expression. + */ +class CompileError : public std::invalid_argument { + public: + explicit CompileError(const std::string& error) : + std::invalid_argument(error) {} +}; + +/** + * Exception object that is thrown when + * an error occured while mathing a + * pattern to an expression. + */ +class MatchError : public std::invalid_argument { + public: + MatchError(const std::string& error) : + std::invalid_argument(error) {} +}; + +/** +* This class implements in its way +* some of the libpcre library. +*/ + +class Pattern { + + public: + + /** + * Constructor for a regular expression + * pattern evaluator/matcher. + * + * @param pattern + * The regular expression to + * be used for this instance. + */ + + Pattern(const std::string& pattern, + bool matchGlobally); + + /** + * Destructor. Pcre pattern gets freed + * here. + */ + ~Pattern(); + + /** + * Get the substring matched in a capturing + * group (named or unnamed). + * + * This methods only performs a basic lookup + * inside its internal substring table. Thus, + * matches() should have been called prior to + * this method in order to obtain the desired + * output. + * + * @param groupName The name of the group + * + * @return the substring matched by the + * regular expression designated + * the group name. + */ + std::string group(const char *groupName); + + /** + * Try to match the compiled pattern with the implicit + * subject. + * + * @return true If the subject matches the pattern, + * false otherwise. + * + * @pre The regular expression should have been + * compiled prior to the execution of this method. + * + * @post The internal substring table will be updated + * with the new matches. Therefore, subsequent + * calls to group may return different results. + */ + bool matches(); + + /** + * Split the subject into a list of substrings. + * + * @return A vector of substrings. + * + * @pre The regular expression should have been + * compiled prior to the execution of this method. + * + * @post The internal subject won't be affected by this + * by this operation. In other words: subject_before = + * subject_after. + */ + std::vector<std::string> split(); + + void updateSubject(const std::string& subject) { + subject_ = subject; + } + + private: + /** + * Get the start position of the overall match. + * + * @return the start position of the overall match. + */ + size_t start() const; + + /** + * Get the end position of the overall match. + * + * @return the end position of the overall match. + */ + size_t end() const; + + NON_COPYABLE(Pattern); + // The regular expression that represents that pattern. + std::string pattern_; + + // The optional subject string. + std::string subject_; + + /** + * PCRE struct that + * contains the compiled regular + * expression + */ + pcre * re_; + + // The internal output vector used by PCRE. + std::vector<int> ovector_; + + // Current offset in the ovector_; + int offset_[2]; + + // The number of substrings matched after calling pcre_exec. + int count_; + + bool matchGlobally_; +}; +} + +#endif // __PATTERN_H__ + diff --git a/src/sip/pres_sub_client.cpp b/src/sip/pres_sub_client.cpp new file mode 100644 index 0000000000..6ba3810726 --- /dev/null +++ b/src/sip/pres_sub_client.cpp @@ -0,0 +1,621 @@ +/* + * Copyright (C) 2012, 2013 LOTES TM LLC + * Author : Andrey Loukhnov <aol.nnov@gmail.com> + * + * This file is a part of pult5-voip + * + * pult5-voip is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * pult5-voip is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this programm. If not, see <http://www.gnu.org/licenses/>. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify pult5-voip, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, LOTES-TM LLC + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <pj/log.h> +#include <pj/rand.h> +#include <pjsip/sip_module.h> +#include <pjsip/sip_types.h> +#include <pjsip/sip_event.h> +#include <pjsip/sip_transaction.h> +#include <pjsip/sip_dialog.h> +#include <pjsip/sip_endpoint.h> +#include <string> +#include <sstream> +#include <pj/pool.h> +#include <pjsip/sip_ua_layer.h> +#include <pjsip-simple/evsub.h> +#include <unistd.h> + +#include "array_size.h" +#include "pres_sub_client.h" +#include "sipaccount.h" +#include "sippresence.h" +#include "sipvoiplink.h" +#include "sip_utils.h" +#include "manager.h" +#include "client/signal.h" + +#include "logger.h" + +#define PRES_TIMER 300 // 5min + +namespace ring { + +int PresSubClient::modId_ = 0; // used to extract data structure from event_subscription + +void +PresSubClient::pres_client_timer_cb(pj_timer_heap_t * /*th*/, pj_timer_entry *entry) +{ + PresSubClient *c = (PresSubClient *) entry->user_data; + RING_DBG("timeout for %s", c->getURI().c_str()); +} + +/* Callback called when *client* subscription state has changed. */ +void +PresSubClient::pres_client_evsub_on_state(pjsip_evsub *sub, pjsip_event *event) +{ + PJ_UNUSED_ARG(event); + + PresSubClient *pres_client = (PresSubClient *) pjsip_evsub_get_mod_data(sub, modId_); + /* No need to pres->lock() here since the client has a locked dialog*/ + + if (!pres_client) { + RING_WARN("pres_client not found"); + return; + } + + RING_DBG("Subscription for pres_client '%s' is '%s'", pres_client->getURI().c_str(), + pjsip_evsub_get_state_name(sub) ? pjsip_evsub_get_state_name(sub) : "null"); + + pjsip_evsub_state state = pjsip_evsub_get_state(sub); + + SIPPresence * pres = pres_client->getPresence(); + + if (state == PJSIP_EVSUB_STATE_ACCEPTED) { + pres_client->enable(true); + emitSignal<DRing::PresenceSignal::SubscriptionStateChanged>( + pres->getAccount()->getAccountID(), + pres_client->getURI().c_str(), + PJ_TRUE); + + pres->getAccount()->supportPresence(PRESENCE_FUNCTION_SUBSCRIBE, true); + + } else if (state == PJSIP_EVSUB_STATE_TERMINATED) { + int resub_delay = -1; + pj_strdup_with_null(pres_client->pool_, &pres_client->term_reason_, pjsip_evsub_get_termination_reason(sub)); + + emitSignal<DRing::PresenceSignal::SubscriptionStateChanged>( + pres->getAccount()->getAccountID(), + pres_client->getURI().c_str(), + PJ_FALSE); + + pres_client->term_code_ = 200; + + /* Determine whether to resubscribe automatically */ + if (event && event->type == PJSIP_EVENT_TSX_STATE) { + const pjsip_transaction *tsx = event->body.tsx_state.tsx; + + if (pjsip_method_cmp(&tsx->method, &pjsip_subscribe_method) == 0) { + pres_client->term_code_ = tsx->status_code; + std::ostringstream os; + os << pres_client->term_code_; + const std::string error = os.str() + "/" + + std::string(pres_client->term_reason_.ptr, + pres_client->term_reason_.slen); + + std::string msg; + bool subscribe_allowed = PJ_FALSE; + + switch (tsx->status_code) { + case PJSIP_SC_CALL_TSX_DOES_NOT_EXIST: + /* 481: we refreshed too late? resubscribe + * immediately. + */ + /* But this must only happen when the 481 is received + * on subscription refresh request. We MUST NOT try to + * resubscribe automatically if the 481 is received + * on the initial SUBSCRIBE (if server returns this + * response for some reason). + */ + if (pres_client->dlg_->remote.contact) + resub_delay = 500; + msg = "Bad subscribe refresh."; + subscribe_allowed = PJ_TRUE; + break; + + case PJSIP_SC_NOT_FOUND: + msg = "Subscribe context not set for this buddy."; + subscribe_allowed = PJ_TRUE; + break; + + case PJSIP_SC_FORBIDDEN: + msg = "Subscribe not allowed for this buddy."; + subscribe_allowed = PJ_TRUE; + break; + + case PJSIP_SC_PRECONDITION_FAILURE: + msg = "Wrong server."; + break; + } + + /* report error: + * 1) send a signal through DBus + * 2) change the support field in the account schema if the pres_sub's server + * is the same as the account's server + */ + emitSignal<DRing::PresenceSignal::ServerError>( + pres_client->getPresence()->getAccount()->getAccountID(), + error, + msg); + + std::string account_host = std::string(pj_gethostname()->ptr, pj_gethostname()->slen); + std::string sub_host = sip_utils::getHostFromUri(pres_client->getURI()); + + if (not subscribe_allowed and account_host == sub_host) + pres_client->getPresence()->getAccount()->supportPresence(PRESENCE_FUNCTION_SUBSCRIBE, false); + + } else if (pjsip_method_cmp(&tsx->method, &pjsip_notify_method) == 0) { + if (pres_client->isTermReason("deactivated") || pres_client->isTermReason("timeout")) { + /* deactivated: The subscription has been terminated, + * but the subscriber SHOULD retry immediately with + * a new subscription. + */ + /* timeout: The subscription has been terminated + * because it was not refreshed before it expired. + * Clients MAY re-subscribe immediately. The + * "retry-after" parameter has no semantics for + * "timeout". + */ + resub_delay = 500; + } else if (pres_client->isTermReason("probation") || pres_client->isTermReason("giveup")) { + /* probation: The subscription has been terminated, + * but the client SHOULD retry at some later time. + * If a "retry-after" parameter is also present, the + * client SHOULD wait at least the number of seconds + * specified by that parameter before attempting to re- + * subscribe. + */ + /* giveup: The subscription has been terminated because + * the notifier could not obtain authorization in a + * timely fashion. If a "retry-after" parameter is + * also present, the client SHOULD wait at least the + * number of seconds specified by that parameter before + * attempting to re-subscribe; otherwise, the client + * MAY retry immediately, but will likely get put back + * into pending state. + */ + const pjsip_sub_state_hdr *sub_hdr; + pj_str_t sub_state = CONST_PJ_STR("Subscription-State"); + const pjsip_msg *msg; + + msg = event->body.tsx_state.src.rdata->msg_info.msg; + sub_hdr = (const pjsip_sub_state_hdr*) pjsip_msg_find_hdr_by_name(msg, &sub_state, NULL); + + if (sub_hdr && sub_hdr->retry_after > 0) + resub_delay = sub_hdr->retry_after * 1000; + } + + } + } + + /* For other cases of subscription termination, if resubscribe + * timer is not set, schedule with default expiration (plus minus + * some random value, to avoid sending SUBSCRIBEs all at once) + */ + if (resub_delay == -1) { + resub_delay = PRES_TIMER * 1000; + } + + pres_client->sub_ = sub; + pres_client->rescheduleTimer(PJ_TRUE, resub_delay); + + } else { //state==ACTIVE ...... + //This will clear the last termination code/reason + pres_client->term_code_ = 0; + pres_client->term_reason_.ptr = NULL; + } + + /* Clear subscription */ + if (pjsip_evsub_get_state(sub) == PJSIP_EVSUB_STATE_TERMINATED) { + pjsip_evsub_terminate(pres_client->sub_, PJ_FALSE); // = NULL; + pres_client->status_.info_cnt = 0; + pres_client->dlg_ = NULL; + pres_client->rescheduleTimer(PJ_FALSE, 0); + pjsip_evsub_set_mod_data(sub, modId_, NULL); + + pres_client->enable(false); + } +} + +/* Callback when transaction state has changed. */ + void +PresSubClient::pres_client_evsub_on_tsx_state(pjsip_evsub *sub, pjsip_transaction *tsx, pjsip_event *event) +{ + + PresSubClient *pres_client; + pjsip_contact_hdr *contact_hdr; + + pres_client = (PresSubClient *) pjsip_evsub_get_mod_data(sub, modId_); + /* No need to pres->lock() here since the client has a locked dialog*/ + + if (!pres_client) { + RING_WARN("Couldn't find pres_client."); + return; + } + + /* We only use this to update pres_client's Contact, when it's not + * set. + */ + if (pres_client->contact_.slen != 0) { + /* Contact already set */ + return; + } + + /* Only care about 2xx response to outgoing SUBSCRIBE */ + if (tsx->status_code / 100 != 2 || tsx->role != PJSIP_UAC_ROLE || event->type != PJSIP_EVENT_RX_MSG + || pjsip_method_cmp(&tsx->method, pjsip_get_subscribe_method()) != 0) { + return; + } + + /* Find contact header. */ + contact_hdr = (pjsip_contact_hdr*) pjsip_msg_find_hdr(event->body.rx_msg.rdata->msg_info.msg, PJSIP_H_CONTACT, + NULL); + + if (!contact_hdr || !contact_hdr->uri) { + return; + } + + pres_client->contact_.ptr = (char*) pj_pool_alloc(pres_client->pool_, PJSIP_MAX_URL_SIZE); + pres_client->contact_.slen = pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, contact_hdr->uri, pres_client->contact_.ptr, + PJSIP_MAX_URL_SIZE); + + if (pres_client->contact_.slen < 0) + pres_client->contact_.slen = 0; + +} + +/* Callback called when we receive NOTIFY */ +void +PresSubClient::pres_client_evsub_on_rx_notify(pjsip_evsub *sub, pjsip_rx_data *rdata, int *p_st_code, pj_str_t **p_st_text, pjsip_hdr *res_hdr, pjsip_msg_body **p_body) +{ + + PresSubClient *pres_client = (PresSubClient *) pjsip_evsub_get_mod_data(sub, modId_); + + if (!pres_client) { + RING_WARN("Couldn't find pres_client from ev_sub."); + return; + } + /* No need to pres->lock() here since the client has a locked dialog*/ + + pjsip_pres_get_status(sub, &pres_client->status_); + pres_client->reportPresence(); + + /* The default is to send 200 response to NOTIFY. + * Just leave it there.. + */ + PJ_UNUSED_ARG(rdata); + PJ_UNUSED_ARG(p_st_code); + PJ_UNUSED_ARG(p_st_text); + PJ_UNUSED_ARG(res_hdr); + PJ_UNUSED_ARG(p_body); +} + +PresSubClient::PresSubClient(const std::string& uri, SIPPresence *pres) : + pres_(pres), + uri_{0, 0}, + contact_{0, 0}, + display_(), + dlg_(NULL), + monitored_(false), + name_(), + cp_(), + pool_(0), + status_(), + sub_(NULL), + term_code_(0), + term_reason_(), + timer_(), + user_data_(NULL), + lock_count_(0), + lock_flag_(0) +{ + pj_caching_pool_init(&cp_, &pj_pool_factory_default_policy, 0); + pool_ = pj_pool_create(&cp_.factory, "Pres_sub_client", 512, 512, NULL); + uri_ = pj_strdup3(pool_, uri.c_str()); + contact_ = pj_strdup3(pool_, pres_->getAccount()->getFromUri().c_str()); +} + +PresSubClient::~PresSubClient() +{ + RING_DBG("Destroying pres_client object with uri %.*s", uri_.slen, uri_.ptr); + rescheduleTimer(PJ_FALSE, 0); + unsubscribe(); + pj_pool_release(pool_); +} + +bool PresSubClient::isSubscribed() +{ + return monitored_; +} + +std::string PresSubClient::getURI() +{ + std::string res(uri_.ptr, uri_.slen); + return res; +} + +SIPPresence * PresSubClient::getPresence() +{ + return pres_; +} + +bool PresSubClient::isPresent() +{ + return status_.info[0].basic_open; +} + +std::string PresSubClient::getLineStatus() +{ + return std::string(status_.info[0].rpid.note.ptr, status_.info[0].rpid.note.slen); +} + +bool PresSubClient::isTermReason(const std::string &reason) +{ + const std::string myReason(term_reason_.ptr, term_reason_.slen); + return not myReason.compare(reason); +} + +void PresSubClient::rescheduleTimer(bool reschedule, unsigned msec) +{ + if (timer_.id) { + pjsip_endpt_cancel_timer(getSIPVoIPLink()->getEndpoint(), &timer_); + timer_.id = PJ_FALSE; + } + + if (reschedule) { + pj_time_val delay; + + RING_WARN("pres_client %.*s will resubscribe in %u ms (reason: %.*s)", + uri_.slen, uri_.ptr, msec, (int) term_reason_.slen, term_reason_.ptr); + pj_timer_entry_init(&timer_, 0, this, &pres_client_timer_cb); + delay.sec = 0; + delay.msec = msec; + pj_time_val_normalize(&delay); + + if (pjsip_endpt_schedule_timer(getSIPVoIPLink()->getEndpoint(), &timer_, &delay) == PJ_SUCCESS) { + timer_.id = PJ_TRUE; + } + } +} + +void PresSubClient::enable(bool flag) +{ + RING_DBG("pres_client %s is %s monitored.",getURI().c_str(), flag? "":"NOT"); + if (flag and not monitored_) + pres_->addPresSubClient(this); + monitored_ = flag; +} + +void PresSubClient::reportPresence() +{ + /* callback*/ + pres_->reportPresSubClientNotification(getURI(), &status_); +} + +bool PresSubClient::lock() +{ + unsigned i; + + for(i=0; i<50; i++) + { + if (not pres_->tryLock()){ + // FIXME: i/10 in ms, sure!? + std::this_thread::sleep_for(std::chrono::milliseconds(i/10)); + continue; + } + lock_flag_ = PRESENCE_LOCK_FLAG; + + if (dlg_ == NULL) + return true; + + if (pjsip_dlg_try_inc_lock(dlg_) != PJ_SUCCESS) { + lock_flag_ = 0; + pres_->unlock(); + // FIXME: i/10 in ms, sure!? + std::this_thread::sleep_for(std::chrono::milliseconds(i/10)); + continue; + } + + lock_flag_ = PRESENCE_CLIENT_LOCK_FLAG; + pres_->unlock(); + } + + if (lock_flag_ == 0) + { + RING_DBG("pres_client failed to lock : timeout"); + return false; + } + return true; +} + +void PresSubClient::unlock() +{ + if (lock_flag_ & PRESENCE_CLIENT_LOCK_FLAG) + pjsip_dlg_dec_lock(dlg_); + + if (lock_flag_ & PRESENCE_LOCK_FLAG) + pres_->unlock(); +} + +bool PresSubClient::unsubscribe() +{ + if (not lock()) + return false; + + monitored_ = false; + + pjsip_tx_data *tdata; + pj_status_t retStatus; + + if (sub_ == NULL or dlg_ == NULL) { + RING_WARN("PresSubClient already unsubscribed."); + unlock(); + return false; + } + + if (pjsip_evsub_get_state(sub_) == PJSIP_EVSUB_STATE_TERMINATED) { + RING_WARN("pres_client already unsubscribed sub=TERMINATED."); + sub_ = NULL; + unlock(); + return false; + } + + /* Unsubscribe means send a subscribe with timeout=0s*/ + RING_WARN("pres_client %.*s: unsubscribing..", uri_.slen, uri_.ptr); + retStatus = pjsip_pres_initiate(sub_, 0, &tdata); + + if (retStatus == PJ_SUCCESS) { + pres_->fillDoc(tdata, NULL); + retStatus = pjsip_pres_send_request(sub_, tdata); + } + + if (retStatus != PJ_SUCCESS and sub_) { + pjsip_pres_terminate(sub_, PJ_FALSE); + sub_ = NULL; + RING_WARN("Unable to unsubscribe presence", retStatus); + unlock(); + return false; + } + + //pjsip_evsub_set_mod_data(sub_, modId_, NULL); // Not interested with further events + + unlock(); + return true; +} + + +bool PresSubClient::subscribe() +{ + + if (sub_ and dlg_) { //do not bother if already subscribed + pjsip_evsub_terminate(sub_, PJ_FALSE); + RING_DBG("PreseSubClient %.*s: already subscribed. Refresh it.", uri_.slen, uri_.ptr); + } + + //subscribe + pjsip_evsub_user pres_callback; + pjsip_tx_data *tdata; + pj_status_t status; + + /* Event subscription callback. */ + pj_bzero(&pres_callback, sizeof(pres_callback)); + pres_callback.on_evsub_state = &pres_client_evsub_on_state; + pres_callback.on_tsx_state = &pres_client_evsub_on_tsx_state; + pres_callback.on_rx_notify = &pres_client_evsub_on_rx_notify; + + SIPAccount * acc = pres_->getAccount(); + RING_DBG("PresSubClient %.*s: subscribing ", uri_.slen, uri_.ptr); + + + /* Create UAC dialog */ + pj_str_t from = pj_strdup3(pool_, acc->getFromUri().c_str()); + status = pjsip_dlg_create_uac(pjsip_ua_instance(), &from, &contact_, &uri_, NULL, &dlg_); + + if (status != PJ_SUCCESS) { + RING_ERR("Unable to create dialog \n"); + return false; + } + + /* Add credential for auth. */ + if (acc->hasCredentials() and pjsip_auth_clt_set_credentials(&dlg_->auth_sess, acc->getCredentialCount(), acc->getCredInfo()) != PJ_SUCCESS) { + RING_ERR("Could not initialize credentials for subscribe session authentication"); + } + + /* Increment the dialog's lock otherwise when presence session creation + * fails the dialog will be destroyed prematurely. + */ + pjsip_dlg_inc_lock(dlg_); + + status = pjsip_pres_create_uac(dlg_, &pres_callback, PJSIP_EVSUB_NO_EVENT_ID, &sub_); + + if (status != PJ_SUCCESS) { + sub_ = NULL; + RING_WARN("Unable to create presence client", status); + + /* This should destroy the dialog since there's no session + * referencing it + */ + if (dlg_) { + pjsip_dlg_dec_lock(dlg_); + } + + return false; + } + + /* Add credential for authentication */ + if (acc->hasCredentials() and pjsip_auth_clt_set_credentials(&dlg_->auth_sess, acc->getCredentialCount(), acc->getCredInfo()) != PJ_SUCCESS) { + RING_ERR("Could not initialize credentials for invite session authentication"); + return false; + } + + /* Set route-set */ + pjsip_regc *regc = acc->getRegistrationInfo(); + if (regc and acc->hasServiceRoute()) + pjsip_regc_set_route_set(regc, sip_utils::createRouteSet(acc->getServiceRoute(), pres_->getPool())); + + // attach the client data to the sub + pjsip_evsub_set_mod_data(sub_, modId_, this); + + status = pjsip_pres_initiate(sub_, -1, &tdata); + if (status != PJ_SUCCESS) { + if (dlg_) + pjsip_dlg_dec_lock(dlg_); + if (sub_) + pjsip_pres_terminate(sub_, PJ_FALSE); + sub_ = NULL; + RING_WARN("Unable to create initial SUBSCRIBE", status); + return false; + } + +// pjsua_process_msg_data(tdata, NULL); + + status = pjsip_pres_send_request(sub_, tdata); + + if (status != PJ_SUCCESS) { + if (dlg_) + pjsip_dlg_dec_lock(dlg_); + if (sub_) + pjsip_pres_terminate(sub_, PJ_FALSE); + sub_ = NULL; + RING_WARN("Unable to send initial SUBSCRIBE", status); + return false; + } + + pjsip_dlg_dec_lock(dlg_); + return true; +} + +bool PresSubClient::match(PresSubClient *b) +{ + return (b->getURI() == getURI()); +} + +} // namespace ring diff --git a/src/sip/pres_sub_client.h b/src/sip/pres_sub_client.h new file mode 100644 index 0000000000..19398ab68b --- /dev/null +++ b/src/sip/pres_sub_client.h @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2012, 2013 LOTES TM LLC + * Author : Andrey Loukhnov <aol.nnov@gmail.com> + * Author : Patrick Keroulas <patrick.keroulas@savoirfairelinux.com> + * This file is a part of pult5-voip + * + * pult5-voip is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * pult5-voip is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this programm. If not, see <http://www.gnu.org/licenses/>. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify pult5-voip, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, LOTES-TM LLC + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef PRES_SUB_CLIENT_H +#define PRES_SUB_CLIENT_H + +#include <pjsip-simple/presence.h> +#include <pj/timer.h> +#include <pj/pool.h> +#include <string> + +#include <pjsip-simple/evsub.h> +#include <pjsip-simple/evsub_msg.h> +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_transport.h> +#include "noncopyable.h" + +namespace ring { + +class SIPPresence; + +class PresSubClient { + + public: + /** + * Constructor + * @param uri SIP uri of remote user that we want to subscribe, + */ + PresSubClient(const std::string &uri, SIPPresence *pres); + /** + * Destructor. + * Process the the unsubscription before the destruction. + */ + ~PresSubClient(); + /** + * Compare with another pres_client's uris. + * @param b Other pres_client pointer + */ + bool match(PresSubClient *b); + /** + * Enable the monitoring and report signal to the client. + * The PBX server must approve and maintain the subrciption before the pres_client is added in the pres_client list. + * @param flag State of subscription. True if active. + */ + void enable(bool flag); + /** + * Get associated parent presence_module + */ + SIPPresence * getPresence(); + /** + * Data lock function + */ + bool lock(); + /** + * Data unlock function + */ + void unlock(); + /** + * Send a SUBCRIBE to the PXB or directly to a pres_client in the IP2IP context. + */ + bool subscribe(); + /** + * Send a SUBCRIBE to the PXB or directly to a pres_client in the IP2IP context but + * the 0s timeout make the dialog expire immediatly. + */ + bool unsubscribe(); + /** + * Return the monitor variable. + */ + bool isSubscribed(); + /** + * Return the pres_client URI + */ + std::string getURI(); + + /** + * Is the buddy present + */ + bool isPresent(); + + /** + * A message from the URIs + */ + std::string getLineStatus(); + + + /** + * TODO: explain this: + */ + void incLock() { + lock_count_++; + } + void decLock() { + lock_count_--; + } + + private: + + NON_COPYABLE(PresSubClient); + + /** + * Transaction functions of event subscription client side. + */ + static void pres_client_evsub_on_state(pjsip_evsub *sub, pjsip_event *event); + static void pres_client_evsub_on_tsx_state(pjsip_evsub *sub, + pjsip_transaction *tsx, + pjsip_event *event); + static void pres_client_evsub_on_rx_notify(pjsip_evsub *sub, + pjsip_rx_data *rdata, + int *p_st_code, + pj_str_t **p_st_text, + pjsip_hdr *res_hdr, + pjsip_msg_body **p_body); + static void pres_client_timer_cb(pj_timer_heap_t *th, pj_timer_entry *entry); + + /** + * Plan a retry or a renew a subscription. + * @param reschedule Allow for reschedule. + * @param msec Delay value in milliseconds. + */ + void rescheduleTimer(bool reschedule, unsigned msec); + /** + * Callback after a presence notification was received. + * Tranfert info to the SIP account. + */ + void reportPresence(); + /** + * Process the un/subscribe request transmission. + */ + pj_status_t updateSubscription(); + /* + * Compare the reason of a transaction end with the given string. + */ + bool isTermReason(const std::string &); + /** + * return the code after a transaction is terminated. + */ + unsigned getTermCode(); + + SIPPresence *pres_; /**< Associated SIPPresence pointer */ + pj_str_t uri_; /**< pres_client URI. */ + pj_str_t contact_; /**< Contact learned from subscrp. */ + pj_str_t display_; /**< pres_client display name. */ + pjsip_dialog *dlg_; /**< The underlying dialog. */ + pj_bool_t monitored_; /**< Should we monitor? */ + pj_str_t name_; /**< pres_client name. */ + pj_caching_pool cp_; + pj_pool_t *pool_; /**< Pool for this pres_client. */ + pjsip_pres_status status_; /**< pres_client presence status. */ + pjsip_evsub *sub_; /**< pres_client presence subscription */ + unsigned term_code_; /**< Subscription termination code */ + pj_str_t term_reason_; /**< Subscription termination reason */ + pj_timer_entry timer_; /**< Resubscription timer */ + void *user_data_; /**< Application data. */ + int lock_count_; + int lock_flag_; + static int modId_; // used to extract data structure from event_subscription +}; + +} // namespace ring + +#endif /* PRES_SUB_CLIENT_H */ diff --git a/src/sip/pres_sub_server.cpp b/src/sip/pres_sub_server.cpp new file mode 100644 index 0000000000..6a5edf4c01 --- /dev/null +++ b/src/sip/pres_sub_server.cpp @@ -0,0 +1,351 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Patrick Keroulas <patrick.keroulas@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + + +#include "pjsip/sip_multipart.h" + +#include "sipaccount.h" +#include "sipvoiplink.h" +#include "manager.h" +#include "sippresence.h" +#include "logger.h" +#include "pres_sub_server.h" +#include "client/signal.h" + +namespace ring { + +/* Callback called when *server* subscription state has changed. */ +void +PresSubServer::pres_evsub_on_srv_state(pjsip_evsub *sub, pjsip_event *event) +{ + pjsip_rx_data *rdata = event->body.rx_msg.rdata; + + if (!rdata) { + RING_DBG("Presence_subscription_server estate has changed but no rdata."); + return; + } + + auto account = Manager::instance().getIP2IPAccount(); + auto sipaccount = static_cast<SIPAccount *>(account.get()); + + if (!sipaccount) { + RING_ERR("Could not find account IP2IP"); + return; + } + + auto pres = sipaccount->getPresence(); + + if (!pres) { + RING_ERR("Presence not initialized"); + return; + } + + pres->lock(); + PresSubServer *presSubServer = static_cast<PresSubServer *>(pjsip_evsub_get_mod_data(sub, pres->getModId())); + + if (presSubServer) { + RING_DBG("Presence_subscription_server to %s is %s", + presSubServer->remote_, pjsip_evsub_get_state_name(sub)); + pjsip_evsub_state state; + + state = pjsip_evsub_get_state(sub); + + if (state == PJSIP_EVSUB_STATE_TERMINATED) { + pjsip_evsub_set_mod_data(sub, pres->getModId(), NULL); + pres->removePresSubServer(presSubServer); + } + + /* TODO check if other cases should be handled*/ + } + + pres->unlock(); +} + +pj_bool_t +PresSubServer::pres_on_rx_subscribe_request(pjsip_rx_data *rdata) +{ + + pjsip_method *method = &rdata->msg_info.msg->line.req.method; + pj_str_t *str = &method->name; + std::string request(str->ptr, str->slen); +// pj_str_t contact; + pj_status_t status; + pjsip_dialog *dlg; + pjsip_evsub *sub; + pjsip_evsub_user pres_cb; + pjsip_expires_hdr *expires_hdr; + pjsip_status_code st_code; + pj_str_t reason; + pres_msg_data msg_data; + pjsip_evsub_state ev_state; + + + /* Only hande incoming subscribe messages should be processed here. + * Otherwise we return FALSE to let other modules handle it */ + if (pjsip_method_cmp(&rdata->msg_info.msg->line.req.method, pjsip_get_subscribe_method()) != 0) + return PJ_FALSE; + + /* debug msg */ + std::string name(rdata->msg_info.to->name.ptr, rdata->msg_info.to->name.slen); + std::string server(rdata->msg_info.from->name.ptr, rdata->msg_info.from->name.slen); + RING_DBG("Incoming pres_on_rx_subscribe_request for %s, name:%s, server:%s." + , request.c_str() + , name.c_str() + , server.c_str()); + + /* get parents*/ + auto account = Manager::instance().getIP2IPAccount(); + auto sipaccount = static_cast<SIPAccount *>(account.get()); + if (!sipaccount) { + RING_ERR("Could not find account IP2IP"); + return PJ_FALSE; + } + + pjsip_endpoint *endpt = getSIPVoIPLink()->getEndpoint(); + SIPPresence * pres = sipaccount->getPresence(); + pres->lock(); + + /* Create UAS dialog: */ + const pj_str_t contact(sipaccount->getContactHeader()); + status = pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, &contact, &dlg); + + if (status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(status, errmsg, sizeof(errmsg)); + RING_WARN("Unable to create UAS dialog for subscription: %s [status=%d]", errmsg, status); + pres->unlock(); + pjsip_endpt_respond_stateless(endpt, rdata, 400, NULL, NULL, NULL); + return PJ_TRUE; + } + + /* Init callback: */ + pj_bzero(&pres_cb, sizeof(pres_cb)); + pres_cb.on_evsub_state = &pres_evsub_on_srv_state; + + /* Create server presence subscription: */ + status = pjsip_pres_create_uas(dlg, &pres_cb, rdata, &sub); + + if (status != PJ_SUCCESS) { + int code = PJSIP_ERRNO_TO_SIP_STATUS(status); + pjsip_tx_data *tdata; + + RING_WARN("Unable to create server subscription %d", status); + + if (code == 599 || code > 699 || code < 300) { + code = 400; + } + + status = pjsip_dlg_create_response(dlg, rdata, code, NULL, &tdata); + + if (status == PJ_SUCCESS) { + pjsip_dlg_send_response(dlg, pjsip_rdata_get_tsx(rdata), tdata); + } + + pres->unlock(); + return PJ_FALSE; + } + + /* Attach our data to the subscription: */ + char* remote = (char*) pj_pool_alloc(dlg->pool, PJSIP_MAX_URL_SIZE); + status = pjsip_uri_print(PJSIP_URI_IN_REQ_URI, dlg->remote.info->uri, remote, PJSIP_MAX_URL_SIZE); + + if (status < 1) + pj_ansi_strcpy(remote, "<-- url is too long-->"); + else + remote[status] = '\0'; + + //pjsip_uri_print(PJSIP_URI_IN_CONTACT_HDR, dlg->local.info->uri, contact.ptr, PJSIP_MAX_URL_SIZE); + + /* Create a new PresSubServer server and wait for client approve */ + PresSubServer *presSubServer = new PresSubServer(pres, sub, remote, dlg); + pjsip_evsub_set_mod_data(sub, pres->getModId(), presSubServer); + // Notify the client. + emitSignal<DRing::PresenceSignal::NewServerSubscriptionRequest>(presSubServer->remote_); + pres->addPresSubServer(presSubServer); + + /* Capture the value of Expires header. */ + expires_hdr = (pjsip_expires_hdr*) pjsip_msg_find_hdr(rdata->msg_info.msg, PJSIP_H_EXPIRES, NULL); + + if (expires_hdr) + presSubServer->setExpires(expires_hdr->ivalue); + else + presSubServer->setExpires(-1); + + st_code = (pjsip_status_code) 200; + reason = CONST_PJ_STR("OK"); + pj_bzero(&msg_data, sizeof(msg_data)); + pj_list_init(&msg_data.hdr_list); + pjsip_media_type_init(&msg_data.multipart_ctype, NULL, NULL); + pj_list_init(&msg_data.multipart_parts); + + /* Create and send 2xx response to the SUBSCRIBE request: */ + status = pjsip_pres_accept(sub, rdata, st_code, &msg_data.hdr_list); + + if (status != PJ_SUCCESS) { + RING_WARN("Unable to accept presence subscription %d", status); + pjsip_pres_terminate(sub, PJ_FALSE); + pres->unlock(); + return PJ_FALSE; + } + + // Unsubscribe case + ev_state = PJSIP_EVSUB_STATE_ACTIVE; + + if (presSubServer->getExpires() == 0) { + // PJSIP_EVSUB_STATE_TERMINATED + pres->unlock(); + return PJ_TRUE; + } + + /*Send notify immediatly. Replace real status with fake.*/ + + // pjsip_pres_set_status(sub, pres->getStatus()); // real status + + // fake temporary status + pjrpid_element rpid = { + PJRPID_ELEMENT_TYPE_PERSON, + CONST_PJ_STR("20"), + PJRPID_ACTIVITY_UNKNOWN, + CONST_PJ_STR("") // empty note by default + }; + pjsip_pres_status fake_status_data; + pj_bzero(&fake_status_data, sizeof(pjsip_pres_status)); + fake_status_data.info_cnt = 1; + fake_status_data.info[0].basic_open = false; + fake_status_data.info[0].id = CONST_PJ_STR("0"); /* todo: tuplie_id*/ + pj_memcpy(&fake_status_data.info[0].rpid, &rpid, sizeof(pjrpid_element)); + pjsip_pres_set_status(sub, &fake_status_data); + + /* Create and send the the first NOTIFY to active subscription: */ + pj_str_t stateStr = CONST_PJ_STR(""); + pjsip_tx_data *tdata = NULL; + status = pjsip_pres_notify(sub, ev_state, &stateStr, &reason, &tdata); + + if (status == PJ_SUCCESS) { + pres->fillDoc(tdata, &msg_data); + status = pjsip_pres_send_request(sub, tdata); + } + + if (status != PJ_SUCCESS) { + RING_WARN("Unable to create/send NOTIFY %d", status); + pjsip_pres_terminate(sub, PJ_FALSE); + pres->unlock(); + return status; + } + + pres->unlock(); + return PJ_TRUE; +} + +pjsip_module PresSubServer::mod_presence_server = { + NULL, NULL, /* prev, next. */ + CONST_PJ_STR("mod-presence-server"), /* Name. */ + -1, /* Id */ + PJSIP_MOD_PRIORITY_DIALOG_USAGE, + NULL, /* load() */ + NULL, /* start() */ + NULL, /* stop() */ + NULL, /* unload() */ + &pres_on_rx_subscribe_request, /* on_rx_request() */ + NULL, /* on_rx_response() */ + NULL, /* on_tx_request. */ + NULL, /* on_tx_response() */ + NULL, /* on_tsx_state() */ + +}; + + + +PresSubServer::PresSubServer(SIPPresence * pres, pjsip_evsub *evsub, const char *remote, pjsip_dialog *d) + : remote_(remote) + , pres_(pres) + , sub_(evsub) + , dlg_(d) + , expires_(-1) + , approved_(false) +{} + +PresSubServer::~PresSubServer() +{ + //TODO: check if evsub needs to be forced TERMINATED. +} + +void PresSubServer::setExpires(int ms) +{ + expires_ = ms; +} + +int PresSubServer::getExpires() const +{ + return expires_; +} + +bool PresSubServer::matches(const char *s) const +{ + // servers match if they have the same remote uri and the account ID. + return (!(strcmp(remote_, s))) ; +} + +void PresSubServer::approve(bool flag) +{ + approved_ = flag; + RING_DBG("Approve Presence_subscription_server for %s: %s.", remote_, flag ? "true" : "false"); + // attach the real status data + pjsip_pres_set_status(sub_, pres_->getStatus()); +} + + +void PresSubServer::notify() +{ + /* Only send NOTIFY once subscription is active. Some subscriptions + * may still be in NULL (when app is adding a new buddy while in the + * on_incoming_subscribe() callback) or PENDING (when user approval is + * being requested) state and we don't send NOTIFY to these subs until + * the user accepted the request. + */ + if ((pjsip_evsub_get_state(sub_) == PJSIP_EVSUB_STATE_ACTIVE) && (approved_)) { + RING_DBG("Notifying %s.", remote_); + + pjsip_tx_data *tdata; + pjsip_pres_set_status(sub_, pres_->getStatus()); + + if (pjsip_pres_current_notify(sub_, &tdata) == PJ_SUCCESS) { + // add msg header and send + pres_->fillDoc(tdata, NULL); + pjsip_pres_send_request(sub_, tdata); + } else { + RING_WARN("Unable to create/send NOTIFY"); + pjsip_pres_terminate(sub_, PJ_FALSE); + } + } +} + +} // namespace ring diff --git a/src/sip/pres_sub_server.h b/src/sip/pres_sub_server.h new file mode 100644 index 0000000000..e75423537c --- /dev/null +++ b/src/sip/pres_sub_server.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Patrick Keroulas <patrick.keroulas@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + + +#ifndef SERVERPRESENCESUB_H +#define SERVERPRESENCESUB_H + +#include <pj/string.h> +#include <pjsip/sip_types.h> +#include <pjsip-simple/evsub.h> +#include <pjsip-simple/presence.h> +#include <pjsip/sip_module.h> + +#include "noncopyable.h" +#include "array_size.h" + +namespace ring { + +extern pj_bool_t pres_on_rx_subscribe_request(pjsip_rx_data *rdata); + +class SIPpresence; + +class PresSubServer { + + public: + PresSubServer(SIPPresence * pres, pjsip_evsub *evsub, const char *remote, pjsip_dialog *d); + ~PresSubServer(); + /* + * Acces to the evsub expire variable. + * It was recieved in the SUBSCRIBE request. + */ + void setExpires(int ms); + int getExpires() const; + /* + * Match method + * s is the URI (remote) + */ + bool matches(const char *s) const; + /* + * Allow the subscriber for being notified. + */ + void approve(bool flag); + /* + * Notify subscriber with the pres_status_date of the account + */ + void notify(); + + static pjsip_module mod_presence_server; + + private: + static pj_bool_t pres_on_rx_subscribe_request(pjsip_rx_data *rdata); + static void pres_evsub_on_srv_state(pjsip_evsub *sub, pjsip_event *event); + + + NON_COPYABLE(PresSubServer); + /* TODO: add '< >' to URI for consistency */ + const char *remote_; /**< Remote URI. */ + SIPPresence *pres_; + pjsip_evsub *sub_; + pjsip_dialog *dlg_; + int expires_; + bool approved_; +}; + +} // namespace ring + +#endif /* SERVERPRESENCESUB_H */ diff --git a/src/sip/sdes_negotiator.cpp b/src/sip/sdes_negotiator.cpp new file mode 100644 index 0000000000..64d53f0fc1 --- /dev/null +++ b/src/sip/sdes_negotiator.cpp @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Pierre-Luc Bacon <pierre-luc.bacon@savoirfairelinux.com> + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "sdes_negotiator.h" +#include "pattern.h" + +#include <memory> +#include <iostream> +#include <sstream> +#include <algorithm> +#include <stdexcept> + +#include <cstdio> + +namespace ring { + +SdesNegotiator::SdesNegotiator(const std::vector<CryptoSuiteDefinition>& localCapabilites) : + localCapabilities_(localCapabilites) +{} + +std::vector<CryptoAttribute> +SdesNegotiator::parse(const std::vector<std::string>& attributes) +{ + // The patterns below try to follow + // the ABNF grammar rules described in + // RFC4568 section 9.2 with the general + // syntax : + //a=crypto:tag 1*WSP crypto-suite 1*WSP key-params *(1*WSP session-param) + + std::unique_ptr<Pattern> generalSyntaxPattern, tagPattern, cryptoSuitePattern, + keyParamsPattern; + + try { + // used to match white space (which are used as separator) + generalSyntaxPattern.reset(new Pattern("[\x20\x09]+", true)); + + tagPattern.reset(new Pattern("^(?P<tag>[0-9]{1,9})", false)); + + cryptoSuitePattern.reset(new Pattern( + "(?P<cryptoSuite>AES_CM_128_HMAC_SHA1_80|" \ + "AES_CM_128_HMAC_SHA1_32|" \ + "F8_128_HMAC_SHA1_80|" \ + "[A-Za-z0-9_]+)", false)); // srtp-crypto-suite-ext + + keyParamsPattern.reset(new Pattern( + "(?P<srtpKeyMethod>inline|[A-Za-z0-9_]+)\\:" \ + "(?P<srtpKeyInfo>[A-Za-z0-9\x2B\x2F\x3D]+)" \ + "(\\|2\\^(?P<lifetime>[0-9]+)\\|" \ + "(?P<mkiValue>[0-9]+)\\:" \ + "(?P<mkiLength>[0-9]{1,3})\\;?)?", true)); + + } catch (const CompileError& exception) { + throw ParseError("A compile exception occured on a pattern."); + } + + // Take each line from the vector + // and parse its content + + std::vector<CryptoAttribute> cryptoAttributeVector; + + for (const auto &item : attributes) { + + // Split the line into its component + // that we will analyze further down. + std::vector<std::string> sdesLine; + + generalSyntaxPattern->updateSubject(item); + + try { + sdesLine = generalSyntaxPattern->split(); + + if (sdesLine.size() < 3) + throw ParseError("Missing components in SDES line"); + } catch (const MatchError& exception) { + throw ParseError("Error while analyzing the SDES line."); + } + + // Check if the attribute starts with a=crypto + // and get the tag for this line + tagPattern->updateSubject(sdesLine.at(0)); + + std::string tag; + + if (tagPattern->matches()) { + try { + tag = tagPattern->group("tag"); + } catch (const MatchError& exception) { + throw ParseError("Error while parsing the tag field"); + } + } else + return cryptoAttributeVector; + + // Check if the crypto suite is valid and retreive + // its value. + cryptoSuitePattern->updateSubject(sdesLine.at(1)); + + std::string cryptoSuite; + + if (cryptoSuitePattern->matches()) { + try { + cryptoSuite = cryptoSuitePattern->group("cryptoSuite"); + } catch (const MatchError& exception) { + throw ParseError("Error while parsing the crypto-suite field"); + } + } else + return cryptoAttributeVector; + + // Parse one or more key-params field. + keyParamsPattern->updateSubject(sdesLine.at(2)); + + std::string srtpKeyInfo; + std::string srtpKeyMethod; + std::string lifetime; + std::string mkiLength; + std::string mkiValue; + + try { + while (keyParamsPattern->matches()) { + srtpKeyMethod = keyParamsPattern->group("srtpKeyMethod"); + srtpKeyInfo = keyParamsPattern->group("srtpKeyInfo"); + lifetime = keyParamsPattern->group("lifetime"); + mkiValue = keyParamsPattern->group("mkiValue"); + mkiLength = keyParamsPattern->group("mkiLength"); + } + } catch (const MatchError& exception) { + throw ParseError("Error while parsing the key-params field"); + } + + // Add the new CryptoAttribute to the vector + cryptoAttributeVector.emplace_back( + tag, + cryptoSuite, + srtpKeyMethod, + srtpKeyInfo, + lifetime, + mkiValue, + mkiLength + ); + } + return cryptoAttributeVector; +} + +CryptoAttribute +SdesNegotiator::negotiate(const std::vector<std::string>& attributes) const +{ + try { + auto cryptoAttributeVector(parse(attributes)); + for (const auto& iter_offer : cryptoAttributeVector) { + for (const auto& iter_local : localCapabilities_) { + if (iter_offer.getCryptoSuite().compare(iter_local.name)) + return iter_offer; + } + } + } + catch (const ParseError& exception) {} + catch (const MatchError& exception) {} + return {}; +} + +} // namespace ring diff --git a/src/sip/sdes_negotiator.h b/src/sip/sdes_negotiator.h new file mode 100644 index 0000000000..ce37420ad8 --- /dev/null +++ b/src/sip/sdes_negotiator.h @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Pierre-Luc Bacon <pierre-luc.bacon@savoirfairelinux.com> + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ +#ifndef __SDES_NEGOTIATOR_H__ +#define __SDES_NEGOTIATOR_H__ + +#include "media/media_codec.h" + +#include <stdexcept> +#include <string> +#include <vector> + +namespace ring { + +/** + * General exception object that is thrown when + * an error occured with a regular expression + * operation. + */ +class ParseError : public std::invalid_argument { + public: + explicit ParseError(const std::string& error) : + std::invalid_argument(error) {} +}; + +enum CipherMode { + AESCounterMode, + AESF8Mode +}; + +enum MACMode { + HMACSHA1 +}; + +enum KeyMethod { + Inline + // url, maybe at some point +}; + +struct CryptoSuiteDefinition { + const char *name; + int masterKeyLength; + int masterSaltLength; + int srtpLifetime; + int srtcpLifetime; + CipherMode cipher; + int encryptionKeyLength; + MACMode mac; + int srtpAuthTagLength; + int srtcpAuthTagLength; + int srtpAuthKeyLength; + int srtcpAuthKeyLen; +}; + +/** +* List of accepted Crypto-Suites +* as defined in RFC4568 (6.2) +*/ + +static std::vector<CryptoSuiteDefinition> CryptoSuites = { + { "AES_CM_128_HMAC_SHA1_80", + 128, 112, 48, 31, AESCounterMode, 128, HMACSHA1, 80, 80, 160, 160 }, + + { "AES_CM_128_HMAC_SHA1_32", + 128, 112, 48, 31, AESCounterMode, 128, HMACSHA1, 32, 80, 160, 160 }, + + { "F8_128_HMAC_SHA1_80", + 128, 112, 48, 31, AESF8Mode, 128, HMACSHA1, 80, 80, 160, 160 } +}; + +class SdesNegotiator { + /** + * Constructor for an SDES crypto attributes + * negotiator. + * + * @param attribute + * A vector of crypto attributes as defined in + * RFC4568. This string will be parsed + * and a crypto context will be created + * from it. + */ + public: + SdesNegotiator() {} + SdesNegotiator(const std::vector<CryptoSuiteDefinition>& capabilites); + + ring::CryptoAttribute + negotiate(const std::vector<std::string>& attributes) const; + + operator bool() const { + return not localCapabilities_.empty(); + } + + private: + static std::vector<CryptoAttribute> + parse(const std::vector<std::string>& attributes); + + /** + * A vector list containing the remote attributes. + * Multiple crypto lines can be sent, and the + * preferred method is then chosen from that list. + */ + std::vector<CryptoSuiteDefinition> localCapabilities_; +}; + +} // namespace ring + +#endif // __SDES_NEGOTIATOR_H__ diff --git a/src/sip/sdp.cpp b/src/sip/sdp.cpp new file mode 100644 index 0000000000..2075e74ab7 --- /dev/null +++ b/src/sip/sdp.cpp @@ -0,0 +1,774 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * Author: Eloi Bail <eloi.bail@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "sdp.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sipaccount.h" +#include "sipvoiplink.h" +#include "string_utils.h" +#include "base64.h" + +#include "manager.h" +#include "logger.h" +#include "libav_utils.h" +#include "array_size.h" + +#include "media_codec.h" +#include "system_codec_container.h" +#include "intrin.h" // for UNUSED + +#include <algorithm> +#include <cassert> + +namespace ring { + +using std::string; +using std::map; +using std::vector; +using std::stringstream; + +static constexpr int POOL_INITIAL_SIZE = 16384; +static constexpr int POOL_INCREMENT_SIZE = POOL_INITIAL_SIZE; + +Sdp::Sdp(const std::string& id) + : memPool_(nullptr, pj_pool_release) + , publishedIpAddr_() + , publishedIpAddrType_() + , sdesNego_ {CryptoSuites} + , zrtpHelloHash_() + , telephoneEventPayload_(101) // same as asterisk +{ + memPool_.reset(pj_pool_create(&getSIPVoIPLink()->getCachingPool()->factory, + id.c_str(), POOL_INITIAL_SIZE, + POOL_INCREMENT_SIZE, NULL)); + if (not memPool_) + throw std::runtime_error("pj_pool_create() failed"); +} + +Sdp::~Sdp() +{ + SIPAccount::releasePort(localAudioDataPort_); +#ifdef RING_VIDEO + SIPAccount::releasePort(localVideoDataPort_); +#endif +} + +std::shared_ptr<AccountCodecInfo> +Sdp::findCodecBySpec(const std::string &codec, const unsigned clockrate) const +{ + //TODO : only manage a list? + for (const auto& accountCodec : audio_codec_list_) { + auto audioCodecInfo = std::static_pointer_cast<AccountAudioCodecInfo>(accountCodec); + auto& sysCodecInfo = *static_cast<const SystemAudioCodecInfo*>(&audioCodecInfo->systemCodecInfo); + if (sysCodecInfo.name.compare(codec) == 0 and + (audioCodecInfo->isPCMG722() ? + (clockrate == 8000) : + (sysCodecInfo.audioformat.sample_rate == clockrate))) + return accountCodec; + } + + for (const auto& accountCodec : video_codec_list_) { + auto sysCodecInfo = accountCodec->systemCodecInfo; + if (sysCodecInfo.name.compare(codec) == 0) + return accountCodec; + } + return nullptr; +} + +std::shared_ptr<AccountCodecInfo> +Sdp::findCodecByPayload(const unsigned payloadType) +{ + //TODO : only manage a list? + for (const auto& accountCodec : audio_codec_list_) { + auto sysCodecInfo = accountCodec->systemCodecInfo; + if (sysCodecInfo.payloadType == payloadType) + return accountCodec; + } + + for (const auto& accountCodec : video_codec_list_) { + auto sysCodecInfo = accountCodec->systemCodecInfo; + if (sysCodecInfo.payloadType == payloadType) + return accountCodec; + } + return nullptr; +} + +static void +randomFill(std::vector<uint8_t>& dest) +{ + std::uniform_int_distribution<uint8_t> rand_byte(0, 255); + std::random_device rdev; + std::generate(dest.begin(), dest.end(), std::bind(rand_byte, std::ref(rdev))); +} + + +void +Sdp::setActiveLocalSdpSession(const pjmedia_sdp_session* sdp) +{ + activeLocalSession_ = sdp; +} + +void +Sdp::setActiveRemoteSdpSession(const pjmedia_sdp_session *sdp) +{ + if (!sdp) { + RING_ERR("Remote sdp is NULL"); + return; + } + + activeRemoteSession_ = sdp; +} + +pjmedia_sdp_attr * +Sdp::generateSdesAttribute() +{ + static constexpr const unsigned cryptoSuite = 0; + std::vector<uint8_t> keyAndSalt; + keyAndSalt.resize(ring::CryptoSuites[cryptoSuite].masterKeyLength / 8 + + ring::CryptoSuites[cryptoSuite].masterSaltLength/ 8); + // generate keys + randomFill(keyAndSalt); + + std::string tag = "1"; + std::string crypto_attr = tag + " " + + ring::CryptoSuites[cryptoSuite].name + + " inline:" + base64::encode(keyAndSalt); + RING_DBG("%s", crypto_attr.c_str()); + + pj_str_t val { (char*) crypto_attr.c_str(), + static_cast<pj_ssize_t>(crypto_attr.size()) }; + return pjmedia_sdp_attr_create(memPool_.get(), "crypto", &val); +} + +pjmedia_sdp_media * +Sdp::setMediaDescriptorLines(bool audio, bool holding, sip_utils::KeyExchangeProtocol kx) +{ + pjmedia_sdp_media *med = PJ_POOL_ZALLOC_T(memPool_.get(), pjmedia_sdp_media); + + med->desc.media = audio ? pj_str((char*) "audio") : pj_str((char*) "video"); + med->desc.port_count = 1; + med->desc.port = audio ? localAudioDataPort_ : localVideoDataPort_; + + // in case of sdes, media are tagged as "RTP/SAVP", RTP/AVP elsewhere + med->desc.transport = pj_str(kx == sip_utils::KeyExchangeProtocol::NONE ? + (char*) "RTP/AVP" : + (char*) "RTP/SAVP"); + + unsigned dynamic_payload = 96; + + med->desc.fmt_count = audio ? audio_codec_list_.size() : video_codec_list_.size(); + for (unsigned i = 0; i < med->desc.fmt_count; ++i) { + pjmedia_sdp_rtpmap rtpmap; + rtpmap.param.slen = 0; + + std::string channels; // must have the lifetime of rtpmap + std::string enc_name; + unsigned payload; + + if (audio) { + auto accountAudioCodec = std::static_pointer_cast<AccountAudioCodecInfo>(audio_codec_list_[i]); + payload = accountAudioCodec->payloadType; + enc_name = accountAudioCodec->systemCodecInfo.name; + + if (accountAudioCodec->audioformat.nb_channels > 1) { + channels = ring::to_string(accountAudioCodec->audioformat.nb_channels); + rtpmap.param.ptr = (char *) channels.c_str(); + rtpmap.param.slen = strlen(channels.c_str()); // don't include NULL terminator + } + // G722 requires G722/8000 media description even though it's @ 16000 Hz + // See http://tools.ietf.org/html/rfc3551#section-4.5.2 + if (accountAudioCodec->isPCMG722()) + rtpmap.clock_rate = 8000; + else + rtpmap.clock_rate = accountAudioCodec->audioformat.sample_rate; + + } else { + // FIXME: get this key from header + payload = dynamic_payload++; + enc_name = video_codec_list_[i]->systemCodecInfo.name; + rtpmap.clock_rate = 90000; + } + + std::ostringstream s; + s << payload; + pj_strdup2(memPool_.get(), &med->desc.fmt[i], s.str().c_str()); + + // Add a rtpmap field for each codec + // We could add one only for dynamic payloads because the codecs with static RTP payloads + // are entirely defined in the RFC 3351 + rtpmap.pt = med->desc.fmt[i]; + rtpmap.enc_name = pj_str((char*) enc_name.c_str()); + + pjmedia_sdp_attr *attr; + pjmedia_sdp_rtpmap_to_attr(memPool_.get(), &rtpmap, &attr); + med->attr[med->attr_count++] = attr; + +#ifdef RING_VIDEO + if (enc_name == "H264") { + // FIXME: this should not be hardcoded, it will determine what profile and level + // our peer will send us + const auto accountVideoCodec = std::static_pointer_cast<AccountVideoCodecInfo>(video_codec_list_[i]); + const auto profileLevelID = accountVideoCodec->parameters.empty() ? + libav_utils::DEFAULT_H264_PROFILE_LEVEL_ID : + accountVideoCodec->parameters; + std::ostringstream os; + os << "fmtp:" << payload << " " << profileLevelID; + med->attr[med->attr_count++] = pjmedia_sdp_attr_create(memPool_.get(), os.str().c_str(), NULL); + } +#endif + } + + if (audio) { + setTelephoneEventRtpmap(med); + addRTCPAttribute(med); // video has its own RTCP + } + + med->attr[med->attr_count++] = pjmedia_sdp_attr_create(memPool_.get(), holding ? (audio ? "sendonly" : "inactive") : "sendrecv", NULL); + + if (kx == sip_utils::KeyExchangeProtocol::SDES) { + if (pjmedia_sdp_media_add_attr(med, generateSdesAttribute()) != PJ_SUCCESS) + SdpException("Could not add sdes attribute to media"); + } /* else if (kx == sip_utils::KeyExchangeProtocol::ZRTP) { + if (!zrtpHelloHash_.empty()) + addZrtpAttribute(med, zrtpHelloHash_); + } */ + + return med; +} + + +void Sdp::addRTCPAttribute(pjmedia_sdp_media *med) +{ + IpAddr outputAddr = publishedIpAddr_; + outputAddr.setPort(localAudioControlPort_); + pjmedia_sdp_attr *attr = pjmedia_sdp_attr_create_rtcp(memPool_.get(), outputAddr.pjPtr()); + if (attr) + pjmedia_sdp_attr_add(&med->attr_count, med->attr, attr); +} + +void +Sdp::setPublishedIP(const std::string &addr, pj_uint16_t addr_type) +{ + publishedIpAddr_ = addr; + publishedIpAddrType_ = addr_type; + if (localSession_) { + if (addr_type == pj_AF_INET6()) + localSession_->origin.addr_type = pj_str((char*) "IP6"); + else + localSession_->origin.addr_type = pj_str((char*) "IP4"); + localSession_->origin.addr = pj_str((char*) publishedIpAddr_.c_str()); + localSession_->conn->addr = localSession_->origin.addr; + if (pjmedia_sdp_validate(localSession_) != PJ_SUCCESS) + RING_ERR("Could not validate SDP"); + } +} + +void +Sdp::setPublishedIP(const IpAddr& ip_addr) +{ + setPublishedIP(ip_addr, ip_addr.getFamily()); +} + +void Sdp::setTelephoneEventRtpmap(pjmedia_sdp_media *med) +{ + std::ostringstream s; + s << telephoneEventPayload_; + ++med->desc.fmt_count; + pj_strdup2(memPool_.get(), &med->desc.fmt[med->desc.fmt_count - 1], s.str().c_str()); + + pjmedia_sdp_attr *attr_rtpmap = static_cast<pjmedia_sdp_attr *>(pj_pool_zalloc(memPool_.get(), sizeof(pjmedia_sdp_attr))); + attr_rtpmap->name = pj_str((char *) "rtpmap"); + attr_rtpmap->value = pj_str((char *) "101 telephone-event/8000"); + + med->attr[med->attr_count++] = attr_rtpmap; + + pjmedia_sdp_attr *attr_fmtp = static_cast<pjmedia_sdp_attr *>(pj_pool_zalloc(memPool_.get(), sizeof(pjmedia_sdp_attr))); + attr_fmtp->name = pj_str((char *) "fmtp"); + attr_fmtp->value = pj_str((char *) "101 0-15"); + + med->attr[med->attr_count++] = attr_fmtp; +} + +void Sdp::setLocalMediaVideoCapabilities(const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedCodecs) +{ +#ifdef RING_VIDEO + video_codec_list_ = selectedCodecs; +#else + (void) selectedCodecs; +#endif +} + +void Sdp::setLocalMediaAudioCapabilities(const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedCodecs) +{ + audio_codec_list_ = selectedCodecs; +} + +static void +printSession(const pjmedia_sdp_session *session) +{ + char buffer[2048]; + size_t size = pjmedia_sdp_print(session, buffer, sizeof(buffer)); + string sessionStr(buffer, std::min(size, sizeof(buffer))); + RING_DBG("%s", sessionStr.c_str()); +} + +int Sdp::createLocalSession(const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedAudioCodecs, + const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedVideoCodecs, + sip_utils::KeyExchangeProtocol security, + bool holding) +{ + setLocalMediaAudioCapabilities(selectedAudioCodecs); + setLocalMediaVideoCapabilities(selectedVideoCodecs); + + localSession_ = PJ_POOL_ZALLOC_T(memPool_.get(), pjmedia_sdp_session); + localSession_->conn = PJ_POOL_ZALLOC_T(memPool_.get(), pjmedia_sdp_conn); + + /* Initialize the fields of the struct */ + localSession_->origin.version = 0; + pj_time_val tv; + pj_gettimeofday(&tv); + + localSession_->origin.user = pj_str(pj_gethostname()->ptr); + // Use Network Time Protocol format timestamp to ensure uniqueness. + localSession_->origin.id = tv.sec + 2208988800UL; + localSession_->origin.net_type = pj_str((char*) "IN"); + if (publishedIpAddrType_ == pj_AF_INET6()) + localSession_->origin.addr_type = pj_str((char*) "IP6"); + else + localSession_->origin.addr_type = pj_str((char*) "IP4"); + localSession_->origin.addr = pj_str((char*) publishedIpAddr_.c_str()); + localSession_->name = pj_str((char*) PACKAGE_NAME); + localSession_->conn->net_type = localSession_->origin.net_type; + localSession_->conn->addr_type = localSession_->origin.addr_type; + localSession_->conn->addr = localSession_->origin.addr; + + // RFC 3264: An offer/answer model session description protocol + // As the session is created and destroyed through an external signaling mean (SIP), the line + // should have a value of "0 0". + localSession_->time.start = 0; + localSession_->time.stop = 0; + + // For DTMF RTP events + constexpr bool audio = true; + localSession_->media_count = 1; + localSession_->media[0] = setMediaDescriptorLines(audio, holding, security); + if (not selectedVideoCodecs.empty()) { + localSession_->media[1] = setMediaDescriptorLines(!audio, holding, security); + ++localSession_->media_count; + } + + RING_DBG("SDP: Local SDP Session:"); + printSession(localSession_); + + return pjmedia_sdp_validate(localSession_); +} + +bool +Sdp::createOffer(const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedAudioCodecs, + const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedVideoCodecs, + sip_utils::KeyExchangeProtocol security, + bool holding) +{ + if (createLocalSession(selectedAudioCodecs, selectedVideoCodecs, security, holding) != PJ_SUCCESS) { + RING_ERR("Failed to create initial offer"); + return false; + } + + if (pjmedia_sdp_neg_create_w_local_offer(memPool_.get(), localSession_, &negotiator_) != PJ_SUCCESS) { + RING_ERR("Failed to create an initial SDP negotiator"); + return false; + } + + return true; +} + +void Sdp::receiveOffer(const pjmedia_sdp_session* remote, + const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedAudioCodecs, + const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedVideoCodecs, + sip_utils::KeyExchangeProtocol kx, + bool holding) +{ + if (!remote) { + RING_ERR("Remote session is NULL"); + return; + } + + RING_DBG("Remote SDP Session:"); + printSession(remote); + + if (not localSession_ and createLocalSession(selectedAudioCodecs, + selectedVideoCodecs, kx, holding) != PJ_SUCCESS) { + RING_ERR("Failed to create initial offer"); + return; + } + + remoteSession_ = pjmedia_sdp_session_clone(memPool_.get(), remote); + + if (pjmedia_sdp_neg_create_w_remote_offer(memPool_.get(), localSession_, + remoteSession_, &negotiator_) != PJ_SUCCESS) + RING_ERR("Failed to initialize negotiator"); +} + +void Sdp::startNegotiation() +{ + if (negotiator_ == NULL) { + RING_ERR("Can't start negotiation with invalid negotiator"); + return; + } + + const pjmedia_sdp_session *active_local; + const pjmedia_sdp_session *active_remote; + + if (pjmedia_sdp_neg_get_state(negotiator_) != PJMEDIA_SDP_NEG_STATE_WAIT_NEGO) { + RING_WARN("Negotiator not in right state for negotiation"); + return; + } + + if (pjmedia_sdp_neg_negotiate(memPool_.get(), negotiator_, 0) != PJ_SUCCESS) + return; + + if (pjmedia_sdp_neg_get_active_local(negotiator_, &active_local) != PJ_SUCCESS) + RING_ERR("Could not retrieve local active session"); + else + setActiveLocalSdpSession(active_local); + + if (pjmedia_sdp_neg_get_active_remote(negotiator_, &active_remote) != PJ_SUCCESS) + RING_ERR("Could not retrieve remote active session"); + else + setActiveRemoteSdpSession(active_remote); +} + + +std::string +Sdp::getFilteredSdp(const pjmedia_sdp_session* session, unsigned media_keep, unsigned pt_keep) +{ + static constexpr size_t BUF_SZ = 4096; + std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&> tmpPool_( + pj_pool_create(&getSIPVoIPLink()->getCachingPool()->factory, + "tmpSdp", BUF_SZ, BUF_SZ, nullptr), + pj_pool_release + ); + auto cloned = pjmedia_sdp_session_clone(tmpPool_.get(), session); + if (!cloned) { + RING_ERR("Could not clone SDP"); + return ""; + } + + // deactivate non-video media + bool hasKeep = false; + for (unsigned i = 0; i < cloned->media_count; i++) + if (i != media_keep) { + if (pjmedia_sdp_media_deactivate(tmpPool_.get(), cloned->media[i]) != PJ_SUCCESS) + RING_ERR("Could not deactivate media"); + } else { + hasKeep = true; + } + + if (not hasKeep) { + RING_DBG("No media to keep present in SDP"); + return ""; + } + + // Leaking medias will be dropped with tmpPool_ + for (unsigned i = 0; i < cloned->media_count; i++) + if (cloned->media[i]->desc.port == 0) { + std::move(cloned->media+i+1, + cloned->media+cloned->media_count, + cloned->media+i); + cloned->media_count--; + i--; + } + + for (unsigned i = 0; i < cloned->media_count; i++) { + auto media = cloned->media[i]; + + // filter other codecs + for (unsigned c=0; c<media->desc.fmt_count; c++) { + auto& pt = media->desc.fmt[c]; + if (pj_strtoul(&pt) == pt_keep) + continue; + + while (auto attr = pjmedia_sdp_attr_find2(media->attr_count, + media->attr, + "rtpmap", &pt)) + pjmedia_sdp_attr_remove(&media->attr_count, media->attr, attr); + + while (auto attr = pjmedia_sdp_attr_find2(media->attr_count, + media->attr, + "fmt", &pt)) + pjmedia_sdp_attr_remove(&media->attr_count, media->attr, attr); + + std::move(media->desc.fmt+c+1, + media->desc.fmt+media->desc.fmt_count, + media->desc.fmt+c); + media->desc.fmt_count--; + c--; + } + + // we handle crypto ourselfs, don't tell libav about it + while (auto attr = pjmedia_sdp_attr_find2(media->attr_count, + media->attr, "crypto", + nullptr)) { + pjmedia_sdp_attr_remove(&media->attr_count, media->attr, attr); + } + } + + char buffer[BUF_SZ]; + size_t size = pjmedia_sdp_print(cloned, buffer, sizeof(buffer)); + string sessionStr(buffer, std::min(size, sizeof(buffer))); + + return sessionStr; +} + +std::vector<MediaDescription> +Sdp::getMediaSlots(const pjmedia_sdp_session* session, bool remote) const +{ + static constexpr pj_str_t STR_RTPMAP { (char*) "rtpmap", 6 }; + static constexpr pj_str_t STR_FMTP { (char*) "fmtp", 4 }; + + std::vector<MediaDescription> ret; + for (unsigned i = 0; i < session->media_count; i++) { + auto media = session->media[i]; + ret.emplace_back(MediaDescription()); + MediaDescription& descr = ret.back(); + if (!pj_stricmp2(&media->desc.media, "audio")) + descr.type = MEDIA_AUDIO; + else if (!pj_stricmp2(&media->desc.media, "video")) + descr.type = MEDIA_VIDEO; + else + continue; + + descr.enabled = media->desc.port; + if (!descr.enabled) + continue; + + // get connection info + pjmedia_sdp_conn* conn = media->conn ? media->conn : session->conn; + if (not conn) { + RING_ERR("Could not find connection information for media"); + continue; + } + descr.addr = std::string(conn->addr.ptr, conn->addr.slen); + descr.addr.setPort(media->desc.port); + + descr.holding = pjmedia_sdp_attr_find2(media->attr_count, media->attr, "sendonly", nullptr) + || pjmedia_sdp_attr_find2(media->attr_count, media->attr, "inactive", nullptr); + + // get codecs infos + for (unsigned j = 0; j<media->desc.fmt_count; j++) { + const auto rtpMapAttribute = pjmedia_sdp_media_find_attr(media, &STR_RTPMAP, &media->desc.fmt[j]); + if (!rtpMapAttribute) { + RING_ERR("Could not find rtpmap attribute"); + descr.enabled = false; + continue; + } + pjmedia_sdp_rtpmap rtpmap; + if (pjmedia_sdp_attr_get_rtpmap(rtpMapAttribute, &rtpmap) != PJ_SUCCESS || + rtpmap.enc_name.slen == 0) + { + RING_ERR("Could not find payload type %s in SDP", media->desc.fmt[j]); + descr.enabled = false; + continue; + } + const std::string codec_raw(rtpmap.enc_name.ptr, rtpmap.enc_name.slen); + descr.rtp_clockrate = rtpmap.clock_rate; + descr.codec = findCodecBySpec(codec_raw, rtpmap.clock_rate); + if (not descr.codec) { + RING_ERR("Could not find codec %s", codec_raw.c_str()); + descr.enabled = false; + continue; + } + descr.payload_type = pj_strtoul(&rtpmap.pt); + if (descr.type == MEDIA_VIDEO) { + const auto fmtpAttr = pjmedia_sdp_media_find_attr(media, &STR_FMTP, &media->desc.fmt[j]); + //descr.bitrate = getOutgoingVideoField(codec, "bitrate"); + if (fmtpAttr && fmtpAttr->value.ptr && fmtpAttr->value.slen) { + const auto& v = fmtpAttr->value; + descr.parameters = std::string(v.ptr, v.ptr + v.slen); + } + } + // for now, just keep the first codec only + descr.enabled = true; + break; + } + + if (not remote) + descr.receiving_sdp = getFilteredSdp(session, i, descr.payload_type); + + // get crypto info + std::vector<std::string> crypto; + for (unsigned j = 0; j < media->attr_count; j++) { + const auto attribute = media->attr[j]; + if (pj_stricmp2(&attribute->name, "crypto") == 0) + crypto.emplace_back(attribute->value.ptr, attribute->value.slen); + } + descr.crypto = sdesNego_.negotiate(crypto); + } + return ret; +} + +std::vector<Sdp::MediaSlot> +Sdp::getMediaSlots() const +{ + auto loc = getMediaSlots(activeLocalSession_, false); + auto rem = getMediaSlots(activeRemoteSession_, true); + size_t slot_n = std::min(loc.size(), rem.size()); + std::vector<MediaSlot> s; + s.reserve(slot_n); + for (decltype(slot_n) i=0; i<slot_n; i++) + s.emplace_back(std::move(loc[i]), std::move(rem[i])); + return s; +} + +void Sdp::addZrtpAttribute(pjmedia_sdp_media* media, std::string hash) +{ + /* Format: ":version value" */ + std::string val = "1.10 " + hash; + pj_str_t value = { (char*)val.c_str(), static_cast<pj_ssize_t>(val.size()) }; + pjmedia_sdp_attr *attr = pjmedia_sdp_attr_create(memPool_.get(), "zrtp-hash", &value); + if (pjmedia_sdp_media_add_attr(media, attr) != PJ_SUCCESS) + throw SdpException("Could not add zrtp attribute to media"); +} + +void +Sdp::addIceCandidates(unsigned media_index, const std::vector<std::string>& cands) +{ + if (media_index >= localSession_->media_count) { + RING_ERR("addIceCandidates failed: cannot access media#%u (may be deactivated)", media_index); + return; + } + + auto media = localSession_->media[media_index]; + + for (const auto &item : cands) { + pj_str_t val = { (char*) item.c_str(), static_cast<pj_ssize_t>(item.size()) }; + pjmedia_sdp_attr *attr = pjmedia_sdp_attr_create(memPool_.get(), "candidate", &val); + + if (pjmedia_sdp_media_add_attr(media, attr) != PJ_SUCCESS) + throw SdpException("Could not add ICE candidates attribute to media"); + } +} + +std::vector<std::string> +Sdp::getIceCandidates(unsigned media_index) const +{ + auto session = activeRemoteSession_ ? activeRemoteSession_ : remoteSession_; + if (not session) { + RING_ERR("getIceCandidates failed: no remote session"); + return {}; + } + if (media_index >= session->media_count) { + RING_ERR("getIceCandidates failed: cannot access media#%u (may be deactivated)", media_index); + return {}; + } + auto media = session->media[media_index]; + std::vector<std::string> candidates; + + for (unsigned i=0; i < media->attr_count; i++) { + pjmedia_sdp_attr *attribute = media->attr[i]; + if (pj_stricmp2(&attribute->name, "candidate") == 0) + candidates.push_back(std::string(attribute->value.ptr, attribute->value.slen)); + } + + return candidates; +} + +void +Sdp::addIceAttributes(const IceTransport::Attribute&& ice_attrs) +{ + pj_str_t value; + pjmedia_sdp_attr *attr; + + value = { (char*)ice_attrs.ufrag.c_str(), static_cast<pj_ssize_t>(ice_attrs.ufrag.size()) }; + attr = pjmedia_sdp_attr_create(memPool_.get(), "ice-ufrag", &value); + + if (pjmedia_sdp_attr_add(&localSession_->attr_count, localSession_->attr, attr) != PJ_SUCCESS) + throw SdpException("Could not add ICE.ufrag attribute to local SDP"); + + value = { (char*)ice_attrs.pwd.c_str(), static_cast<pj_ssize_t>(ice_attrs.pwd.size()) }; + attr = pjmedia_sdp_attr_create(memPool_.get(), "ice-pwd", &value); + + if (pjmedia_sdp_attr_add(&localSession_->attr_count, localSession_->attr, attr) != PJ_SUCCESS) + throw SdpException("Could not add ICE.pwd attribute to local SDP"); +} + +IceTransport::Attribute +Sdp::getIceAttributes() const +{ + IceTransport::Attribute ice_attrs; + auto session = activeRemoteSession_ ? activeRemoteSession_ : remoteSession_; + assert(session); + return getIceAttributes(session); +} + +IceTransport::Attribute +Sdp::getIceAttributes(const pjmedia_sdp_session* session) +{ + IceTransport::Attribute ice_attrs; + for (unsigned i=0; i < session->attr_count; i++) { + pjmedia_sdp_attr *attribute = session->attr[i]; + if (pj_stricmp2(&attribute->name, "ice-ufrag") == 0) + ice_attrs.ufrag.assign(attribute->value.ptr, attribute->value.slen); + else if (pj_stricmp2(&attribute->name, "ice-pwd") == 0) + ice_attrs.pwd.assign(attribute->value.ptr, attribute->value.slen); + } + return ice_attrs; +} + +void +Sdp::clearIce() +{ + clearIce(localSession_); + clearIce(remoteSession_); +} + +void +Sdp::clearIce(pjmedia_sdp_session* session) +{ + if (not session) + return; + pjmedia_sdp_attr_remove_all(&session->attr_count, session->attr, "ice-ufrag"); + pjmedia_sdp_attr_remove_all(&session->attr_count, session->attr, "ice-pwd"); + pjmedia_sdp_attr_remove_all(&session->attr_count, session->attr, "candidate"); + for (unsigned i=0; i < session->media_count; i++) { + auto media = session->media[i]; + pjmedia_sdp_attr_remove_all(&media->attr_count, media->attr, "candidate"); + } +} + +} // namespace ring diff --git a/src/sip/sdp.h b/src/sip/sdp.h new file mode 100644 index 0000000000..79bb95e020 --- /dev/null +++ b/src/sip/sdp.h @@ -0,0 +1,359 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef SDP_H_ +#define SDP_H_ + +#include "noncopyable.h" +#include "sdes_negotiator.h" +#include "sip_utils.h" +#include "ip_utils.h" +#include "ice_transport.h" +#include "media_codec.h" +#include "sip_utils.h" + +#include <pjmedia/sdp.h> +#include <pjmedia/sdp_neg.h> +#include <pjsip/sip_transport.h> +#include <pjlib.h> +#include <pjsip_ua.h> +#include <pjmedia/errno.h> +#include <pj/pool.h> +#include <pj/assert.h> + +#include <map> +#include <vector> +#include <string> +#include <stdexcept> + +namespace ring { + +namespace test { +class SDPTest; +} + +class AudioCodec; + +class SdpException : public std::runtime_error { + public: + SdpException(const std::string& str="") : + std::runtime_error("SDP: SdpException occured: " + str) {} +}; + +class Sdp { + public: + + /* + * Class Constructor. + * + * @param memory pool + */ + Sdp(const std::string& id); + + ~Sdp(); + + /** + * Read accessor. Get the local passive sdp session information before negotiation + * + * @return The structure that describes a SDP session + */ + pjmedia_sdp_session *getLocalSdpSession() { + return localSession_; + } + + const pjmedia_sdp_session *getActiveLocalSdpSession() const { + return activeLocalSession_; + } + + /** + * Read accessor. Get the remote passive sdp session information before negotiation + * + * @return The structure that describe the SDP session + */ + pjmedia_sdp_session *getRemoteSdpSession() { + return remoteSession_; + } + + const pjmedia_sdp_session *getActiveRemoteSdpSession() const { + return activeRemoteSession_; + } + + /** + * Set the negotiated sdp offer from the sip payload. + * + * @param sdp the negotiated offer + */ + void setActiveLocalSdpSession(const pjmedia_sdp_session *sdp); + + /** + * Retrieve the negotiated sdp offer from the sip payload. + * + * @param sdp the negotiated offer + */ + void setActiveRemoteSdpSession(const pjmedia_sdp_session *sdp); + + /* + * On building an invite outside a dialog, build the local offer and create the + * SDP negotiator instance with it. + * @returns true if offer was created, false otherwise + */ + bool createOffer(const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedAudioCodecs, + const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedVideoCodecs, + sip_utils::KeyExchangeProtocol, + bool holding=false); + + /* + * On receiving an invite outside a dialog, build the local offer and create the + * SDP negotiator instance with the remote offer. + * + * @param remote The remote offer + */ + void receiveOffer(const pjmedia_sdp_session* remote, + const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedAudioCodecs, + const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedVideoCodecs, + sip_utils::KeyExchangeProtocol, + bool holding=false); + + /** + * Start the sdp negotiation. + */ + void startNegotiation(); + + /** + * Remove all media in the session media vector. + */ + void cleanSessionMedia(); + + /* + * Write accessor. Set the local IP address that will be used in the sdp session + */ + void setPublishedIP(const std::string &addr, pj_uint16_t addr_type = pj_AF_UNSPEC()); + void setPublishedIP(const IpAddr& addr); + + /* + * Read accessor. Get the local IP address + */ + IpAddr getPublishedIPAddr() const { + return publishedIpAddr_; + } + + std::string getPublishedIP() const { + return publishedIpAddr_; + } + + void setLocalPublishedAudioPort(int port) { + localAudioDataPort_ = port; + localAudioControlPort_ = port + 1; + } + + void setLocalPublishedAudioPorts(int audio_port, int control_port) { + localAudioDataPort_ = audio_port; + localAudioControlPort_ = control_port; + } + + void setLocalPublishedVideoPort (int port) { + localVideoDataPort_ = port; + localVideoControlPort_ = port + 1; + } + + void setLocalPublishedVideoPorts(int video_port, int control_port) { + localVideoDataPort_ = video_port; + localVideoControlPort_ = control_port; + } + + unsigned int getLocalVideoPort() const { + return localVideoDataPort_; + } + + unsigned int getLocalVideoControlPort() const { + return localVideoControlPort_; + } + + unsigned int getLocalAudioPort() const { + return localAudioDataPort_; + } + + unsigned int getLocalAudioControlPort() const { + return localAudioControlPort_; + } + + std::vector<MediaDescription> + getMediaSlots(const pjmedia_sdp_session* session, bool remote) const; + + using MediaSlot = std::pair<MediaDescription, MediaDescription>; + std::vector<MediaSlot> getMediaSlots() const; + + /** + * Set the zrtp hash that was previously calculated from the hello message in the zrtp layer. + * This hash value is unique at the media level. Therefore, if video support is added, one would + * have to set the correct zrtp-hash value in the corresponding media section. + * @param hash The hello hash of a rtp session. (Only audio at the moment) + */ + void setZrtpHash(const std::string& hash) { + zrtpHelloHash_ = hash; + } + + unsigned int getTelephoneEventType() const { + return telephoneEventPayload_; + } + + void addIceAttributes(const IceTransport::Attribute&& ice_attrs); + IceTransport::Attribute getIceAttributes() const; + static IceTransport::Attribute getIceAttributes(const pjmedia_sdp_session* session); + + void addIceCandidates(unsigned media_index, + const std::vector<std::string>& cands); + + std::vector<std::string> getIceCandidates(unsigned media_index) const; + + void clearIce(); + + private: + friend class test::SDPTest; + + NON_COPYABLE(Sdp); + + std::string getLineFromSession(const pjmedia_sdp_session *sess, const std::string &keyword) const; + std::string getOutgoingVideoField(const std::string &codec, const char *key) const; + void getProfileLevelID(const pjmedia_sdp_session *session, std::string &dest, int payload) const; + + /** + * Returns the printed original SDP filtered with only the specified media index and codec remaining. + */ + static std::string getFilteredSdp(const pjmedia_sdp_session* session, unsigned media_keep, unsigned pt_keep); + + static void clearIce(pjmedia_sdp_session* session); + + /** + * The pool to allocate memory + */ + std::unique_ptr<pj_pool_t, decltype(pj_pool_release)&> memPool_; + + /** negotiator */ + pjmedia_sdp_neg *negotiator_ {nullptr}; + + /** + * Local SDP + */ + pjmedia_sdp_session *localSession_ {nullptr}; + + /** + * Remote SDP + */ + pjmedia_sdp_session *remoteSession_ {nullptr}; + + /** + * The negotiated SDP remote session + * Explanation: each endpoint's offer is negotiated, and a new sdp offer results from this + * negotiation, with the compatible media from each part + */ + const pjmedia_sdp_session *activeLocalSession_ {nullptr}; + + /** + * The negotiated SDP remote session + * Explanation: each endpoint's offer is negotiated, and a new sdp offer results from this + * negotiation, with the compatible media from each part + */ + const pjmedia_sdp_session *activeRemoteSession_ {nullptr}; + + /** + * Codec Map used for offer + */ + std::vector<std::shared_ptr<AccountCodecInfo>> audio_codec_list_; + std::vector<std::shared_ptr<AccountCodecInfo>> video_codec_list_; + + std::string publishedIpAddr_; + pj_uint16_t publishedIpAddrType_; + + int localAudioDataPort_ {0}; + int localAudioControlPort_ {0}; + int localVideoDataPort_ {0}; + int localVideoControlPort_ {0}; + + SdesNegotiator sdesNego_; + std::string zrtpHelloHash_; + + unsigned int telephoneEventPayload_; + + /* + * Build the sdp media section + * Add rtpmap field if necessary + */ + pjmedia_sdp_media *setMediaDescriptorLines(bool audio, bool holding, sip_utils::KeyExchangeProtocol); + pjmedia_sdp_attr *generateSdesAttribute(); + + void setTelephoneEventRtpmap(pjmedia_sdp_media *med); + + /** + * Build the local media capabilities for this session + * @param List of codec in preference order + */ + void setLocalMediaAudioCapabilities(const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedAudioCodecs); + void setLocalMediaVideoCapabilities(const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedVideoCodecs); + + /* + * Build the local SDP offer + */ + int createLocalSession(const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedAudioCodecs, + const std::vector<std::shared_ptr<AccountCodecInfo>>& selectedVideoCodecs, + sip_utils::KeyExchangeProtocol, + bool holding); + + /* + * Adds a sdes attribute to the given media section. + * + * @param media The media to add the srtp attribute to + * @throw SdpException + */ + void addSdesAttribute(const std::vector<std::string>& crypto); + + /* + * Adds a zrtp-hash attribute to + * the given media section. The hello hash is + * available only after is has been computed + * in the AudioZrtpSession constructor. + * + * @param media The media to add the zrtp-hash attribute to + * @param hash The hash to which the attribute should be set to + * @throw SdpException + */ + void addZrtpAttribute(pjmedia_sdp_media* media, std::string hash); + + void addRTCPAttribute(pjmedia_sdp_media *med); + + std::shared_ptr<AccountCodecInfo> findCodecByPayload(const unsigned payloadType); + std::shared_ptr<AccountCodecInfo> findCodecBySpec(const std::string &codecName, const unsigned clockrate=0) const; +}; + +} // namespace ring + +#endif diff --git a/src/sip/sip_utils.cpp b/src/sip/sip_utils.cpp new file mode 100644 index 0000000000..0df195756a --- /dev/null +++ b/src/sip/sip_utils.cpp @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "sip_utils.h" +#include "logger.h" +#include "utf8_utils.h" + +#include <pjsip.h> +#include <pjsip_ua.h> +#include <pjlib-util.h> +#include <pjnath.h> +#include <pjnath/stun_config.h> +#include <pj/string.h> +#include <pjsip/sip_types.h> +#include <pjsip/sip_uri.h> +#include <pj/list.h> + +#include <netdb.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <vector> +#include <algorithm> + +namespace ring { namespace sip_utils { + +std::string +fetchHeaderValue(pjsip_msg *msg, const std::string &field) +{ + pj_str_t name = pj_str((char*) field.c_str()); + pjsip_generic_string_hdr *hdr = static_cast<pjsip_generic_string_hdr*>(pjsip_msg_find_hdr_by_name(msg, &name, NULL)); + + if (!hdr) + return ""; + + std::string value(hdr->hvalue.ptr, hdr->hvalue.slen); + + size_t pos = value.find("\n"); + + if (pos != std::string::npos) + return value.substr(0, pos); + else + return ""; +} + +pjsip_route_hdr * +createRouteSet(const std::string &route, pj_pool_t *hdr_pool) +{ + pjsip_route_hdr *route_set = pjsip_route_hdr_create(hdr_pool); + + std::string host; + int port = 0; + size_t found = route.find(":"); + if (found != std::string::npos) { + host = route.substr(0, found); + port = atoi(route.substr(found + 1, route.length() - found).c_str()); + } else + host = route; + + pjsip_route_hdr *routing = pjsip_route_hdr_create(hdr_pool); + pjsip_sip_uri *url = pjsip_sip_uri_create(hdr_pool, 0); + url->lr_param = 1; + routing->name_addr.uri = (pjsip_uri*) url; + pj_strdup2(hdr_pool, &url->host, host.c_str()); + url->port = port; + + RING_DBG("Adding route %s", host.c_str()); + pj_list_push_back(route_set, pjsip_hdr_clone(hdr_pool, routing)); + + return route_set; +} + +// FIXME: replace with regex +std::string +parseDisplayName(const char * buffer) +{ + // Start in From: in short and long form + const char* from_header = strstr(buffer, "\nFrom: "); + if (!from_header) + from_header = strstr(buffer, "\nf: "); + if (!from_header) + return ""; + + std::string temp(from_header); + + // Cut at end of line + temp = temp.substr(0, temp.find("\n", 1)); + + size_t begin_displayName = temp.find("\""); + size_t end_displayName; + if (begin_displayName != std::string::npos) { + // parse between quotes + end_displayName = temp.find("\"", begin_displayName + 1); + if (end_displayName == std::string::npos) + return ""; + } else { + // parse without quotes + end_displayName = temp.find("<"); + if (end_displayName != std::string::npos) { + begin_displayName = temp.find_first_not_of(" ", temp.find(":")); + if (begin_displayName == std::string::npos) + return ""; + + // omit trailing/leading spaces + begin_displayName++; + end_displayName--; + if (end_displayName == begin_displayName) + return ""; + } else { + return ""; + } + } + + std::string displayName = temp.substr(begin_displayName + 1, + end_displayName - begin_displayName - 1); + + // Filter out invalid UTF-8 characters to avoid getting kicked from D-Bus + if (not utf8_validate(displayName)) { + return utf8_make_valid(displayName); + } + + return displayName; +} + +void +stripSipUriPrefix(std::string& sipUri) +{ + // Remove sip: prefix + static const char SIP_PREFIX[] = "sip:"; + size_t found = sipUri.find(SIP_PREFIX); + + if (found != std::string::npos) + sipUri.erase(found, (sizeof SIP_PREFIX) - 1); + + // URI may or may not be between brackets + found = sipUri.find("<"); + if (found != std::string::npos) + sipUri.erase(found, 1); + + found = sipUri.find("@"); + + if (found != std::string::npos) + sipUri.erase(found); + + found = sipUri.find(">"); + if (found != std::string::npos) + sipUri.erase(found, 1); +} + +std::string +getHostFromUri(const std::string& sipUri) +{ + std::string hostname(sipUri); + size_t found = hostname.find("@"); + if (found != std::string::npos) + hostname.erase(0, found+1); + + found = hostname.find(">"); + if (found != std::string::npos) + hostname.erase(found, 1); + + return hostname; +} + +void +addContactHeader(const pj_str_t *contact_str, pjsip_tx_data *tdata) +{ + pjsip_contact_hdr *contact = pjsip_contact_hdr_create(tdata->pool); + contact->uri = pjsip_parse_uri(tdata->pool, contact_str->ptr, + contact_str->slen, PJSIP_PARSE_URI_AS_NAMEADDR); + // remove old contact header (if present) + pjsip_msg_find_remove_hdr(tdata->msg, PJSIP_H_CONTACT, NULL); + pjsip_msg_add_hdr(tdata->msg, (pjsip_hdr*) contact); +} + + +void +sip_strerror(pj_status_t code) +{ + char err_msg[PJ_ERR_MSG_SIZE]; + pj_strerror(code, err_msg, sizeof err_msg); + RING_ERR("%d: %s", code, err_msg); +} + +}} // namespace ring::sip_utils diff --git a/src/sip/sip_utils.h b/src/sip/sip_utils.h new file mode 100644 index 0000000000..e64fbba888 --- /dev/null +++ b/src/sip/sip_utils.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef SIP_UTILS_H_ +#define SIP_UTILS_H_ + +#include "ip_utils.h" +#include "media_codec.h" +#include "media/audio/audiobuffer.h" + +#include <pjsip/sip_msg.h> +#include <pjlib.h> + +#include <utility> +#include <string> +#include <vector> +#include <cstring> // strcmp + +struct pjsip_msg; + +namespace ring { namespace sip_utils { + +static constexpr int DEFAULT_SIP_PORT {5060}; +static constexpr int DEFAULT_SIP_TLS_PORT {5061}; + +enum class KeyExchangeProtocol { NONE, SDES, ZRTP }; + +constexpr const char* getKeyExchangeName(KeyExchangeProtocol kx) { + return kx == KeyExchangeProtocol::SDES ? "sdes" : ( + kx == KeyExchangeProtocol::ZRTP ? "zrtp" : ""); +} + +static inline KeyExchangeProtocol getKeyExchangeProtocol(const char* name) { + return !std::strcmp("sdes", name) ? KeyExchangeProtocol::SDES : KeyExchangeProtocol::NONE; +} + +/** + * Helper function to parser header from incoming sip messages + * @return Header from SIP message + */ +std::string fetchHeaderValue(pjsip_msg *msg, const std::string &field); + +pjsip_route_hdr * +createRouteSet(const std::string &route, pj_pool_t *hdr_pool); + +void stripSipUriPrefix(std::string& sipUri); + +std::string parseDisplayName(const char * buffer); + +std::string getHostFromUri(const std::string& sipUri); + +void addContactHeader(const pj_str_t *contactStr, pjsip_tx_data *tdata); + +void sip_strerror(pj_status_t code); + +}} // namespace ring::sip_utils + +#endif // SIP_UTILS_H_ diff --git a/src/sip/sipaccount.cpp b/src/sip/sipaccount.cpp new file mode 100644 index 0000000000..9066aa9053 --- /dev/null +++ b/src/sip/sipaccount.cpp @@ -0,0 +1,2155 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Pierre-Luc Bacon <pierre-luc.bacon@savoirfairelinux.com> + * Author : Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "sipaccount.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "intrin.h" + +#include "sdp.h" +#include "sipvoiplink.h" +#include "sipcall.h" +#include "sip_utils.h" +#include "array_size.h" + +#include "call_factory.h" + +#include "sippresence.h" + +#include <yaml-cpp/yaml.h> + +#include "account_schema.h" +#include "config/yamlparser.h" +#include "logger.h" +#include "manager.h" +#include "client/signal.h" + +#ifdef RING_VIDEO +#include "libav_utils.h" +#endif + +#include "system_codec_container.h" + +#include "upnp/upnp_control.h" +#include "ip_utils.h" +#include "string_utils.h" + +#include <unistd.h> +#include <pwd.h> + +#include <algorithm> +#include <array> +#include <memory> +#include <sstream> +#include <cstdlib> + +namespace ring { + +using yaml_utils::parseValue; +using yaml_utils::parseVectorMap; + +static const int MIN_REGISTRATION_TIME = 60; +static const int DEFAULT_REGISTRATION_TIME = 3600; +static const char *const VALID_TLS_METHODS[] = {"Default", "TLSv1", "SSLv3", "SSLv23"}; + +constexpr const char * const SIPAccount::ACCOUNT_TYPE; + +#if HAVE_TLS + +// Empty cypher list will use default cypher list for the transport type on GnuTLS +const CipherArray SIPAccount::TLSv1_DEFAULT_CIPHER_LIST = {}; +const CipherArray SIPAccount::SSLv3_DEFAULT_CIPHER_LIST = {}; +const CipherArray SIPAccount::SSLv23_DEFAULT_CIPHER_LIST = {}; + +#endif + +static void +registration_cb(pjsip_regc_cbparam *param) +{ + if (!param) { + RING_ERR("registration callback parameter is null"); + return; + } + + auto account = static_cast<SIPAccount *>(param->token); + if (!account) { + RING_ERR("account doesn't exist in registration callback"); + return; + } + + account->onRegister(param); +} + +SIPAccount::SIPAccount(const std::string& accountID, bool presenceEnabled) + : SIPAccountBase(accountID) + , auto_rereg_() + , credentials_() + , regc_(nullptr) + , bRegister_(false) + , registrationExpire_(MIN_REGISTRATION_TIME) + , serviceRoute_() + , cred_() + , tlsSetting_() + , ciphers_(100) + , tlsMethod_("TLSv1") + , tlsCiphers_() + , tlsServerName_(0, 0) + , tlsVerifyServer_(false) + , tlsVerifyClient_(true) + , tlsRequireClientCertificate_(true) + , tlsNegotiationTimeoutSec_("2") + , zrtpDisplaySas_(true) + , zrtpDisplaySasOnce_(false) + , zrtpHelloHash_(true) + , zrtpNotSuppWarning_(true) + , registrationStateDetailed_() + , keepAliveEnabled_(false) + , keepAliveTimer_() + , keepAliveTimerActive_(false) + , receivedParameter_("") + , rPort_(-1) + , via_addr_() + , contactBuffer_() + , contact_{contactBuffer_, 0} + , contactRewriteMethod_(2) + , allowViaRewrite_(true) + , allowContactRewrite_(1) + , contactOverwritten_(false) + , via_tp_(nullptr) + , presence_(presenceEnabled ? new SIPPresence(this) : nullptr) +{ + via_addr_.host.ptr = 0; + via_addr_.host.slen = 0; + via_addr_.port = 0; + + if (isIP2IP()) + alias_ = IP2IP_PROFILE; +} + +SIPAccount::~SIPAccount() +{ + // ensure that no registration callbacks survive past this point + destroyRegistrationInfo(); + setTransport(); + + delete presence_; +} + +std::shared_ptr<SIPCall> +SIPAccount::newIncomingCall(const std::string& from UNUSED) +{ + auto& manager = Manager::instance(); + return manager.callFactory.newCall<SIPCall, SIPAccount>(*this, manager.getNewCallID(), Call::INCOMING); +} + +template <> +std::shared_ptr<SIPCall> +SIPAccount::newOutgoingCall(const std::string& toUrl) +{ + std::string to; + std::string toUri; + int family; + + auto& manager = Manager::instance(); + auto call = manager.callFactory.newCall<SIPCall, SIPAccount>(*this, manager.getNewCallID(), Call::OUTGOING); + + if (isIP2IP()) { + bool ipv6 = false; +#if HAVE_IPV6 + ipv6 = IpAddr::isIpv6(toUrl); +#endif + to = ipv6 ? IpAddr(toUrl).toString(false, true) : toUrl; + family = ipv6 ? pj_AF_INET6() : pj_AF_INET(); + + // TODO: resolve remote host using SIPVoIPLink::resolveSrvName + std::shared_ptr<SipTransport> t = +#if HAVE_TLS + isTlsEnabled() ? link_->sipTransportBroker->getTlsTransport(tlsListener_, IpAddr(sip_utils::getHostFromUri(to))) : +#endif + transport_; + setTransport(t); + call->setTransport(t); + + RING_DBG("New %s IP to IP call to %s", ipv6?"IPv6":"IPv4", to.c_str()); + } + else { + to = toUrl; + call->setTransport(transport_); + // FIXME : for now, use the same address family as the SIP transport + family = pjsip_transport_type_get_af(getTransportType()); + + RING_DBG("UserAgent: New registered account call to %s", toUrl.c_str()); + } + + // If toUrl is not a well formatted sip URI, use account information to process it + if (toUrl.find("sip:") != std::string::npos or + toUrl.find("sips:") != std::string::npos) + toUri = toUrl; + else + toUri = getToUri(to); + call->initIceTransport(true); + + call->setIPToIP(isIP2IP()); + call->setSecure(isTlsEnabled()); + call->setPeerNumber(toUri); + call->initRecFilename(to); + + const auto localAddress = ip_utils::getInterfaceAddr(getLocalInterface(), family); + call->setCallMediaLocal(localAddress); + + IpAddr addrSdp; + if (getUPnPActive()) { + /* use UPnP addr, or published addr if its set */ + addrSdp = getPublishedSameasLocal() ? + getUPnPIpAddress() : getPublishedIpAddress(); + } else { + addrSdp = isStunEnabled() or (not getPublishedSameasLocal()) ? + getPublishedIpAddress() : localAddress; + } + + /* fallback on local address */ + if (not addrSdp) addrSdp = localAddress; + + // Building the local SDP offer + auto& sdp = call->getSDP(); + + if (getPublishedSameasLocal()) + sdp.setPublishedIP(addrSdp); + else + sdp.setPublishedIP(getPublishedAddress()); + + const bool created = sdp.createOffer( + getActiveAccountCodecInfoList(MEDIA_AUDIO), + getActiveAccountCodecInfoList(videoEnabled_ ? MEDIA_VIDEO : MEDIA_NONE), + getSrtpKeyExchange() + ); + + if (not created or not SIPStartCall(call)) + throw VoipLinkException("Could not send outgoing INVITE request for new call"); + + return call; +} + +void +SIPAccount::onTransportStateChanged(pjsip_transport_state state, const pjsip_transport_state_info *info) +{ + pj_status_t currentStatus = transportStatus_; + RING_DBG("Transport state changed to %s for account %s !", SipTransport::stateToStr(state), accountID_.c_str()); + if (!SipTransport::isAlive(transport_, state)) { + if (info) { + char err_msg[128]; + err_msg[0] = '\0'; + pj_str_t descr = pj_strerror(info->status, err_msg, sizeof(err_msg)); + transportStatus_ = info->status; + transportError_ = std::string(descr.ptr, descr.slen); + RING_ERR("Transport disconnected: %.*s", descr.slen, descr.ptr); + } + else { + // This is already the generic error used by pjsip. + transportStatus_ = PJSIP_SC_SERVICE_UNAVAILABLE; + transportError_ = ""; + } + setRegistrationState(RegistrationState::ERROR_GENERIC, PJSIP_SC_TSX_TRANSPORT_ERROR); + setTransport(); + } + else { + // The status can be '0', this is the same as OK + transportStatus_ = info && info->status ? info->status : PJSIP_SC_OK; + transportError_ = ""; + } + + // Notify the client of the new transport state + if (currentStatus != transportStatus_) + emitSignal<DRing::ConfigurationSignal::VolatileDetailsChanged>(accountID_, getVolatileAccountDetails()); +} + +void +SIPAccount::setTransport(const std::shared_ptr<SipTransport>& t) +{ + if (t == transport_) + return; + if (transport_) { + RING_DBG("Removing transport from account"); + if (regc_) + pjsip_regc_release_transport(regc_); + transport_->removeStateListener(reinterpret_cast<uintptr_t>(this)); + } + + transport_ = t; + + if (transport_) + transport_->addStateListener(reinterpret_cast<uintptr_t>(this), std::bind(&SIPAccount::onTransportStateChanged, this, std::placeholders::_1, std::placeholders::_2)); +} + +pjsip_tpselector +SIPAccount::getTransportSelector() { + if (!transport_) + return SIPVoIPLink::getTransportSelector(nullptr); + return SIPVoIPLink::getTransportSelector(transport_->get()); +} + +std::shared_ptr<Call> +SIPAccount::newOutgoingCall(const std::string& toUrl) +{ + return newOutgoingCall<SIPCall>(toUrl); +} + +bool +SIPAccount::SIPStartCall(std::shared_ptr<SIPCall>& call) +{ + // Add Ice headers to local SDP if ice transport exist + call->setupLocalSDPFromIce(); + + std::string toUri(call->getPeerNumber()); // expecting a fully well formed sip uri + + pj_str_t pjTo = pj_str((char*) toUri.c_str()); + + // Create the from header + std::string from(getFromUri()); + pj_str_t pjFrom = pj_str((char*) from.c_str()); + + auto transport = call->getTransport(); + if (!transport) { + RING_ERR("Unable to start call without transport"); + return false; + } + + pj_str_t pjContact = getContactHeader(transport->get()); + const std::string debugContactHeader(pj_strbuf(&pjContact), pj_strlen(&pjContact)); + RING_DBG("contact header: %s / %s -> %s", + debugContactHeader.c_str(), from.c_str(), toUri.c_str()); + + pjsip_dialog *dialog = NULL; + + if (pjsip_dlg_create_uac(pjsip_ua_instance(), &pjFrom, &pjContact, &pjTo, NULL, &dialog) != PJ_SUCCESS) { + RING_ERR("Unable to create SIP dialogs for user agent client when " + "calling %s", toUri.c_str()); + return false; + } + + pj_str_t subj_hdr_name = CONST_PJ_STR("Subject"); + pjsip_hdr* subj_hdr = (pjsip_hdr*) pjsip_parse_hdr(dialog->pool, &subj_hdr_name, (char *) "Phone call", 10, NULL); + + pj_list_push_back(&dialog->inv_hdr, subj_hdr); + + pjsip_inv_session* inv = nullptr; + if (pjsip_inv_create_uac(dialog, call->getSDP().getLocalSdpSession(), 0, &inv) != PJ_SUCCESS) { + RING_ERR("Unable to create invite session for user agent client"); + return false; + } + + if (!inv) { + RING_ERR("Call invite is not initialized"); + return PJ_FALSE; + } + + call->inv.reset(inv); + + updateDialogViaSentBy(dialog); + + if (hasServiceRoute()) + pjsip_dlg_set_route_set(dialog, sip_utils::createRouteSet(getServiceRoute(), call->inv->pool)); + + if (hasCredentials() and pjsip_auth_clt_set_credentials(&dialog->auth_sess, getCredentialCount(), getCredInfo()) != PJ_SUCCESS) { + RING_ERR("Could not initialize credentials for invite session authentication"); + return false; + } + + call->inv->mod_data[link_->getModId()] = call.get(); + + pjsip_tx_data *tdata; + + if (pjsip_inv_invite(call->inv.get(), &tdata) != PJ_SUCCESS) { + RING_ERR("Could not initialize invite messager for this call"); + return false; + } + + const pjsip_tpselector tp_sel = link_->getTransportSelector(transport->get()); + if (pjsip_dlg_set_transport(dialog, &tp_sel) != PJ_SUCCESS) { + RING_ERR("Unable to associate transport for invite session dialog"); + return false; + } + + if (pjsip_inv_send_msg(call->inv.get(), tdata) != PJ_SUCCESS) { + call->inv.reset(); + RING_ERR("Unable to send invite message for this call"); + return false; + } + + call->setConnectionState(Call::PROGRESSING); + call->setState(Call::ACTIVE); + + return true; +} + +void SIPAccount::serialize(YAML::Emitter &out) +{ + out << YAML::BeginMap; + SIPAccountBase::serialize(out); + + out << YAML::Key << Conf::PORT_KEY << YAML::Value << localPort_; + + // each credential is a map, and we can have multiple credentials + out << YAML::Key << Conf::CRED_KEY << YAML::Value << credentials_; + out << YAML::Key << Conf::KEEP_ALIVE_ENABLED << YAML::Value << keepAliveEnabled_; + + out << YAML::Key << PRESENCE_MODULE_ENABLED_KEY << YAML::Value << (presence_ and presence_->isEnabled()); + out << YAML::Key << Conf::PRESENCE_PUBLISH_SUPPORTED_KEY << YAML::Value << (presence_ and presence_->isSupported(PRESENCE_FUNCTION_PUBLISH)); + out << YAML::Key << Conf::PRESENCE_SUBSCRIBE_SUPPORTED_KEY << YAML::Value << (presence_ and presence_->isSupported(PRESENCE_FUNCTION_SUBSCRIBE)); + + out << YAML::Key << Preferences::REGISTRATION_EXPIRE_KEY << YAML::Value << registrationExpire_; + out << YAML::Key << Conf::SERVICE_ROUTE_KEY << YAML::Value << serviceRoute_; + + out << YAML::Key << Conf::STUN_ENABLED_KEY << YAML::Value << stunEnabled_; + out << YAML::Key << Conf::STUN_SERVER_KEY << YAML::Value << stunServer_; + + // tls submap + out << YAML::Key << Conf::TLS_KEY << YAML::Value << YAML::BeginMap; + SIPAccountBase::serializeTls(out); + out << YAML::Key << Conf::TLS_ENABLE_KEY << YAML::Value << tlsEnable_; + out << YAML::Key << Conf::TLS_PORT_KEY << YAML::Value << tlsListenerPort_; + out << YAML::Key << Conf::VERIFY_CLIENT_KEY << YAML::Value << tlsVerifyClient_; + out << YAML::Key << Conf::VERIFY_SERVER_KEY << YAML::Value << tlsVerifyServer_; + out << YAML::Key << Conf::REQUIRE_CERTIF_KEY << YAML::Value << tlsRequireClientCertificate_; + out << YAML::Key << Conf::TIMEOUT_KEY << YAML::Value << tlsNegotiationTimeoutSec_; + out << YAML::Key << Conf::CIPHERS_KEY << YAML::Value << tlsCiphers_; + out << YAML::Key << Conf::METHOD_KEY << YAML::Value << tlsMethod_; + out << YAML::Key << Conf::SERVER_KEY << YAML::Value << tlsServerName_; + out << YAML::EndMap; + + // srtp submap + out << YAML::Key << Conf::SRTP_KEY << YAML::Value << YAML::BeginMap; + out << YAML::Key << Conf::KEY_EXCHANGE_KEY << YAML::Value << sip_utils::getKeyExchangeName(srtpKeyExchange_); + out << YAML::Key << Conf::RTP_FALLBACK_KEY << YAML::Value << srtpFallback_; + out << YAML::EndMap; + + // zrtp submap + out << YAML::Key << Conf::ZRTP_KEY << YAML::Value << YAML::BeginMap; + out << YAML::Key << Conf::DISPLAY_SAS_KEY << YAML::Value << zrtpDisplaySas_; + out << YAML::Key << Conf::DISPLAY_SAS_ONCE_KEY << YAML::Value << zrtpDisplaySasOnce_; + out << YAML::Key << Conf::HELLO_HASH_ENABLED_KEY << YAML::Value << zrtpHelloHash_; + out << YAML::Key << Conf::NOT_SUPP_WARNING_KEY << YAML::Value << zrtpNotSuppWarning_; + out << YAML::EndMap; + + out << YAML::EndMap; +} + +void SIPAccount::usePublishedAddressPortInVIA() +{ + via_addr_.host.ptr = (char *) publishedIpAddress_.c_str(); + via_addr_.host.slen = publishedIpAddress_.size(); + via_addr_.port = publishedPort_; +} + +void SIPAccount::useUPnPAddressPortInVIA() +{ + via_addr_.host.ptr = (char *) getUPnPIpAddress().toString().c_str(); + via_addr_.host.slen = getUPnPIpAddress().toString().size(); + via_addr_.port = publishedPortUsed_; +} + +template <typename T> +static void +validate(std::string &member, const std::string ¶m, const T& valid) +{ + const auto begin = std::begin(valid); + const auto end = std::end(valid); + if (find(begin, end, param) != end) + member = param; + else + RING_ERR("Invalid parameter \"%s\"", param.c_str()); +} + +void SIPAccount::unserialize(const YAML::Node &node) +{ + SIPAccountBase::unserialize(node); + if (not publishedSameasLocal_) + usePublishedAddressPortInVIA(); + + int port = sip_utils::DEFAULT_SIP_PORT; + parseValue(node, Conf::PORT_KEY, port); + localPort_ = port; + + if (not isIP2IP()) parseValue(node, Preferences::REGISTRATION_EXPIRE_KEY, registrationExpire_); + + if (not isIP2IP()) parseValue(node, Conf::KEEP_ALIVE_ENABLED, keepAliveEnabled_); + + bool presEnabled = false; + parseValue(node, PRESENCE_MODULE_ENABLED_KEY, presEnabled); + enablePresence(presEnabled); + bool publishSupported = false; + parseValue(node, Conf::PRESENCE_PUBLISH_SUPPORTED_KEY, publishSupported); + bool subscribeSupported = false; + parseValue(node, Conf::PRESENCE_SUBSCRIBE_SUPPORTED_KEY, subscribeSupported); + if (presence_) { + presence_->support(PRESENCE_FUNCTION_PUBLISH, publishSupported); + presence_->support(PRESENCE_FUNCTION_SUBSCRIBE, subscribeSupported); + } + + if (not isIP2IP()) parseValue(node, Conf::SERVICE_ROUTE_KEY, serviceRoute_); + + // stun enabled + if (not isIP2IP()) parseValue(node, Conf::STUN_ENABLED_KEY, stunEnabled_); + if (not isIP2IP()) parseValue(node, Conf::STUN_SERVER_KEY, stunServer_); + + // Init stun server name with default server name + stunServerName_ = pj_str((char*) stunServer_.data()); + + const auto &credsNode = node[Conf::CRED_KEY]; + const auto creds = parseVectorMap(credsNode, {Conf::CONFIG_ACCOUNT_PASSWORD, + Conf::CONFIG_ACCOUNT_REALM, Conf::CONFIG_ACCOUNT_USERNAME}); + setCredentials(creds); + + // get zrtp submap + const auto &zrtpMap = node[Conf::ZRTP_KEY]; + + parseValue(zrtpMap, Conf::DISPLAY_SAS_KEY, zrtpDisplaySas_); + parseValue(zrtpMap, Conf::DISPLAY_SAS_ONCE_KEY, zrtpDisplaySasOnce_); + parseValue(zrtpMap, Conf::HELLO_HASH_ENABLED_KEY, zrtpHelloHash_); + parseValue(zrtpMap, Conf::NOT_SUPP_WARNING_KEY, zrtpNotSuppWarning_); + + // get tls submap + const auto &tlsMap = node[Conf::TLS_KEY]; + + parseValue(tlsMap, Conf::TLS_ENABLE_KEY, tlsEnable_); + parseValue(tlsMap, Conf::TLS_PORT_KEY, tlsListenerPort_); + parseValue(tlsMap, Conf::CIPHERS_KEY, tlsCiphers_); + + std::string tmpMethod(tlsMethod_); + parseValue(tlsMap, Conf::METHOD_KEY, tmpMethod); + validate(tlsMethod_, tmpMethod, VALID_TLS_METHODS); + + parseValue(tlsMap, Conf::SERVER_KEY, tlsServerName_); + parseValue(tlsMap, Conf::REQUIRE_CERTIF_KEY, tlsRequireClientCertificate_); + parseValue(tlsMap, Conf::VERIFY_CLIENT_KEY, tlsVerifyClient_); + parseValue(tlsMap, Conf::VERIFY_SERVER_KEY, tlsVerifyServer_); + // FIXME + parseValue(tlsMap, Conf::TIMEOUT_KEY, tlsNegotiationTimeoutSec_); + + // get srtp submap + const auto &srtpMap = node[Conf::SRTP_KEY]; + std::string tmpKey; + parseValue(srtpMap, Conf::KEY_EXCHANGE_KEY, tmpKey); + srtpKeyExchange_ = sip_utils::getKeyExchangeProtocol(tmpKey.c_str()); + parseValue(srtpMap, Conf::RTP_FALLBACK_KEY, srtpFallback_); +} + +template <typename T> +static void +parseInt(const std::map<std::string, std::string> &details, const char *key, T &i) +{ + const auto iter = details.find(key); + if (iter == details.end()) { + RING_ERR("Couldn't find key %s", key); + return; + } + i = atoi(iter->second.c_str()); +} + +void SIPAccount::setAccountDetails(const std::map<std::string, std::string> &details) +{ + SIPAccountBase::setAccountDetails(details); + + parseInt(details, Conf::CONFIG_LOCAL_PORT, localPort_); + + // SIP specific account settings + parseString(details, Conf::CONFIG_ACCOUNT_ROUTESET, serviceRoute_); + + if (not publishedSameasLocal_) + usePublishedAddressPortInVIA(); + + parseString(details, Conf::CONFIG_STUN_SERVER, stunServer_); + parseBool(details, Conf::CONFIG_STUN_ENABLE, stunEnabled_); + parseInt(details, Conf::CONFIG_ACCOUNT_REGISTRATION_EXPIRE, registrationExpire_); + + if (registrationExpire_ < MIN_REGISTRATION_TIME) + registrationExpire_ = MIN_REGISTRATION_TIME; + + parseBool(details, Conf::CONFIG_KEEP_ALIVE_ENABLED, keepAliveEnabled_); + bool presenceEnabled = false; + parseBool(details, Conf::CONFIG_PRESENCE_ENABLED, presenceEnabled); + enablePresence(presenceEnabled); + + // srtp settings + parseBool(details, Conf::CONFIG_ZRTP_DISPLAY_SAS, zrtpDisplaySas_); + parseBool(details, Conf::CONFIG_ZRTP_DISPLAY_SAS_ONCE, zrtpDisplaySasOnce_); + parseBool(details, Conf::CONFIG_ZRTP_NOT_SUPP_WARNING, zrtpNotSuppWarning_); + parseBool(details, Conf::CONFIG_ZRTP_HELLO_HASH, zrtpHelloHash_); + + // TLS settings + parseBool(details, Conf::CONFIG_TLS_ENABLE, tlsEnable_); + parseInt(details, Conf::CONFIG_TLS_LISTENER_PORT, tlsListenerPort_); + auto iter = details.find(Conf::CONFIG_TLS_METHOD); + if (iter != details.end()) + validate(tlsMethod_, iter->second, VALID_TLS_METHODS); + parseString(details, Conf::CONFIG_TLS_CIPHERS, tlsCiphers_); + parseString(details, Conf::CONFIG_TLS_SERVER_NAME, tlsServerName_); + parseBool(details, Conf::CONFIG_TLS_VERIFY_SERVER, tlsVerifyServer_); + parseBool(details, Conf::CONFIG_TLS_VERIFY_CLIENT, tlsVerifyClient_); + parseBool(details, Conf::CONFIG_TLS_REQUIRE_CLIENT_CERTIFICATE, tlsRequireClientCertificate_); + parseString(details, Conf::CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC, tlsNegotiationTimeoutSec_); + parseBool(details, Conf::CONFIG_TLS_VERIFY_SERVER, tlsVerifyServer_); + parseBool(details, Conf::CONFIG_TLS_VERIFY_CLIENT, tlsVerifyClient_); + parseBool(details, Conf::CONFIG_TLS_REQUIRE_CLIENT_CERTIFICATE, tlsRequireClientCertificate_); + parseString(details, Conf::CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC, tlsNegotiationTimeoutSec_); + + // srtp settings + parseBool(details, Conf::CONFIG_SRTP_RTP_FALLBACK, srtpFallback_); + iter = details.find(Conf::CONFIG_SRTP_KEY_EXCHANGE); + if (iter != details.end()) + srtpKeyExchange_ = sip_utils::getKeyExchangeProtocol(iter->second.c_str()); + + if (credentials_.empty()) { // credentials not set, construct 1 entry + RING_WARN("No credentials set, inferring them..."); + std::vector<std::map<std::string, std::string> > v; + std::map<std::string, std::string> map; + map[Conf::CONFIG_ACCOUNT_USERNAME] = username_; + parseString(details, Conf::CONFIG_ACCOUNT_PASSWORD, map[Conf::CONFIG_ACCOUNT_PASSWORD]); + map[Conf::CONFIG_ACCOUNT_REALM] = "*"; + v.push_back(map); + setCredentials(v); + } +} + +static std::string retrievePassword(const std::map<std::string, std::string>& map, const std::string &username) +{ + std::map<std::string, std::string>::const_iterator map_iter_username; + std::map<std::string, std::string>::const_iterator map_iter_password; + map_iter_username = map.find(Conf::CONFIG_ACCOUNT_USERNAME); + + if (map_iter_username != map.end()) { + if (map_iter_username->second == username) { + map_iter_password = map.find(Conf::CONFIG_ACCOUNT_PASSWORD); + + if (map_iter_password != map.end()) { + return map_iter_password->second; + } + } + } + + return ""; +} + +std::map<std::string, std::string> +SIPAccount::getAccountDetails() const +{ + auto a = SIPAccountBase::getAccountDetails(); + + a[Conf::CONFIG_ACCOUNT_PASSWORD] = ""; + if (hasCredentials()) { + for (const auto &vect_item : credentials_) { + const std::string password = retrievePassword(vect_item, username_); + if (not password.empty()) + a.emplace(Conf::CONFIG_ACCOUNT_PASSWORD, password); + } + } + + a.emplace(Conf::CONFIG_LOCAL_PORT, ring::to_string(localPort_)); + a.emplace(Conf::CONFIG_ACCOUNT_ROUTESET, serviceRoute_); + a.emplace(Conf::CONFIG_ACCOUNT_REGISTRATION_EXPIRE, ring::to_string(registrationExpire_)); + a.emplace(Conf::CONFIG_STUN_ENABLE, stunEnabled_ ? TRUE_STR : FALSE_STR); + a.emplace(Conf::CONFIG_STUN_SERVER, stunServer_); + a.emplace(Conf::CONFIG_KEEP_ALIVE_ENABLED, keepAliveEnabled_ ? TRUE_STR : FALSE_STR); + + a.emplace(Conf::CONFIG_PRESENCE_ENABLED, presence_ and presence_->isEnabled()? TRUE_STR : FALSE_STR); + a.emplace(Conf::CONFIG_PRESENCE_PUBLISH_SUPPORTED, presence_ and presence_->isSupported(PRESENCE_FUNCTION_PUBLISH)? TRUE_STR : FALSE_STR); + a.emplace(Conf::CONFIG_PRESENCE_SUBSCRIBE_SUPPORTED, presence_ and presence_->isSupported(PRESENCE_FUNCTION_SUBSCRIBE)? TRUE_STR : FALSE_STR); + + auto tlsSettings(getTlsSettings()); + a.insert(tlsSettings.begin(), tlsSettings.end()); + + a.emplace(Conf::CONFIG_SRTP_KEY_EXCHANGE, sip_utils::getKeyExchangeName(srtpKeyExchange_)); + a.emplace(Conf::CONFIG_SRTP_ENABLE, isSrtpEnabled() ? TRUE_STR : FALSE_STR); + a.emplace(Conf::CONFIG_SRTP_RTP_FALLBACK, srtpFallback_ ? TRUE_STR : FALSE_STR); + a.emplace(Conf::CONFIG_ZRTP_DISPLAY_SAS, zrtpDisplaySas_ ? TRUE_STR : FALSE_STR); + a.emplace(Conf::CONFIG_ZRTP_DISPLAY_SAS_ONCE, zrtpDisplaySasOnce_ ? TRUE_STR : FALSE_STR); + a.emplace(Conf::CONFIG_ZRTP_HELLO_HASH, zrtpHelloHash_ ? TRUE_STR : FALSE_STR); + a.emplace(Conf::CONFIG_ZRTP_NOT_SUPP_WARNING, zrtpNotSuppWarning_ ? TRUE_STR : FALSE_STR); + return a; +} + +std::map<std::string, std::string> +SIPAccount::getVolatileAccountDetails() const +{ + auto a = SIPAccountBase::getVolatileAccountDetails(); + a.emplace(Conf::CONFIG_ACCOUNT_REGISTRATION_STATE_CODE, ring::to_string(registrationStateDetailed_.first)); + a.emplace(Conf::CONFIG_ACCOUNT_REGISTRATION_STATE_DESC, registrationStateDetailed_.second); + + if (presence_) { + a.emplace(Conf::CONFIG_PRESENCE_STATUS, presence_->isOnline() ? TRUE_STR : FALSE_STR); + a.emplace(Conf::CONFIG_PRESENCE_NOTE, presence_->getNote()); + } + +#if HAVE_TLS + //TODO +#endif + + return a; +} + +bool SIPAccount::mapPortUPnP() +{ + if (getUPnPActive()) { + /* create port mapping from published port to local port to the local IP + * note that since different accounts can use the same port, + * it may already be open, thats OK + * + * if the desired port is taken by another client, then it will try to map + * a different port, if succesfull, then we have to use that port for SIP + */ + uint16_t port_used; + if (upnp_->addAnyMapping(publishedPort_, localPort_, ring::upnp::PortType::UDP, false, false, &port_used)) { + if (port_used != publishedPort_) + RING_DBG("UPnP could not map published port %u for SIP, using %u instead", publishedPort_, port_used); + publishedPortUsed_ = port_used; + return true; + } else { + /* failed to map any port */ + return false; + } + } else { + /* not using UPnP, so return true */ + return true; + } +} + +void SIPAccount::doRegister() +{ + if (not isEnabled()) { + RING_WARN("Account must be enabled to register, ignoring"); + return; + } + + RING_DBG("doRegister %s", hostname_.c_str()); + + /* if UPnP is enabled, then wait for IGD to complete registration */ + if ( upnpEnabled_ ) { + RING_DBG("UPnP: waiting for IGD to register SIP account"); + setRegistrationState(RegistrationState::TRYING); + auto shared = shared_from_this(); + std::thread{ [shared] { + /* We have to register the external thread so it could access the pjsip frameworks */ + if (!pj_thread_is_registered()) { +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) + static thread_local pj_thread_desc desc; + static thread_local pj_thread_t *this_thread; +#else + static __thread pj_thread_desc desc; + static __thread pj_thread_t *this_thread; +#endif + RING_DBG("Registering thread with pjlib"); + pj_thread_register(NULL, desc, &this_thread); + } + + auto this_ = std::static_pointer_cast<SIPAccount>(shared).get(); + if ( not this_->mapPortUPnP()) + RING_WARN("UPnP: Could not successfully map SIP port with UPnP, continuing with account registration anyways."); + this_->doRegister1_(); + }}.detach(); + } else + doRegister1_(); +} + +void SIPAccount::doRegister1_() +{ + if (hostname_.empty() || isIP2IP()) { + doRegister2_(); + return; + } + + auto shared = shared_from_this(); + link_->resolveSrvName( + hostname_, + tlsEnable_ ? PJSIP_TRANSPORT_TLS : PJSIP_TRANSPORT_UDP, + [shared](std::vector<IpAddr> host_ips) { + auto this_ = std::static_pointer_cast<SIPAccount>(shared).get(); + if (host_ips.empty()) { + RING_ERR("Can't resolve hostname for registration."); + this_->setRegistrationState(RegistrationState::ERROR_GENERIC, PJSIP_SC_NOT_FOUND); + return; + } + this_->hostIp_ = host_ips[0]; + this_->doRegister2_(); + } + ); +} + +void SIPAccount::doRegister2_() +{ + bool ipv6 = false; + if (isIP2IP()) { + RING_DBG("doRegister isIP2IP."); +#if HAVE_IPV6 + ipv6 = ip_utils::getInterfaceAddr(interface_).isIpv6(); +#endif + } else if (!hostIp_) { + setRegistrationState(RegistrationState::ERROR_GENERIC, PJSIP_SC_NOT_FOUND); + RING_ERR("Hostname not resolved."); + return; + } +#if HAVE_IPV6 + else + ipv6 = hostIp_.isIpv6(); +#endif + +#if HAVE_TLS + // Init TLS settings if the user wants to use TLS + if (tlsEnable_) { + RING_DBG("TLS is enabled for account %s", accountID_.c_str()); + + // Dropping current calls already using the transport is currently required + // with TLS. + freeAccount(); + + transportType_ = ipv6 ? PJSIP_TRANSPORT_TLS6 : PJSIP_TRANSPORT_TLS; + initTlsConfiguration(); + + if (!tlsListener_) { + tlsListener_ = link_->sipTransportBroker->getTlsListener( + SipTransportDescr {getTransportType(), getTlsListenerPort(), getLocalInterface()}, + getTlsSetting()); + if (!tlsListener_) { + setRegistrationState(RegistrationState::ERROR_GENERIC); + RING_ERR("Error creating TLS listener."); + return; + } + } + } else +#endif + { + tlsListener_.reset(); + transportType_ = ipv6 ? PJSIP_TRANSPORT_UDP6 : PJSIP_TRANSPORT_UDP; + } + + // Init STUN settings for this account if the user selected it + if (stunEnabled_) { + transportType_ = PJSIP_TRANSPORT_START_OTHER; + initStunConfiguration(); + } else { + stunServerName_ = pj_str((char*) stunServer_.c_str()); + } + + // In our definition of the ip2ip profile (aka Direct IP Calls), + // no registration should be performed + if (isIP2IP()) { + // If we use Tls for IP2IP, transports will be created on connection. + if (!tlsEnable_) + setTransport(link_->sipTransportBroker->getUdpTransport( + SipTransportDescr { getTransportType(), getLocalPort(), getLocalInterface() } + )); + return; + } + + try { + RING_WARN("Creating transport"); + transport_.reset(); +#if HAVE_TLS + if (isTlsEnabled()) { + setTransport(link_->sipTransportBroker->getTlsTransport(tlsListener_, hostIp_)); + } else +#endif + { + setTransport(link_->sipTransportBroker->getUdpTransport( + SipTransportDescr { getTransportType(), getLocalPort(), getLocalInterface() } + )); + } + if (!transport_) + throw VoipLinkException("Can't create transport"); + + sendRegister(); + } catch (const VoipLinkException &e) { + RING_ERR("%s", e.what()); + setRegistrationState(RegistrationState::ERROR_GENERIC); + return; + } + + if (presence_ and presence_->isEnabled()) { + presence_->subscribeClient(getFromUri(), true); // self presence subscription + presence_->sendPresence(true, ""); // try to publish whatever the status is. + } +} + +void SIPAccount::doUnregister(std::function<void(bool)> released_cb) +{ + tlsListener_.reset(); + if (isIP2IP()) { + if (released_cb) + released_cb(false); + return; + } + + try { + sendUnregister(); + } catch (const VoipLinkException &e) { + RING_ERR("doUnregister %s", e.what()); + } + + // remove the transport from the account + if (transport_) + setTransport(); + if (released_cb) + released_cb(true); + + /* RING_DBG("UPnP: removing port mapping for SIP account."); */ + upnp_->removeMappings(); +} + +void SIPAccount::startKeepAliveTimer() +{ + if (isTlsEnabled()) + return; + + if (isIP2IP()) + return; + + if (keepAliveTimerActive_) + return; + + RING_DBG("Start keep alive timer for account %s", getAccountID().c_str()); + + // make sure here we have an entirely new timer + memset(&keepAliveTimer_, 0, sizeof(pj_timer_entry)); + + pj_time_val keepAliveDelay_; + keepAliveTimer_.cb = &SIPAccount::keepAliveRegistrationCb; + keepAliveTimer_.user_data = this; + keepAliveTimer_.id = timerIdDist_(rand_); + + // expiration may be undetermined during the first registration request + if (registrationExpire_ == 0) { + RING_DBG("Registration Expire: 0, taking 60 instead"); + keepAliveDelay_.sec = 3600; + } else { + RING_DBG("Registration Expire: %d", registrationExpire_); + keepAliveDelay_.sec = registrationExpire_ + MIN_REGISTRATION_TIME; + } + + keepAliveDelay_.msec = 0; + + keepAliveTimerActive_ = true; + + link_->registerKeepAliveTimer(keepAliveTimer_, keepAliveDelay_); +} + +void SIPAccount::stopKeepAliveTimer() +{ + if (keepAliveTimerActive_) { + RING_DBG("Stop keep alive timer %d for account %s", keepAliveTimer_.id, getAccountID().c_str()); + keepAliveTimerActive_ = false; + link_->cancelKeepAliveTimer(keepAliveTimer_); + } +} + +void +SIPAccount::sendRegister() +{ + if (not isEnabled()) { + RING_WARN("Account must be enabled to register, ignoring"); + return; + } + + setRegister(true); + setRegistrationState(RegistrationState::TRYING); + + pjsip_regc *regc = nullptr; + if (pjsip_regc_create(link_->getEndpoint(), (void *) this, ®istration_cb, ®c) != PJ_SUCCESS) + throw VoipLinkException("UserAgent: Unable to create regc structure."); + + std::string srvUri(getServerUri()); + pj_str_t pjSrv = pj_str((char*) srvUri.c_str()); + + // Generate the FROM header + std::string from(getFromUri()); + pj_str_t pjFrom = pj_str((char*) from.c_str()); + + // Get the received header + std::string received(getReceivedParameter()); + + // Get the contact header + const pj_str_t pjContact(getContactHeader()); + + if (transport_) { + if (getUPnPActive() or not getPublishedSameasLocal() or (not received.empty() and received != getPublishedAddress())) { + pjsip_host_port *via = getViaAddr(); + RING_DBG("Setting VIA sent-by to %.*s:%d", via->host.slen, via->host.ptr, via->port); + + if (pjsip_regc_set_via_sent_by(regc, via, transport_->get()) != PJ_SUCCESS) + throw VoipLinkException("Unable to set the \"sent-by\" field"); + } else if (isStunEnabled()) { + if (pjsip_regc_set_via_sent_by(regc, getViaAddr(), transport_->get()) != PJ_SUCCESS) + throw VoipLinkException("Unable to set the \"sent-by\" field"); + } + } + + pj_status_t status; + + //RING_DBG("pjsip_regc_init from:%s, srv:%s, contact:%s", from.c_str(), srvUri.c_str(), std::string(pj_strbuf(&pjContact), pj_strlen(&pjContact)).c_str()); + if ((status = pjsip_regc_init(regc, &pjSrv, &pjFrom, &pjFrom, 1, &pjContact, getRegistrationExpire())) != PJ_SUCCESS) { + sip_utils::sip_strerror(status); + throw VoipLinkException("Unable to initialize account registration structure"); + } + + if (hasServiceRoute()) + pjsip_regc_set_route_set(regc, sip_utils::createRouteSet(getServiceRoute(), link_->getPool())); + + pjsip_regc_set_credentials(regc, getCredentialCount(), getCredInfo()); + + pjsip_hdr hdr_list; + pj_list_init(&hdr_list); + std::string useragent(getUserAgentName()); + pj_str_t pJuseragent = pj_str((char*) useragent.c_str()); + const pj_str_t STR_USER_AGENT = CONST_PJ_STR("User-Agent"); + + pjsip_generic_string_hdr *h = pjsip_generic_string_hdr_create(link_->getPool(), &STR_USER_AGENT, &pJuseragent); + pj_list_push_back(&hdr_list, (pjsip_hdr*) h); + pjsip_regc_add_headers(regc, &hdr_list); + pjsip_tx_data *tdata; + + if (pjsip_regc_register(regc, PJ_TRUE, &tdata) != PJ_SUCCESS) + throw VoipLinkException("Unable to initialize transaction data for account registration"); + + const pjsip_tpselector tp_sel = getTransportSelector(); + if (pjsip_regc_set_transport(regc, &tp_sel) != PJ_SUCCESS) + throw VoipLinkException("Unable to set transport"); + + // pjsip_regc_send increment the transport ref count by one, + if ((status = pjsip_regc_send(regc, tdata)) != PJ_SUCCESS) { + sip_utils::sip_strerror(status); + throw VoipLinkException("Unable to send account registration request"); + } + + setRegistrationInfo(regc); +} + +void +SIPAccount::onRegister(pjsip_regc_cbparam *param) +{ + if (param->regc != getRegistrationInfo()) + return; + + if (param->status != PJ_SUCCESS) { + RING_ERR("SIP registration error %d", param->status); + destroyRegistrationInfo(); + stopKeepAliveTimer(); + setRegistrationState(RegistrationState::ERROR_GENERIC, param->code); + } else if (param->code < 0 || param->code >= 300) { + RING_ERR("SIP registration failed, status=%d (%.*s)", + param->code, (int)param->reason.slen, param->reason.ptr); + destroyRegistrationInfo(); + stopKeepAliveTimer(); + switch (param->code) { + case PJSIP_SC_FORBIDDEN: + setRegistrationState(RegistrationState::ERROR_AUTH, param->code); + break; + case PJSIP_SC_NOT_FOUND: + setRegistrationState(RegistrationState::ERROR_HOST, param->code); + break; + case PJSIP_SC_REQUEST_TIMEOUT: + setRegistrationState(RegistrationState::ERROR_HOST, param->code); + break; + case PJSIP_SC_SERVICE_UNAVAILABLE: + setRegistrationState(RegistrationState::ERROR_SERVICE_UNAVAILABLE, param->code); + break; + default: + setRegistrationState(RegistrationState::ERROR_GENERIC, param->code); + } + } else if (PJSIP_IS_STATUS_IN_CLASS(param->code, 200)) { + + // Update auto registration flag + resetAutoRegistration(); + + if (param->expiration < 1) { + destroyRegistrationInfo(); + /* Stop keep-alive timer if any. */ + stopKeepAliveTimer(); + RING_DBG("Unregistration success"); + setRegistrationState(RegistrationState::UNREGISTERED, param->code); + } else { + /* TODO Check and update SIP outbound status first, since the result + * will determine if we should update re-registration + */ + // update_rfc5626_status(acc, param->rdata); + + if (checkNATAddress(param, link_->getPool())) + RING_WARN("Contact overwritten"); + + /* TODO Check and update Service-Route header */ + if (hasServiceRoute()) + pjsip_regc_set_route_set(param->regc, sip_utils::createRouteSet(getServiceRoute(), link_->getPool())); + + // start the periodic registration request based on Expire header + // account determines itself if a keep alive is required + if (isKeepAliveEnabled()) + startKeepAliveTimer(); + + setRegistrationState(RegistrationState::REGISTERED, param->code); + } + } + + /* Check if we need to auto retry registration. Basically, registration + * failure codes triggering auto-retry are those of temporal failures + * considered to be recoverable in relatively short term. + */ + switch (param->code) { + case PJSIP_SC_REQUEST_TIMEOUT: + case PJSIP_SC_INTERNAL_SERVER_ERROR: + case PJSIP_SC_BAD_GATEWAY: + case PJSIP_SC_SERVICE_UNAVAILABLE: + case PJSIP_SC_SERVER_TIMEOUT: + scheduleReregistration(link_->getEndpoint()); + break; + + default: + /* Global failure */ + if (PJSIP_IS_STATUS_IN_CLASS(param->code, 600)) + scheduleReregistration(link_->getEndpoint()); + } + setRegistrationExpire(param->expiration); +} + +void +SIPAccount::sendUnregister() +{ + // This may occurs if account failed to register and is in state INVALID + if (!isRegistered()) { + setRegistrationState(RegistrationState::UNREGISTERED); + return; + } + + // Make sure to cancel any ongoing timers before unregister + stopKeepAliveTimer(); + + pjsip_regc *regc = getRegistrationInfo(); + if (!regc) + throw VoipLinkException("Registration structure is NULL"); + + pjsip_tx_data *tdata = nullptr; + if (pjsip_regc_unregister(regc, &tdata) != PJ_SUCCESS) + throw VoipLinkException("Unable to unregister sip account"); + + pj_status_t status; + if ((status = pjsip_regc_send(regc, tdata)) != PJ_SUCCESS) { + sip_utils::sip_strerror(status); + throw VoipLinkException("Unable to send request to unregister sip account"); + } + + setRegister(false); +} + +#if HAVE_TLS +pjsip_ssl_method SIPAccount::sslMethodStringToPjEnum(const std::string& method) +{ + if (method == "Default") + return PJSIP_SSL_UNSPECIFIED_METHOD; + + if (method == "TLSv1") + return PJSIP_TLSV1_METHOD; + + if (method == "SSLv3") + return PJSIP_SSLV3_METHOD; + + if (method == "SSLv23") + return PJSIP_SSLV23_METHOD; + + return PJSIP_SSL_UNSPECIFIED_METHOD; +} + +/** + * PJSIP aborts if our cipher list exceeds 1000 characters + */ +void SIPAccount::trimCiphers() +{ + size_t sum = 0; + unsigned count = 0; + static const size_t MAX_CIPHERS_STRLEN = 1000; + for (const auto &item : ciphers_) { + sum += strlen(pj_ssl_cipher_name(item)); + if (sum > MAX_CIPHERS_STRLEN) + break; + ++count; + } + ciphers_.resize(count); +} + +void SIPAccount::initTlsConfiguration() +{ + pjsip_tls_setting_default(&tlsSetting_); + tlsSetting_.method = sslMethodStringToPjEnum(tlsMethod_); + + // Determine the cipher list supported on this machine + CipherArray avail_ciphers(256); + unsigned cipherNum = avail_ciphers.size(); + if (pj_ssl_cipher_get_availables(&avail_ciphers.front(), &cipherNum) != PJ_SUCCESS) + RING_ERR("Could not determine cipher list on this system"); + avail_ciphers.resize(cipherNum); + + ciphers_.clear(); +#if PJ_VERSION_NUM > (2 << 24 | 2 << 16) + if (not tlsCiphers_.empty()) { + std::stringstream ss(tlsCiphers_); + std::string item; + while (std::getline(ss, item, ' ')) { + if (item.empty()) continue; + auto item_cid = pj_ssl_cipher_id(item.c_str()); + if (item_cid != PJ_TLS_UNKNOWN_CIPHER) { + RING_WARN("Valid cipher: %s", item.c_str()); + ciphers_.push_back(item_cid); + } + else + RING_ERR("Invalid cipher: %s", item.c_str()); + } + } +#endif + if (ciphers_.empty()) { + ciphers_ = (tlsSetting_.method == PJSIP_TLSV1_METHOD) ? TLSv1_DEFAULT_CIPHER_LIST : ( + (tlsSetting_.method == PJSIP_SSLV3_METHOD) ? SSLv3_DEFAULT_CIPHER_LIST : ( + (tlsSetting_.method == PJSIP_SSLV23_METHOD) ? SSLv23_DEFAULT_CIPHER_LIST : CipherArray {} )); + } + ciphers_.erase(std::remove_if(ciphers_.begin(), ciphers_.end(), [&](pj_ssl_cipher c) { + return std::find(avail_ciphers.cbegin(), avail_ciphers.cend(), c) == avail_ciphers.cend(); + }), ciphers_.end()); + + trimCiphers(); + + pj_cstr(&tlsSetting_.ca_list_file, tlsCaListFile_.c_str()); + pj_cstr(&tlsSetting_.cert_file, tlsCertificateFile_.c_str()); + pj_cstr(&tlsSetting_.privkey_file, tlsPrivateKeyFile_.c_str()); + pj_cstr(&tlsSetting_.password, tlsPassword_.c_str()); + + RING_DBG("Using %u ciphers", ciphers_.size()); + tlsSetting_.ciphers_num = ciphers_.size(); + tlsSetting_.ciphers = &ciphers_.front(); + + tlsSetting_.verify_server = tlsVerifyServer_; + tlsSetting_.verify_client = tlsVerifyClient_; + tlsSetting_.require_client_cert = tlsRequireClientCertificate_; + + tlsSetting_.timeout.sec = atol(tlsNegotiationTimeoutSec_.c_str()); + + tlsSetting_.qos_type = PJ_QOS_TYPE_BEST_EFFORT; + tlsSetting_.qos_ignore_error = PJ_TRUE; +} + +#endif + +void SIPAccount::initStunConfiguration() +{ + size_t pos; + std::string stunServer, serverName, serverPort; + + stunServer = stunServer_; + // Init STUN socket + pos = stunServer.find(':'); + + if (pos == std::string::npos) { + stunServerName_ = pj_str((char*) stunServer.data()); + stunPort_ = PJ_STUN_PORT; + //stun_status = pj_sockaddr_in_init (&stun_srv.ipv4, &stun_adr, (pj_uint16_t) 3478); + } else { + serverName = stunServer.substr(0, pos); + serverPort = stunServer.substr(pos + 1); + stunPort_ = atoi(serverPort.data()); + stunServerName_ = pj_str((char*) serverName.data()); + //stun_status = pj_sockaddr_in_init (&stun_srv.ipv4, &stun_adr, (pj_uint16_t) nPort); + } +} + +void SIPAccount::loadConfig() +{ + if (registrationExpire_ == 0) + registrationExpire_ = DEFAULT_REGISTRATION_TIME; /** Default expire value for registration */ + +#if HAVE_TLS + + if (tlsEnable_) { + initTlsConfiguration(); + transportType_ = PJSIP_TRANSPORT_TLS; + } else +#endif + transportType_ = PJSIP_TRANSPORT_UDP; +} + +bool SIPAccount::fullMatch(const std::string& username, const std::string& hostname, pjsip_endpoint *endpt, pj_pool_t *pool) const +{ + return userMatch(username) and hostnameMatch(hostname, endpt, pool); +} + +bool SIPAccount::userMatch(const std::string& username) const +{ + return !username.empty() and username == username_; +} + +bool SIPAccount::hostnameMatch(const std::string& hostname, pjsip_endpoint * /*endpt*/, pj_pool_t * /*pool*/) const +{ + if (hostname == hostname_) + return true; + const auto a = ip_utils::getAddrList(hostname); + const auto b = ip_utils::getAddrList(hostname_); + return ip_utils::haveCommonAddr(a, b); +} + +bool SIPAccount::proxyMatch(const std::string& hostname, pjsip_endpoint * /*endpt*/, pj_pool_t * /*pool*/) const +{ + if (hostname == serviceRoute_) + return true; + const auto a = ip_utils::getAddrList(hostname); + const auto b = ip_utils::getAddrList(hostname_); + return ip_utils::haveCommonAddr(a, b); +} + +std::string SIPAccount::getLoginName() +{ + struct passwd * user_info = getpwuid(getuid()); + return user_info ? user_info->pw_name : ""; +} + +std::string SIPAccount::getFromUri() const +{ + std::string scheme; + std::string transport; + std::string username(username_); + std::string hostname(hostname_); + + // UDP does not require the transport specification + if (transportType_ == PJSIP_TRANSPORT_TLS || transportType_ == PJSIP_TRANSPORT_TLS6) { + scheme = "sips:"; + transport = ";transport=" + std::string(pjsip_transport_get_type_name(transportType_)); + } else + scheme = "sip:"; + + // Get login name if username is not specified + if (username_.empty()) + username = getLoginName(); + + // Get machine hostname if not provided + if (hostname_.empty()) + hostname = std::string(pj_gethostname()->ptr, pj_gethostname()->slen); + +#if HAVE_IPV6 + if (IpAddr::isIpv6(hostname)) + hostname = IpAddr(hostname).toString(false, true); +#endif + + return "<" + scheme + username + "@" + hostname + transport + ">"; +} + +std::string SIPAccount::getToUri(const std::string& username) const +{ + std::string scheme; + std::string transport; + std::string hostname; + + // UDP does not require the transport specification + if (transportType_ == PJSIP_TRANSPORT_TLS || transportType_ == PJSIP_TRANSPORT_TLS6) { + scheme = "sips:"; + transport = ";transport=" + std::string(pjsip_transport_get_type_name(transportType_)); + } else + scheme = "sip:"; + + // Check if scheme is already specified + if (username.find("sip") == 0) + scheme = ""; + + // Check if hostname is already specified + if (username.find("@") == std::string::npos) + hostname = hostname_; + +#if HAVE_IPV6 + if (not hostname.empty() and IpAddr::isIpv6(hostname)) + hostname = IpAddr(hostname).toString(false, true); +#endif + + return "<" + scheme + username + (hostname.empty() ? "" : "@") + hostname + transport + ">"; +} + +std::string SIPAccount::getServerUri() const +{ + std::string scheme; + std::string transport; + + // UDP does not require the transport specification + if (transportType_ == PJSIP_TRANSPORT_TLS || transportType_ == PJSIP_TRANSPORT_TLS6) { + scheme = "sips:"; + transport = ";transport=" + std::string(pjsip_transport_get_type_name(transportType_)); + } else { + scheme = "sip:"; + } + + std::string host; +#if HAVE_IPV6 + if (IpAddr::isIpv6(hostname_)) + host = IpAddr(hostname_).toString(false, true); + else +#endif + host = hostname_; + + return "<" + scheme + host + transport + ">"; +} + + +pj_str_t +SIPAccount::getContactHeader(pjsip_transport* t) +{ + if (!t && transport_) + t = transport_->get(); + if (!t) + RING_ERR("Transport not created yet"); + + if (contact_.slen and contactOverwritten_) + return contact_; + + // The transport type must be specified, in our case START_OTHER refers to stun transport + pjsip_transport_type_e transportType = transportType_; + + if (transportType == PJSIP_TRANSPORT_START_OTHER) + transportType = PJSIP_TRANSPORT_UDP; + + // Else we determine this infor based on transport information + std::string address; + pj_uint16_t port; + + link_->findLocalAddressFromTransport( + t, + transportType, + hostname_, + address, port); + + if (getUPnPActive() and getUPnPIpAddress()) { + address = getUPnPIpAddress().toString(); + port = publishedPortUsed_; + useUPnPAddressPortInVIA(); + RING_DBG("Using UPnP address %s and port %d", address.c_str(), port); + } else if (not publishedSameasLocal_) { + address = publishedIpAddress_; + port = publishedPort_; + RING_DBG("Using published address %s and port %d", address.c_str(), port); + } else if (stunEnabled_) { + link_->findLocalAddressFromSTUN( + t, + &stunServerName_, + stunPort_, + address, port); + setPublishedAddress(address); + publishedPort_ = port; + usePublishedAddressPortInVIA(); + } else { + if (!receivedParameter_.empty()) { + address = receivedParameter_; + RING_DBG("Using received address %s", address.c_str()); + } + + if (rPort_ != -1 and rPort_ != 0) { + port = rPort_; + RING_DBG("Using received port %d", port); + } + } + + // UDP does not require the transport specification + std::string scheme; + std::string transport; + +#if HAVE_IPV6 + /* Enclose IPv6 address in square brackets */ + if (IpAddr::isIpv6(address)) { + address = IpAddr(address).toString(false, true); + } +#endif + + if (transportType != PJSIP_TRANSPORT_UDP and transportType != PJSIP_TRANSPORT_UDP6) { + scheme = "sips:"; + transport = ";transport=" + std::string(pjsip_transport_get_type_name(transportType)); + } else + scheme = "sip:"; + + contact_.slen = pj_ansi_snprintf(contact_.ptr, PJSIP_MAX_URL_SIZE, + "%s%s<%s%s%s%s:%d%s>", + displayName_.c_str(), + (displayName_.empty() ? "" : " "), + scheme.c_str(), + username_.c_str(), + (username_.empty() ? "" : "@"), + address.c_str(), + port, + transport.c_str()); + return contact_; +} + +pjsip_host_port +SIPAccount::getHostPortFromSTUN(pj_pool_t *pool) +{ + std::string addr; + pj_uint16_t port; + link_->findLocalAddressFromSTUN( + transport_ ? transport_->get() : nullptr, + &stunServerName_, + stunPort_, + addr, port); + pjsip_host_port result; + pj_strdup2(pool, &result.host, addr.c_str()); + result.host.slen = addr.length(); + result.port = port; + return result; +} + +const std::vector<std::string>& +SIPAccount::getSupportedCiphers() const +{ + //Currently, both OpenSSL and GNUTLS implementations are static + //reloading this for each account is unnecessary + static std::vector<std::string> availCiphers {}; + + // LIMITATION Assume the size might change, if there aren't any ciphers, + // this will cause the cache to be repopulated at each call for nothing. + if (availCiphers.empty()) { + unsigned cipherNum = 256; + CipherArray avail_ciphers(cipherNum); + if (pj_ssl_cipher_get_availables(&avail_ciphers.front(), &cipherNum) != PJ_SUCCESS) + RING_ERR("Could not determine cipher list on this system"); + avail_ciphers.resize(cipherNum); + availCiphers.reserve(cipherNum); + for (const auto &item : avail_ciphers) { + if (item > 0) // 0 doesn't have a name + availCiphers.push_back(pj_ssl_cipher_name(item)); + } + } + return availCiphers; +} + +void SIPAccount::keepAliveRegistrationCb(UNUSED pj_timer_heap_t *th, pj_timer_entry *te) +{ + SIPAccount *sipAccount = static_cast<SIPAccount *>(te->user_data); + + if (sipAccount == nullptr) { + RING_ERR("SIP account is nullptr while registering a new keep alive timer"); + return; + } + + RING_ERR("Keep alive registration callback for account %s", sipAccount->getAccountID().c_str()); + + // IP2IP default does not require keep-alive + if (sipAccount->isIP2IP()) + return; + + // TLS is connection oriented and does not require keep-alive + if (sipAccount->isTlsEnabled()) + return; + + sipAccount->stopKeepAliveTimer(); + + if (sipAccount->isRegistered()) + sipAccount->doRegister(); +} + +static std::string +computeMd5HashFromCredential(const std::string& username, + const std::string& password, + const std::string& realm) +{ +#define MD5_APPEND(pms,buf,len) pj_md5_update(pms, (const pj_uint8_t*)buf, len) + + pj_md5_context pms; + + /* Compute md5 hash = MD5(username ":" realm ":" password) */ + pj_md5_init(&pms); + MD5_APPEND(&pms, username.data(), username.length()); + MD5_APPEND(&pms, ":", 1); + MD5_APPEND(&pms, realm.data(), realm.length()); + MD5_APPEND(&pms, ":", 1); + MD5_APPEND(&pms, password.data(), password.length()); +#undef MD5_APPEND + + unsigned char digest[16]; + pj_md5_final(&pms, digest); + + char hash[32]; + + for (int i = 0; i < 16; ++i) + pj_val_to_hex_digit(digest[i], &hash[2 * i]); + + return std::string(hash, 32); +} + +void SIPAccount::setCredentials(const std::vector<std::map<std::string, std::string> >& creds) +{ + // we can not authenticate without credentials + if (creds.empty()) { + RING_ERR("Cannot authenticate with empty credentials list"); + return; + } + + using std::vector; + using std::string; + using std::map; + + bool md5HashingEnabled = Manager::instance().preferences.getMd5Hash(); + + credentials_ = creds; + + /* md5 hashing */ + for (auto &it : credentials_) { + map<string, string>::const_iterator val = it.find(Conf::CONFIG_ACCOUNT_USERNAME); + const std::string username = val != it.end() ? val->second : ""; + val = it.find(Conf::CONFIG_ACCOUNT_REALM); + const std::string realm(val != it.end() ? val->second : ""); + val = it.find(Conf::CONFIG_ACCOUNT_PASSWORD); + const std::string password(val != it.end() ? val->second : ""); + + if (md5HashingEnabled) { + // TODO: Fix this. + // This is an extremly weak test in order to check + // if the password is a hashed value. This is done + // because deleteCredential() is called before this + // method. Therefore, we cannot check if the value + // is different from the one previously stored in + // the configuration file. This is to avoid to + // re-hash a hashed password. + + if (password.length() != 32) + it[Conf::CONFIG_ACCOUNT_PASSWORD] = computeMd5HashFromCredential(username, password, realm); + } + } + + // Create the credential array + cred_.resize(credentials_.size()); + + size_t i = 0; + + for (const auto &item : credentials_) { + map<string, string>::const_iterator val = item.find(Conf::CONFIG_ACCOUNT_PASSWORD); + const std::string password = val != item.end() ? val->second : ""; + int dataType = (md5HashingEnabled and password.length() == 32) + ? PJSIP_CRED_DATA_DIGEST + : PJSIP_CRED_DATA_PLAIN_PASSWD; + + val = item.find(Conf::CONFIG_ACCOUNT_USERNAME); + + if (val != item.end()) + cred_[i].username = pj_str((char*) val->second.c_str()); + + cred_[i].data = pj_str((char*) password.c_str()); + + val = item.find(Conf::CONFIG_ACCOUNT_REALM); + + if (val != item.end()) + cred_[i].realm = pj_str((char*) val->second.c_str()); + + cred_[i].data_type = dataType; + cred_[i].scheme = pj_str((char*) "digest"); + ++i; + } +} + +const std::vector<std::map<std::string, std::string> > & +SIPAccount::getCredentials() const +{ + return credentials_; +} + +void +SIPAccount::setRegistrationState(RegistrationState state, unsigned details_code) +{ + std::string details_str; + const pj_str_t *description = pjsip_get_status_text(details_code); + if (description) + details_str = {description->ptr, (size_t)description->slen}; + setRegistrationStateDetailed({details_code, details_str}); + Account::setRegistrationState(state, details_code, details_str); +} + +std::string SIPAccount::getUserAgentName() const +{ + if (not hasCustomUserAgent_ or userAgent_.empty()) + return DEFAULT_USER_AGENT; + return userAgent_; +} + +std::map<std::string, std::string> SIPAccount::getIp2IpDetails() const +{ + assert(isIP2IP()); + std::map<std::string, std::string> ip2ipAccountDetails; + ip2ipAccountDetails[Conf::CONFIG_SRTP_KEY_EXCHANGE] = sip_utils::getKeyExchangeName(srtpKeyExchange_); + ip2ipAccountDetails[Conf::CONFIG_SRTP_ENABLE] = isSrtpEnabled() ? TRUE_STR : FALSE_STR; + ip2ipAccountDetails[Conf::CONFIG_SRTP_RTP_FALLBACK] = srtpFallback_ ? TRUE_STR : FALSE_STR; + ip2ipAccountDetails[Conf::CONFIG_ZRTP_DISPLAY_SAS] = zrtpDisplaySas_ ? TRUE_STR : FALSE_STR; + ip2ipAccountDetails[Conf::CONFIG_ZRTP_HELLO_HASH] = zrtpHelloHash_ ? TRUE_STR : FALSE_STR; + ip2ipAccountDetails[Conf::CONFIG_ZRTP_NOT_SUPP_WARNING] = zrtpNotSuppWarning_ ? TRUE_STR : FALSE_STR; + ip2ipAccountDetails[Conf::CONFIG_ZRTP_DISPLAY_SAS_ONCE] = zrtpDisplaySasOnce_ ? TRUE_STR : FALSE_STR; + ip2ipAccountDetails[Conf::CONFIG_LOCAL_PORT] = ring::to_string(localPort_); + + auto tlsSettings(getTlsSettings()); + std::copy(tlsSettings.begin(), tlsSettings.end(), + std::inserter(ip2ipAccountDetails, ip2ipAccountDetails.end())); + + return ip2ipAccountDetails; +} + +std::map<std::string, std::string> +SIPAccount::getTlsSettings() const +{ + return { + {Conf::CONFIG_TLS_ENABLE, tlsEnable_ ? TRUE_STR : FALSE_STR}, + {Conf::CONFIG_TLS_LISTENER_PORT, ring::to_string(tlsListenerPort_)}, + {Conf::CONFIG_TLS_CA_LIST_FILE, tlsCaListFile_}, + {Conf::CONFIG_TLS_CERTIFICATE_FILE, tlsCertificateFile_}, + {Conf::CONFIG_TLS_PRIVATE_KEY_FILE, tlsPrivateKeyFile_}, + {Conf::CONFIG_TLS_PASSWORD, tlsPassword_}, + {Conf::CONFIG_TLS_METHOD, tlsMethod_}, + {Conf::CONFIG_TLS_CIPHERS, tlsCiphers_}, + {Conf::CONFIG_TLS_SERVER_NAME, tlsServerName_}, + {Conf::CONFIG_TLS_VERIFY_SERVER, tlsVerifyServer_ ? TRUE_STR : FALSE_STR}, + {Conf::CONFIG_TLS_VERIFY_CLIENT, tlsVerifyClient_ ? TRUE_STR : FALSE_STR}, + {Conf::CONFIG_TLS_REQUIRE_CLIENT_CERTIFICATE, tlsRequireClientCertificate_ ? TRUE_STR : FALSE_STR}, + {Conf::CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC, tlsNegotiationTimeoutSec_} + }; +} + +static void +set_opt(const std::map<std::string, std::string> &details, const char *key, std::string &val) +{ + std::map<std::string, std::string>::const_iterator it = details.find(key); + + if (it != details.end()) + val = it->second; +} + +static void +set_opt(const std::map<std::string, std::string> &details, const char *key, bool &val) +{ + std::map<std::string, std::string>::const_iterator it = details.find(key); + + if (it != details.end()) + val = it->second == TRUE_STR; +} + +static void +set_opt(const std::map<std::string, std::string> &details, const char *key, pj_uint16_t &val) +{ + std::map<std::string, std::string>::const_iterator it = details.find(key); + + if (it != details.end()) + val = atoi(it->second.c_str()); +} + +void SIPAccount::setTlsSettings(const std::map<std::string, std::string>& details) +{ + assert(isIP2IP()); + set_opt(details, Conf::CONFIG_TLS_LISTENER_PORT, tlsListenerPort_); + set_opt(details, Conf::CONFIG_TLS_ENABLE, tlsEnable_); + set_opt(details, Conf::CONFIG_TLS_CA_LIST_FILE, tlsCaListFile_); + set_opt(details, Conf::CONFIG_TLS_CERTIFICATE_FILE, tlsCertificateFile_); + set_opt(details, Conf::CONFIG_TLS_PRIVATE_KEY_FILE, tlsPrivateKeyFile_); + set_opt(details, Conf::CONFIG_TLS_PASSWORD, tlsPassword_); + set_opt(details, Conf::CONFIG_TLS_METHOD, tlsMethod_); + set_opt(details, Conf::CONFIG_TLS_CIPHERS, tlsCiphers_); + set_opt(details, Conf::CONFIG_TLS_SERVER_NAME, tlsServerName_); + set_opt(details, Conf::CONFIG_TLS_VERIFY_CLIENT, tlsVerifyClient_); + set_opt(details, Conf::CONFIG_TLS_REQUIRE_CLIENT_CERTIFICATE, tlsRequireClientCertificate_); + set_opt(details, Conf::CONFIG_TLS_NEGOTIATION_TIMEOUT_SEC, tlsNegotiationTimeoutSec_); +} + +bool SIPAccount::isIP2IP() const +{ + return accountID_ == IP2IP_PROFILE; +} + +SIPPresence * SIPAccount::getPresence() const +{ + return presence_; +} + +/** + * Enable the presence module + */ +void +SIPAccount::enablePresence(const bool& enabled) +{ + if (!presence_) { + RING_ERR("Presence not initialized"); + return; + } + + RING_DBG("Presence enabled for %s : %s.", + accountID_.c_str(), + enabled? TRUE_STR : FALSE_STR); + + presence_->enable(enabled); +} + +/** + * Set the presence (PUBLISH/SUBSCRIBE) support flags + * and process the change. + */ +void +SIPAccount::supportPresence(int function, bool enabled) +{ + if (!presence_) { + RING_ERR("Presence not initialized"); + return; + } + + if (presence_->isSupported(function) == enabled) + return; + + RING_DBG("Presence support for %s (%s: %s).", accountID_.c_str(), + function == PRESENCE_FUNCTION_PUBLISH ? "publish" : "subscribe", + enabled ? TRUE_STR : FALSE_STR); + presence_->support(function, enabled); + + // force presence to disable when nothing is supported + if (not presence_->isSupported(PRESENCE_FUNCTION_PUBLISH) and + not presence_->isSupported(PRESENCE_FUNCTION_SUBSCRIBE)) + enablePresence(false); + + Manager::instance().saveConfig(); + emitSignal<DRing::ConfigurationSignal::AccountsChanged>(); +} + +MatchRank +SIPAccount::matches(const std::string &userName, const std::string &server, + pjsip_endpoint *endpt, pj_pool_t *pool) const +{ + if (fullMatch(userName, server, endpt, pool)) { + RING_DBG("Matching account id in request is a fullmatch %s@%s", userName.c_str(), server.c_str()); + return MatchRank::FULL; + } else if (hostnameMatch(server, endpt, pool)) { + RING_DBG("Matching account id in request with hostname %s", server.c_str()); + return MatchRank::PARTIAL; + } else if (userMatch(userName)) { + RING_DBG("Matching account id in request with username %s", userName.c_str()); + return MatchRank::PARTIAL; + } else if (proxyMatch(server, endpt, pool)) { + RING_DBG("Matching account id in request with proxy %s", server.c_str()); + return MatchRank::PARTIAL; + } else { + return MatchRank::NONE; + } +} + +void +SIPAccount::destroyRegistrationInfo() +{ + if (!regc_) return; + pjsip_regc_destroy(regc_); + regc_ = nullptr; +} + +void +SIPAccount::resetAutoRegistration() +{ + auto_rereg_.active = PJ_FALSE; + auto_rereg_.attempt_cnt = 0; +} + +/* Update NAT address from the REGISTER response */ +bool +SIPAccount::checkNATAddress(pjsip_regc_cbparam *param, pj_pool_t *pool) +{ + pjsip_transport *tp = param->rdata->tp_info.transport; + + /* Get the received and rport info */ + pjsip_via_hdr *via = param->rdata->msg_info.via; + int rport; + if (via->rport_param < 1) { + /* Remote doesn't support rport */ + rport = via->sent_by.port; + if (rport == 0) { + pjsip_transport_type_e tp_type; + tp_type = (pjsip_transport_type_e) tp->key.type; + rport = pjsip_transport_get_default_port_for_type(tp_type); + } + } else { + rport = via->rport_param; + } + + const pj_str_t *via_addr = via->recvd_param.slen != 0 ? + &via->recvd_param : &via->sent_by.host; + + /* If allowViaRewrite_ is enabled, we save the Via "received" address + * from the response. + */ + if (allowViaRewrite_ and (via_addr_.host.slen == 0 or via_tp_ != tp)) { + if (pj_strcmp(&via_addr_.host, via_addr)) + pj_strdup(pool, &via_addr_.host, via_addr); + + via_addr_.port = rport; + via_tp_ = tp; + pjsip_regc_set_via_sent_by(regc_, &via_addr_, via_tp_); + } + + /* Only update if account is configured to auto-update */ + if (not allowContactRewrite_) + return false; + + /* Compare received and rport with the URI in our registration */ + const pj_str_t STR_CONTACT = { (char*) "Contact", 7 }; + pjsip_contact_hdr *contact_hdr = (pjsip_contact_hdr*) + pjsip_parse_hdr(pool, &STR_CONTACT, contact_.ptr, contact_.slen, nullptr); + pj_assert(contact_hdr != nullptr); + pjsip_sip_uri *uri = (pjsip_sip_uri*) contact_hdr->uri; + pj_assert(uri != nullptr); + uri = (pjsip_sip_uri*) pjsip_uri_get_uri(uri); + + if (uri->port == 0) { + pjsip_transport_type_e tp_type; + tp_type = (pjsip_transport_type_e) tp->key.type; + uri->port = pjsip_transport_get_default_port_for_type(tp_type); + } + + /* Convert IP address strings into sockaddr for comparison. + * (http://trac.pjsip.org/repos/ticket/863) + */ + IpAddr contact_addr, recv_addr; + pj_status_t status = pj_sockaddr_parse(pj_AF_UNSPEC(), 0, &uri->host, contact_addr.pjPtr()); + if (status == PJ_SUCCESS) + status = pj_sockaddr_parse(pj_AF_UNSPEC(), 0, via_addr, recv_addr.pjPtr()); + + bool matched; + if (status == PJ_SUCCESS) { + // Compare the addresses as sockaddr according to the ticket above + matched = (uri->port == rport and contact_addr == recv_addr); + } else { + // Compare the addresses as string, as before + matched = (uri->port == rport and pj_stricmp(&uri->host, via_addr) == 0); + } + + if (matched) { + // Address doesn't change + return false; + } + + /* Get server IP */ + IpAddr srv_ip = {std::string(param->rdata->pkt_info.src_name)}; + + /* At this point we've detected that the address as seen by registrar. + * has changed. + */ + + /* Do not switch if both Contact and server's IP address are + * public but response contains private IP. A NAT in the middle + * might have messed up with the SIP packets. See: + * http://trac.pjsip.org/repos/ticket/643 + * + * This exception can be disabled by setting allow_contact_rewrite + * to 2. In this case, the switch will always be done whenever there + * is difference in the IP address in the response. + */ + if (allowContactRewrite_ != 2 and + not contact_addr.isPrivate() and + not srv_ip.isPrivate() and + recv_addr.isPrivate()) { + /* Don't switch */ + return false; + } + + /* Also don't switch if only the port number part is different, and + * the Via received address is private. + * See http://trac.pjsip.org/repos/ticket/864 + */ + if (allowContactRewrite_ != 2 and contact_addr == recv_addr and recv_addr.isPrivate()) { + /* Don't switch */ + return false; + } + + std::string via_addrstr(via_addr->ptr, via_addr->slen); +#if HAVE_IPV6 + /* Enclose IPv6 address in square brackets */ + if (IpAddr::isIpv6(via_addrstr)) + via_addrstr = IpAddr(via_addrstr).toString(false, true); +#endif + + RING_WARN("IP address change detected for account %s " + "(%.*s:%d --> %s:%d). Updating registration " + "(using method %d)", + accountID_.c_str(), + (int) uri->host.slen, + uri->host.ptr, + uri->port, + via_addrstr.c_str(), + rport, + contactRewriteMethod_); + + pj_assert(contactRewriteMethod_ == 1 or contactRewriteMethod_ == 2); + + std::shared_ptr<SipTransport> tmp_tp {nullptr}; + if (contactRewriteMethod_ == 1) { + /* Save transport in case we're gonna reuse it */ + tmp_tp = transport_; + /* Unregister current contact */ + sendUnregister(); + destroyRegistrationInfo(); + } + + /* + * Build new Contact header + */ + { + char *tmp; + char transport_param[32]; + int len; + + /* Don't add transport parameter if it's UDP */ + if (tp->key.type != PJSIP_TRANSPORT_UDP and + tp->key.type != PJSIP_TRANSPORT_UDP6) { + pj_ansi_snprintf(transport_param, sizeof(transport_param), + ";transport=%s", + pjsip_transport_get_type_name( + (pjsip_transport_type_e)tp->key.type)); + } else { + transport_param[0] = '\0'; + } + + tmp = (char*) pj_pool_alloc(pool, PJSIP_MAX_URL_SIZE); + len = pj_ansi_snprintf(tmp, PJSIP_MAX_URL_SIZE, + "<sip:%s%s%s:%d%s>", + username_.c_str(), + (not username_.empty() ? "@" : ""), + via_addrstr.c_str(), + rport, + transport_param); + if (len < 1) { + RING_ERR("URI too long"); + return false; + } + + pj_str_t tmp_str = {tmp, len}; + pj_strncpy_with_null(&contact_, &tmp_str, PJSIP_MAX_URL_SIZE); + } + + if (contactRewriteMethod_ == 2 && regc_ != nullptr) { + contactOverwritten_ = true; + + /* Unregister old contact */ + try { + tmp_tp = transport_; + sendUnregister(); + } catch (const VoipLinkException &e) { + RING_ERR("%s", e.what()); + } + + pjsip_regc_update_contact(regc_, 1, &contact_); + + /* Perform new registration */ + try { + sendRegister(); + } catch (const VoipLinkException &e) { + RING_ERR("%s", e.what()); + } + } + + return true; +} + +/* Auto re-registration timeout callback */ +void +SIPAccount::autoReregTimerCb(pj_timer_heap_t * /*th*/, pj_timer_entry *te) +{ + auto context = static_cast<std::pair<SIPAccount *, pjsip_endpoint *> *>(te->user_data); + SIPAccount *acc = context->first; + pjsip_endpoint *endpt = context->second; + + /* Check if the reregistration timer is still valid, e.g: while waiting + * timeout timer application might have deleted the account or disabled + * the auto-reregistration. + */ + if (not acc->auto_rereg_.active) { + delete context; + return; + } + + /* Start re-registration */ + acc->auto_rereg_.attempt_cnt++; + try { + acc->sendRegister(); + } catch (const VoipLinkException &e) { + RING_ERR("%s", e.what()); + acc->scheduleReregistration(endpt); + } + delete context; +} + +/* Schedule reregistration for specified account. Note that the first + * re-registration after a registration failure will be done immediately. + * Also note that this function should be called within PJSUA mutex. + */ +void +SIPAccount::scheduleReregistration(pjsip_endpoint *endpt) +{ + if (!isEnabled()) + return; + + /* Cancel any re-registration timer */ + if (auto_rereg_.timer.id) { + auto_rereg_.timer.id = PJ_FALSE; + pjsip_endpt_cancel_timer(endpt, &auto_rereg_.timer); + } + + /* Update re-registration flag */ + auto_rereg_.active = PJ_TRUE; + + /* Set up timer for reregistration */ + auto_rereg_.timer.cb = &SIPAccount::autoReregTimerCb; + auto_rereg_.timer.user_data = new std::pair<SIPAccount *, pjsip_endpoint *>(this, endpt); + + /* Reregistration attempt. The first attempt will be done immediately. */ + pj_time_val delay; + const int FIRST_RETRY_INTERVAL = 60; + const int RETRY_INTERVAL = 300; + delay.sec = auto_rereg_.attempt_cnt ? RETRY_INTERVAL : FIRST_RETRY_INTERVAL; + delay.msec = 0; + + /* Randomize interval by +/- 10 secs */ + if (delay.sec >= 10) { + delay.msec = delay10ZeroDist_(rand_); + } else { + delay.sec = 0; + delay.msec = delay10PosDist_(rand_); + } + + pj_time_val_normalize(&delay); + + RING_WARN("Scheduling re-registration retry in %u seconds..", delay.sec); + auto_rereg_.timer.id = PJ_TRUE; + if (pjsip_endpt_schedule_timer(endpt, &auto_rereg_.timer, &delay) != PJ_SUCCESS) + auto_rereg_.timer.id = PJ_FALSE; +} + +void SIPAccount::updateDialogViaSentBy(pjsip_dialog *dlg) +{ + if (allowViaRewrite_ && via_addr_.host.slen > 0) + pjsip_dlg_set_via_sent_by(dlg, &via_addr_, via_tp_); +} + +} // namespace ring diff --git a/src/sip/sipaccount.h b/src/sip/sipaccount.h new file mode 100644 index 0000000000..8aeb31e4ee --- /dev/null +++ b/src/sip/sipaccount.h @@ -0,0 +1,814 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Alexandre Bourget <alexandre.bourget@savoirfairelinux.com> + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * Author: Pierre-Luc Bacon <pierre-luc.bacon@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef SIPACCOUNT_H +#define SIPACCOUNT_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sipaccountbase.h" +#include "siptransport.h" +#include "noncopyable.h" +#include "ring_types.h" // enable_if_base_of + +#include <pjsip/sip_transport_tls.h> +#include <pjsip/sip_types.h> +#include <pjsip-ua/sip_regc.h> + +#include <vector> +#include <map> + +namespace YAML { + class Node; + class Emitter; +} + +namespace ring { + +namespace Conf { + const char *const KEEP_ALIVE_ENABLED = "keepAlive"; + + // TODO: write an object to store credential which implement serializable + const char *const SRTP_KEY = "srtp"; + const char *const SRTP_ENABLE_KEY = "enable"; + const char *const KEY_EXCHANGE_KEY = "keyExchange"; + const char *const RTP_FALLBACK_KEY = "rtpFallback"; + + // TODO: wirte an object to store zrtp params wich implement serializable + const char *const ZRTP_KEY = "zrtp"; + const char *const DISPLAY_SAS_KEY = "displaySas"; + const char *const DISPLAY_SAS_ONCE_KEY = "displaySasOnce"; + const char *const HELLO_HASH_ENABLED_KEY = "helloHashEnabled"; + const char *const NOT_SUPP_WARNING_KEY = "notSuppWarning"; +} + +typedef std::vector<pj_ssl_cipher> CipherArray; + +class SIPPresence; +class SIPCall; + +/** + * @file sipaccount.h + * @brief A SIP Account specify SIP specific functions and object = SIPCall/SIPVoIPLink) + */ +class SIPAccount : public SIPAccountBase { + public: + constexpr static const char * const IP2IP_PROFILE = "IP2IP"; + constexpr static const char * const ACCOUNT_TYPE = "SIP"; + + /** + * Constructor + * @param accountID The account identifier + */ + SIPAccount(const std::string& accountID, bool presenceEnabled); + + ~SIPAccount(); + + const char* getAccountType() const { + return ACCOUNT_TYPE; + } + + pjsip_host_port getHostPortFromSTUN(pj_pool_t *pool); + + std::string getUserAgentName() const; + void setRegistrationStateDetailed(const std::pair<int, std::string> &details) { + registrationStateDetailed_ = details; + } + + void updateDialogViaSentBy(pjsip_dialog *dlg); + + void resetAutoRegistration(); + bool checkNATAddress(pjsip_regc_cbparam *param, pj_pool_t *pool); + + /** + * Returns true if this is the IP2IP account + */ + bool isIP2IP() const; + + /** + * Serialize internal state of this account for configuration + * @param out Emitter to which state will be saved + */ + virtual void serialize(YAML::Emitter &out); + + /** + * Populate the internal state for this account based on info stored in the configuration file + * @param The configuration node for this account + */ + virtual void unserialize(const YAML::Node &node); + + /** + * Return an map containing the internal state of this account. Client application can use this method to manage + * account info. + * @return A map containing the account information. + */ + virtual std::map<std::string, std::string> getAccountDetails() const; + + /** + * Retrieve volatile details such as recent registration errors + * @return std::map< std::string, std::string > The account volatile details + */ + virtual std::map<std::string, std::string> getVolatileAccountDetails() const; + + /** + * Return the information for the default IP to IP account + */ + std::map<std::string, std::string> getIp2IpDetails() const; + + /** + * Return the TLS settings, mainly used to return security information to + * a client application + */ + std::map<std::string, std::string> getTlsSettings() const; + + /** + * Manage the TLS settings from a client application + */ + void setTlsSettings(const std::map<std::string, std::string>& details); + + /** + * Actually useless, since config loading is done in init() + */ + void loadConfig(); + + /** + * Initialize the SIP voip link with the account parameters and send registration + */ + void doRegister(); + + /** + * Send unregistration. + */ + void doUnregister(std::function<void(bool)> cb = std::function<void(bool)>()); + + /** + * Start the keep alive function, once started, the account will be registered periodically + * a new REGISTER request is sent bey the client application. The account must be initially + * registered for this call to be effective. + */ + void startKeepAliveTimer(); + + /** + * Stop the keep alive timer. Once canceled, no further registration will be scheduled + */ + void stopKeepAliveTimer(); + + /** + * Build and send SIP registration request + */ + void sendRegister(); + + /** + * Build and send SIP unregistration request + * @param destroy_transport If true, attempt to destroy the transport. + */ + void sendUnregister(); + + const pjsip_cred_info* getCredInfo() const { + return cred_.data(); + } + + /** + * Get the number of credentials defined for + * this account. + * @param none + * @return int The number of credentials set for this account. + */ + unsigned getCredentialCount() const { + return credentials_.size(); + } + + bool hasCredentials() const { + return not credentials_.empty(); + } + + void setCredentials(const std::vector<std::map<std::string, std::string> >& details); + + const std::vector<std::map<std::string, std::string> > & + getCredentials() const; + + void setRegistrationState(RegistrationState state, unsigned code=0); + + /** + * A client sendings a REGISTER request MAY suggest an expiration + * interval that indicates how long the client would like the + * registration to be valid. + * + * @return the expiration value. + */ + unsigned getRegistrationExpire() const { + if (registrationExpire_ == 0) + return PJSIP_REGC_EXPIRATION_NOT_SPECIFIED; + + return registrationExpire_; + } + + /** + * Set the expiration for this account as found in + * the "Expire" sip header or the CONTACT's "expire" param. + */ + void setRegistrationExpire(int expire) { + if (expire > 0) + registrationExpire_ = expire; + } + + /** + * Doubles the Expiration Interval sepecified for registration. + */ + void doubleRegistrationExpire() { + registrationExpire_ *= 2; + + if (registrationExpire_ < 0) + registrationExpire_ = 0; + } + + /** + * Registration flag + */ + bool isRegistered() const { + return bRegister_; + } + + /** + * Set registration flag + */ + void setRegister(bool result) { + bRegister_ = result; + } + + /** + * Get the registration stucture that is used + * for PJSIP in the registration process. + * Settings are loaded from configuration file. + * @return pjsip_regc* A pointer to the registration structure + */ + pjsip_regc* getRegistrationInfo() { + return regc_; + } + + /** + * Set the registration structure that is used + * for PJSIP in the registration process; + * @pram A pointer to the new registration structure + * @return void + */ + void setRegistrationInfo(pjsip_regc *regc) { + if (regc_) destroyRegistrationInfo(); + regc_ = regc; + } + + void destroyRegistrationInfo(); + + /** + * Get the port on which the transport/listener should use, or is + * actually using. + * @return pj_uint16 The port used for that account + */ + pj_uint16_t getLocalPort() const { + return localPort_; + } + + /** + * Set the new port on which this account is running over. + * @pram port The port used by this account. + */ + void setLocalPort(pj_uint16_t port) { + localPort_ = port; + } + + /** + * @return pjsip_tls_setting structure, filled from the configuration + * file, that can be used directly by PJSIP to initialize + * TLS transport. + */ + pjsip_tls_setting * getTlsSetting() { + return &tlsSetting_; + } + + /** + * Get the local port for TLS listener. + * @return pj_uint16 The port used for that account + */ + pj_uint16_t getTlsListenerPort() const { + return tlsListenerPort_; + } + + /** + * @return pj_str_t , filled from the configuration + * file, that can be used directly by PJSIP to initialize + * an alternate UDP transport. + */ + std::string getStunServer() const { + return stunServer_; + } + void setStunServer(const std::string &srv) { + stunServer_ = srv; + } + + pj_str_t getStunServerName() const { + return stunServerName_; + } + + const std::vector<std::string>& getSupportedCiphers() const; + + /** + * @return pj_uint8_t structure, filled from the configuration + * file, that can be used directly by PJSIP to initialize + * an alternate UDP transport. + */ + pj_uint16_t getStunPort() const { + return stunPort_; + } + + /** + * @return bool Tells if current transport for that + * account is set to OTHER. + */ + bool isStunEnabled() const { + return stunEnabled_; + } + + /** + * @return pj_str_t "From" uri based on account information. + * From RFC3261: "The To header field first and foremost specifies the desired + * logical" recipient of the request, or the address-of-record of the + * user or resource that is the target of this request. [...] As such, it is + * very important that the From URI not contain IP addresses or the FQDN + * of the host on which the UA is running, since these are not logical + * names." + */ + std::string getFromUri() const; + + /** + * This method adds the correct scheme, hostname and append + * the ;transport= parameter at the end of the uri, in accordance with RFC3261. + * It is expected that "port" is present in the internal hostname_. + * + * @return pj_str_t "To" uri based on @param username + * @param username A string formatted as : "username" + */ + std::string getToUri(const std::string& username) const; + + /** + * In the current version of Ring, "srv" uri is obtained in the preformated + * way: hostname:port. This method adds the correct scheme and append + * the ;transport= parameter at the end of the uri, in accordance with RFC3261. + * + * @return pj_str_t "server" uri based on @param hostPort + * @param hostPort A string formatted as : "hostname:port" + */ + std::string getServerUri() const; + + /** + * Get the contact header for + * @return pj_str_t The contact header based on account information + */ + pj_str_t getContactHeader(pjsip_transport* = nullptr); + + + std::string getServiceRoute() const { + return serviceRoute_; + } + + bool hasServiceRoute() const { return not serviceRoute_.empty(); } + + virtual bool isTlsEnabled() const { + return tlsEnable_; + } + + virtual sip_utils::KeyExchangeProtocol getSrtpKeyExchange() const { + return srtpKeyExchange_; + } + + virtual bool getSrtpFallback() const { + return srtpFallback_; + } + + bool getZrtpHelloHash() const { + return zrtpHelloHash_; + } + + void setReceivedParameter(const std::string &received) { + receivedParameter_ = received; + via_addr_.host.ptr = (char *) receivedParameter_.c_str(); + via_addr_.host.slen = receivedParameter_.size(); + } + + std::string getReceivedParameter() const { + return receivedParameter_; + } + + pjsip_host_port * + getViaAddr() { + return &via_addr_; + } + + int getRPort() const { + if (rPort_ == -1) + return localPort_; + else + return rPort_; + } + + void setRPort(int rPort) { + rPort_ = rPort; + via_addr_.port = rPort; + } + + /** + * Timer used to periodically send re-register request based + * on the "Expire" sip header (or the "expire" Contact parameter) + */ + static void keepAliveRegistrationCb(pj_timer_heap_t *th, pj_timer_entry *te); + + bool isKeepAliveEnabled() const { + return keepAliveEnabled_; + } + + void setTransport(const std::shared_ptr<SipTransport>& = nullptr); + + virtual inline std::shared_ptr<SipTransport> getTransport() { + return transport_; + } + + inline pjsip_transport_type_e getTransportType() const { + return transportType_; + } + + /** + * Shortcut for SipTransport::getTransportSelector(account.getTransport()). + */ + pjsip_tpselector getTransportSelector(); + + /* Returns true if the username and/or hostname match this account */ + MatchRank matches(const std::string &username, const std::string &hostname, pjsip_endpoint *endpt, pj_pool_t *pool) const; + + /** + * Presence management + */ + SIPPresence * getPresence() const; + + /** + * Activate the module. + * @param function Publish or subscribe to enable + * @param enable Flag + */ + void enablePresence(const bool& enable); + /** + * Activate the publish/subscribe. + * @param enable Flag + */ + void supportPresence(int function, bool enable); + + void scheduleReregistration(pjsip_endpoint *endpt); + + /** + * Implementation of Account::newOutgoingCall() + * Note: keep declaration before newOutgoingCall template. + */ + std::shared_ptr<Call> newOutgoingCall(const std::string& toUrl); + + /** + * Create outgoing SIPCall. + * @param[in] toUrl The address to call + * @return std::shared_ptr<T> A shared pointer on the created call. + * The type of this instance is given in template argument. + * This type can be any base class of SIPCall class (included). + */ + template <class T=SIPCall> + std::shared_ptr<enable_if_base_of<T, SIPCall> > + newOutgoingCall(const std::string& toUrl); + + /** + * Create incoming SIPCall. + * @param[in] from The origin uri of the call + * @return std::shared_ptr<T> A shared pointer on the created call. + * The type of this instance is given in template argument. + * This type can be any base class of SIPCall class (included). + */ + std::shared_ptr<SIPCall> + newIncomingCall(const std::string& from); + + void onRegister(pjsip_regc_cbparam *param); + + private: + void doRegister1_(); + void doRegister2_(); + + /** + * Set the internal state for this account, mainly used to manage account details from the client application. + * @param The map containing the account information. + */ + void setAccountDetails(const std::map<std::string, std::string> &details); + + NON_COPYABLE(SIPAccount); + + std::shared_ptr<Call> newRegisteredAccountCall(const std::string& id, + const std::string& toUrl); + + /** + * Start a SIP Call + * @param call The current call + * @return true if all is correct + */ + bool SIPStartCall(std::shared_ptr<SIPCall>& call); + + void usePublishedAddressPortInVIA(); + void useUPnPAddressPortInVIA(); + bool fullMatch(const std::string &username, const std::string &hostname, pjsip_endpoint *endpt, pj_pool_t *pool) const; + bool userMatch(const std::string &username) const; + bool hostnameMatch(const std::string &hostname, pjsip_endpoint *endpt, pj_pool_t *pool) const; + bool proxyMatch(const std::string &hostname, pjsip_endpoint *endpt, pj_pool_t *pool) const; + + bool isSrtpEnabled() const { + return srtpKeyExchange_ != sip_utils::KeyExchangeProtocol::NONE; + } + + /** + * Callback called by the transport layer when the registration + * transport state changes. + */ + virtual void onTransportStateChanged(pjsip_transport_state state, const pjsip_transport_state_info *info); + + struct { + pj_bool_t active; /**< Flag of reregister status. */ + pj_timer_entry timer; /**< Timer for reregistration. */ + void *reg_tp; /**< Transport for registration. */ + unsigned attempt_cnt; /**< Attempt counter. */ + } auto_rereg_; /**< Reregister/reconnect data. */ + + std::uniform_int_distribution<decltype(pj_time_val::msec)> delay10ZeroDist_ {-10000, 10000}; + std::uniform_int_distribution<decltype(pj_time_val::msec)> delay10PosDist_ {0, 10000}; + + static void autoReregTimerCb(pj_timer_heap_t *th, pj_timer_entry *te); + + /** + * Map of credential for this account + */ + std::vector< std::map<std::string, std::string > > credentials_; + + std::shared_ptr<SipTransport> transport_ {}; + + std::shared_ptr<TlsListener> tlsListener_ {}; + + /** + * Transport type used for this sip account. Currently supported types: + * PJSIP_TRANSPORT_UNSPECIFIED + * PJSIP_TRANSPORT_UDP + * PJSIP_TRANSPORT_TLS + */ + pjsip_transport_type_e transportType_ {PJSIP_TRANSPORT_UNSPECIFIED}; + +#if HAVE_TLS + + static const CipherArray TLSv1_DEFAULT_CIPHER_LIST; + static const CipherArray SSLv3_DEFAULT_CIPHER_LIST; + static const CipherArray SSLv23_DEFAULT_CIPHER_LIST; + + /** + * Maps a string description of the SSL method + * to the corresponding enum value in pjsip_ssl_method. + * @param method The string representation + * @return pjsip_ssl_method The corresponding value in the enum + */ + static pjsip_ssl_method sslMethodStringToPjEnum(const std::string& method); + + /** + * Initializes tls settings from configuration file. + */ + void initTlsConfiguration(); + + /** + * PJSIP aborts if the string length of our cipher list is too + * great, so this function forces our cipher list to fit this constraint. + */ + void trimCiphers(); + +#endif + + /** + * Initializes STUN config from the config file + */ + void initStunConfiguration(); + + /** + * If username is not provided, as it happens for Direct ip calls, + * fetch the Real Name field of the user that is currently + * running this program. + * @return std::string The login name under which Ring is running. + */ + static std::string getLoginName(); + + /** + * Maps require port via UPnP + */ + bool mapPortUPnP(); + + /** + * Resolved IP of hostname_ (for registration) + */ + IpAddr hostIp_; + + /** + * The pjsip client registration information + */ + pjsip_regc *regc_; + + /** + * To check if the account is registered + */ + bool bRegister_; + + /** + * Network settings + */ + int registrationExpire_; + + /** + * Optional list of SIP service this + */ + std::string serviceRoute_; + + + /** + * Credential information stored for further registration. + */ + std::vector<pjsip_cred_info> cred_; + + /** + * The TLS settings, used only if tls is chosen as a sip transport. + */ + pjsip_tls_setting tlsSetting_; + + /** + * Allocate a vector to be used by pjsip to store the supported ciphers on this system. + */ + CipherArray ciphers_; + + /** + * Determine if STUN public address resolution is required to register this account. In this case a + * STUN server hostname must be specified. + */ + bool stunEnabled_ {false}; + + /** + * The stun server hostname (optional), used to provide the public IP address in case the softphone + * stay behind a NAT. + */ + std::string stunServer_ {}; + + /** + * The STUN server name (hostname) + */ + pj_str_t stunServerName_ {nullptr, 0}; + + /** + * The STUN server port + */ + pj_uint16_t stunPort_ {PJ_STUN_PORT}; + + /** + * Local port to whih this account is bound + */ + pj_uint16_t localPort_ {sip_utils::DEFAULT_SIP_PORT}; + + /** + * The TLS listener port + */ + pj_uint16_t tlsListenerPort_ {sip_utils::DEFAULT_SIP_TLS_PORT}; + + bool tlsEnable_ {false}; + std::string tlsMethod_; + std::string tlsCiphers_; + std::string tlsServerName_; + bool tlsVerifyServer_; + bool tlsVerifyClient_; + bool tlsRequireClientCertificate_; + std::string tlsNegotiationTimeoutSec_; + + /** + * Specifies the type of key exchange used for SRTP (sdes/zrtp), if any. + * This only determine if the media channel is secured. + */ + sip_utils::KeyExchangeProtocol srtpKeyExchange_ {sip_utils::KeyExchangeProtocol::NONE}; + + /** + * Determine if the softphone should fallback on non secured media channel if SRTP negotiation fails. + * Make sure other SIP endpoints share the same behavior since it could result in encrypted data to be + * played through the audio device. + */ + bool srtpFallback_ {}; + + /** + * Determine if the SAS sould be displayed on client side. SAS is a 4-charcter string + * that end users should verbaly validate to ensure the channel is secured. Used especially + * to prevent man-in-the-middle attack. + */ + bool zrtpDisplaySas_; + + /** + * Only display SAS 4-character string once at the begining of the call. + */ + bool zrtpDisplaySasOnce_; + + bool zrtpHelloHash_; + bool zrtpNotSuppWarning_; + + /** + * Details about the registration state. + * This is a protocol Code:Description pair. + */ + std::pair<int, std::string> registrationStateDetailed_; + + /** + * Determine if the keep alive timer will be activated or not + */ + bool keepAliveEnabled_; + + /** + * Timer used to regularrly send re-register request based + * on the "Expire" sip header (or the "expire" Contact parameter) + */ + pj_timer_entry keepAliveTimer_; + std::uniform_int_distribution<decltype(pj_timer_entry::id)> timerIdDist_ {}; + + /** + * Once enabled, this variable tells if the keepalive timer is activated + * for this accout + */ + bool keepAliveTimerActive_; + + /** + * Optional: "received" parameter from VIA header + */ + std::string receivedParameter_; + + /** + * Optional: "rport" parameter from VIA header + */ + int rPort_; + + /** + * Optional: via_addr construct from received parameters + */ + pjsip_host_port via_addr_; + + char contactBuffer_[PJSIP_MAX_URL_SIZE]; + pj_str_t contact_; + int contactRewriteMethod_; + bool allowViaRewrite_; + /* Undocumented feature in pjsip, this can == 2 */ + int allowContactRewrite_; + bool contactOverwritten_; + pjsip_transport *via_tp_; + + /** + * Presence data structure + */ + SIPPresence * presence_; + + /** + * SIP port actually used, + * this holds the actual port used for SIP, which may not be the port + * selected in the configuration in the case that UPnP is used and the + * configured port is already used by another client + */ + pj_uint16_t publishedPortUsed_ {sip_utils::DEFAULT_SIP_PORT}; +}; + +} // namespace ring + +#endif diff --git a/src/sip/sipaccountbase.cpp b/src/sip/sipaccountbase.cpp new file mode 100644 index 0000000000..6af744aca8 --- /dev/null +++ b/src/sip/sipaccountbase.cpp @@ -0,0 +1,285 @@ +/* + * Copyright (C) 2014-2015 Savoir-Faire Linux Inc. + * + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + + +#include "sipaccountbase.h" +#include "sipvoiplink.h" + +#ifdef RING_VIDEO +#include "libav_utils.h" +#endif + +#include "account_schema.h" +#include "manager.h" + +#include "config/yamlparser.h" +#include <yaml-cpp/yaml.h> + +#include "client/signal.h" +#include "string_utils.h" + +#include <type_traits> + +namespace ring { + +SIPAccountBase::SIPAccountBase(const std::string& accountID) + : Account(accountID), link_(getSIPVoIPLink()) +{} + +SIPAccountBase::~SIPAccountBase() {} + +template <typename T> +static void +validate(std::string &member, const std::string ¶m, const T& valid) +{ + const auto begin = std::begin(valid); + const auto end = std::end(valid); + if (find(begin, end, param) != end) + member = param; + else + RING_ERR("Invalid parameter \"%s\"", param.c_str()); +} + +static void +updateRange(uint16_t min, uint16_t max, std::pair<uint16_t, uint16_t> &range) +{ + if (min > 0 and (max > min) and max <= SIPAccountBase::MAX_PORT - 2) { + range.first = min; + range.second = max; + } +} + +static void +unserializeRange(const YAML::Node &node, const char *minKey, const char *maxKey, std::pair<uint16_t, uint16_t> &range) +{ + int tmpMin = 0; + int tmpMax = 0; + yaml_utils::parseValue(node, minKey, tmpMin); + yaml_utils::parseValue(node, maxKey, tmpMax); + updateRange(tmpMin, tmpMax, range); +} + +static void +addRangeToDetails(std::map<std::string, std::string> &a, const char *minKey, + const char *maxKey, + const std::pair<uint16_t, uint16_t> &range) +{ + a.emplace(minKey, ring::to_string(range.first)); + a.emplace(maxKey, ring::to_string(range.second)); +} + +template <typename T> +static void +parseInt(const std::map<std::string, std::string> &details, const char *key, T &i) +{ + const auto iter = details.find(key); + if (iter == details.end()) { + RING_ERR("Couldn't find key %s", key); + return; + } + i = atoi(iter->second.c_str()); +} + +void SIPAccountBase::serialize(YAML::Emitter &out) +{ + Account::serialize(out); + + out << YAML::Key << Conf::AUDIO_PORT_MAX_KEY << YAML::Value << audioPortRange_.second; + out << YAML::Key << Conf::AUDIO_PORT_MIN_KEY << YAML::Value << audioPortRange_.first; + out << YAML::Key << Conf::DTMF_TYPE_KEY << YAML::Value << dtmfType_; + out << YAML::Key << Conf::INTERFACE_KEY << YAML::Value << interface_; + out << YAML::Key << Conf::PUBLISH_ADDR_KEY << YAML::Value << publishedIpAddress_; + out << YAML::Key << Conf::PUBLISH_PORT_KEY << YAML::Value << publishedPort_; + out << YAML::Key << Conf::SAME_AS_LOCAL_KEY << YAML::Value << publishedSameasLocal_; + + out << YAML::Key << VIDEO_ENABLED_KEY << YAML::Value << videoEnabled_; + out << YAML::Key << Conf::VIDEO_PORT_MAX_KEY << YAML::Value << videoPortRange_.second; + out << YAML::Key << Conf::VIDEO_PORT_MIN_KEY << YAML::Value << videoPortRange_.first; +} + +void SIPAccountBase::serializeTls(YAML::Emitter &out) +{ + out << YAML::Key << Conf::CALIST_KEY << YAML::Value << tlsCaListFile_; + out << YAML::Key << Conf::CERTIFICATE_KEY << YAML::Value << tlsCertificateFile_; + out << YAML::Key << Conf::TLS_PASSWORD_KEY << YAML::Value << tlsPassword_; + out << YAML::Key << Conf::PRIVATE_KEY_KEY << YAML::Value << tlsPrivateKeyFile_; +} + +void SIPAccountBase::unserialize(const YAML::Node &node) +{ + using yaml_utils::parseValue; + using yaml_utils::parseVectorMap; + + Account::unserialize(node); + + parseValue(node, VIDEO_ENABLED_KEY, videoEnabled_); + + parseValue(node, Conf::INTERFACE_KEY, interface_); + parseValue(node, Conf::SAME_AS_LOCAL_KEY, publishedSameasLocal_); + std::string publishedIpAddress; + parseValue(node, Conf::PUBLISH_ADDR_KEY, publishedIpAddress); + IpAddr publishedIp = publishedIpAddress; + if (publishedIp and not publishedSameasLocal_) + setPublishedAddress(publishedIp); + + int port = sip_utils::DEFAULT_SIP_PORT; + parseValue(node, Conf::PUBLISH_PORT_KEY, port); + publishedPort_ = port; + + parseValue(node, Conf::DTMF_TYPE_KEY, dtmfType_); + + // get tls submap + const auto &tlsMap = node[Conf::TLS_KEY]; + parseValue(tlsMap, Conf::CERTIFICATE_KEY, tlsCertificateFile_); + parseValue(tlsMap, Conf::CALIST_KEY, tlsCaListFile_); + parseValue(tlsMap, Conf::TLS_PASSWORD_KEY, tlsPassword_); + parseValue(tlsMap, Conf::PRIVATE_KEY_KEY, tlsPrivateKeyFile_); + + unserializeRange(node, Conf::AUDIO_PORT_MIN_KEY, Conf::AUDIO_PORT_MAX_KEY, audioPortRange_); + unserializeRange(node, Conf::VIDEO_PORT_MIN_KEY, Conf::VIDEO_PORT_MAX_KEY, videoPortRange_); +} + +void SIPAccountBase::setAccountDetails(const std::map<std::string, std::string> &details) +{ + Account::setAccountDetails(details); + + parseBool(details, Conf::CONFIG_VIDEO_ENABLED, videoEnabled_); + + // general sip settings + parseString(details, Conf::CONFIG_LOCAL_INTERFACE, interface_); + parseBool(details, Conf::CONFIG_PUBLISHED_SAMEAS_LOCAL, publishedSameasLocal_); + parseString(details, Conf::CONFIG_PUBLISHED_ADDRESS, publishedIpAddress_); + parseInt(details, Conf::CONFIG_PUBLISHED_PORT, publishedPort_); + + parseString(details, Conf::CONFIG_ACCOUNT_DTMF_TYPE, dtmfType_); + + int tmpMin = -1; + parseInt(details, Conf::CONFIG_ACCOUNT_AUDIO_PORT_MIN, tmpMin); + int tmpMax = -1; + parseInt(details, Conf::CONFIG_ACCOUNT_AUDIO_PORT_MAX, tmpMax); + updateRange(tmpMin, tmpMax, audioPortRange_); +#ifdef RING_VIDEO + tmpMin = -1; + parseInt(details, Conf::CONFIG_ACCOUNT_VIDEO_PORT_MIN, tmpMin); + tmpMax = -1; + parseInt(details, Conf::CONFIG_ACCOUNT_VIDEO_PORT_MAX, tmpMax); + updateRange(tmpMin, tmpMax, videoPortRange_); +#endif + + // TLS + parseString(details, Conf::CONFIG_TLS_CA_LIST_FILE, tlsCaListFile_); + parseString(details, Conf::CONFIG_TLS_CERTIFICATE_FILE, tlsCertificateFile_); + parseString(details, Conf::CONFIG_TLS_PRIVATE_KEY_FILE, tlsPrivateKeyFile_); + parseString(details, Conf::CONFIG_TLS_PASSWORD, tlsPassword_); +} + +std::map<std::string, std::string> +SIPAccountBase::getAccountDetails() const +{ + auto a = Account::getAccountDetails(); + a.emplace(Conf::CONFIG_VIDEO_ENABLED, videoEnabled_ ? TRUE_STR : FALSE_STR); + + addRangeToDetails(a, Conf::CONFIG_ACCOUNT_AUDIO_PORT_MIN, Conf::CONFIG_ACCOUNT_AUDIO_PORT_MAX, audioPortRange_); +#ifdef RING_VIDEO + addRangeToDetails(a, Conf::CONFIG_ACCOUNT_VIDEO_PORT_MIN, Conf::CONFIG_ACCOUNT_VIDEO_PORT_MAX, videoPortRange_); +#endif + + a.emplace(Conf::CONFIG_ACCOUNT_DTMF_TYPE, dtmfType_); + a.emplace(Conf::CONFIG_LOCAL_INTERFACE, interface_); + a.emplace(Conf::CONFIG_PUBLISHED_PORT, ring::to_string(publishedPort_)); + a.emplace(Conf::CONFIG_PUBLISHED_SAMEAS_LOCAL, publishedSameasLocal_ ? TRUE_STR : FALSE_STR); + a.emplace(Conf::CONFIG_PUBLISHED_ADDRESS, publishedIpAddress_); + + a.emplace(Conf::CONFIG_TLS_CA_LIST_FILE, tlsCaListFile_); + a.emplace(Conf::CONFIG_TLS_CERTIFICATE_FILE, tlsCertificateFile_); + a.emplace(Conf::CONFIG_TLS_PRIVATE_KEY_FILE, tlsPrivateKeyFile_); + a.emplace(Conf::CONFIG_TLS_PASSWORD, tlsPassword_); + return a; +} + +std::map<std::string, std::string> +SIPAccountBase::getVolatileAccountDetails() const +{ + auto a = Account::getVolatileAccountDetails(); + + // replace value from Account for IP2IP + if (isIP2IP()) + a[Conf::CONFIG_ACCOUNT_REGISTRATION_STATUS] = "READY"; + + a.emplace(Conf::CONFIG_TRANSPORT_STATE_CODE, ring::to_string(transportStatus_)); + a.emplace(Conf::CONFIG_TRANSPORT_STATE_DESC, transportError_); + return a; +} + +auto +SIPAccountBase::getPortsReservation() noexcept -> decltype(getPortsReservation()) +{ + // Note: static arrays are zero-initialized + static std::remove_reference<decltype(getPortsReservation())>::type portsInUse; + return portsInUse; +} + +// returns even number in range [lower, upper] +uint16_t +SIPAccountBase::acquireRandomEvenPort(const std::pair<uint16_t, uint16_t>& range) const +{ + std::uniform_int_distribution<uint16_t> dist(range.first/2, range.second/2); + uint16_t result; + + do { + result = 2 * dist(rand_); + } while (getPortsReservation()[result / 2]); + + getPortsReservation()[result / 2] = true; + return result; +} + +void +SIPAccountBase::releasePort(uint16_t port) noexcept +{ + getPortsReservation()[port / 2] = false; +} + +uint16_t +SIPAccountBase::generateAudioPort() const +{ + return acquireRandomEvenPort(audioPortRange_); +} + +#ifdef RING_VIDEO +uint16_t +SIPAccountBase::generateVideoPort() const +{ + return acquireRandomEvenPort(videoPortRange_); +} +#endif + +} // namespace ring diff --git a/src/sip/sipaccountbase.h b/src/sip/sipaccountbase.h new file mode 100644 index 0000000000..0874f81cf4 --- /dev/null +++ b/src/sip/sipaccountbase.h @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2014-2015 Savoir-Faire Linux Inc. + * + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef SIPACCOUNTBASE_H +#define SIPACCOUNTBASE_H + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "account.h" + +#include "sip_utils.h" +#include "ip_utils.h" +#include "noncopyable.h" + +#include <pjsip/sip_types.h> + +#include <array> +#include <vector> +#include <map> +#include <memory> + +namespace ring { + +namespace Conf { + // SIP specific configuration keys + const char *const INTERFACE_KEY = "interface"; + const char *const PORT_KEY = "port"; + const char *const PUBLISH_ADDR_KEY = "publishAddr"; + const char *const PUBLISH_PORT_KEY = "publishPort"; + const char *const SAME_AS_LOCAL_KEY = "sameasLocal"; + const char *const DTMF_TYPE_KEY = "dtmfType"; + const char *const SERVICE_ROUTE_KEY = "serviceRoute"; + const char *const PRESENCE_ENABLED_KEY = "presenceEnabled"; + const char *const PRESENCE_PUBLISH_SUPPORTED_KEY = "presencePublishSupported"; + const char *const PRESENCE_SUBSCRIBE_SUPPORTED_KEY = "presenceSubscribeSupported"; + const char *const PRESENCE_STATUS_KEY = "presenceStatus"; + const char *const PRESENCE_NOTE_KEY = "presenceNote"; + + // TODO: write an object to store tls params which implement serializable + const char *const TLS_KEY = "tls"; + const char *const TLS_PORT_KEY = "tlsPort"; + const char *const CERTIFICATE_KEY = "certificate"; + const char *const CALIST_KEY = "calist"; + const char *const CIPHERS_KEY = "ciphers"; + const char *const TLS_ENABLE_KEY = "enable"; + const char *const METHOD_KEY = "method"; + const char *const TIMEOUT_KEY = "timeout"; + const char *const TLS_PASSWORD_KEY = "password"; + const char *const PRIVATE_KEY_KEY = "privateKey"; + const char *const REQUIRE_CERTIF_KEY = "requireCertif"; + const char *const SERVER_KEY = "server"; + const char *const VERIFY_CLIENT_KEY = "verifyClient"; + const char *const VERIFY_SERVER_KEY = "verifyServer"; + + const char *const STUN_ENABLED_KEY = "stunEnabled"; + const char *const STUN_SERVER_KEY = "stunServer"; + const char *const CRED_KEY = "credential"; + const char *const AUDIO_PORT_MIN_KEY = "audioPortMin"; + const char *const AUDIO_PORT_MAX_KEY = "audioPortMax"; + const char *const VIDEO_PORT_MIN_KEY = "videoPortMin"; + const char *const VIDEO_PORT_MAX_KEY = "videoPortMax"; +} + +typedef std::vector<pj_ssl_cipher> CipherArray; + +class SIPVoIPLink; +class SIPCall; + +/** + * @file sipaccount.h + * @brief A SIP Account specify SIP specific functions and object = SIPCall/SIPVoIPLink) + */ + +enum class MatchRank {NONE, PARTIAL, FULL}; + +class SIPAccountBase : public Account { +public: + constexpr static const char * const OVERRTP_STR = "overrtp"; + constexpr static const char * const SIPINFO_STR = "sipinfo"; + constexpr static unsigned MAX_PORT {65536}; + constexpr static unsigned HALF_MAX_PORT {MAX_PORT / 2}; + + /** + * Constructor + * @param accountID The account identifier + */ + SIPAccountBase(const std::string& accountID); + + virtual ~SIPAccountBase(); + + /** + * Create incoming SIPCall. + * @param[in] id The ID of the call + * @return std::shared_ptr<T> A shared pointer on the created call. + * The type of this instance is given in template argument. + * This type can be any base class of SIPCall class (included). + */ + virtual std::shared_ptr<SIPCall> + newIncomingCall(const std::string& from) = 0; + + virtual bool isStunEnabled() const { + return false; + } + + virtual pj_str_t getStunServerName() const { return pj_str_t {nullptr, 0}; }; + + virtual pj_uint16_t getStunPort() const { return 0; }; + + virtual std::string getDtmfType() const { + return dtmfType_; + } + + /** + * Determine if TLS is enabled for this account. TLS provides a secured channel for + * SIP signalization. It is independant than the media encription provided by SRTP or ZRTP. + */ + virtual bool isTlsEnabled() const { + return false; + } + + /** + * Get the local interface name on which this account is bound. + */ + const std::string& getLocalInterface() const { + return interface_; + } + + /** + * Get the public IP address set by the user for this account. + * If this setting is not provided, the local bound adddress + * will be used. + * @return std::string The public IPv4 or IPv6 address formatted in standard notation. + */ + std::string getPublishedAddress() const { + return publishedIpAddress_; + } + + IpAddr getPublishedIpAddress() const { + return publishedIp_; + } + + void setPublishedAddress(const IpAddr& ip_addr) { + publishedIp_ = ip_addr; + publishedIpAddress_ = ip_addr.toString(); + } + + /** + * Get the published port, which is the port to be advertised as the port + * for the chosen SIP transport. + * @return pj_uint16 The port used for that account + */ + pj_uint16_t getPublishedPort() const { + return (pj_uint16_t) publishedPort_; + } + + /** + * Set the published port, which is the port to be advertised as the port + * for the chosen SIP transport. + * @pram port The port used by this account. + */ + void setPublishedPort(pj_uint16_t port) { + publishedPort_ = port; + } + + /** + * Get a flag which determine the usage in sip headers of either the local + * IP address and port (_localAddress and localPort_) or to an address set + * manually (_publishedAddress and publishedPort_). + */ + bool getPublishedSameasLocal() const { + return publishedSameasLocal_; + } + + virtual sip_utils::KeyExchangeProtocol getSrtpKeyExchange() const = 0; + + virtual bool getSrtpFallback() const = 0; + + /** + * Get the contact header for + * @return pj_str_t The contact header based on account information + */ + virtual pj_str_t getContactHeader(pjsip_transport* = nullptr) = 0; + + virtual std::string getToUri(const std::string& username) const = 0; + + /** + * Socket port generators for media + * Note: given ports are application wide, a port cannot be given again + * by any account instances until it's released by the static method + * releasePort(). + */ + uint16_t generateAudioPort() const; +#ifdef RING_VIDEO + uint16_t generateVideoPort() const; +#endif + static void releasePort(uint16_t port) noexcept; + +protected: + virtual void serialize(YAML::Emitter &out); + virtual void serializeTls(YAML::Emitter &out); + virtual void unserialize(const YAML::Node &node); + + virtual void setAccountDetails(const std::map<std::string, std::string> &details); + + virtual std::map<std::string, std::string> getAccountDetails() const; + + /** + * Retrieve volatile details such as recent registration errors + * @return std::map< std::string, std::string > The account volatile details + */ + virtual std::map<std::string, std::string> getVolatileAccountDetails() const; + + /** + * Voice over IP Link contains a listener thread and calls + */ + std::shared_ptr<SIPVoIPLink> link_; + + /** + * interface name on which this account is bound + */ + std::string interface_ {"default"}; + + /** + * Flag which determine if localIpAddress_ or publishedIpAddress_ is used in + * sip headers + */ + bool publishedSameasLocal_ {true}; + + /** + * Published IP address, used only if defined by the user in account + * configuration + */ + IpAddr publishedIp_ {}; + + std::string publishedIpAddress_ {}; + + /** + * Published port, used only if defined by the user + */ + pj_uint16_t publishedPort_ {sip_utils::DEFAULT_SIP_PORT}; + + std::string tlsCaListFile_; + std::string tlsCertificateFile_; + std::string tlsPrivateKeyFile_; + std::string tlsPassword_; + + /** + * DTMF type used for this account SIPINFO or RTP + */ + std::string dtmfType_ {OVERRTP_STR}; + + pj_status_t transportStatus_ {PJSIP_SC_TRYING}; + std::string transportError_ {}; + + /* + * Port range for audio RTP ports + */ + std::pair<uint16_t, uint16_t> audioPortRange_ {16384, 32766}; + + /** + * Port range for video RTP ports + */ + std::pair<uint16_t, uint16_t> videoPortRange_ {49152, (MAX_PORT) - 2}; + + static std::array<bool, HALF_MAX_PORT>& getPortsReservation() noexcept; + uint16_t acquireRandomEvenPort(const std::pair<uint16_t, uint16_t>& range) const; + +private: + NON_COPYABLE(SIPAccountBase); + +}; + +} // namespace ring + +#endif diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp new file mode 100644 index 0000000000..16babd4d1d --- /dev/null +++ b/src/sip/sipcall.cpp @@ -0,0 +1,978 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Alexandre Bourget <alexandre.bourget@savoirfairelinux.com> + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * Author: Laurielle Lea <laurielle.lea@savoirfairelinux.com> + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "call_factory.h" +#include "sipcall.h" +#include "sipaccount.h" // for SIPAccount::ACCOUNT_TYPE +#include "sipaccountbase.h" +#include "sipvoiplink.h" +#include "sdes_negotiator.h" +#include "logger.h" // for _debug +#include "sdp.h" +#include "manager.h" +#include "array_size.h" +#include "string_utils.h" +#include "upnp/upnp_control.h" + +#include "audio/audio_rtp_session.h" + +#include "system_codec_container.h" + +#if HAVE_INSTANT_MESSAGING +#include "im/instant_messaging.h" +#endif + +#include "dring/call_const.h" +#include "client/signal.h" + +#ifdef RING_VIDEO +#include "client/videomanager.h" +#include "video/video_rtp_session.h" +#include "dring/videomanager_interface.h" +#include <chrono> +#endif + +namespace ring { + +#ifdef RING_VIDEO +static DeviceParams +getVideoSettings() +{ + const auto& videomon = ring::getVideoDeviceMonitor(); + return videomon.getDeviceParams(videomon.getDefaultDevice()); +} +#endif + +static constexpr int DEFAULT_ICE_INIT_TIMEOUT {10}; // seconds +static constexpr int DEFAULT_ICE_NEGO_TIMEOUT {60}; // seconds + +// SDP media Ids +static constexpr int SDP_AUDIO_MEDIA_ID {0}; +static constexpr int SDP_VIDEO_MEDIA_ID {1}; + +// ICE components Id used on SIP +static constexpr int ICE_AUDIO_RTP_COMPID {0}; +static constexpr int ICE_AUDIO_RTCP_COMPID {1}; +static constexpr int ICE_VIDEO_RTP_COMPID {2}; +static constexpr int ICE_VIDEO_RTCP_COMPID {3}; + +const char* const SIPCall::LINK_TYPE = SIPAccount::ACCOUNT_TYPE; + +static void +dtmfSend(SIPCall &call, char code, const std::string &dtmf) +{ + if (dtmf == SIPAccount::OVERRTP_STR) { + RING_WARN("DTMF over RTP not supported yet"); + return; + } else if (dtmf != SIPAccount::SIPINFO_STR) { + RING_WARN("Unknown DTMF type %s, defaulting to %s instead", + dtmf.c_str(), SIPAccount::SIPINFO_STR); + } // else : dtmf == SIPINFO + + int duration = Manager::instance().voipPreferences.getPulseLength(); + char dtmf_body[1000]; + const char *normal_str= "Signal=%c\r\nDuration=%d\r\n"; + const char *flash_str = "Signal=%d\r\nDuration=%d\r\n"; + const char *str; + + // handle flash code + if (code == '!') { + str = flash_str; + code = 16; + } else { + str = normal_str; + } + + snprintf(dtmf_body, sizeof dtmf_body - 1, str, code, duration); + call.sendSIPInfo(dtmf_body, "dtmf-relay"); +} + +SIPCall::SIPCall(SIPAccountBase& account, const std::string& id, Call::CallType type) + : Call(account, id, type) + , avformatrtp_(new AudioRtpSession(id)) +#ifdef RING_VIDEO + // The ID is used to associate video streams to calls + , videortp_(id, getVideoSettings()) +#endif + , sdp_(new Sdp(id)) +{ + if (account.getUPnPActive()) + upnp_.reset(new upnp::Controller()); +} + +SIPCall::~SIPCall() +{ + setTransport({}); + inv.reset(); // prevents callback usage +} + +SIPAccountBase& +SIPCall::getSIPAccount() const +{ + return static_cast<SIPAccountBase&>(getAccount()); +} + +void +SIPCall::setCallMediaLocal(const pj_sockaddr& localIP) +{ + setLocalIp(localIP); + + if (getLocalAudioPort() == 0 +#ifdef RING_VIDEO + || getLocalVideoPort() == 0 +#endif + ) + generateMediaPorts(); +} + +void +SIPCall::generateMediaPorts() +{ + auto& account = getSIPAccount(); + + // Reference: http://www.cs.columbia.edu/~hgs/rtp/faq.html#ports + // We only want to set ports to new values if they haven't been set + const unsigned callLocalAudioPort = account.generateAudioPort(); + if (getLocalAudioPort() != 0) + account.releasePort(getLocalAudioPort()); + setLocalAudioPort(callLocalAudioPort); + sdp_->setLocalPublishedAudioPort(callLocalAudioPort); + +#ifdef RING_VIDEO + // https://projects.savoirfairelinux.com/issues/17498 + const unsigned int callLocalVideoPort = account.generateVideoPort(); + if (getLocalVideoPort() != 0) + account.releasePort(getLocalVideoPort()); + // this should already be guaranteed by SIPAccount + assert(getLocalAudioPort() != callLocalVideoPort); + setLocalVideoPort(callLocalVideoPort); + sdp_->setLocalPublishedVideoPort(callLocalVideoPort); +#endif +} + +void SIPCall::setContactHeader(pj_str_t *contact) +{ + pj_strcpy(&contactHeader_, contact); +} + +void +SIPCall::setTransport(const std::shared_ptr<SipTransport>& t) +{ + const auto list_id = reinterpret_cast<uintptr_t>(this); + if (transport_) + transport_->removeStateListener(list_id); + transport_ = t; + + if (transport_) { + std::weak_ptr<SIPCall> wthis_ = std::static_pointer_cast<SIPCall>(shared_from_this()); + + // listen for transport destruction + transport_->addStateListener(list_id, + [wthis_, t, list_id] (pjsip_transport_state state, + const pjsip_transport_state_info*) + { + if (auto this_ = wthis_.lock()) { + // end the call if the SIP transport is shut down + if (not SipTransport::isAlive(t, state)) { + RING_WARN("Ending call because underlying SIP transport was closed"); + Manager::instance().callFailure(*this_); + this_->removeCall(); + } + } else // should not happen + t->removeStateListener(list_id); + }); + } +} + +/** + * Send a reINVITE inside an active dialog to modify its state + * Local SDP session should be modified before calling this method + */ + +int +SIPCall::SIPSessionReinvite() +{ + // Generate new ports to receive the new media stream + // LibAV doesn't discriminate SSRCs and will be confused about Seq changes on a given port + generateMediaPorts(); + sdp_->clearIce(); + auto& acc = getSIPAccount(); + sdp_->createOffer(acc.getActiveAccountCodecInfoList(MEDIA_AUDIO), + acc.getActiveAccountCodecInfoList(acc.isVideoEnabled() ? MEDIA_VIDEO : MEDIA_NONE), + acc.getSrtpKeyExchange(), + getState() == Call::HOLD); + if (initIceTransport(true)) + setupLocalSDPFromIce(); + + pjmedia_sdp_session *local_sdp = sdp_->getLocalSdpSession(); + + pjsip_tx_data *tdata; + + if (local_sdp and inv and inv->pool_prov + and pjsip_inv_reinvite(inv.get(), NULL, local_sdp, &tdata) == PJ_SUCCESS) { + if (pjsip_inv_send_msg(inv.get(), tdata) == PJ_SUCCESS) + return PJ_SUCCESS; + else + inv.reset(); + } + + return !PJ_SUCCESS; +} + +void +SIPCall::sendSIPInfo(const char *const body, const char *const subtype) +{ + if (not inv or not inv->dlg) + throw VoipLinkException("Couldn't get invite dialog"); + + pj_str_t methodName = CONST_PJ_STR("INFO"); + pjsip_method method; + pjsip_method_init_np(&method, &methodName); + + /* Create request message. */ + pjsip_tx_data *tdata; + + if (pjsip_dlg_create_request(inv->dlg, &method, -1, &tdata) != PJ_SUCCESS) { + RING_ERR("Could not create dialog"); + return; + } + + /* Create "application/<subtype>" message body. */ + pj_str_t content; + pj_cstr(&content, body); + const pj_str_t type = CONST_PJ_STR("application"); + pj_str_t pj_subtype; + pj_cstr(&pj_subtype, subtype); + tdata->msg->body = pjsip_msg_body_create(tdata->pool, &type, &pj_subtype, &content); + + if (tdata->msg->body == NULL) + pjsip_tx_data_dec_ref(tdata); + else + pjsip_dlg_send_request(inv->dlg, tdata, getSIPVoIPLink()->getModId(), NULL); +} + +void +SIPCall::updateSDPFromSTUN() +{ + RING_WARN("SIPCall::updateSDPFromSTUN() not implemented", __func__); +} + +void SIPCall::answer() +{ + auto& account = getSIPAccount(); + + if (not inv) + throw VoipLinkException("No invite session for this call"); + + if (!inv->neg) { + RING_WARN("Negotiator is NULL, we've received an INVITE without an SDP"); + pjmedia_sdp_session *dummy = 0; + getSIPVoIPLink()->createSDPOffer(inv.get(), &dummy); + + if (account.isStunEnabled()) + updateSDPFromSTUN(); + } + + pj_str_t contact(account.getContactHeader(transport_ ? transport_->get() : nullptr)); + setContactHeader(&contact); + + pjsip_tx_data *tdata; + if (!inv->last_answer) + throw std::runtime_error("Should only be called for initial answer"); + + // answer with SDP if no SDP was given in initial invite (i.e. inv->neg is NULL) + if (pjsip_inv_answer(inv.get(), PJSIP_SC_OK, NULL, !inv->neg ? sdp_->getLocalSdpSession() : NULL, &tdata) != PJ_SUCCESS) + throw std::runtime_error("Could not init invite request answer (200 OK)"); + + // contactStr must stay in scope as long as tdata + if (contactHeader_.slen) { + RING_DBG("Answering with contact header: %.*s", contactHeader_.slen, contactHeader_.ptr); + sip_utils::addContactHeader(&contactHeader_, tdata); + } + + if (pjsip_inv_send_msg(inv.get(), tdata) != PJ_SUCCESS) { + inv.reset(); + throw std::runtime_error("Could not send invite request answer (200 OK)"); + } + + setConnectionState(CONNECTED); + setState(ACTIVE); +} + +static void +sendEndSessionMsg(pjsip_inv_session* inv, int status, const pj_str_t* contact_str) +{ + pjsip_tx_data* tdata = nullptr; + if (pjsip_inv_end_session(inv, status, nullptr, &tdata) != PJ_SUCCESS || !tdata) + return; + + sip_utils::addContactHeader(contact_str, tdata); + + if (pjsip_inv_send_msg(inv, tdata) != PJ_SUCCESS) { + RING_ERR("pjsip error: failed to sind end session message"); + return; + } +} + +void +SIPCall::hangup(int reason) +{ + // Stop all RTP streams + stopAllMedia(); + + if (not inv or not inv->dlg) { + removeCall(); + throw VoipLinkException("No invite session for this call"); + } + + pjsip_route_hdr *route = inv->dlg->route_set.next; + while (route and route != &inv->dlg->route_set) { + char buf[1024]; + int printed = pjsip_hdr_print_on(route, buf, sizeof(buf)); + + if (printed >= 0) { + buf[printed] = '\0'; + RING_DBG("Route header %s", buf); + } + + route = route->next; + } + + const int status = reason ? reason : + inv->state <= PJSIP_INV_STATE_EARLY and inv->role != PJSIP_ROLE_UAC ? + PJSIP_SC_CALL_TSX_DOES_NOT_EXIST : + inv->state >= PJSIP_INV_STATE_DISCONNECTED ? PJSIP_SC_DECLINE : + 0; + const pj_str_t contactStr(getSIPAccount().getContactHeader(transport_ ? transport_->get() : nullptr)); + + // Notify the peer + sendEndSessionMsg(inv.get(), status, &contactStr); + + inv.reset(); + removeCall(); +} + +void +SIPCall::refuse() +{ + if (!isIncoming() or getConnectionState() == Call::CONNECTED or !inv) + return; + + stopAllMedia(); + + const pj_str_t contactStr(getSIPAccount().getContactHeader(transport_ ? transport_->get() : nullptr)); + + // Notify the peer + sendEndSessionMsg(inv.get(), PJSIP_SC_DECLINE, &contactStr); + + inv.reset(); + removeCall(); +} + +static void +transfer_client_cb(pjsip_evsub *sub, pjsip_event *event) +{ + auto link = getSIPVoIPLink(); + if (not link) { + RING_ERR("no more VoIP link"); + return; + } + + auto mod_ua_id = link->getModId(); + + switch (pjsip_evsub_get_state(sub)) { + case PJSIP_EVSUB_STATE_ACCEPTED: + if (!event) + return; + + pj_assert(event->type == PJSIP_EVENT_TSX_STATE && event->body.tsx_state.type == PJSIP_EVENT_RX_MSG); + break; + + case PJSIP_EVSUB_STATE_TERMINATED: + pjsip_evsub_set_mod_data(sub, mod_ua_id, NULL); + break; + + case PJSIP_EVSUB_STATE_ACTIVE: { + if (!event) + return; + + pjsip_rx_data* r_data = event->body.rx_msg.rdata; + + if (!r_data) + return; + + std::string request(pjsip_rx_data_get_info(r_data)); + + pjsip_status_line status_line = { 500, *pjsip_get_status_text(500) }; + + if (!r_data->msg_info.msg) + return; + + if (r_data->msg_info.msg->line.req.method.id == PJSIP_OTHER_METHOD and + request.find("NOTIFY") != std::string::npos) { + pjsip_msg_body *body = r_data->msg_info.msg->body; + + if (!body) + return; + + if (pj_stricmp2(&body->content_type.type, "message") or + pj_stricmp2(&body->content_type.subtype, "sipfrag")) + return; + + if (pjsip_parse_status_line((char*) body->data, body->len, &status_line) != PJ_SUCCESS) + return; + } + + if (!r_data->msg_info.cid) + return; + + auto call = static_cast<SIPCall *>(pjsip_evsub_get_mod_data(sub, mod_ua_id)); + if (!call) + return; + + if (status_line.code / 100 == 2) { + pjsip_tx_data *tdata; + + if (!call->inv) + return; + + if (pjsip_inv_end_session(call->inv.get(), PJSIP_SC_GONE, NULL, &tdata) == PJ_SUCCESS) { + if (pjsip_inv_send_msg(call->inv.get(), tdata) != PJ_SUCCESS) + call->inv.reset(); + } + + Manager::instance().hangupCall(call->getCallId()); + pjsip_evsub_set_mod_data(sub, mod_ua_id, NULL); + } + + break; + } + + default: + break; + } +} + +bool +SIPCall::transferCommon(pj_str_t *dst) +{ + if (not inv or not inv->dlg) + return false; + + pjsip_evsub_user xfer_cb; + pj_bzero(&xfer_cb, sizeof(xfer_cb)); + xfer_cb.on_evsub_state = &transfer_client_cb; + + pjsip_evsub *sub; + + if (pjsip_xfer_create_uac(inv->dlg, &xfer_cb, &sub) != PJ_SUCCESS) + return false; + + /* Associate this voiplink of call with the client subscription + * We can not just associate call with the client subscription + * because after this function, we can no find the cooresponding + * voiplink from the call any more. But the voiplink is useful! + */ + pjsip_evsub_set_mod_data(sub, getSIPVoIPLink()->getModId(), this); + + /* + * Create REFER request. + */ + pjsip_tx_data *tdata; + + if (pjsip_xfer_initiate(sub, dst, &tdata) != PJ_SUCCESS) + return false; + + /* Send. */ + if (pjsip_xfer_send_request(sub, tdata) != PJ_SUCCESS) + return false; + + return true; +} + +void +SIPCall::transfer(const std::string& to) +{ + auto& account = getSIPAccount(); + + stopRecording(); + + std::string toUri; + pj_str_t dst = { 0, 0 }; + + toUri = account.getToUri(to); + pj_cstr(&dst, toUri.c_str()); + RING_DBG("Transferring to %.*s", dst.slen, dst.ptr); + + if (!transferCommon(&dst)) + throw VoipLinkException("Couldn't transfer"); +} + +bool +SIPCall::attendedTransfer(const std::string& to) +{ + const auto toCall = Manager::instance().callFactory.getCall<SIPCall>(to); + if (!toCall) + return false; + + if (not toCall->inv or not toCall->inv->dlg) + return false; + + pjsip_dialog *target_dlg = toCall->inv->dlg; + pjsip_uri *uri = (pjsip_uri*) pjsip_uri_get_uri(target_dlg->remote.info->uri); + + char str_dest_buf[PJSIP_MAX_URL_SIZE * 2] = { '<' }; + pj_str_t dst = { str_dest_buf, 1 }; + + dst.slen += pjsip_uri_print(PJSIP_URI_IN_REQ_URI, uri, str_dest_buf + 1, sizeof(str_dest_buf) - 1); + dst.slen += pj_ansi_snprintf(str_dest_buf + dst.slen, + sizeof(str_dest_buf) - dst.slen, + "?" + "Replaces=%.*s" + "%%3Bto-tag%%3D%.*s" + "%%3Bfrom-tag%%3D%.*s>", + (int)target_dlg->call_id->id.slen, + target_dlg->call_id->id.ptr, + (int)target_dlg->remote.info->tag.slen, + target_dlg->remote.info->tag.ptr, + (int)target_dlg->local.info->tag.slen, + target_dlg->local.info->tag.ptr); + + return transferCommon(&dst); +} + +void +SIPCall::onhold() +{ + if (not setState(Call::HOLD)) + return; + + stopAllMedia(); + + if (getConnectionState() == Call::CONNECTED) { + if (SIPSessionReinvite() != PJ_SUCCESS) + RING_WARN("Reinvite failed"); + } +} + +void +SIPCall::offhold() +{ + auto& account = getSIPAccount(); + + try { + if (account.isStunEnabled()) + internalOffHold([&] { updateSDPFromSTUN(); }); + else + internalOffHold([] {}); + + } catch (const SdpException &e) { + RING_ERR("%s", e.what()); + throw VoipLinkException("SDP issue in offhold"); + } +} + +void +SIPCall::internalOffHold(const std::function<void()>& sdp_cb) +{ + if (not setState(Call::ACTIVE)) + return; + + sdp_cb(); + + if (getConnectionState() == Call::CONNECTED) { + if (SIPSessionReinvite() != PJ_SUCCESS) { + RING_WARN("Reinvite failed, resuming hold"); + onhold(); + } + } +} + +void +SIPCall::switchInput(const std::string& resource) +{ +#ifdef RING_VIDEO + videoInput_ = resource; + if (SIPSessionReinvite() != PJ_SUCCESS) + RING_WARN("Reinvite failed"); +#endif +} + +void +SIPCall::peerHungup() +{ + // Stop all RTP streams + stopAllMedia(); + + if (not inv) + throw VoipLinkException("No invite session for this call"); + + // User hangup current call. Notify peer + pjsip_tx_data *tdata = NULL; + + if (pjsip_inv_end_session(inv.get(), 404, NULL, &tdata) != PJ_SUCCESS || !tdata) + return; + + if (auto ret = pjsip_inv_send_msg(inv.get(), tdata) == PJ_SUCCESS) { + // Make sure user data is NULL in callbacks + inv->mod_data[getSIPVoIPLink()->getModId()] = NULL; + } else { + inv.reset(); + sip_utils::sip_strerror(ret); + } +} + +void +SIPCall::carryingDTMFdigits(char code) +{ + dtmfSend(*this, code, getSIPAccount().getDtmfType()); +} + +#if HAVE_INSTANT_MESSAGING +void +SIPCall::sendTextMessage(const std::string &message, const std::string &from) +{ + if (not inv) + throw VoipLinkException("No invite session for this call"); + + /* Send IM message */ + InstantMessaging::UriList list; + InstantMessaging::UriEntry entry; + entry[InstantMessaging::IM_XML_URI] = std::string("\"" + from + "\""); // add double quotes for xml formating + list.push_front(entry); + auto msg = InstantMessaging::appendUriList(message, list); + InstantMessaging::send_sip_message(inv.get(), getCallId(), msg); +} +#endif // HAVE_INSTANT_MESSAGING + +void +SIPCall::onServerFailure(int code) +{ + Manager::instance().callFailure(*this, code); + removeCall(); +} + +void +SIPCall::onClosed() +{ + Manager::instance().peerHungupCall(*this); + removeCall(); + Manager::instance().checkAudio(); +} + +void +SIPCall::onAnswered() +{ + if (getConnectionState() != Call::CONNECTED) { + setConnectionState(Call::CONNECTED); + setState(Call::ACTIVE); + Manager::instance().peerAnsweredCall(*this); + } +} + +void +SIPCall::onPeerRinging() +{ + setConnectionState(Call::RINGING); + Manager::instance().peerRingingCall(*this); +} + +void +SIPCall::setupLocalSDPFromIce() +{ + if (not iceTransport_) { + RING_WARN("null icetransport: no attributes added to SDP"); + return; + } + + if (waitForIceInitialization(DEFAULT_ICE_INIT_TIMEOUT) <= 0) { + RING_ERR("ICE init failed, ICE will not be used for medias"); + return; + } + + sdp_->addIceAttributes(iceTransport_->getLocalAttributes()); + + // Add video and audio channels + sdp_->addIceCandidates(SDP_AUDIO_MEDIA_ID, iceTransport_->getLocalCandidates(ICE_AUDIO_RTP_COMPID)); + sdp_->addIceCandidates(SDP_AUDIO_MEDIA_ID, iceTransport_->getLocalCandidates(ICE_AUDIO_RTCP_COMPID)); +#ifdef RING_VIDEO + sdp_->addIceCandidates(SDP_VIDEO_MEDIA_ID, iceTransport_->getLocalCandidates(ICE_VIDEO_RTP_COMPID)); + sdp_->addIceCandidates(SDP_VIDEO_MEDIA_ID, iceTransport_->getLocalCandidates(ICE_VIDEO_RTCP_COMPID)); +#endif +} + +std::vector<IceCandidate> +SIPCall::getAllRemoteCandidates() +{ + std::vector<IceCandidate> rem_candidates; + + auto addSDPCandidates = [this](unsigned sdpMediaId, + std::vector<IceCandidate>& out) { + IceCandidate cand; + for (auto& line : sdp_->getIceCandidates(sdpMediaId)) { + if (iceTransport_->getCandidateFromSDP(line, cand)) { + RING_ERR("Remote candidate: %s", line.c_str()); + out.emplace_back(cand); + } + } + }; + + addSDPCandidates(SDP_AUDIO_MEDIA_ID, rem_candidates); +#ifdef RING_VIDEO + addSDPCandidates(SDP_VIDEO_MEDIA_ID, rem_candidates); +#endif + + return rem_candidates; +} + +bool +SIPCall::startIce() +{ + if (not iceTransport_) + return false; + if (iceTransport_->isStarted() || iceTransport_->isCompleted()) { + RING_DBG("ICE already started"); + return true; + } + auto rem_ice_attrs = sdp_->getIceAttributes(); + if (rem_ice_attrs.ufrag.empty() or rem_ice_attrs.pwd.empty()) { + RING_ERR("ICE empty attributes"); + return false; + } + return iceTransport_->start(rem_ice_attrs, getAllRemoteCandidates()); +} + +void +SIPCall::startAllMedia() +{ + if (isSecure() && not transport_->isSecure()) { + RING_ERR("Can't perform secure call over insecure SIP transport"); + Manager::instance().callFailure(*this); + removeCall(); + return; + } + auto slots = sdp_->getMediaSlots(); + unsigned ice_comp_id = 0; + bool peer_holding {true}; + + for (const auto& slot : slots) { + const auto& local = slot.first; + const auto& remote = slot.second; + + if (local.type != remote.type) { + RING_ERR("Inconsistent media types between local and remote for SDP media slot"); + continue; + } + + RtpSession* rtp = local.type == MEDIA_AUDIO + ? static_cast<RtpSession*>(avformatrtp_.get()) +#ifdef RING_VIDEO + : static_cast<RtpSession*>(&videortp_); +#else + : nullptr; +#endif + + if (not rtp) + continue; + + if (!local.codec) { + RING_ERR("No codec defined in local media slot"); + continue; + } + if (!remote.codec) { + RING_ERR("No codec defined in remote media slot"); + continue; + } + + peer_holding &= remote.holding; + + if (isSecure() && (not local.crypto || not remote.crypto)) { + RING_ERR("Can't perform secure call over insecure RTP transport"); + continue; + } + +#ifdef RING_VIDEO + if (local.type == MEDIA_VIDEO) { + if (videoInput_.empty()) + videoInput_ = videoManager.videoDeviceMonitor.getMRLForDefaultDevice(); + videortp_.switchInput(videoInput_); + } +#endif + rtp->updateMedia(remote, local); + if (isIceRunning()) { + rtp->start(newIceSocket(ice_comp_id + 0), + newIceSocket(ice_comp_id + 1)); + ice_comp_id += 2; + } else + rtp->start(); + } + if (peerHolding_ != peer_holding) { + peerHolding_ = peer_holding; + emitSignal<DRing::CallSignal::PeerHold>(getCallId(), peerHolding_); + } +} + +void +SIPCall::stopAllMedia() +{ + RING_DBG("SIPCall %s: stopping all medias", getCallId().c_str()); + avformatrtp_->stop(); +#ifdef RING_VIDEO + videortp_.stop(); +#endif +} + +void +SIPCall::onMediaUpdate() +{ + RING_WARN("SIPCall::onMediaUpdate"); + stopAllMedia(); + openPortsUPnP(); + + if (startIce()) { + auto this_ = std::static_pointer_cast<SIPCall>(shared_from_this()); + auto ice = iceTransport_; + auto iceTimeout = std::chrono::steady_clock::now() + std::chrono::seconds(10); + Manager::instance().addTask([=] { + if (ice != this_->iceTransport_) { + RING_ERR("ICE transport replaced"); + return false; + } + /* First step: wait for an ICE transport for SIP channel */ + if (this_->iceTransport_->isFailed() or std::chrono::steady_clock::now() >= iceTimeout) { + RING_DBG("ice init failed (or timeout)"); + this_->setConnectionState(Call::DISCONNECTED); + Manager::instance().callFailure(*this_); // signal client + this_->removeCall(); + return false; + } + if (not this_->iceTransport_->isRunning()) + return true; + startAllMedia(); + return false; + }); + } else { + RING_WARN("Starting medias without ICE"); + startAllMedia(); + } +} + +void +SIPCall::onReceiveOffer(const pjmedia_sdp_session* offer) +{ + sdp_->clearIce(); + auto& acc = getSIPAccount(); + sdp_->receiveOffer(offer, + acc.getActiveAccountCodecInfoList(MEDIA_AUDIO), + acc.getActiveAccountCodecInfoList(acc.isVideoEnabled() ? MEDIA_VIDEO : MEDIA_NONE), + acc.getSrtpKeyExchange(), + getState() == Call::HOLD + ); + auto ice_attrs = Sdp::getIceAttributes(offer); + if (not ice_attrs.ufrag.empty() and not ice_attrs.pwd.empty()) { + if (initIceTransport(false)) + setupLocalSDPFromIce(); + } + sdp_->startNegotiation(); + pjsip_inv_set_sdp_answer(inv.get(), sdp_->getLocalSdpSession()); +} + +void +SIPCall::openPortsUPnP() +{ + if (upnp_) { + /** + * Try to open the desired ports with UPnP, + * if they are used, use the alternative port and update the SDP session with the newly chosen port(s) + * + * TODO: the inital ports were chosen from the list of available ports and were marked as used + * the newly selected port should possibly be checked against the list of used ports and marked + * as used, the old port should be "released" + */ + RING_DBG("UPnP: openening ports via upnp for SDP session."); + uint16_t audio_port_used; + if (upnp_->addAnyMapping(sdp_->getLocalAudioPort(), upnp::PortType::UDP, true, &audio_port_used)) { + uint16_t control_port_used; + if (upnp_->addAnyMapping(sdp_->getLocalAudioControlPort(), upnp::PortType::UDP, true, &control_port_used)) { + sdp_->setLocalPublishedAudioPorts(audio_port_used, control_port_used); + } + } +#ifdef RING_VIDEO + uint16_t video_port_used; + if (upnp_->addAnyMapping(sdp_->getLocalVideoPort(), upnp::PortType::UDP, true, &video_port_used)) { + uint16_t control_port_used; + if (upnp_->addAnyMapping(sdp_->getLocalVideoControlPort(), upnp::PortType::UDP, true, &control_port_used)) { + sdp_->setLocalPublishedVideoPorts(video_port_used, control_port_used); + } + } +#endif + } +} + +std::map<std::string, std::string> +SIPCall::getDetails() const +{ + auto details = Call::getDetails(); + details.emplace(DRing::Call::Details::PEER_HOLDING, peerHolding_ ? TRUE_STR : FALSE_STR); + if (transport_ and transport_->isSecure()) { + const auto& tlsInfos = transport_->getTlsInfos(); + auto cipher = pj_ssl_cipher_name(tlsInfos.cipher); + if (tlsInfos.cipher and not cipher) + RING_WARN("Unknown cipher: %d", tlsInfos.cipher); + details.emplace(DRing::Call::Details::TLS_CIPHER, cipher ? cipher : ""); + details.emplace(DRing::Call::Details::TLS_PEER_CERT, tlsInfos.peerCert.toString()); + } + return details; +} + +void +SIPCall::setSecure(bool sec) +{ + if (srtpEnabled_) + return; + if (sec && getConnectionState() != DISCONNECTED) { + throw std::runtime_error("Can't enable security since call is already connected"); + } + srtpEnabled_ = sec; +} + +void +SIPCall::InvSessionDeleter::operator ()(pjsip_inv_session* inv) const noexcept +{ + // prevent this from getting accessed in callbacks + // RING_WARN: this is not thread-safe! + inv->mod_data[getSIPVoIPLink()->getModId()] = nullptr; +} + +} // namespace ring diff --git a/src/sip/sipcall.h b/src/sip/sipcall.h new file mode 100644 index 0000000000..e70dbd7e75 --- /dev/null +++ b/src/sip/sipcall.h @@ -0,0 +1,262 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Bourget <alexandre.bourget@savoirfairelinux.com> + * Author: Yan Morin <yan.morin@savoirfairelinux.com> + * Author : Laurielle Lea <laurielle.lea@savoirfairelinux.com> + * Author : Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __SIPCALL_H__ +#define __SIPCALL_H__ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "call.h" +#include "sip_utils.h" + +#ifdef RING_VIDEO +#include "media/video/video_rtp_session.h" +#endif + +#include "noncopyable.h" + +#include "pjsip/sip_config.h" + +#include <memory> + +struct pjsip_evsub; +struct pjsip_inv_session; +struct pjmedia_sdp_session; + +namespace ring { + +class Sdp; +class SIPAccountBase; +class SipTransport; +class AudioRtpSession; + +namespace upnp { +class Controller; +} + +/** + * @file sipcall.h + * @brief SIPCall are SIP implementation of a normal Call + */ +class SIPCall : public Call +{ + public: + static const char* const LINK_TYPE; + + protected: + /** + * Constructor (protected) + * @param id The call identifier + * @param type The type of the call. Could be Incoming + * Outgoing + */ + SIPCall(SIPAccountBase& account, const std::string& id, Call::CallType type); + + public: + /** + * Destructor + */ + ~SIPCall(); + + /** + * Return the SDP's manager of this call + */ + Sdp& getSDP() { + return *sdp_; + } + + /** + * Returns a pointer to the AudioRtpSession object + */ + AudioRtpSession& getAVFormatRTP() const { + return *avformatrtp_; + } + +#ifdef RING_VIDEO + /** + * Returns a pointer to the VideoRtp object + */ + video::VideoRtpSession& getVideoRtp () { + return videortp_; + } +#endif + + /** + * The invite session to be reused in case of transfer + */ + struct InvSessionDeleter { + void operator()(pjsip_inv_session*) const noexcept; + }; + + std::unique_ptr<pjsip_inv_session, InvSessionDeleter> inv; + + void setSecure(bool sec); + + bool isSecure() const { + return srtpEnabled_; + } + + void setCallMediaLocal(const pj_sockaddr& localIP); + + void generateMediaPorts(); + + void setContactHeader(pj_str_t *contact); + + void setTransport(const std::shared_ptr<SipTransport>& t); + + inline const std::shared_ptr<SipTransport>& getTransport() { + return transport_; + } + + void sendSIPInfo(const char *const body, const char *const subtype); + + void answer(); + + void hangup(int reason); + + void refuse(); + + void transfer(const std::string& to); + + bool attendedTransfer(const std::string& to); + + void onhold(); + + void offhold(); + + void switchInput(const std::string& resource); + + void peerHungup(); + + void carryingDTMFdigits(char code); + +#if HAVE_INSTANT_MESSAGING + void sendTextMessage(const std::string& message, + const std::string& from); +#endif + + SIPAccountBase& getSIPAccount() const; + + void updateSDPFromSTUN(); + + /** + * Tell the user that the call is ringing + * @param + */ + void onPeerRinging(); + + /** + * Tell the user that the call was answered + * @param + */ + void onAnswered(); + + /** + * Handling 5XX/6XX error + * @param + */ + void onServerFailure(int code=0); + + /** + * Peer close the connection + * @param + */ + void onClosed(); + + void setupLocalSDPFromIce(); + + bool startIce(); + + void startAllMedia(); + + void onMediaUpdate(); + + void onReceiveOffer(const pjmedia_sdp_session *offer); + + void openPortsUPnP(); + + virtual std::map<std::string, std::string> getDetails() const; + + private: + NON_COPYABLE(SIPCall); + + void stopAllMedia(); + + /** + * Transfer method used for both type of transfer + */ + bool transferCommon(pj_str_t *dst); + + void internalOffHold(const std::function<void()> &SDPUpdateFunc); + + int SIPSessionReinvite(); + + std::vector<IceCandidate> getAllRemoteCandidates(); + + std::unique_ptr<AudioRtpSession> avformatrtp_; + +#ifdef RING_VIDEO + /** + * Video Rtp Session factory + */ + video::VideoRtpSession videortp_; + + std::string videoInput_; +#endif + + bool srtpEnabled_ {false}; + + /** + * Hold the transport used for SIP communication. + * Will be different from the account registration transport for + * non-IP2IP calls. + */ + std::shared_ptr<SipTransport> transport_ {}; + + /** + * The SDP session + */ + std::unique_ptr<Sdp> sdp_; + bool peerHolding_ {false}; + + char contactBuffer_[PJSIP_MAX_URL_SIZE] {}; + pj_str_t contactHeader_ {contactBuffer_, 0}; + + std::unique_ptr<ring::upnp::Controller> upnp_; +}; + +} // namespace ring + +#endif // __SIPCALL_H__ diff --git a/src/sip/sippresence.cpp b/src/sip/sippresence.cpp new file mode 100644 index 0000000000..871ce2ec2e --- /dev/null +++ b/src/sip/sippresence.cpp @@ -0,0 +1,537 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Patrick Keroulas <patrick.keroulas@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + + +#include "sippresence.h" + +#include "logger.h" +#include "manager.h" +#include "sipaccount.h" +#include "sip_utils.h" +#include "pres_sub_server.h" +#include "pres_sub_client.h" +#include "sipvoiplink.h" +#include "client/signal.h" + +#include <thread> +#include <sstream> + +#define MAX_N_SUB_SERVER 50 +#define MAX_N_SUB_CLIENT 50 + +namespace ring { + +SIPPresence::SIPPresence(SIPAccount *acc) + : publish_sess_() + , status_data_() + , enabled_(false) + , publish_supported_(false) + , subscribe_supported_(false) + , status_(false) + , note_(" ") + , acc_(acc) + , sub_server_list_() //IP2IP context + , sub_client_list_() + , cp_() + , pool_() +{ + /* init pool */ + pj_caching_pool_init(&cp_, &pj_pool_factory_default_policy, 0); + pool_ = pj_pool_create(&cp_.factory, "pres", 1000, 1000, NULL); + if (!pool_) + throw std::runtime_error("Could not allocate pool for presence"); + + /* init default status */ + updateStatus(false, " "); +} + + +SIPPresence::~SIPPresence() +{ + /* Flush the lists */ + // FIXME: Can't destroy/unsubscribe buddies properly. + // Is the transport usable when the account is being destroyed? + //for (const auto & c : sub_client_list_) + // delete(c); + sub_client_list_.clear(); + sub_server_list_.clear(); + + pj_pool_release(pool_); + pj_caching_pool_destroy(&cp_); +} + +SIPAccount *SIPPresence::getAccount() const +{ + return acc_; +} + +pjsip_pres_status * SIPPresence::getStatus() +{ + return &status_data_; +} + +int SIPPresence::getModId() const +{ + return getSIPVoIPLink()->getModId(); +} + +pj_pool_t* SIPPresence::getPool() const +{ + return pool_; +} + +void SIPPresence::enable(bool enabled) +{ + enabled_ = enabled; +} + +void SIPPresence::support(int function, bool supported) +{ + if (function == PRESENCE_FUNCTION_PUBLISH) + publish_supported_ = supported; + else if (function == PRESENCE_FUNCTION_SUBSCRIBE) + subscribe_supported_ = supported; +} + +bool SIPPresence::isSupported(int function) +{ + if (function == PRESENCE_FUNCTION_PUBLISH) + return publish_supported_; + else if (function == PRESENCE_FUNCTION_SUBSCRIBE) + return subscribe_supported_; + + return false; +} + +void SIPPresence::updateStatus(bool status, const std::string ¬e) +{ + //char* pj_note = (char*) pj_pool_alloc(pool_, "50"); + + pjrpid_element rpid = { + PJRPID_ELEMENT_TYPE_PERSON, + CONST_PJ_STR("0"), + PJRPID_ACTIVITY_UNKNOWN, + pj_str((char *) note.c_str()) + }; + + /* fill activity if user not available. */ + if (note == "away") + rpid.activity = PJRPID_ACTIVITY_AWAY; + else if (note == "busy") + rpid.activity = PJRPID_ACTIVITY_BUSY; + /* + else // TODO: is there any other possibilities + RING_DBG("Presence : no activity"); + */ + + pj_bzero(&status_data_, sizeof(status_data_)); + status_data_.info_cnt = 1; + status_data_.info[0].basic_open = status; + + // at most we will have 3 digits + NULL termination + char buf[4]; + pj_utoa(rand() % 1000, buf); + status_data_.info[0].id = pj_strdup3(pool_, buf); + + pj_memcpy(&status_data_.info[0].rpid, &rpid, sizeof(pjrpid_element)); + /* "contact" field is optionnal */ +} + +void SIPPresence::sendPresence(bool status, const std::string ¬e) +{ + updateStatus(status, note); + + //if ((not publish_supported_) or (not enabled_)) + // return; + + if (acc_->isIP2IP()) + notifyPresSubServer(); // to each subscribers + else + publish(this); // to the PBX server +} + + +void SIPPresence::reportPresSubClientNotification(const std::string& uri, pjsip_pres_status * status) +{ + /* Update our info. See pjsua_buddy_get_info() for additionnal ideas*/ + const std::string acc_ID = acc_->getAccountID(); + const std::string basic(status->info[0].basic_open ? "open" : "closed"); + const std::string note(status->info[0].rpid.note.ptr, status->info[0].rpid.note.slen); + RING_DBG(" Received status of PresSubClient %s(acc:%s): status=%s note=%s", uri.c_str(), acc_ID.c_str(), basic.c_str(), note.c_str()); + + if (uri == acc_->getFromUri()) { + // save the status of our own account + status_ = status->info[0].basic_open; + note_ = note; + } + // report status to client signal + emitSignal<DRing::PresenceSignal::NewBuddyNotification>(acc_ID, uri, status->info[0].basic_open, note); +} + +void SIPPresence::subscribeClient(const std::string& uri, bool flag) +{ + /* if an account has a server that doesn't support SUBSCRIBE, it's still possible + * to subscribe to someone on another server */ + /* + std::string account_host = std::string(pj_gethostname()->ptr, pj_gethostname()->slen); + std::string sub_host = sip_utils::getHostFromUri(uri); + if (((not subscribe_supported_) && (account_host == sub_host)) + or (not enabled_)) + return; + */ + + /* Check if the buddy was already subscribed */ + for (const auto & c : sub_client_list_) { + if (c->getURI() == uri) { + //RING_DBG("-PresSubClient:%s exists in the list. Replace it.", uri.c_str()); + if (flag) + c->subscribe(); + else + c->unsubscribe(); + return; + } + } + + if (sub_client_list_.size() >= MAX_N_SUB_CLIENT) { + RING_WARN("Can't add PresSubClient, max number reached."); + return; + } + + if (flag) { + PresSubClient *c = new PresSubClient(uri, this); + if (!(c->subscribe())) { + RING_WARN("Failed send subscribe."); + delete c; + } + // the buddy has to be accepted before being added in the list + } +} + +void SIPPresence::addPresSubClient(PresSubClient *c) +{ + if (sub_client_list_.size() < MAX_N_SUB_CLIENT) { + sub_client_list_.push_back(c); + RING_DBG("New Presence_subscription_client added (list[%i]).", sub_client_list_.size()); + } else { + RING_WARN("Max Presence_subscription_client is reach."); + // let the client alive //delete c; + } +} + +void SIPPresence::removePresSubClient(PresSubClient *c) +{ + RING_DBG("Remove Presence_subscription_client from the buddy list."); + sub_client_list_.remove(c); +} + +void SIPPresence::approvePresSubServer(const std::string& uri, bool flag) +{ + for (const auto & s : sub_server_list_) { + if (s->matches((char *) uri.c_str())) { + s->approve(flag); + // return; // 'return' would prevent multiple-time subscribers from spam + } + } +} + + +void SIPPresence::addPresSubServer(PresSubServer *s) +{ + if (sub_server_list_.size() < MAX_N_SUB_SERVER) { + sub_server_list_.push_back(s); + } else { + RING_WARN("Max Presence_subscription_server is reach."); + // let de server alive // delete s; + } +} + +void SIPPresence::removePresSubServer(PresSubServer *s) +{ + sub_server_list_.remove(s); + RING_DBG("Presence_subscription_server removed"); +} + +void SIPPresence::notifyPresSubServer() +{ + RING_DBG("Iterating through IP2IP Presence_subscription_server:"); + + for (const auto & s : sub_server_list_) + s->notify(); +} + +void SIPPresence::lock() +{ + mutex_.lock(); +} + +bool SIPPresence::tryLock() +{ + return mutex_.try_lock(); +} + +void SIPPresence::unlock() +{ + mutex_.unlock(); +} + +void SIPPresence::fillDoc(pjsip_tx_data *tdata, const pres_msg_data *msg_data) +{ + + if (tdata->msg->type == PJSIP_REQUEST_MSG) { + const pj_str_t STR_USER_AGENT = CONST_PJ_STR("User-Agent"); + std::string useragent(acc_->getUserAgentName()); + pj_str_t pJuseragent = pj_str((char*) useragent.c_str()); + pjsip_hdr *h = (pjsip_hdr*) pjsip_generic_string_hdr_create(tdata->pool, &STR_USER_AGENT, &pJuseragent); + pjsip_msg_add_hdr(tdata->msg, h); + } + + if (msg_data == NULL) + return; + + const pjsip_hdr *hdr; + hdr = msg_data->hdr_list.next; + + while (hdr && hdr != &msg_data->hdr_list) { + pjsip_hdr *new_hdr; + new_hdr = (pjsip_hdr*) pjsip_hdr_clone(tdata->pool, hdr); + RING_DBG("adding header", new_hdr->name.ptr); + pjsip_msg_add_hdr(tdata->msg, new_hdr); + hdr = hdr->next; + } + + if (msg_data->content_type.slen && msg_data->msg_body.slen) { + pjsip_msg_body *body; + const pj_str_t type = CONST_PJ_STR("application"); + const pj_str_t subtype = CONST_PJ_STR("pidf+xml"); + body = pjsip_msg_body_create(tdata->pool, &type, &subtype, &msg_data->msg_body); + tdata->msg->body = body; + } +} + +static const pjsip_publishc_opt my_publish_opt = {true}; // this is queue_request + +/* + * Client presence publication callback. + */ +void +SIPPresence::publish_cb(struct pjsip_publishc_cbparam *param) +{ + SIPPresence *pres = (SIPPresence*) param->token; + + if (param->code / 100 != 2 || param->status != PJ_SUCCESS) { + + pjsip_publishc_destroy(param->pubc); + pres->publish_sess_ = NULL; + std::ostringstream os; + os << param->code; + const std::string error = os.str() + " / "+ std::string(param->reason.ptr, param->reason.slen); + + if (param->status != PJ_SUCCESS) { + char errmsg[PJ_ERR_MSG_SIZE]; + pj_strerror(param->status, errmsg, sizeof(errmsg)); + RING_ERR("Client (PUBLISH) failed, status=%d, msg=%s", param->status, errmsg); + emitSignal<DRing::PresenceSignal::ServerError>( + pres->getAccount()->getAccountID(), + error, + errmsg); + + } else if (param->code == 412) { + /* 412 (Conditional Request Failed) + * The PUBLISH refresh has failed, retry with new one. + */ + RING_WARN("Publish retry."); + publish(pres); + } else if ((param->code == PJSIP_SC_BAD_EVENT) || (param->code == PJSIP_SC_NOT_IMPLEMENTED)){ //489 or 501 + RING_WARN("Client (PUBLISH) failed (%s)",error.c_str()); + + emitSignal<DRing::PresenceSignal::ServerError>( + pres->getAccount()->getAccountID(), + error, + "Publish not supported."); + + pres->getAccount()->supportPresence(PRESENCE_FUNCTION_PUBLISH, false); + } + + } else { + if (param->expiration < 1) { + /* Could happen if server "forgot" to include Expires header + * in the response. We will not renew, so destroy the pubc. + */ + pjsip_publishc_destroy(param->pubc); + pres->publish_sess_ = NULL; + } + + pres->getAccount()->supportPresence(PRESENCE_FUNCTION_PUBLISH, true); + } +} + +/* + * Send PUBLISH request. + */ +pj_status_t +SIPPresence::send_publish(SIPPresence * pres) +{ + pjsip_tx_data *tdata; + pj_status_t status; + + RING_DBG("Send PUBLISH (%s).", pres->getAccount()->getAccountID().c_str()); + + SIPAccount * acc = pres->getAccount(); + std::string contactWithAngles = acc->getFromUri(); + contactWithAngles.erase(contactWithAngles.find('>')); + int semicolon = contactWithAngles.find_first_of(":"); + std::string contactWithoutAngles = contactWithAngles.substr(semicolon + 1); +// pj_str_t contact = pj_str(strdup(contactWithoutAngles.c_str())); +// pj_memcpy(&status_data.info[0].contact, &contt, sizeof(pj_str_t));; + + /* Create PUBLISH request */ + char *bpos; + pj_str_t entity; + + status = pjsip_publishc_publish(pres->publish_sess_, PJ_TRUE, &tdata); + pj_str_t from = pj_strdup3(pres->pool_, acc->getFromUri().c_str()); + + if (status != PJ_SUCCESS) { + RING_ERR("Error creating PUBLISH request", status); + goto on_error; + } + + if ((bpos = pj_strchr(&from, '<')) != NULL) { + char *epos = pj_strchr(&from, '>'); + + if (epos - bpos < 2) { + pj_assert(!"Unexpected invalid URI"); + status = PJSIP_EINVALIDURI; + goto on_error; + } + + entity.ptr = bpos + 1; + entity.slen = epos - bpos - 1; + } else { + entity = from; + } + + /* Create and add PIDF message body */ + status = pjsip_pres_create_pidf(tdata->pool, pres->getStatus(), + &entity, &tdata->msg->body); + + pres_msg_data msg_data; + + if (status != PJ_SUCCESS) { + RING_ERR("Error creating PIDF for PUBLISH request"); + pjsip_tx_data_dec_ref(tdata); + goto on_error; + } + + pj_bzero(&msg_data, sizeof(msg_data)); + pj_list_init(&msg_data.hdr_list); + pjsip_media_type_init(&msg_data.multipart_ctype, NULL, NULL); + pj_list_init(&msg_data.multipart_parts); + + pres->fillDoc(tdata, &msg_data); + + /* Send the PUBLISH request */ + status = pjsip_publishc_send(pres->publish_sess_, tdata); + + if (status == PJ_EPENDING) { + RING_WARN("Previous request is in progress, "); + } else if (status != PJ_SUCCESS) { + RING_ERR("Error sending PUBLISH request"); + goto on_error; + } + + return PJ_SUCCESS; + +on_error: + + if (pres->publish_sess_) { + pjsip_publishc_destroy(pres->publish_sess_); + pres->publish_sess_ = NULL; + } + + return status; +} + + +/* Create client publish session */ +pj_status_t +SIPPresence::publish(SIPPresence *pres) +{ + pj_status_t status; + const pj_str_t STR_PRESENCE = CONST_PJ_STR("presence"); + SIPAccount * acc = pres->getAccount(); + pjsip_endpoint *endpt = getSIPVoIPLink()->getEndpoint(); + + /* Create and init client publication session */ + + /* Create client publication */ + status = pjsip_publishc_create(endpt, &my_publish_opt, + pres, &publish_cb, + &pres->publish_sess_); + + if (status != PJ_SUCCESS) { + pres->publish_sess_ = NULL; + RING_ERR("Failed to create a publish seesion."); + return status; + } + + /* Initialize client publication */ + pj_str_t from = pj_strdup3(pres->pool_, acc->getFromUri().c_str()); + status = pjsip_publishc_init(pres->publish_sess_, &STR_PRESENCE, &from, &from, &from, 0xFFFF); + + if (status != PJ_SUCCESS) { + RING_ERR("Failed to init a publish session"); + pres->publish_sess_ = NULL; + return status; + } + + /* Add credential for authentication */ + if (acc->hasCredentials() and pjsip_publishc_set_credentials(pres->publish_sess_, acc->getCredentialCount(), acc->getCredInfo()) != PJ_SUCCESS) { + RING_ERR("Could not initialize credentials for invite session authentication"); + return status; + } + + /* Set route-set */ + // FIXME: is this really necessary? + pjsip_regc *regc = acc->getRegistrationInfo(); + if (regc and acc->hasServiceRoute()) + pjsip_regc_set_route_set(regc, sip_utils::createRouteSet(acc->getServiceRoute(), pres->getPool())); + + /* Send initial PUBLISH request */ + status = send_publish(pres); + + if (status != PJ_SUCCESS) + return status; + + return PJ_SUCCESS; +} + +} // namespace ring diff --git a/src/sip/sippresence.h b/src/sip/sippresence.h new file mode 100644 index 0000000000..b96e4a2b4a --- /dev/null +++ b/src/sip/sippresence.h @@ -0,0 +1,258 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Patrick Keroulas <patrick.keroulas@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef SIPPRESENCE_H +#define SIPPRESENCE_H + +#include <string> +#include <list> +#include <mutex> + +#include "noncopyable.h" +#include "pjsip/sip_types.h" +#include "pjsip/sip_msg.h" +#include "pjsip/sip_multipart.h" +#include "pjsip-simple/publish.h" +#include "pjsip-simple/presence.h" +#include "pjsip-simple/rpid.h" +#include <pj/pool.h> + +#define PRESENCE_FUNCTION_PUBLISH 0 +#define PRESENCE_FUNCTION_SUBSCRIBE 1 +#define PRESENCE_LOCK_FLAG 1 +#define PRESENCE_CLIENT_LOCK_FLAG 2 + +/** + * TODO Clean this: + */ +struct pj_caching_pool; + +namespace ring { + +struct pres_msg_data { + /** + * Additional message headers as linked list. Application can add + * headers to the list by creating the header, either from the heap/pool + * or from temporary local variable, and add the header using + * linked list operation. See pjsip_apps.c for some sample codes. + */ + pjsip_hdr hdr_list; + + /** + * MIME type of optional message body. + */ + pj_str_t content_type; + + /** + * Optional message body to be added to the message, only when the + * message doesn't have a body. + */ + pj_str_t msg_body; + + /** + * Content type of the multipart body. If application wants to send + * multipart message bodies, it puts the parts in \a parts and set + * the content type in \a multipart_ctype. If the message already + * contains a body, the body will be added to the multipart bodies. + */ + pjsip_media_type multipart_ctype; + + /** + * List of multipart parts. If application wants to send multipart + * message bodies, it puts the parts in \a parts and set the content + * type in \a multipart_ctype. If the message already contains a body, + * the body will be added to the multipart bodies. + */ + pjsip_multipart_part multipart_parts; +}; + +class SIPAccount; +class PresSubClient; +class PresSubServer; + + +/** + * @file sippresence.h + * @brief A SIP Presence manages buddy subscription in both PBX and IP2IP contexts. + */ + +class SIPPresence { + + public: + + /** + * Constructor + * @param acc the associated sipaccount + */ + SIPPresence(SIPAccount * acc); + /** + * Destructor + */ + ~SIPPresence(); + + /** + * Return associated sipaccount + */ + SIPAccount * getAccount() const; + /** + * Return presence data. + */ + pjsip_pres_status * getStatus(); + /** + * Return presence module ID which is actually the same as the VOIP link + */ + int getModId() const; + /** + * Return a pool for generic functions. + */ + pj_pool_t* getPool() const; + /** + * Activate the module. + * @param enable Flag + */ + void enable(bool enabled); + /** + * Support the presence function publish/subscribe. + * @param function Publish or subscribe to enable + * @param enable Flag + */ + void support(int function, bool enabled); + /** + * Fill xml document, the header and the body + */ + void fillDoc(pjsip_tx_data *tdata, const pres_msg_data *msg_data); + /** + * Modify the presence data + * @param status is basically "open" or "close" + */ + void updateStatus(bool status, const std::string ¬e); + /** + * Send the presence data in a PUBLISH to the PBX or in a NOTIFY + * to a remote subscriber (IP2IP) + */ + void sendPresence(bool status, const std::string ¬e); + /** + * Send a signal to the client on DBus. The signal contain the status + * of a remote user. + */ + void reportPresSubClientNotification(const std::string& uri, pjsip_pres_status * status); + /** + * Send a SUBSCRIBE request to PBX/IP2IP + * @param buddyUri Remote user that we want to subscribe + */ + void subscribeClient(const std::string& uri, bool flag); + /** + * Add a buddy in the buddy list. + * @param b PresSubClient pointer + */ + void addPresSubClient(PresSubClient *b); + /** + * Remove a buddy from the list. + * @param b PresSubClient pointer + */ + void removePresSubClient(PresSubClient *b); + + /** + * IP2IP context. + * Process new subscription based on client decision. + * @param flag client decision. + * @param uri uri of the remote subscriber + */ + void approvePresSubServer(const std::string& uri, bool flag); + /** + * IP2IP context. + * Add a server associated to a subscriber in the list. + * @param s PresenceSubcription pointer. + */ + void addPresSubServer(PresSubServer *s); + /** + * IP2IP context. + * Remove a server associated to a subscriber from the list. + * @param s PresenceSubcription pointer. + */ + void removePresSubServer(PresSubServer *s); + /** + * IP2IP context. + * Iterate through the subscriber list and send NOTIFY to each. + */ + void notifyPresSubServer(); + + bool isEnabled(){ + return enabled_; + } + + bool isSupported(int function); + + std::list< PresSubClient *> getClientSubscriptions() { + return sub_client_list_; + } + + bool isOnline(){ + return status_; + } + + std::string getNote(){ + return note_; + } + + void lock(); + bool tryLock(); + void unlock(); + + private: + NON_COPYABLE(SIPPresence); + + static pj_status_t publish(SIPPresence *pres); + static void publish_cb(struct pjsip_publishc_cbparam *param); + static pj_status_t send_publish(SIPPresence *pres); + + pjsip_publishc *publish_sess_; /**< Client publication session.*/ + pjsip_pres_status status_data_; /**< Presence Data to be published.*/ + + pj_bool_t enabled_; + pj_bool_t publish_supported_; /**< the server allow for status publishing */ + pj_bool_t subscribe_supported_; /**< the server allow for buddy subscription */ + + bool status_; /**< Status received from the server*/ + std::string note_; /**< Note received from the server*/ + SIPAccount * acc_; /**< Associated SIP account. */ + std::list< PresSubServer *> sub_server_list_; /**< Subscribers list.*/ + std::list< PresSubClient *> sub_client_list_; /**< Subcribed buddy list.*/ + + std::recursive_mutex mutex_; + unsigned mutex_nesting_level_; + pj_caching_pool cp_; + pj_pool_t *pool_; +}; + +} // namespace ring + +#endif diff --git a/src/sip/siptransport.cpp b/src/sip/siptransport.cpp new file mode 100644 index 0000000000..7b0fe75a70 --- /dev/null +++ b/src/sip/siptransport.cpp @@ -0,0 +1,472 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "siptransport.h" +#include "sip_utils.h" +#include "ip_utils.h" + +#include "ringdht/sip_transport_ice.h" +#include "ringdht/sips_transport_ice.h" + +#include "array_size.h" +#include "intrin.h" +#include "sipvoiplink.h" + +#include <pjsip.h> +#include <pjsip/sip_types.h> +#if HAVE_TLS +#include <pjsip/sip_transport_tls.h> +#include <pj/ssl_sock.h> +#endif +#include <pjnath.h> +#include <pjnath/stun_config.h> +#include <pjlib.h> +#include <pjlib-util.h> + +#include <stdexcept> +#include <sstream> +#include <algorithm> + +#define RETURN_IF_FAIL(A, VAL, M, ...) if (!(A)) { RING_ERR(M, ##__VA_ARGS__); return (VAL); } + +namespace ring { + +constexpr const char* TRANSPORT_STATE_STR[] = { + "CONNECTED", "DISCONNECTED", "SHUTDOWN", "DESTROY", "UNKNOWN STATE" +}; +constexpr const size_t TRANSPORT_STATE_SZ = RING_ARRAYSIZE(TRANSPORT_STATE_STR); + +std::string +SipTransportDescr::toString() const +{ + std::stringstream ss; + ss << "{" << pjsip_transport_get_type_desc(type) << " on " << interface << ":" << listenerPort << "}"; + return ss.str(); +} + +void +SipTransport::deleteTransport(pjsip_transport* t) +{ + pjsip_transport_shutdown(t); + pjsip_transport_dec_ref(t); +} + +SipTransport::SipTransport(pjsip_transport* t) + : transport_(nullptr, deleteTransport) +{ + if (not t or pjsip_transport_add_ref(t) != PJ_SUCCESS) + throw std::runtime_error("invalid transport"); + + // Set pointer here, right after the successful pjsip_transport_add_ref + transport_.reset(t); + + RING_DBG("SipTransport@%p {tr=%p {rc=%u}}", + this, transport_.get(), pj_atomic_get(transport_->ref_cnt)); +} + +SipTransport::SipTransport(pjsip_transport* t, + const std::shared_ptr<TlsListener>& l) + : SipTransport(t) +{ + tlsListener_ = l; +} + +SipTransport::~SipTransport() +{ + RING_DBG("~SipTransport@%p {tr=%p {rc=%u}}", + this, transport_.get(), pj_atomic_get(transport_->ref_cnt)); +} + +bool +SipTransport::isAlive(UNUSED const std::shared_ptr<SipTransport>& t, + pjsip_transport_state state) +{ + return state != PJSIP_TP_STATE_DISCONNECTED +#if PJ_VERSION_NUM > (2 << 24 | 1 << 16) + && state != PJSIP_TP_STATE_SHUTDOWN + && state != PJSIP_TP_STATE_DESTROY +#else + && t && t->get() + && !t->get()->is_shutdown + && !t->get()->is_destroying +#endif + ; +} + +const char* +SipTransport::stateToStr(pjsip_transport_state state) +{ + return TRANSPORT_STATE_STR[std::min<size_t>(state, TRANSPORT_STATE_SZ-1)]; +} + +void +SipTransport::stateCallback(pjsip_transport_state state, + const pjsip_transport_state_info *info) +{ +#if HAVE_TLS + auto extInfo = static_cast<const pjsip_tls_state_info*>(info->ext_info); + if (isSecure() && extInfo && extInfo->ssl_sock_info && extInfo->ssl_sock_info->established) { + auto tlsInfo = extInfo->ssl_sock_info; + tlsInfos_.proto = tlsInfo->proto; + tlsInfos_.cipher = tlsInfo->cipher; + tlsInfos_.verifyStatus = (pj_ssl_cert_verify_flag_t)tlsInfo->verify_status; + const auto& peer_crt = tlsInfo->remote_cert_info->cert_raw; + if (peer_crt.ptr && peer_crt.slen) + tlsInfos_.peerCert = {std::vector<uint8_t>(peer_crt.ptr, peer_crt.ptr + peer_crt.slen)}; + else + tlsInfos_.peerCert = {}; + } else { + tlsInfos_.proto = PJ_SSL_SOCK_PROTO_DEFAULT; + tlsInfos_.cipher = PJ_TLS_UNKNOWN_CIPHER; + tlsInfos_.peerCert = {}; + } +#endif + + std::vector<SipTransportStateCallback> cbs; + { + std::lock_guard<std::mutex> lock(stateListenersMutex_); + cbs.reserve(stateListeners_.size()); + for (auto& l : stateListeners_) + cbs.push_back(l.second); + } + for (auto& cb : cbs) + cb(state, info); +} + +void +SipTransport::addStateListener(uintptr_t lid, SipTransportStateCallback cb) +{ + std::lock_guard<std::mutex> lock(stateListenersMutex_); + auto pair = stateListeners_.insert(std::make_pair(lid, cb)); + if (not pair.second) + pair.first->second = cb; +} + +bool +SipTransport::removeStateListener(uintptr_t lid) +{ + std::lock_guard<std::mutex> lock(stateListenersMutex_); + auto it = stateListeners_.find(lid); + if (it != stateListeners_.end()) { + stateListeners_.erase(it); + return true; + } + return false; +} + +SipTransportBroker::SipTransportBroker(pjsip_endpoint *endpt, + pj_caching_pool& cp, pj_pool_t& pool) : +cp_(cp), pool_(pool), endpt_(endpt) +{ +/*#if HAVE_DHT + pjsip_transport_register_type(PJSIP_TRANSPORT_DATAGRAM, "ICE", + pjsip_transport_get_default_port_for_type(PJSIP_TRANSPORT_UDP), + &ice_pj_transport_type_); +#endif*/ + RING_DBG("SipTransportBroker@%p", this); +} + +SipTransportBroker::~SipTransportBroker() +{ + RING_DBG("~SipTransportBroker@%p", this); + + shutdown(); + + udpTransports_.clear(); + transports_.clear(); + + RING_DBG("destroying SipTransportBroker@%p", this); +} + +void +SipTransportBroker::transportStateChanged(pjsip_transport* tp, + pjsip_transport_state state, + const pjsip_transport_state_info* info) +{ + RING_DBG("pjsip transport@%p %s -> %s", + tp, tp->info, SipTransport::stateToStr(state)); + + // First make sure that this transport is handled by us + // and remove it from any mapping if destroy pending or done. + + std::shared_ptr<SipTransport> sipTransport; + + { + std::lock_guard<std::mutex> lock(transportMapMutex_); + auto key = transports_.find(tp); + if (key == transports_.end()) { + RING_WARN("spurious pjsip transport state change"); + return; + } + + sipTransport = key->second.lock(); + +#if PJ_VERSION_NUM > (2 << 24 | 1 << 16) + bool destroyed = state == PJSIP_TP_STATE_DESTROY; +#else + bool destroyed = tp->is_destroying; +#endif + + // maps cleanup + if (destroyed) { + RING_DBG("unmap pjsip transport@%p {SipTransport@%p}", + tp, sipTransport.get()); + transports_.erase(key); + + // If UDP + const auto type = tp->key.type; + if (type == PJSIP_TRANSPORT_UDP or type == PJSIP_TRANSPORT_UDP6) { + const auto updKey = std::find_if( + udpTransports_.cbegin(), udpTransports_.cend(), + [tp](const std::pair<SipTransportDescr, pjsip_transport*>& pair) { + return pair.second == tp; + }); + if (updKey != udpTransports_.cend()) + udpTransports_.erase(updKey); + } + } + } + + // Propagate the event to the appropriate transport + // Note the SipTransport may not be in our mappings if marked as dead + if (sipTransport) + sipTransport->stateCallback(state, info); +} + +std::shared_ptr<SipTransport> +SipTransportBroker::addTransport(pjsip_transport* t) +{ + if (t) { + std::lock_guard<std::mutex> lock(transportMapMutex_); + + auto key = transports_.find(t); + if (key != transports_.end()) { + if (auto sipTr = key->second.lock()) + return sipTr; + } + + auto sipTr = std::make_shared<SipTransport>(t); + if (key != transports_.end()) + key->second = sipTr; + else + transports_.emplace(std::make_pair(t, sipTr)); + return sipTr; + } + + return nullptr; +} + +void +SipTransportBroker::shutdown() +{ + std::unique_lock<std::mutex> lock(transportMapMutex_); + for (auto& t : transports_) { + if (auto transport = t.second.lock()) { + pjsip_transport_shutdown(transport->get()); + } + } +} + +std::shared_ptr<SipTransport> +SipTransportBroker::getUdpTransport(const SipTransportDescr& descr) +{ + std::lock_guard<std::mutex> lock(transportMapMutex_); + auto itp = udpTransports_.find(descr); + if (itp != udpTransports_.end()) { + auto it = transports_.find(itp->second); + if (it != transports_.end()) { + if (auto spt = it->second.lock()) { + RING_DBG("Reusing transport %s", descr.toString().c_str()); + return spt; + } + else { + // Transport still exists but have not been destroyed yet. + RING_WARN("Recycling transport %s", descr.toString().c_str()); + auto ret = std::make_shared<SipTransport>(itp->second); + it->second = ret; + return ret; + } + } else { + RING_WARN("Cleaning up UDP transport %s", descr.toString().c_str()); + udpTransports_.erase(itp); + } + } + auto ret = createUdpTransport(descr); + if (ret) { + udpTransports_[descr] = ret->get(); + transports_[ret->get()] = ret; + } + return ret; +} + +std::shared_ptr<SipTransport> +SipTransportBroker::createUdpTransport(const SipTransportDescr& d) +{ + RETURN_IF_FAIL(d.listenerPort != 0, nullptr, "Could not determine port for this transport"); + auto family = pjsip_transport_type_get_af(d.type); + + IpAddr listeningAddress = (d.interface == ip_utils::DEFAULT_INTERFACE) ? + ip_utils::getAnyHostAddr(family) : + ip_utils::getInterfaceAddr(d.interface, family); + listeningAddress.setPort(d.listenerPort); + + RETURN_IF_FAIL(listeningAddress, nullptr, "Could not determine IP address for this transport"); + pjsip_transport *transport = nullptr; + pj_status_t status = listeningAddress.isIpv4() + ? pjsip_udp_transport_start (endpt_, &static_cast<const pj_sockaddr_in&>(listeningAddress), nullptr, 1, &transport) + : pjsip_udp_transport_start6(endpt_, &static_cast<const pj_sockaddr_in6&>(listeningAddress), nullptr, 1, &transport); + if (status != PJ_SUCCESS) { + RING_ERR("UDP IPv%s Transport did not start on %s", + listeningAddress.isIpv4() ? "4" : "6", + listeningAddress.toString(true).c_str()); + sip_utils::sip_strerror(status); + return nullptr; + } + + RING_DBG("Created UDP transport on %s : %s", d.interface.c_str(), listeningAddress.toString(true).c_str()); + auto ret = std::make_shared<SipTransport>(transport); + // dec ref because the refcount starts at 1 and SipTransport increments it ? + // pjsip_transport_dec_ref(transport); + return ret; +} + +#if HAVE_TLS +std::shared_ptr<TlsListener> +SipTransportBroker::getTlsListener(const SipTransportDescr& d, const pjsip_tls_setting* settings) +{ + RETURN_IF_FAIL(settings, nullptr, "TLS settings not specified"); + auto family = pjsip_transport_type_get_af(d.type); + + IpAddr listeningAddress = (d.interface == ip_utils::DEFAULT_INTERFACE) ? + ip_utils::getAnyHostAddr(family) : + ip_utils::getInterfaceAddr(d.interface, family); + listeningAddress.setPort(d.listenerPort); + + RETURN_IF_FAIL(listeningAddress, nullptr, "Could not determine IP address for this transport"); + RING_DBG("Creating TLS listener %s on %s...", d.toString().c_str(), listeningAddress.toString(true).c_str()); +#if 0 + RING_DBG(" ca_list_file : %s", settings->ca_list_file.ptr); + RING_DBG(" cert_file : %s", settings->cert_file.ptr); + RING_DBG(" ciphers_num : %d", settings->ciphers_num); + RING_DBG(" verify server %d client %d client_cert %d", settings->verify_server, settings->verify_client, settings->require_client_cert); + RING_DBG(" reuse_addr : %d", settings->reuse_addr); +#endif + + pjsip_tpfactory *listener = nullptr; + const pj_status_t status = pjsip_tls_transport_start2(endpt_, settings, listeningAddress.pjPtr(), nullptr, 1, &listener); + if (status != PJ_SUCCESS) { + RING_ERR("TLS listener did not start"); + sip_utils::sip_strerror(status); + return nullptr; + } + return std::make_shared<TlsListener>(listener); +} + +std::shared_ptr<SipTransport> +SipTransportBroker::getTlsTransport(const std::shared_ptr<TlsListener>& l, const IpAddr& remote) +{ + if (!l || !remote) + return nullptr; + IpAddr remoteAddr {remote}; + if (remoteAddr.getPort() == 0) + remoteAddr.setPort(pjsip_transport_get_default_port_for_type(l->get()->type)); + + RING_DBG("Get new TLS transport to %s", remoteAddr.toString(true).c_str()); + pjsip_tpselector sel {PJSIP_TPSELECTOR_LISTENER, { + .listener = l->get() + }}; + pjsip_transport *transport = nullptr; + pj_status_t status = pjsip_endpt_acquire_transport( + endpt_, + l->get()->type, + remoteAddr.pjPtr(), + remoteAddr.getLength(), + &sel, + &transport); + + if (!transport || status != PJ_SUCCESS) { + RING_ERR("Could not get new TLS transport"); + sip_utils::sip_strerror(status); + return nullptr; + } + auto ret = std::make_shared<SipTransport>(transport, l); + pjsip_transport_dec_ref(transport); + { + std::lock_guard<std::mutex> lock(transportMapMutex_); + transports_[ret->get()] = ret; + } + return ret; +} +#endif + +#if HAVE_DHT +std::shared_ptr<SipTransport> +SipTransportBroker::getIceTransport(const std::shared_ptr<IceTransport> ice, + unsigned comp_id) +{ + auto sip_ice_tr = std::unique_ptr<SipIceTransport>( + new SipIceTransport(endpt_, pool_, ice_pj_transport_type_, ice, comp_id)); + auto tr = sip_ice_tr->getTransportBase(); + auto sip_tr = std::make_shared<SipTransport>(tr); + sip_ice_tr.release(); // managed by PJSIP now + + { + std::lock_guard<std::mutex> lock(transportMapMutex_); + // we do not check for key existance as we've just created it + // (member of new SipIceTransport instance) + transports_.emplace(std::make_pair(tr, sip_tr)); + } + return sip_tr; +} + +std::shared_ptr<SipTransport> +SipTransportBroker::getTlsIceTransport(const std::shared_ptr<ring::IceTransport> ice, + unsigned comp_id, + const tls::TlsParams& params) +{ + auto sip_ice_tr = std::unique_ptr<tls::SipsIceTransport>( + new tls::SipsIceTransport(endpt_, params, ice, comp_id)); + auto tr = sip_ice_tr->getTransportBase(); + auto sip_tr = std::make_shared<SipTransport>(tr); + sip_ice_tr.release(); // managed by PJSIP now + + { + std::lock_guard<std::mutex> lock(transportMapMutex_); + // we do not check for key existance as we've just created it + // (member of new SipIceTransport instance) + transports_.emplace(std::make_pair(tr, sip_tr)); + } + return sip_tr; +} +#endif + +} // namespace ring diff --git a/src/sip/siptransport.h b/src/sip/siptransport.h new file mode 100644 index 0000000000..07e0999489 --- /dev/null +++ b/src/sip/siptransport.h @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef SIPTRANSPORT_H_ +#define SIPTRANSPORT_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sip_utils.h" + +#include "noncopyable.h" +#include "logger.h" + +#include <opendht/crypto.h> + +#include <pjsip.h> +#include <pjnath/stun_config.h> + +#include <functional> +#include <mutex> +#include <condition_variable> +#include <map> +#include <string> +#include <vector> +#include <list> +#include <memory> + +namespace ring { + +struct SipTransportDescr +{ + SipTransportDescr() {} + SipTransportDescr(pjsip_transport_type_e t) + : type(t), listenerPort(pjsip_transport_get_default_port_for_type(t)) {} + SipTransportDescr(pjsip_transport_type_e t, pj_uint16_t port, std::string i) + : type(t), listenerPort(port), interface(i) {} + + static inline pjsip_transport_type_e actualType(pjsip_transport_type_e t) { + return (t == PJSIP_TRANSPORT_START_OTHER) ? PJSIP_TRANSPORT_UDP : t; + } + + inline bool operator==(SipTransportDescr const& o) const { + return actualType(type) == actualType(o.type) + && listenerPort == o.listenerPort + && interface == o.interface; + } + + inline bool operator<(SipTransportDescr const& o) const { + return actualType(type) < actualType(o.type) + || listenerPort < o.listenerPort + || std::hash<std::string>()(interface) < std::hash<std::string>()(o.interface); + } + + std::string toString() const; + + pjsip_transport_type_e type {PJSIP_TRANSPORT_UNSPECIFIED}; + pj_uint16_t listenerPort {sip_utils::DEFAULT_SIP_PORT}; + std::string interface {"default"}; +}; + +struct TlsListener +{ + TlsListener() {} + TlsListener(pjsip_tpfactory* f) : listener(f) {} + virtual ~TlsListener() { + RING_DBG("Destroying listener"); + listener->destroy(listener); + } + pjsip_tpfactory* get() { + return listener; + } +private: + NON_COPYABLE(TlsListener); + pjsip_tpfactory* listener {nullptr}; +}; + +struct TlsInfos { + pj_ssl_cipher cipher; + pj_ssl_sock_proto proto; + pj_ssl_cert_verify_flag_t verifyStatus; + dht::crypto::Certificate peerCert; +}; + +using SipTransportStateCallback = std::function<void(pjsip_transport_state, const pjsip_transport_state_info*)>; + +/** + * SIP transport wraps pjsip_transport. + */ +class SipTransport +{ + public: + SipTransport(pjsip_transport*); + SipTransport(pjsip_transport*, const std::shared_ptr<TlsListener>&); + + ~SipTransport(); + + static const char* stateToStr(pjsip_transport_state state); + + void stateCallback(pjsip_transport_state state, const pjsip_transport_state_info *info); + + pjsip_transport* get() { + return transport_.get(); + } + + void addStateListener(uintptr_t lid, SipTransportStateCallback cb); + bool removeStateListener(uintptr_t lid); + + bool isSecure() const { + return PJSIP_TRANSPORT_IS_SECURE(transport_); + } + + const TlsInfos& getTlsInfos() const { + return tlsInfos_; + } + + static bool isAlive(const std::shared_ptr<SipTransport>&, pjsip_transport_state state); + + private: + NON_COPYABLE(SipTransport); + + static void deleteTransport(pjsip_transport* t); + + std::unique_ptr<pjsip_transport, decltype(deleteTransport)&> transport_; + std::shared_ptr<TlsListener> tlsListener_; + std::map<uintptr_t, SipTransportStateCallback> stateListeners_; + std::mutex stateListenersMutex_; + + TlsInfos tlsInfos_; +}; + +class IpAddr; +class IceTransport; +namespace tls { + struct TlsParams; +}; + +/** + * Manages the transports and receive callbacks from PJSIP + */ +class SipTransportBroker +{ +public: + SipTransportBroker(pjsip_endpoint *endpt, pj_caching_pool& cp, pj_pool_t& pool); + ~SipTransportBroker(); + + std::shared_ptr<SipTransport> getUdpTransport(const SipTransportDescr&); + +#if HAVE_TLS + std::shared_ptr<TlsListener> + getTlsListener(const SipTransportDescr&, const pjsip_tls_setting*); + + std::shared_ptr<SipTransport> + getTlsTransport(const std::shared_ptr<TlsListener>&, const IpAddr& remote); +#endif + +#if HAVE_DHT + std::shared_ptr<SipTransport> + getIceTransport(const std::shared_ptr<IceTransport>, unsigned comp_id); + + std::shared_ptr<SipTransport> + getTlsIceTransport(const std::shared_ptr<IceTransport>, unsigned comp_id, + const tls::TlsParams&); +#endif + + std::shared_ptr<SipTransport> addTransport(pjsip_transport*); + + /** + * Start gracefull shutdown procedure for all transports + */ + void shutdown(); + + void transportStateChanged(pjsip_transport*, pjsip_transport_state, const pjsip_transport_state_info*); + +private: + NON_COPYABLE(SipTransportBroker); + + /** + * Create SIP UDP transport from account's setting + * @param account The account for which a transport must be created. + * @param IP protocol version to use, can be pj_AF_INET() or pj_AF_INET6() + * @return a pointer to the new transport + */ + std::shared_ptr<SipTransport> createUdpTransport(const SipTransportDescr&); + + /** + * List of transports so we can bubble the events up. + */ + std::map<pjsip_transport*, std::weak_ptr<SipTransport>> transports_ {}; + std::mutex transportMapMutex_ {}; + + /** + * Transports are stored in this map in order to retreive them in case + * several accounts would share the same port number. + */ + std::map<SipTransportDescr, pjsip_transport*> udpTransports_; + + /** + * Storage for SIP/ICE transport instances. + */ +#if HAVE_DHT + int ice_pj_transport_type_ {PJSIP_TRANSPORT_START_OTHER}; +#endif + + pj_caching_pool& cp_; + pj_pool_t& pool_; + pjsip_endpoint *endpt_; + +}; + +} // namespace ring + +#endif // SIPTRANSPORT_H_ diff --git a/src/sip/sipvoiplink.cpp b/src/sip/sipvoiplink.cpp new file mode 100644 index 0000000000..dff75967e8 --- /dev/null +++ b/src/sip/sipvoiplink.cpp @@ -0,0 +1,1358 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Yun Liu <yun.liu@savoirfairelinux.com> + * Author: Pierre-Luc Bacon <pierre-luc.bacon@savoirfairelinux.com> + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "sipvoiplink.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "sdp.h" +#include "sipcall.h" +#include "sipaccount.h" + +#if HAVE_DHT +#include "ringdht/ringaccount.h" +#endif + +#include "manager.h" +#if HAVE_SDES +#include "sdes_negotiator.h" +#endif + +#if HAVE_INSTANT_MESSAGING +#include "im/instant_messaging.h" +#endif + +#include "system_codec_container.h" +#include "audio/audio_rtp_session.h" + +#ifdef RING_VIDEO +#include "video/video_rtp_session.h" +#include "client/videomanager.h" +#endif + +#include "client/signal.h" + +#include "pres_sub_server.h" + +#include "array_size.h" +#include "ip_utils.h" +#include "sip_utils.h" +#include "string_utils.h" +#include "logger.h" + +#include <pjsip/sip_endpoint.h> +#include <pjsip/sip_uri.h> + +#include <pjsip-simple/presence.h> +#include <pjsip-simple/publish.h> + +#include <istream> +#include <algorithm> + +namespace ring { + +/**************** EXTERN VARIABLES AND FUNCTIONS (callbacks) **************************/ + +/** + * Set audio and video (SDP) configuration for a call + * localport, localip, localexternalport + * @param call a SIPCall valid pointer + */ +static pj_caching_pool pool_cache; +static pj_pool_t *pool_; +static pjsip_endpoint *endpt_; +static pjsip_module mod_ua_; + +static void sdp_media_update_cb(pjsip_inv_session *inv, pj_status_t status); +static void sdp_request_offer_cb(pjsip_inv_session *inv, const pjmedia_sdp_session *offer); +static void sdp_create_offer_cb(pjsip_inv_session *inv, pjmedia_sdp_session **p_offer); +static void invite_session_state_changed_cb(pjsip_inv_session *inv, pjsip_event *e); +static void outgoing_request_forked_cb(pjsip_inv_session *inv, pjsip_event *e); +static void transaction_state_changed_cb(pjsip_inv_session *inv, pjsip_transaction *tsx, pjsip_event *e); + +pj_caching_pool* SIPVoIPLink::cp_ = &pool_cache; + +decltype(getGlobalInstance<SIPVoIPLink>)& getSIPVoIPLink = getGlobalInstance<SIPVoIPLink, 1>; + +/** + * Helper function to process refer function on call transfer + */ +static void onCallTransfered(pjsip_inv_session *inv, pjsip_rx_data *rdata); + +static void +handleIncomingOptions(pjsip_rx_data *rdata) +{ + pjsip_tx_data *tdata; + + if (pjsip_endpt_create_response(endpt_, rdata, PJSIP_SC_OK, NULL, &tdata) != PJ_SUCCESS) + return; + +#define ADD_HDR(hdr) do { \ + const pjsip_hdr *cap_hdr = hdr; \ + if (cap_hdr) \ + pjsip_msg_add_hdr (tdata->msg, (pjsip_hdr*) pjsip_hdr_clone (tdata->pool, cap_hdr)); \ +} while (0) +#define ADD_CAP(cap) ADD_HDR(pjsip_endpt_get_capability(endpt_, cap, NULL)); + + ADD_CAP(PJSIP_H_ALLOW); + ADD_CAP(PJSIP_H_ACCEPT); + ADD_CAP(PJSIP_H_SUPPORTED); + ADD_HDR(pjsip_evsub_get_allow_events_hdr(NULL)); + + pjsip_response_addr res_addr; + pjsip_get_response_addr(tdata->pool, rdata, &res_addr); + + if (pjsip_endpt_send_response(endpt_, &res_addr, tdata, NULL, NULL) != PJ_SUCCESS) + pjsip_tx_data_dec_ref(tdata); +} + +// return PJ_FALSE so that eventuall other modules will handle these requests +// TODO: move Voicemail to separate module +static pj_bool_t +transaction_response_cb(pjsip_rx_data *rdata) +{ + pjsip_dialog *dlg = pjsip_rdata_get_dlg(rdata); + + if (!dlg) + return PJ_FALSE; + + pjsip_transaction *tsx = pjsip_rdata_get_tsx(rdata); + + if (!tsx or tsx->method.id != PJSIP_INVITE_METHOD) + return PJ_FALSE; + + if (tsx->status_code / 100 == 2) { + /** + * Send an ACK message inside a transaction. PJSIP send automatically, non-2xx ACK response. + * ACK for a 2xx response must be send using this method. + */ + pjsip_tx_data *tdata; + + if (rdata->msg_info.cseq) { + pjsip_dlg_create_request(dlg, &pjsip_ack_method, rdata->msg_info.cseq->cseq, &tdata); + pjsip_dlg_send_request(dlg, tdata, -1, NULL); + } + } + + return PJ_FALSE; +} + +static pj_status_t +try_respond_stateless(pjsip_endpoint *endpt, pjsip_rx_data *rdata, int st_code, + const pj_str_t *st_text, const pjsip_hdr *hdr_list, + const pjsip_msg_body *body) +{ + /* Check that no UAS transaction has been created for this request. + * If UAS transaction has been created for this request, application + * MUST send the response statefully using that transaction. + */ + if (!pjsip_rdata_get_tsx(rdata)) + return pjsip_endpt_respond_stateless(endpt, rdata, st_code, st_text, hdr_list, body); + else + RING_ERR("Transaction has been created for this request, send response " + "statefully instead"); + + return !PJ_SUCCESS; +} + +static pj_bool_t +transaction_request_cb(pjsip_rx_data *rdata) +{ + if (!rdata or !rdata->msg_info.msg) { + RING_ERR("rx_data is NULL"); + return PJ_FALSE; + } + + pjsip_method *method = &rdata->msg_info.msg->line.req.method; + + if (!method) { + RING_ERR("method is NULL"); + return PJ_FALSE; + } + + if (method->id == PJSIP_ACK_METHOD && pjsip_rdata_get_dlg(rdata)) + return PJ_FALSE; + + if (!rdata->msg_info.to or !rdata->msg_info.from or !rdata->msg_info.via) { + RING_ERR("Missing From, To or Via fields"); + return PJ_FALSE; + } + const pjsip_sip_uri *sip_to_uri = (pjsip_sip_uri *) pjsip_uri_get_uri(rdata->msg_info.to->uri); + const pjsip_sip_uri *sip_from_uri = (pjsip_sip_uri *) pjsip_uri_get_uri(rdata->msg_info.from->uri); + const pjsip_host_port& sip_via = rdata->msg_info.via->sent_by; + + if (!sip_to_uri or !sip_from_uri or !sip_via.host.ptr) { + RING_ERR("NULL uri"); + return PJ_FALSE; + } + std::string toUsername(sip_to_uri->user.ptr, sip_to_uri->user.slen); + std::string toHost(sip_to_uri->host.ptr, sip_to_uri->host.slen); + std::string viaHostname(sip_via.host.ptr, sip_via.host.slen); + const std::string remote_user(sip_from_uri->user.ptr, sip_from_uri->user.slen); + const std::string remote_hostname(sip_from_uri->host.ptr, sip_from_uri->host.slen); + + auto link = getSIPVoIPLink(); + if (not link) { + RING_ERR("no more VoIP link"); + return PJ_FALSE; + } + + auto account(link->guessAccount(toUsername, viaHostname, remote_hostname)); + if (!account) { + RING_ERR("NULL account"); + return PJ_FALSE; + } + + const auto& account_id = account->getAccountID(); + std::string displayName(sip_utils::parseDisplayName(rdata->msg_info.msg_buf)); + pjsip_msg_body *body = rdata->msg_info.msg->body; + + if (method->id == PJSIP_OTHER_METHOD) { + pj_str_t *str = &method->name; + std::string request(str->ptr, str->slen); + + if (request.find("NOTIFY") != std::string::npos) { + if (body and body->data) { + int voicemail = 0; + int ret = sscanf((const char*) body->data, "Voice-Message: %d/", &voicemail); + + if (ret == 1 and voicemail != 0) + Manager::instance().startVoiceMessageNotification(account_id, voicemail); + } + } + + try_respond_stateless(endpt_, rdata, PJSIP_SC_OK, NULL, NULL, NULL); + + return PJ_FALSE; + } else if (method->id == PJSIP_OPTIONS_METHOD) { + handleIncomingOptions(rdata); + return PJ_FALSE; + } else if (method->id != PJSIP_INVITE_METHOD && method->id != PJSIP_ACK_METHOD) { + try_respond_stateless(endpt_, rdata, PJSIP_SC_METHOD_NOT_ALLOWED, NULL, NULL, NULL); + return PJ_FALSE; + } + + pjmedia_sdp_session *r_sdp; + + if (!body || pjmedia_sdp_parse(rdata->tp_info.pool, (char*) body->data, body->len, &r_sdp) != PJ_SUCCESS) + r_sdp = NULL; + + if (account->getActiveAccountCodecInfoIdList(MEDIA_AUDIO).empty()) { + try_respond_stateless(endpt_, rdata, PJSIP_SC_NOT_ACCEPTABLE_HERE, NULL, NULL, NULL); + + return PJ_FALSE; + } + + // Verify that we can handle the request + unsigned options = 0; + + if (pjsip_inv_verify_request(rdata, &options, NULL, NULL, endpt_, NULL) != PJ_SUCCESS) { + try_respond_stateless(endpt_, rdata, PJSIP_SC_METHOD_NOT_ALLOWED, NULL, NULL, NULL); + return PJ_FALSE; + } + + Manager::instance().hookPreference.runHook(rdata->msg_info.msg); + + auto call = account->newIncomingCall(remote_user); + if (!call) { + return PJ_FALSE; + } + + char tmp[PJSIP_MAX_URL_SIZE]; + size_t length = pjsip_uri_print(PJSIP_URI_IN_FROMTO_HDR, sip_from_uri, tmp, PJSIP_MAX_URL_SIZE); + std::string peerNumber(tmp, length); + sip_utils::stripSipUriPrefix(peerNumber); + + if (not remote_user.empty() and not remote_hostname.empty()) + peerNumber = remote_user + "@" + remote_hostname; + + // RING_DBG("transaction_request_cb viaHostname %s toUsername %s addrToUse %s addrSdp %s peerNumber: %s" , + // viaHostname.c_str(), toUsername.c_str(), addrToUse.toString().c_str(), addrSdp.toString().c_str(), peerNumber.c_str()); + + // Append PJSIP transport to the broker's SipTransport list + auto transport = link->sipTransportBroker->addTransport(rdata->tp_info.transport); + if (!transport) { + if (account->getAccountType() == SIPAccount::ACCOUNT_TYPE) { + RING_WARN("Using transport from account."); + transport = std::static_pointer_cast<SIPAccount>(account)->getTransport(); + } + if (!transport) { + RING_ERR("No suitable transport to answer this call."); + return PJ_FALSE; + } + } + call->setTransport(transport); + + // FIXME : for now, use the same address family as the SIP transport + auto family = pjsip_transport_type_get_af(pjsip_transport_get_type_from_flag(transport->get()->flag)); + IpAddr addrToUse = ip_utils::getInterfaceAddr(account->getLocalInterface(), family); + + IpAddr addrSdp; + if (account->getUPnPActive()) { + /* use UPnP addr, or published addr if its set */ + addrSdp = account->getPublishedSameasLocal() ? + account->getUPnPIpAddress() : account->getPublishedIpAddress(); + } else { + addrSdp = account->isStunEnabled() or (not account->getPublishedSameasLocal()) + ? account->getPublishedIpAddress() : addrToUse; + } + + /* fallback on local address */ + if (not addrSdp) addrSdp = addrToUse; + + call->setConnectionState(Call::PROGRESSING); + call->setPeerNumber(peerNumber); + call->setDisplayName(displayName); + call->initRecFilename(peerNumber); + call->setCallMediaLocal(addrToUse); + call->getSDP().setPublishedIP(addrSdp); + + if (account->isStunEnabled()) + call->updateSDPFromSTUN(); + + call->getSDP().receiveOffer(r_sdp, + account->getActiveAccountCodecInfoList(MEDIA_AUDIO), + account->getActiveAccountCodecInfoList(MEDIA_VIDEO), + account->getSrtpKeyExchange() + ); + auto ice_attrs = Sdp::getIceAttributes(r_sdp); + if (not ice_attrs.ufrag.empty() and not ice_attrs.pwd.empty()) { + if (not call->getIceTransport()) { + RING_DBG("Initializing ICE transport"); + call->initIceTransport(false); + } + call->setupLocalSDPFromIce(); + } + + pjsip_dialog *dialog = nullptr; + if (pjsip_dlg_create_uas(pjsip_ua_instance(), rdata, nullptr, &dialog) != PJ_SUCCESS) { + RING_ERR("Could not create uas"); + call.reset(); + try_respond_stateless(endpt_, rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, nullptr, nullptr, nullptr); + return PJ_FALSE; + } + + pjsip_tpselector tp_sel = SIPVoIPLink::getTransportSelector(transport->get()); + if (!dialog or pjsip_dlg_set_transport(dialog, &tp_sel) != PJ_SUCCESS) { + RING_ERR("Could not set transport for dialog"); + return PJ_FALSE; + } + + pjsip_inv_session* inv = nullptr; + pjsip_inv_create_uas(dialog, rdata, call->getSDP().getLocalSdpSession(), PJSIP_INV_SUPPORT_ICE, &inv); + + if (!inv) { + RING_ERR("Call invite is not initialized"); + return PJ_FALSE; + } + + inv->mod_data[mod_ua_.id] = call.get(); + call->inv.reset(inv); + + // Check whether Replaces header is present in the request and process accordingly. + pjsip_dialog *replaced_dlg; + pjsip_tx_data *response; + + if (pjsip_replaces_verify_request(rdata, &replaced_dlg, PJ_FALSE, &response) != PJ_SUCCESS) { + RING_ERR("Something wrong with Replaces request."); + call.reset(); + + // Something wrong with the Replaces header. + if (response) { + pjsip_response_addr res_addr; + pjsip_get_response_addr(response->pool, rdata, &res_addr); + pjsip_endpt_send_response(endpt_, &res_addr, response, + NULL, NULL); + } else { + try_respond_stateless(endpt_, rdata, PJSIP_SC_INTERNAL_SERVER_ERROR, NULL, NULL, NULL); + } + + return PJ_FALSE; + } + + // Check if call has been transfered + pjsip_tx_data *tdata = 0; + + // If Replace header present + if (replaced_dlg) { + // Always answer the new INVITE with 200 if the replaced call is in early or confirmed state. + if (pjsip_inv_answer(call->inv.get(), PJSIP_SC_OK, NULL, NULL, &response) == PJ_SUCCESS) { + if (pjsip_inv_send_msg(call->inv.get(), response) != PJ_SUCCESS) + call->inv.reset(); + } + + // Get the INVITE session associated with the replaced dialog. + pjsip_inv_session *replaced_inv = pjsip_dlg_get_inv_session(replaced_dlg); + + // Disconnect the "replaced" INVITE session. + if (pjsip_inv_end_session(replaced_inv, PJSIP_SC_GONE, NULL, &tdata) == PJ_SUCCESS && tdata) { + if (pjsip_inv_send_msg(replaced_inv, tdata)) + call->inv.reset(); + } + } else { // Proceed with normal call flow + if (pjsip_inv_initial_answer(call->inv.get(), rdata, PJSIP_SC_TRYING, NULL, NULL, &tdata) != PJ_SUCCESS) { + RING_ERR("Could not answer invite"); + return PJ_FALSE; + } + + if (pjsip_inv_send_msg(call->inv.get(), tdata) != PJ_SUCCESS) { + RING_ERR("Could not send msg for invite"); + call->inv.reset(); + return PJ_FALSE; + } + + call->setConnectionState(Call::TRYING); + + if (pjsip_inv_answer(call->inv.get(), PJSIP_SC_RINGING, NULL, NULL, &tdata) != PJ_SUCCESS) { + RING_ERR("Could not answer invite"); + return PJ_FALSE; + } + + // contactStr must stay in scope as long as tdata + const pj_str_t contactStr(account->getContactHeader(transport->get())); + sip_utils::addContactHeader(&contactStr, tdata); + + if (pjsip_inv_send_msg(call->inv.get(), tdata) != PJ_SUCCESS) { + RING_ERR("Could not send msg for invite"); + call->inv.reset(); + return PJ_FALSE; + } + + call->setConnectionState(Call::RINGING); + + Manager::instance().incomingCall(*call, account_id); + } + + return PJ_FALSE; +} + +static void +tp_state_callback(pjsip_transport* tp, pjsip_transport_state state, + const pjsip_transport_state_info* info) +{ + // There is no way (at writing) to link a user data to a PJSIP transport. + // So we obtain it from the global SIPVoIPLink instance that owns it. + // Be sure the broker's owner is not deleted during proccess + if (auto sipLink = getSIPVoIPLink()) { + if (auto& broker = sipLink->sipTransportBroker) + broker->transportStateChanged(tp, state, info); + else + RING_ERR("SIPVoIPLink with invalid SipTransportBroker"); + } else + RING_ERR("no more VoIP link"); +} + +/*************************************************************************************************/ + +pjsip_endpoint * SIPVoIPLink::getEndpoint() +{ + return endpt_; +} + +pjsip_module * SIPVoIPLink::getMod() +{ + return &mod_ua_; +} + +pj_pool_t* SIPVoIPLink::getPool() const +{ + return pool_; +} + +SIPVoIPLink::SIPVoIPLink() +{ +#define TRY(ret) do { \ + if (ret != PJ_SUCCESS) \ + throw VoipLinkException(#ret " failed"); \ +} while (0) + + pj_caching_pool_init(cp_, &pj_pool_factory_default_policy, 0); + pool_ = pj_pool_create(&cp_->factory, PACKAGE, 4096, 4096, nullptr); + if (!pool_) + throw VoipLinkException("UserAgent: Could not initialize memory pool"); + + TRY(pjsip_endpt_create(&cp_->factory, pj_gethostname()->ptr, &endpt_)); + + auto ns = ip_utils::getLocalNameservers(); + if (not ns.empty()) { + std::vector<pj_str_t> dns_nameservers(ns.size()); + for (unsigned i=0, n=ns.size(); i<n; i++) { + char hbuf[NI_MAXHOST]; + getnameinfo((sockaddr*)&ns[i], ns[i].getLength(), hbuf, sizeof(hbuf), nullptr, 0, NI_NUMERICHOST); + RING_DBG("Using SIP nameserver: %s", hbuf); + pj_strdup2(pool_, &dns_nameservers[i], hbuf); + } + pj_dns_resolver* resv; + TRY(pjsip_endpt_create_resolver(endpt_, &resv)); + TRY(pj_dns_resolver_set_ns(resv, ns.size(), dns_nameservers.data(), nullptr)); + TRY(pjsip_endpt_set_resolver(endpt_, resv)); + } + + sipTransportBroker.reset(new SipTransportBroker(endpt_, *cp_, *pool_)); + + auto status = pjsip_tpmgr_set_state_cb(pjsip_endpt_get_tpmgr(endpt_), + tp_state_callback); + if (status != PJ_SUCCESS) { + RING_ERR("Can't set transport callback"); + sip_utils::sip_strerror(status); + } + + if (!ip_utils::getLocalAddr()) + throw VoipLinkException("UserAgent: Unable to determine network capabilities"); + + TRY(pjsip_tsx_layer_init_module(endpt_)); + TRY(pjsip_ua_init_module(endpt_, nullptr)); + TRY(pjsip_replaces_init_module(endpt_)); // See the Replaces specification in RFC 3891 + TRY(pjsip_100rel_init_module(endpt_)); + + // Initialize and register ring module + mod_ua_.name = pj_str((char*) PACKAGE); + mod_ua_.id = -1; + mod_ua_.priority = PJSIP_MOD_PRIORITY_APPLICATION; + mod_ua_.on_rx_request = &transaction_request_cb; + mod_ua_.on_rx_response = &transaction_response_cb; + TRY(pjsip_endpt_register_module(endpt_, &mod_ua_)); + + TRY(pjsip_evsub_init_module(endpt_)); + TRY(pjsip_xfer_init_module(endpt_)); + + // presence/publish management + TRY(pjsip_pres_init_module(endpt_, pjsip_evsub_instance())); + TRY(pjsip_endpt_register_module(endpt_, &PresSubServer::mod_presence_server)); + + static const pjsip_inv_callback inv_cb = { + invite_session_state_changed_cb, + outgoing_request_forked_cb, + transaction_state_changed_cb, + sdp_request_offer_cb, +#if PJ_VERSION_NUM > (2 << 24 | 1 << 16) + nullptr /* on_rx_reinvite */, +#endif + sdp_create_offer_cb, + sdp_media_update_cb, + nullptr /* on_send_ack */, + nullptr /* on_redirected */, + }; + TRY(pjsip_inv_usage_init(endpt_, &inv_cb)); + + static const pj_str_t allowed[] = { + CONST_PJ_STR("INFO"), + CONST_PJ_STR("OPTIONS"), + CONST_PJ_STR("MESSAGE"), + CONST_PJ_STR("PUBLISH"), + }; + + pjsip_endpt_add_capability(endpt_, &mod_ua_, PJSIP_H_ALLOW, nullptr, PJ_ARRAY_SIZE(allowed), allowed); + + static const pj_str_t text_plain = CONST_PJ_STR("text/plain"); + pjsip_endpt_add_capability(endpt_, &mod_ua_, PJSIP_H_ACCEPT, nullptr, 1, &text_plain); + + static const pj_str_t accepted = CONST_PJ_STR("application/sdp"); + pjsip_endpt_add_capability(endpt_, &mod_ua_, PJSIP_H_ACCEPT, nullptr, 1, &accepted); + + TRY(pjsip_replaces_init_module(endpt_)); +#undef TRY + + // ready to handle events + // Implementation note: we don't use std::bind(xxx, this) here + // as handleEvents needs a valid instance to be called. + Manager::instance().registerEventHandler((uintptr_t)this, + [this]{ handleEvents(); }); + + RING_DBG("SIPVoIPLink@%p", this); +} + +SIPVoIPLink::~SIPVoIPLink() +{ + RING_DBG("~SIPVoIPLink@%p", this); + + // Remaining calls should not happen as possible upper callbacks + // may be called and another instance of SIPVoIPLink can be re-created! + + if (not Manager::instance().callFactory.empty<SIPCall>()) + RING_ERR("%d SIP calls remains!", + Manager::instance().callFactory.callCount<SIPCall>()); + + sipTransportBroker->shutdown(); + + const int MAX_TIMEOUT_ON_LEAVING = 5; + for (int timeout = 0; + pjsip_tsx_layer_get_tsx_count() and timeout < MAX_TIMEOUT_ON_LEAVING; + timeout++) + sleep(1); + + pjsip_tpmgr_set_state_cb(pjsip_endpt_get_tpmgr(endpt_), nullptr); + Manager::instance().unregisterEventHandler((uintptr_t)this); + handleEvents(); + + sipTransportBroker.reset(); + + pjsip_endpt_destroy(endpt_); + pj_pool_release(pool_); + pj_caching_pool_destroy(cp_); + + RING_DBG("destroying SIPVoIPLink@%p", this); +} + +std::shared_ptr<SIPAccountBase> +SIPVoIPLink::guessAccount(const std::string& userName, + const std::string& server, + const std::string& fromUri) const +{ + RING_DBG("username = %s, server = %s, from = %s", userName.c_str(), server.c_str(), fromUri.c_str()); + // Try to find the account id from username and server name by full match + + auto result = std::static_pointer_cast<SIPAccountBase>(Manager::instance().getIP2IPAccount()); // default result + MatchRank best = MatchRank::NONE; + +#if HAVE_DHT + // DHT accounts + for (const auto& account : Manager::instance().getAllAccounts<RingAccount>()) { + if (!account) + continue; + const MatchRank match(account->matches(userName, server)); + + // return right away if this is a full match + if (match == MatchRank::FULL) { + return account; + } else if (match > best) { + best = match; + result = account; + } + } +#endif + + // SIP accounts + for (const auto& account : Manager::instance().getAllAccounts<SIPAccount>()) { + if (!account) + continue; + const MatchRank match(account->matches(userName, server, endpt_, pool_)); + + // return right away if this is a full match + if (match == MatchRank::FULL) { + return account; + } else if (match > best) { + best = match; + result = account; + } + } + + return result; +} + +// Called from EventThread::run (not main thread) +void +SIPVoIPLink::handleEvents() +{ + // We have to register the external thread so it could access the pjsip frameworks + if (!pj_thread_is_registered()) { +#if __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) + static thread_local pj_thread_desc desc; + static thread_local pj_thread_t *this_thread; +#else + static __thread pj_thread_desc desc; + static __thread pj_thread_t *this_thread; +#endif + RING_DBG("Registering thread"); + pj_thread_register(NULL, desc, &this_thread); + } + + static const pj_time_val timeout = {0, 0}; // polling + auto ret = pjsip_endpt_handle_events(endpt_, &timeout); + if (ret != PJ_SUCCESS) + sip_utils::sip_strerror(ret); + +#ifdef RING_VIDEO + dequeKeyframeRequests(); +#endif +} + +void SIPVoIPLink::registerKeepAliveTimer(pj_timer_entry &timer, pj_time_val &delay) +{ + RING_DBG("Register new keep alive timer %d with delay %d", timer.id, delay.sec); + + if (timer.id == -1) + RING_WARN("Timer already scheduled"); + + switch (pjsip_endpt_schedule_timer(endpt_, &timer, &delay)) { + case PJ_SUCCESS: + break; + + default: + RING_ERR("Could not schedule new timer in pjsip endpoint"); + + /* fallthrough */ + case PJ_EINVAL: + RING_ERR("Invalid timer or delay entry"); + break; + + case PJ_EINVALIDOP: + RING_ERR("Invalid timer entry, maybe already scheduled"); + break; + } +} + +void SIPVoIPLink::cancelKeepAliveTimer(pj_timer_entry& timer) +{ + pjsip_endpt_cancel_timer(endpt_, &timer); +} + +#ifdef RING_VIDEO +// Called from a video thread +void +SIPVoIPLink::enqueueKeyframeRequest(const std::string &id) +{ + if (auto link = getSIPVoIPLink()) { + std::lock_guard<std::mutex> lock(link->keyframeRequestsMutex_); + link->keyframeRequests_.push(id); + } else + RING_ERR("no more VoIP link"); +} + +// Called from SIP event thread +void +SIPVoIPLink::dequeKeyframeRequests() +{ + int max_requests = 20; + + while (not keyframeRequests_.empty() and max_requests--) { + std::lock_guard<std::mutex> lock(keyframeRequestsMutex_); + const std::string &id(keyframeRequests_.front()); + requestKeyframe(id); + keyframeRequests_.pop(); + } +} + +// Called from SIP event thread +void +SIPVoIPLink::requestKeyframe(const std::string &callID) +{ + std::shared_ptr<SIPCall> call; + const int tries = 10; + + for (int i = 0; !call and i < tries; ++i) + call = Manager::instance().callFactory.getCall<SIPCall>(callID); // fixme: need a try version + + if (!call) + return; + + const char * const BODY = + "<?xml version=\"1.0\" encoding=\"utf-8\" ?>" + "<media_control><vc_primitive><to_encoder>" + "<picture_fast_update/>" + "</to_encoder></vc_primitive></media_control>"; + + RING_DBG("Sending video keyframe request via SIP INFO"); + call->sendSIPInfo(BODY, "media_control+xml"); +} +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Private functions +/////////////////////////////////////////////////////////////////////////////// + +static void +invite_session_state_changed_cb(pjsip_inv_session *inv, pjsip_event *ev) +{ + if (!inv) + return; + + auto call_ptr = static_cast<SIPCall*>(inv->mod_data[mod_ua_.id]); + if (!call_ptr) { + RING_WARN("invite_session_state_changed_cb: can't find related call"); + return; + } + auto call = std::static_pointer_cast<SIPCall>(call_ptr->shared_from_this()); + + if (ev and inv->state != PJSIP_INV_STATE_CONFIRMED) { + const auto tsx = ev->body.tsx_state.tsx; + if (auto status_code = tsx ? tsx->status_code : 404) { + const pj_str_t* description = pjsip_get_status_text(status_code); + RING_DBG("SIP invite session state change: %d %.*s", status_code, description->slen, description->ptr); + } + } + + if (inv->state == PJSIP_INV_STATE_EARLY and ev and ev->body.tsx_state.tsx and + ev->body.tsx_state.tsx->role == PJSIP_ROLE_UAC) { + call->onPeerRinging(); + } else if (inv->state == PJSIP_INV_STATE_CONFIRMED and ev) { + // After we sent or received a ACK - The connection is established + call->onAnswered(); + } else if (inv->state == PJSIP_INV_STATE_DISCONNECTED) { + //std::string accId(call->getAccountId()); + + switch (inv->cause) { + // The call terminates normally - BYE / CANCEL + case PJSIP_SC_OK: + case PJSIP_SC_REQUEST_TERMINATED: + call->onClosed(); + break; + + case PJSIP_SC_DECLINE: + if (inv->role != PJSIP_ROLE_UAC) + break; + + case PJSIP_SC_NOT_FOUND: + case PJSIP_SC_REQUEST_TIMEOUT: + case PJSIP_SC_NOT_ACCEPTABLE_HERE: /* no compatible codecs */ + case PJSIP_SC_NOT_ACCEPTABLE_ANYWHERE: + case PJSIP_SC_UNSUPPORTED_MEDIA_TYPE: + case PJSIP_SC_UNAUTHORIZED: + case PJSIP_SC_FORBIDDEN: + case PJSIP_SC_REQUEST_PENDING: + case PJSIP_SC_ADDRESS_INCOMPLETE: + default: + RING_WARN("PJSIP_INV_STATE_DISCONNECTED: %d %d", + inv->cause, ev ? ev->type : -1); + call->onServerFailure(inv->cause); + break; + } + } +} + +static void +sdp_request_offer_cb(pjsip_inv_session *inv, const pjmedia_sdp_session *offer) +{ + if (!inv) + return; + + auto call_ptr = static_cast<SIPCall*>(inv->mod_data[mod_ua_.id]); + if (!call_ptr) + return; + auto call = std::static_pointer_cast<SIPCall>(call_ptr->shared_from_this()); + call->onReceiveOffer(offer); +} + +static void +sdp_create_offer_cb(pjsip_inv_session *inv, pjmedia_sdp_session **p_offer) +{ + if (!inv or !p_offer) + return; + + auto call_ptr = static_cast<SIPCall*>(inv->mod_data[mod_ua_.id]); + if (!call_ptr) + return; + auto call = std::static_pointer_cast<SIPCall>(call_ptr->shared_from_this()); + + const auto& account = call->getSIPAccount(); + auto family = pj_AF_INET(); + // FIXME : for now, use the same address family as the SIP transport + if (auto dlg = inv->dlg) { + if (dlg->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT) { + if (auto tr = dlg->tp_sel.u.transport) + family = tr->local_addr.addr.sa_family; + } else if (dlg->tp_sel.type == PJSIP_TPSELECTOR_TRANSPORT) { + if (auto tr = dlg->tp_sel.u.listener) + family = tr->local_addr.addr.sa_family; + } + } + auto ifaceAddr = ip_utils::getInterfaceAddr(account.getLocalInterface(), family); + + IpAddr address; + if (account.getUPnPActive()) { + /* use UPnP addr, or published addr if its set */ + address = account.getPublishedSameasLocal() ? + account.getUPnPIpAddress() : account.getPublishedIpAddress(); + } else { + address = account.getPublishedSameasLocal() ? + ifaceAddr : account.getPublishedIpAddress(); + } + + /* fallback on local address */ + if (not address) address = ifaceAddr; + + call->setCallMediaLocal(address); + + auto& localSDP = call->getSDP(); + localSDP.setPublishedIP(address); + const bool created = localSDP.createOffer( + account.getActiveAccountCodecInfoList(MEDIA_AUDIO), + account.getActiveAccountCodecInfoList(account.isVideoEnabled() ? MEDIA_VIDEO : MEDIA_NONE), + account.getSrtpKeyExchange() + ); + + if (created) + *p_offer = localSDP.getLocalSdpSession(); +} + +static void +dump_sdp_session(const pjmedia_sdp_session* sdp_session, const char* header) +{ + char buffer[4096] {}; + + if (pjmedia_sdp_print(sdp_session, buffer, sizeof buffer) == -1) { + RING_ERR("%sSDP too big for dump", header); + return; + } + + RING_DBG("%s%s", header, buffer); +} + +static const pjmedia_sdp_session* +get_active_remote_sdp(pjsip_inv_session *inv) +{ + const pjmedia_sdp_session* sdp_session {}; + + if (pjmedia_sdp_neg_get_active_remote(inv->neg, &sdp_session) != PJ_SUCCESS) { + RING_ERR("Active remote not present"); + return nullptr; + } + + if (pjmedia_sdp_validate(sdp_session) != PJ_SUCCESS) { + RING_ERR("Invalid remote SDP session"); + return nullptr; + } + + dump_sdp_session(sdp_session, "Remote active SDP Session:\n"); + return sdp_session; +} + +static const pjmedia_sdp_session* +get_active_local_sdp(pjsip_inv_session *inv) +{ + const pjmedia_sdp_session* sdp_session {}; + + if (pjmedia_sdp_neg_get_active_local(inv->neg, &sdp_session) != PJ_SUCCESS) { + RING_ERR("Active local not present"); + return nullptr; + } + + if (pjmedia_sdp_validate(sdp_session) != PJ_SUCCESS) { + RING_ERR("Invalid local SDP session"); + return nullptr; + } + + dump_sdp_session(sdp_session, "Local active SDP Session:\n"); + return sdp_session; +} + +// This callback is called after SDP offer/answer session has completed. +static void +sdp_media_update_cb(pjsip_inv_session *inv, pj_status_t status) +{ + if (!inv) + return; + + auto call_ptr = static_cast<SIPCall*>(inv->mod_data[mod_ua_.id]); + if (!call_ptr) { + RING_DBG("Call declined by peer, SDP negotiation stopped"); + return; + } + auto call = std::static_pointer_cast<SIPCall>(call_ptr->shared_from_this()); + + if (status != PJ_SUCCESS) { + const int reason = inv->state != PJSIP_INV_STATE_NULL and + inv->state != PJSIP_INV_STATE_CONFIRMED ? + PJSIP_SC_UNSUPPORTED_MEDIA_TYPE : 0; + + RING_WARN("Could not negotiate offer"); + call->hangup(reason); + Manager::instance().callFailure(*call); + return; + } + + if (!inv->neg) { + RING_WARN("No negotiator for this session"); + return; + } + + const auto localSDP = get_active_local_sdp(inv); + const auto remoteSDP = get_active_remote_sdp(inv); + + // Update our sdp manager + auto& sdp = call->getSDP(); + + // Set active SDP sessions + sdp.setActiveLocalSdpSession(localSDP); + sdp.setActiveRemoteSdpSession(remoteSDP); + + call->onMediaUpdate(); +} + +static void +outgoing_request_forked_cb(pjsip_inv_session * /*inv*/, pjsip_event * /*e*/) +{} + +static bool +handle_media_control(pjsip_inv_session * inv, pjsip_transaction *tsx, pjsip_event *event) +{ + /* + * Incoming INFO request for media control. + */ + const pj_str_t STR_APPLICATION = CONST_PJ_STR("application"); + const pj_str_t STR_MEDIA_CONTROL_XML = CONST_PJ_STR("media_control+xml"); + pjsip_rx_data *rdata = event->body.tsx_state.src.rdata; + pjsip_msg_body *body = rdata->msg_info.msg->body; + + if (body and body->len and pj_stricmp(&body->content_type.type, &STR_APPLICATION) == 0 and + pj_stricmp(&body->content_type.subtype, &STR_MEDIA_CONTROL_XML) == 0) { + pj_str_t control_st; + + /* Apply and answer the INFO request */ + pj_strset(&control_st, (char *) body->data, body->len); + const pj_str_t PICT_FAST_UPDATE = CONST_PJ_STR("picture_fast_update"); + + if (pj_strstr(&control_st, &PICT_FAST_UPDATE)) { +#ifdef RING_VIDEO + RING_DBG("handling picture fast update request"); + auto call = static_cast<SIPCall*>(inv->mod_data[mod_ua_.id]); + if (call) + call->getVideoRtp().forceKeyFrame(); + + pjsip_tx_data *tdata; + pj_status_t status = pjsip_endpt_create_response(tsx->endpt, rdata, + PJSIP_SC_OK, NULL, &tdata); + + if (status == PJ_SUCCESS) { + status = pjsip_tsx_send_msg(tsx, tdata); + return true; + } + +#else + (void) inv; + (void) tsx; +#endif + } + } + + return false; +} + +static void +sendOK(pjsip_dialog *dlg, pjsip_rx_data *r_data, pjsip_transaction *tsx) +{ + pjsip_tx_data* t_data; + + if (pjsip_dlg_create_response(dlg, r_data, PJSIP_SC_OK, NULL, &t_data) == PJ_SUCCESS) + pjsip_dlg_send_response(dlg, tsx, t_data); +} + +static void +transaction_state_changed_cb(pjsip_inv_session * inv, pjsip_transaction *tsx, + pjsip_event *event) +{ + if (!tsx or !event or !inv or tsx->role != PJSIP_ROLE_UAS or + tsx->state != PJSIP_TSX_STATE_TRYING) + return; + + // Handle the refer method + if (pjsip_method_cmp(&tsx->method, &pjsip_refer_method) == 0) { + onCallTransfered(inv, event->body.tsx_state.src.rdata); + return; + } + + if (tsx->role == PJSIP_ROLE_UAS and tsx->state == PJSIP_TSX_STATE_TRYING) { + if (handle_media_control(inv, tsx, event)) + return; + } + + auto call_ptr = static_cast<SIPCall*>(inv->mod_data[mod_ua_.id]); + if (!call_ptr) + return; + auto call = std::static_pointer_cast<SIPCall>(call_ptr->shared_from_this()); + + if (event->body.rx_msg.rdata) { + pjsip_rx_data *r_data = event->body.rx_msg.rdata; + + if (r_data->msg_info.msg->line.req.method.id == PJSIP_OTHER_METHOD) { + std::string request(pjsip_rx_data_get_info(r_data)); + RING_DBG("%s", request.c_str()); + + if (request.find("NOTIFY") == std::string::npos and + request.find("INFO") != std::string::npos) { + sendOK(inv->dlg, r_data, tsx); + return; + } + + pjsip_msg_body *body(r_data->msg_info.msg->body); + + if (body and body->len > 0) { + const std::string msg(static_cast<char *>(body->data), body->len); + RING_DBG("%s", msg.c_str()); + + if (msg.find("Not found") != std::string::npos) { + RING_ERR("Received 404 Not found"); + sendOK(inv->dlg, r_data, tsx); + return; + } else if (msg.find("Ringing") != std::string::npos and call) { + if (call) + call->onPeerRinging(); + else + RING_WARN("Ringing state on non existing call"); + sendOK(inv->dlg, r_data, tsx); + return; + } else if (msg.find("Ok") != std::string::npos) { + sendOK(inv->dlg, r_data, tsx); + return; + } + } + } + } + +#if HAVE_INSTANT_MESSAGING + if (!call) + return; + + // Incoming TEXT message + pjsip_rx_data *r_data = event->body.tsx_state.src.rdata; + + // Get the message inside the transaction + if (!r_data or !r_data->msg_info.msg->body) + return; + + const char *formattedMsgPtr = static_cast<const char*>(r_data->msg_info.msg->body->data); + + if (!formattedMsgPtr) + return; + + std::string formattedMessage(formattedMsgPtr, strlen(formattedMsgPtr)); + + try { + // retreive the recipient-list of this message + std::string urilist = InstantMessaging::findTextUriList(formattedMessage); + auto list = InstantMessaging::parseXmlUriList(urilist); + + // If no item present in the list, peer is considered as the sender + std::string from; + + if (list.empty()) { + from = call->getPeerNumber(); + } else { + from = list.front()[InstantMessaging::IM_XML_URI]; + + if (from == "Me") + from = call->getPeerNumber(); + } + + // strip < and > characters in case of an IP address + if (from[0] == '<' && from[from.size() - 1] == '>') + from = from.substr(1, from.size() - 2); + + Manager::instance().incomingMessage(call->getCallId(), from, + InstantMessaging::findTextMessage(formattedMessage)); + + // Respond with a 200/OK + sendOK(inv->dlg, r_data, tsx); + + } catch (const InstantMessaging::InstantMessageException &except) { + RING_ERR("%s", except.what()); + } +#endif +} + +static void +onCallTransfered(pjsip_inv_session *inv, pjsip_rx_data *rdata) +{ + auto call_ptr = static_cast<SIPCall*>(inv->mod_data[mod_ua_.id]); + if (!call_ptr) + return; + auto currentCall = std::static_pointer_cast<SIPCall>(call_ptr->shared_from_this()); + + static const pj_str_t str_refer_to = CONST_PJ_STR("Refer-To"); + pjsip_generic_string_hdr *refer_to = static_cast<pjsip_generic_string_hdr*> + (pjsip_msg_find_hdr_by_name(rdata->msg_info.msg, &str_refer_to, NULL)); + + if (!refer_to) { + pjsip_dlg_respond(inv->dlg, rdata, 400, NULL, NULL, NULL); + return; + } + + try { + Manager::instance().newOutgoingCall(std::string(refer_to->hvalue.ptr, + refer_to->hvalue.slen), + currentCall->getAccountId()); + Manager::instance().hangupCall(currentCall->getCallId()); + } catch (const VoipLinkException &e) { + RING_ERR("%s", e.what()); + } +} + +int SIPVoIPLink::getModId() +{ + return mod_ua_.id; +} + +void SIPVoIPLink::createSDPOffer(pjsip_inv_session *inv, pjmedia_sdp_session **p_offer) +{ sdp_create_offer_cb(inv, p_offer); } + +void +SIPVoIPLink::resolveSrvName(const std::string &name, pjsip_transport_type_e type, SrvResolveCallback cb) +{ + if (name.length() >= PJ_MAX_HOSTNAME) { + RING_ERR("Hostname is too long"); + cb({}); + return; + } + + pjsip_host_info host_info { + 0, type, {{(char*)name.data(), (pj_ssize_t)name.size()}, 0}, + }; + + auto token = std::hash<std::string>()(name + to_string(type)); + { + std::lock_guard<std::mutex> lock(resolveMutex_); + resolveCallbacks_[token] = [cb](pj_status_t s, const pjsip_server_addresses* r) { + try { + if (s != PJ_SUCCESS || !r) { + sip_utils::sip_strerror(s); + throw std::runtime_error("Can't resolve address"); + } else { + std::vector<IpAddr> ips; + ips.reserve(r->count); + for (unsigned i=0; i < r->count; i++) + ips.push_back(r->entry[i].addr); + cb(ips); + } + } catch (const std::exception& e) { + RING_ERR("Error resolving address: %s", e.what()); + cb({}); + } + }; + } + + pjsip_endpt_resolve(endpt_, pool_, &host_info, (void*)token, resolver_callback); +} + +void +SIPVoIPLink::resolver_callback(pj_status_t status, void *token, const struct pjsip_server_addresses *addr) +{ + if (auto link = getSIPVoIPLink()) { + std::lock_guard<std::mutex> lock(link->resolveMutex_); + auto it = link->resolveCallbacks_.find((uintptr_t)token); + if (it != link->resolveCallbacks_.end()) { + it->second(status, addr); + link->resolveCallbacks_.erase(it); + } + } else + RING_ERR("no more VoIP link"); +} + +#define RETURN_IF_NULL(A, M, ...) \ + if ((A) == NULL) { RING_WARN(M, ##__VA_ARGS__); return; } + +void +SIPVoIPLink::findLocalAddressFromTransport(pjsip_transport* transport, + pjsip_transport_type_e transportType, + const std::string& host, + std::string& addr, + pj_uint16_t& port) const +{ + // Initialize the sip port with the default SIP port + port = pjsip_transport_get_default_port_for_type(transportType); + + // Initialize the sip address with the hostname + const auto pjMachineName = pj_gethostname(); + addr = std::string(pjMachineName->ptr, pjMachineName->slen); + + // Update address and port with active transport + RETURN_IF_NULL(transport, + "Transport is NULL in findLocalAddress, using local address %s :%d", + addr.c_str(), port); + + // get the transport manager associated with the SIP enpoint + auto tpmgr = pjsip_endpt_get_tpmgr(endpt_); + RETURN_IF_NULL(tpmgr, + "Transport manager is NULL in findLocalAddress, using local address %s :%d", + addr.c_str(), port); + + pj_str_t pjstring; + pj_cstr(&pjstring, host.c_str()); + + auto tp_sel = getTransportSelector(transport); + pjsip_tpmgr_fla2_param param = { transportType, &tp_sel, pjstring, PJ_FALSE, + {nullptr, 0}, 0, nullptr }; + if (pjsip_tpmgr_find_local_addr2(tpmgr, pool_, ¶m) != PJ_SUCCESS) { + RING_WARN("Could not retrieve local address and port from transport, using %s :%d", + addr.c_str(), port); + return; + } + + // Update local address based on the transport type + addr = std::string(param.ret_addr.ptr, param.ret_addr.slen); + + // Determine the local port based on transport information + port = param.ret_port; +} + +void +SIPVoIPLink::findLocalAddressFromSTUN(pjsip_transport* transport, + pj_str_t* stunServerName, + int stunPort, + std::string& addr, + pj_uint16_t& port) const +{ + // Initialize the sip port with the default SIP port + port = sip_utils::DEFAULT_SIP_PORT; + + // Initialize the sip address with the hostname + const pj_str_t* pjMachineName = pj_gethostname(); + addr = std::string(pjMachineName->ptr, pjMachineName->slen); + + // Update address and port with active transport + RETURN_IF_NULL(transport, + "Transport is NULL in findLocalAddress, using local address %s:%d", + addr.c_str(), port); + + IpAddr mapped_addr; + pj_sock_t sipSocket = pjsip_udp_transport_get_socket(transport); + const pjstun_setting stunOpt = {PJ_TRUE, *stunServerName, stunPort, + *stunServerName, stunPort}; + const pj_status_t stunStatus = pjstun_get_mapped_addr2(&cp_->factory, + &stunOpt, 1, + &sipSocket, + &static_cast<pj_sockaddr_in&>(mapped_addr)); + + switch (stunStatus) { + case PJLIB_UTIL_ESTUNNOTRESPOND: + RING_ERR("No response from STUN server %.*s", + stunServerName->slen, stunServerName->ptr); + return; + case PJLIB_UTIL_ESTUNSYMMETRIC: + RING_ERR("Different mapped addresses are returned by servers."); + return; + case PJ_SUCCESS: + port = mapped_addr.getPort(); + addr = mapped_addr.toString(); + default: + break; + } + + RING_WARN("Using address %s provided by STUN server %.*s", + IpAddr(mapped_addr).toString(true).c_str(), stunServerName->slen, + stunServerName->ptr); +} +#undef RETURN_IF_NULL +} // namespace ring diff --git a/src/sip/sipvoiplink.h b/src/sip/sipvoiplink.h new file mode 100644 index 0000000000..80556d5a84 --- /dev/null +++ b/src/sip/sipvoiplink.h @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * Author: Yun Liu <yun.liu@savoirfairelinux.com> + * Author: Pierre-Luc Bacon <pierre-luc.bacon@savoirfairelinux.com> + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * Author : Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef SIPVOIPLINK_H_ +#define SIPVOIPLINK_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "ring_types.h" +#include "ip_utils.h" +#include "noncopyable.h" + +#include <pjsip.h> +#include <pjlib.h> +#include <pjsip_ua.h> +#include <pjlib-util.h> +#include <pjnath.h> +#include <pjnath/stun_config.h> + +#ifdef RING_VIDEO +#include <queue> +#endif +#include <map> +#include <mutex> +#include <memory> + +namespace ring { + +class SIPCall; +class SIPAccountBase; +class SIPVoIPLink; +class SipTransportBroker; + +typedef std::map<std::string, std::shared_ptr<SIPCall> > SipCallMap; + +extern decltype(getGlobalInstance<SIPVoIPLink>)& getSIPVoIPLink; + +/** + * @file sipvoiplink.h + * @brief Specific VoIPLink for SIP (SIP core for incoming and outgoing events). + * This class is based on the singleton design pattern. + * One SIPVoIPLink can handle multiple SIP accounts, but all the SIP accounts have all the same SIPVoIPLink + */ + +class SIPVoIPLink { + public: +#ifdef __ANDROID__ + static void setSipLogger(); +#endif + + SIPVoIPLink(); + ~SIPVoIPLink(); + + /** + * Event listener. Each event send by the call manager is received and handled from here + */ + void handleEvents(); + + /* Returns a list of all callIDs */ + std::vector<std::string> getCallIDs(); + + /** + * Register a new keepalive registration timer to this endpoint + */ + void registerKeepAliveTimer(pj_timer_entry& timer, pj_time_val& delay); + + /** + * Abort currently registered timer + */ + void cancelKeepAliveTimer(pj_timer_entry& timer); + + /** + * Get the memory pool factory since each calls has its own memory pool + */ + pj_caching_pool *getMemoryPoolFactory(); + + /** + * Create the default UDP transport according ot Ip2Ip profile settings + */ + void createDefaultSipUdpTransport(); + + public: + static void createSDPOffer(pjsip_inv_session *inv, + pjmedia_sdp_session **p_offer); + + /** + * Instance that maintain and manage transport (UDP, TLS) + */ + std::unique_ptr<SipTransportBroker> sipTransportBroker; + +#ifdef RING_VIDEO + static void enqueueKeyframeRequest(const std::string &callID); +#endif + + typedef std::function<void(std::vector<IpAddr>)> SrvResolveCallback; + void resolveSrvName(const std::string &name, pjsip_transport_type_e type, SrvResolveCallback cb); + static void resolver_callback(pj_status_t status, void *token, const struct pjsip_server_addresses *addr); + + /** + * Guess the account related to an incoming SIP call. + */ + std::shared_ptr<SIPAccountBase> + guessAccount(const std::string& userName, + const std::string& server, + const std::string& fromUri) const; + + int getModId(); + pjsip_endpoint * getEndpoint(); + pjsip_module * getMod(); + + pj_caching_pool* getCachingPool() const { + return cp_; + } + + pj_pool_t* getPool() const; + + /** + * Get the correct address to use (ie advertised) from + * a uri. The corresponding transport that should be used + * with that uri will be discovered. + * + * @param uri The uri from which we want to discover the address to use + * @param transport The transport to use to discover the address + */ + void findLocalAddressFromTransport(pjsip_transport* transport, + pjsip_transport_type_e transportType, + const std::string& host, + std::string& address, + pj_uint16_t& port) const; + + void findLocalAddressFromSTUN(pjsip_transport* transport, + pj_str_t* stunServerName, + int stunPort, std::string& address, + pj_uint16_t& port) const; + + /** + * Initialize the transport selector + * @param transport A transport associated with an account + * @return A transport selector structure + */ + static inline pjsip_tpselector getTransportSelector(pjsip_transport *transport) { + pjsip_tpselector tp = {PJSIP_TPSELECTOR_TRANSPORT, {transport}}; + return tp; + } + + private: + NON_COPYABLE(SIPVoIPLink); + +#ifdef RING_VIDEO + void dequeKeyframeRequests(); + void requestKeyframe(const std::string &callID); + std::mutex keyframeRequestsMutex_ {}; + std::queue<std::string> keyframeRequests_ {}; +#endif + + static pj_caching_pool* cp_; + + std::mutex resolveMutex_ {}; + std::map<uintptr_t, std::function<void(pj_status_t, const pjsip_server_addresses*)>> resolveCallbacks_; + + friend class SIPTest; +}; + +} // namespace ring + +#endif // SIPVOIPLINK_H_ diff --git a/src/sip/tlsvalidator.cpp b/src/sip/tlsvalidator.cpp new file mode 100644 index 0000000000..2837bf2dc1 --- /dev/null +++ b/src/sip/tlsvalidator.cpp @@ -0,0 +1,1157 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> + * Vittorio Giovara <vittorio.giovara@savoirfairelinux.com> + * Emmanuel Lepage <emmanuel.lepage@savoirfairelinux.com> + * Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "tlsvalidator.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "fileutils.h" +#include "logger.h" +#include "security_const.h" + +#include "gnutls_support.h" + +#include <sstream> +#include <iomanip> + +#include <cstdio> +#include <cerrno> +#include <cassert> +#include <ctime> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <netinet/tcp.h> +#include <netinet/in.h> +#include <netdb.h> +#include <unistd.h> +#include <fcntl.h> + +namespace ring { + +//Map the internal ring Enum class of the exported names + +const EnumClassNames<TlsValidator::CheckValues> TlsValidator::CheckValuesNames = {{ + /* CheckValues Name */ + /* PASSED */ DRing::Certificate::CheckValuesNames::PASSED , + /* FAILED */ DRing::Certificate::CheckValuesNames::FAILED , + /* UNSUPPORTED */ DRing::Certificate::CheckValuesNames::UNSUPPORTED , + /* ISO_DATE */ DRing::Certificate::CheckValuesNames::ISO_DATE , + /* CUSTOM */ DRing::Certificate::CheckValuesNames::CUSTOM , + /* CUSTOM */ DRing::Certificate::CheckValuesNames::DATE , +}}; + +const CallbackMatrix1D<TlsValidator::CertificateCheck, TlsValidator, TlsValidator::CheckResult> TlsValidator::checkCallback = {{ + /* CertificateCheck Callback */ + /*HAS_PRIVATE_KEY */ &TlsValidator::hasPrivateKey , + /*EXPIRED */ &TlsValidator::notExpired , + /*STRONG_SIGNING */ &TlsValidator::strongSigning , + /*NOT_SELF_SIGNED */ &TlsValidator::notSelfSigned , + /*KEY_MATCH */ &TlsValidator::keyMatch , + /*PRIVATE_KEY_STORAGE_PERMISSION */ &TlsValidator::privateKeyStoragePermissions , + /*PUBLIC_KEY_STORAGE_PERMISSION */ &TlsValidator::publicKeyStoragePermissions , + /*PRIVATEKEY_DIRECTORY_PERMISSIONS */ &TlsValidator::privateKeyDirectoryPermissions , + /*PUBLICKEY_DIRECTORY_PERMISSIONS */ &TlsValidator::publicKeyDirectoryPermissions , + /*PRIVATE_KEY_STORAGE_LOCATION */ &TlsValidator::privateKeyStorageLocation , + /*PUBLIC_KEY_STORAGE_LOCATION */ &TlsValidator::publicKeyStorageLocation , + /*PRIVATE_KEY_SELINUX_ATTRIBUTES */ &TlsValidator::privateKeySelinuxAttributes , + /*PUBLIC_KEY_SELINUX_ATTRIBUTES */ &TlsValidator::publicKeySelinuxAttributes , + /*EXIST */ &TlsValidator::exist , + /*VALID */ &TlsValidator::valid , + /*VALID_AUTHORITY */ &TlsValidator::validAuthority , + /*KNOWN_AUTHORITY */ &TlsValidator::knownAuthority , + /*NOT_REVOKED */ &TlsValidator::notRevoked , + /*AUTHORITY_MISMATCH */ &TlsValidator::authorityMatch , + /*UNEXPECTED_OWNER */ &TlsValidator::expectedOwner , + /*NOT_ACTIVATED */ &TlsValidator::activated , +}}; + +const CallbackMatrix1D<TlsValidator::CertificateDetails, TlsValidator, TlsValidator::CheckResult> TlsValidator::getterCallback = {{ + /* EXPIRATION_DATE */ &TlsValidator::getExpirationDate , + /* ACTIVATION_DATE */ &TlsValidator::getActivationDate , + /* REQUIRE_PRIVATE_KEY_PASSWORD */ &TlsValidator::requirePrivateKeyPassword , + /* PUBLIC_SIGNATURE */ &TlsValidator::getPublicSignature , + /* VERSION_NUMBER */ &TlsValidator::getVersionNumber , + /* SERIAL_NUMBER */ &TlsValidator::getSerialNumber , + /* ISSUER */ &TlsValidator::getIssuer , + /* SUBJECT_KEY_ALGORITHM */ &TlsValidator::getSubjectKeyAlgorithm , + /* CN */ &TlsValidator::getCN , + /* N */ &TlsValidator::getN , + /* O */ &TlsValidator::getO , + /* SIGNATURE_ALGORITHM */ &TlsValidator::getSignatureAlgorithm , + /* MD5_FINGERPRINT */ &TlsValidator::getMd5Fingerprint , + /* SHA1_FINGERPRINT */ &TlsValidator::getSha1Fingerprint , + /* PUBLIC_KEY_ID */ &TlsValidator::getPublicKeyId , + /* ISSUER_DN */ &TlsValidator::getIssuerDN , + /* NEXT_EXPECTED_UPDATE_DATE */ &TlsValidator::getIssuerDN , // TODO + /* OUTGOING_SERVER */ &TlsValidator::outgoingServer , +}}; + +const Matrix1D<TlsValidator::CertificateCheck, TlsValidator::CheckValuesType> TlsValidator::enforcedCheckType = {{ + /* CertificateCheck Callback */ + /*HAS_PRIVATE_KEY */ CheckValuesType::BOOLEAN , + /*EXPIRED */ CheckValuesType::BOOLEAN , + /*STRONG_SIGNING */ CheckValuesType::BOOLEAN , + /*NOT_SELF_SIGNED */ CheckValuesType::BOOLEAN , + /*KEY_MATCH */ CheckValuesType::BOOLEAN , + /*PRIVATE_KEY_STORAGE_PERMISSION */ CheckValuesType::BOOLEAN , + /*PUBLIC_KEY_STORAGE_PERMISSION */ CheckValuesType::BOOLEAN , + /*PRIVATEKEY_DIRECTORY_PERMISSIONS */ CheckValuesType::BOOLEAN , + /*PUBLICKEY_DIRECTORY_PERMISSIONS */ CheckValuesType::BOOLEAN , + /*PRIVATE_KEY_STORAGE_LOCATION */ CheckValuesType::BOOLEAN , + /*PUBLIC_KEY_STORAGE_LOCATION */ CheckValuesType::BOOLEAN , + /*PRIVATE_KEY_SELINUX_ATTRIBUTES */ CheckValuesType::BOOLEAN , + /*PUBLIC_KEY_SELINUX_ATTRIBUTES */ CheckValuesType::BOOLEAN , + /*EXIST */ CheckValuesType::BOOLEAN , + /*VALID */ CheckValuesType::BOOLEAN , + /*VALID_AUTHORITY */ CheckValuesType::BOOLEAN , + /*KNOWN_AUTHORITY */ CheckValuesType::BOOLEAN , + /*NOT_REVOKED */ CheckValuesType::BOOLEAN , + /*AUTHORITY_MISMATCH */ CheckValuesType::BOOLEAN , + /*UNEXPECTED_OWNER */ CheckValuesType::BOOLEAN , + /*NOT_ACTIVATED */ CheckValuesType::BOOLEAN , +}}; + +const EnumClassNames<TlsValidator::CertificateCheck> TlsValidator::CertificateCheckNames = {{ + /* CertificateCheck Name */ + /*HAS_PRIVATE_KEY */ DRing::Certificate::ChecksNames::HAS_PRIVATE_KEY , + /*EXPIRED */ DRing::Certificate::ChecksNames::EXPIRED , + /*STRONG_SIGNING */ DRing::Certificate::ChecksNames::STRONG_SIGNING , + /*NOT_SELF_SIGNED */ DRing::Certificate::ChecksNames::NOT_SELF_SIGNED , + /*KEY_MATCH */ DRing::Certificate::ChecksNames::KEY_MATCH , + /*PRIVATE_KEY_STORAGE_PERMISSION */ DRing::Certificate::ChecksNames::PRIVATE_KEY_STORAGE_PERMISSION , + /*PUBLIC_KEY_STORAGE_PERMISSION */ DRing::Certificate::ChecksNames::PUBLIC_KEY_STORAGE_PERMISSION , + /*PRIVATEKEY_DIRECTORY_PERMISSIONS */ DRing::Certificate::ChecksNames::PRIVATE_KEY_DIRECTORY_PERMISSIONS , + /*PUBLICKEY_DIRECTORY_PERMISSIONS */ DRing::Certificate::ChecksNames::PUBLIC_KEY_DIRECTORY_PERMISSIONS , + /*PRIVATE_KEY_STORAGE_LOCATION */ DRing::Certificate::ChecksNames::PRIVATE_KEY_STORAGE_LOCATION , + /*PUBLIC_KEY_STORAGE_LOCATION */ DRing::Certificate::ChecksNames::PUBLIC_KEY_STORAGE_LOCATION , + /*PRIVATE_KEY_SELINUX_ATTRIBUTES */ DRing::Certificate::ChecksNames::PRIVATE_KEY_SELINUX_ATTRIBUTES , + /*PUBLIC_KEY_SELINUX_ATTRIBUTES */ DRing::Certificate::ChecksNames::PUBLIC_KEY_SELINUX_ATTRIBUTES , + /*EXIST */ DRing::Certificate::ChecksNames::EXIST , + /*VALID */ DRing::Certificate::ChecksNames::VALID , + /*VALID_AUTHORITY */ DRing::Certificate::ChecksNames::VALID_AUTHORITY , + /*KNOWN_AUTHORITY */ DRing::Certificate::ChecksNames::KNOWN_AUTHORITY , + /*NOT_REVOKED */ DRing::Certificate::ChecksNames::NOT_REVOKED , + /*AUTHORITY_MISMATCH */ DRing::Certificate::ChecksNames::AUTHORITY_MISMATCH , + /*UNEXPECTED_OWNER */ DRing::Certificate::ChecksNames::UNEXPECTED_OWNER , + /*NOT_ACTIVATED */ DRing::Certificate::ChecksNames::NOT_ACTIVATED , +}}; + +const EnumClassNames<TlsValidator::CertificateDetails> TlsValidator::CertificateDetailsNames = {{ + /* EXPIRATION_DATE */ DRing::Certificate::DetailsNames::EXPIRATION_DATE , + /* ACTIVATION_DATE */ DRing::Certificate::DetailsNames::ACTIVATION_DATE , + /* REQUIRE_PRIVATE_KEY_PASSWORD */ DRing::Certificate::DetailsNames::REQUIRE_PRIVATE_KEY_PASSWORD , + /* PUBLIC_SIGNATURE */ DRing::Certificate::DetailsNames::PUBLIC_SIGNATURE , + /* VERSION_NUMBER */ DRing::Certificate::DetailsNames::VERSION_NUMBER , + /* SERIAL_NUMBER */ DRing::Certificate::DetailsNames::SERIAL_NUMBER , + /* ISSUER */ DRing::Certificate::DetailsNames::ISSUER , + /* SUBJECT_KEY_ALGORITHM */ DRing::Certificate::DetailsNames::SUBJECT_KEY_ALGORITHM , + /* CN */ DRing::Certificate::DetailsNames::CN , + /* N */ DRing::Certificate::DetailsNames::N , + /* O */ DRing::Certificate::DetailsNames::O , + /* SIGNATURE_ALGORITHM */ DRing::Certificate::DetailsNames::SIGNATURE_ALGORITHM , + /* MD5_FINGERPRINT */ DRing::Certificate::DetailsNames::MD5_FINGERPRINT , + /* SHA1_FINGERPRINT */ DRing::Certificate::DetailsNames::SHA1_FINGERPRINT , + /* PUBLIC_KEY_ID */ DRing::Certificate::DetailsNames::PUBLIC_KEY_ID , + /* ISSUER_DN */ DRing::Certificate::DetailsNames::ISSUER_DN , + /* NEXT_EXPECTED_UPDATE_DATE */ DRing::Certificate::DetailsNames::NEXT_EXPECTED_UPDATE_DATE , + /* OUTGOING_SERVER */ DRing::Certificate::DetailsNames::OUTGOING_SERVER , + +}}; + +const EnumClassNames<const TlsValidator::CheckValuesType> TlsValidator::CheckValuesTypeNames = {{ + /* Type Name */ + /* BOOLEAN */ DRing::Certificate::ChecksValuesTypesNames::BOOLEAN , + /* ISO_DATE */ DRing::Certificate::ChecksValuesTypesNames::ISO_DATE , + /* CUSTOM */ DRing::Certificate::ChecksValuesTypesNames::CUSTOM , + /* NUMBER */ DRing::Certificate::ChecksValuesTypesNames::NUMBER , +}}; + +const Matrix2D<TlsValidator::CheckValuesType , TlsValidator::CheckValues , bool> TlsValidator::acceptedCheckValuesResult = {{ + /* Type PASSED FAILED UNSUPPORTED ISO_DATE CUSTOM NUMBER */ + /* BOOLEAN */ {{ true , true , true , false , false ,false }}, + /* ISO_DATE */ {{ false , false , true , true , false , false }}, + /* CUSTOM */ {{ false , false , true , false , true , false }}, + /* NUMBER */ {{ false , false , true , false , false , true }}, +}}; + + +TlsValidator::TlsValidator(const std::string& certificate, const std::string& privatekey) + : gtlsGIG_ {tls::GnuTlsGlobalInit::make_guard()} + , certificatePath_(certificate) + , privateKeyPath_(privatekey) + , certificateFound_(false) +{ + try { + x509crt_ = {fileutils::loadFile(certificatePath_)}; + certificateContent_ = x509crt_.getPacked(); + certificateFound_ = true; + } catch (const std::exception& e) { + throw TlsValidatorException("Can't load certificate"); + } + + try { + privateKeyContent_ = fileutils::loadFile(privateKeyPath_); + dht::crypto::PrivateKey key_tmp(privateKeyContent_); + privateKeyFound_ = true; + } catch (const std::exception& e) { + privateKeyContent_.clear(); + } +} + +TlsValidator::TlsValidator(const std::vector<uint8_t>& certificate_raw) + : gtlsGIG_ {tls::GnuTlsGlobalInit::make_guard()} + , certificateFound_(true) +{ + try { + x509crt_ = {certificate_raw}; + certificateContent_ = x509crt_.getPacked(); + certificateFound_ = true; + } catch (const std::exception& e) { + throw TlsValidatorException("Can't load certificate"); + } +} + +TlsValidator::~TlsValidator() +{ +} + +/** + * This method convert results into validated strings + * + * @todo The date should be validated, this is currently not an issue + */ +std::string TlsValidator::getStringValue(const TlsValidator::CertificateCheck check, const TlsValidator::CheckResult result) +{ + assert(acceptedCheckValuesResult[enforcedCheckType[check]][result.first]); + + switch(result.first) { + case CheckValues::PASSED: + case CheckValues::FAILED: + case CheckValues::UNSUPPORTED: + return CheckValuesNames[result.first]; + case CheckValues::ISO_DATE: + // TODO validate date + // return CheckValues::FAILED; + return result.second; + case CheckValues::NUMBER: + // TODO Validate numbers + case CheckValues::CUSTOM: + return result.second; + default: + // Consider any other case (such as forced int->CheckValues casting) as failed + return CheckValuesNames[CheckValues::FAILED]; + }; +} + +/** + * Check if all boolean check passed + * return true if there was no ::FAILED checks + * + * Checks functions are not "const", so this function isn't + */ +bool TlsValidator::isValid(bool verbose) +{ + for (const CertificateCheck check : Matrix0D<CertificateCheck>()) { + if (enforcedCheckType[check] == CheckValuesType::BOOLEAN) { + if (((this->*(checkCallback[check]))()).first == CheckValues::FAILED) { + if (verbose) + RING_WARN("Check failed: %s", CertificateCheckNames[check]); + return false; + } + } + } + return true; +} + +/** + * Convert all checks results into a string map + */ +std::map<std::string,std::string> TlsValidator::getSerializedChecks() +{ + std::map<std::string,std::string> ret; + if (not certificateFound_) { + // Instead of checking `certificateFound` everywhere, handle it once + ret[CertificateCheckNames[CertificateCheck::EXIST]] + = getStringValue(CertificateCheck::EXIST, exist()); + } + else { + for (const CertificateCheck check : Matrix0D<CertificateCheck>()) + ret[CertificateCheckNames[check]] = getStringValue(check,(this->*(checkCallback[check]))()); + } + + return ret; +} + +/** + * Get a map with all common certificate details + */ +std::map<std::string,std::string> TlsValidator::getSerializedDetails() +{ + std::map<std::string,std::string> ret; + if (certificateFound_) { + for (const CertificateDetails det : Matrix0D<CertificateDetails>()) { + const CheckResult r = (this->*(getterCallback[det]))(); + std::string val; + // TODO move this to a fuction + switch (r.first) { + case CheckValues::PASSED: + case CheckValues::FAILED: + case CheckValues::UNSUPPORTED: + val = CheckValuesNames[r.first]; + break; + case CheckValues::ISO_DATE: + // TODO validate date + case CheckValues::NUMBER: + // TODO Validate numbers + case CheckValues::CUSTOM: + default: + val = r.second; + break; + }; + ret[CertificateDetailsNames[det]] = val; + } + } + return ret; +} + +/** + * Set an authority + */ +void TlsValidator::setCaTlsValidator(const TlsValidator& validator) +{ + caChecked_ = false; + caCert_ = (TlsValidator*)(&validator); +} + +/** + * Helper method to return UNSUPPORTED when an error is detected + */ +static TlsValidator::CheckResult checkError(int err, char* copy_buffer, size_t size) +{ + return TlsValidator::TlsValidator::CheckResult( + err == GNUTLS_E_SUCCESS ? + TlsValidator::CheckValues::CUSTOM : TlsValidator::CheckValues::UNSUPPORTED, + err == GNUTLS_E_SUCCESS ? + std::string(copy_buffer, size) : "" + ); +} + +/** + * Some fields, such as the binary signature need to be converted to an + * ASCII-hexadecimal representation before being sent to DBus as it will cause the + * process to assert + */ +static std::string binaryToHex(const uint8_t* input, size_t input_sz) +{ + std::ostringstream ret; + ret << std::hex; + for (size_t i=0; i<input_sz; i++) + ret << std::setfill('0') << std::setw(2) << (unsigned)input[i]; + return ret.str(); +} + +/** + * Convert a time_t to an ISO date string + */ +static TlsValidator::CheckResult formatDate(const time_t time) +{ + char buffer[12]; + struct tm* timeinfo = localtime(&time); + strftime(buffer, sizeof(buffer), "%F\0", timeinfo); + return TlsValidator::CheckResult(TlsValidator::CheckValues::ISO_DATE, buffer); +} + +/** + * Helper method to return UNSUPPORTED when an error is detected + * + * This method also convert the output to binary + */ +static TlsValidator::CheckResult checkBinaryError(int err, char* copy_buffer, size_t resultSize) +{ + if (err == GNUTLS_E_SUCCESS) + return TlsValidator::CheckResult(TlsValidator::CheckValues::CUSTOM, binaryToHex(reinterpret_cast<uint8_t*>(copy_buffer), resultSize)); + else + return TlsValidator::CheckResult(TlsValidator::CheckValues::UNSUPPORTED, ""); +} + +/** + * Check if a certificate has been signed with the authority + */ +unsigned int TlsValidator::compareToCa() +{ + // Those check can only be applied when a valid CA is present + if (certificateFound_ or (not caCert_) or caCert_->valid().first == CheckValues::FAILED) + return GNUTLS_CERT_SIGNER_NOT_FOUND; + + // Don't check unless the certificate changed + if (caChecked_) + return caValidationOutput_; + + const int err = gnutls_x509_crt_verify( + x509crt_.cert, &caCert_->x509crt_.cert, 1, 0, &caValidationOutput_); + + if (err) + return GNUTLS_CERT_SIGNER_NOT_FOUND; + + return caValidationOutput_; +} + +#if 0 // disabled, see .h for reason +/** + * Verify if a hostname is valid + * + * @warning This function is blocking + * + * Mainly based on Fedora Defensive Coding tutorial + * https://docs.fedoraproject.org/en-US/Fedora_Security_Team/html/Defensive_Coding/sect-Defensive_Coding-TLS-Client-GNUTLS.html + */ +int TlsValidator::verifyHostnameCertificate(const std::string& host, const uint16_t port) +{ + int err, arg, res = -1; + unsigned int status = (unsigned) -1; + const char *errptr = nullptr; + gnutls_session_t session = nullptr; + gnutls_certificate_credentials_t cred = nullptr; + unsigned int certslen = 0; + const gnutls_datum_t *certs = nullptr; + gnutls_x509_crt_t cert = nullptr; + + char buf[4096]; + int sockfd; + struct sockaddr_in name; + struct hostent *hostinfo; + const int one = 1; + fd_set fdset; + struct timeval tv; + + if (!host.size() || !port) { + RING_ERR("Wrong parameters used - host %s, port %d.", host.c_str(), port); + return res; + } + + /* Create the socket. */ + sockfd = socket (PF_INET, SOCK_STREAM, 0); + if (sockfd < 0) { + RING_ERR("Could not create socket."); + return res; + } + /* Set non-blocking so we can dected timeouts. */ + arg = fcntl(sockfd, F_GETFL, nullptr); + if (arg < 0) + goto out; + arg |= O_NONBLOCK; + if (fcntl(sockfd, F_SETFL, arg) < 0) + goto out; + + /* Give the socket a name. */ + memset(&name, 0, sizeof(name)); + name.sin_family = AF_INET; + name.sin_port = htons(port); + hostinfo = gethostbyname(host.c_str()); + if (hostinfo == nullptr) { + RING_ERR("Unknown host %s.", host.c_str()); + goto out; + } + name.sin_addr = *(struct in_addr *)hostinfo->h_addr; + /* Connect to the address specified in name struct. */ + err = connect(sockfd, (struct sockaddr *)&name, sizeof(name)); + if (err < 0) { + /* Connection in progress, use select to see if timeout is reached. */ + if (errno == EINPROGRESS) { + do { + FD_ZERO(&fdset); + FD_SET(sockfd, &fdset); + tv.tv_sec = 10; // 10 second timeout + tv.tv_usec = 0; + err = select(sockfd + 1, nullptr, &fdset, nullptr, &tv); + if (err < 0 && errno != EINTR) { + RING_ERR("Could not connect to hostname %s at port %d", + host.c_str(), port); + goto out; + } else if (err > 0) { + /* Select returned, if so_error is clean we are ready. */ + int so_error; + socklen_t len = sizeof(so_error); + getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &so_error, &len); + + if (so_error) { + RING_ERR("Connection delayed."); + goto out; + } + break; // exit do-while loop + } else { + RING_ERR("Connection timeout."); + goto out; + } + } while(1); + } else { + RING_ERR("Could not connect to hostname %s at port %d", host.c_str(), port); + goto out; + } + } + /* Set the socked blocking again. */ + arg = fcntl(sockfd, F_GETFL, nullptr); + if (arg < 0) + goto out; + arg &= ~O_NONBLOCK; + if (fcntl(sockfd, F_SETFL, arg) < 0) + goto out; + + /* Disable Nagle algorithm that slows down the SSL handshake. */ + err = setsockopt(sockfd, IPPROTO_TCP, TCP_NODELAY, &one, sizeof(one)); + if (err < 0) { + RING_ERR("Could not set TCP_NODELAY."); + goto out; + } + + + /* Load the trusted CA certificates. */ + err = gnutls_certificate_allocate_credentials(&cred); + if (err != GNUTLS_E_SUCCESS) { + RING_ERR("Could not allocate credentials - %s", gnutls_strerror(err)); + goto out; + } + err = gnutls_certificate_set_x509_system_trust(cred); + if (err != GNUTLS_E_SUCCESS) { + RING_ERR("Could not load credentials."); + goto out; + } + + /* Create the session object. */ + err = gnutls_init(&session, GNUTLS_CLIENT); + if (err != GNUTLS_E_SUCCESS) { + RING_ERR("Could not init session -%s\n", gnutls_strerror(err)); + goto out; + } + + /* Configure the cipher preferences. The default set should be good enough. */ + err = gnutls_priority_set_direct(session, "NORMAL", &errptr); + if (err != GNUTLS_E_SUCCESS) { + RING_ERR("Could not set up ciphers - %s (%s)", gnutls_strerror(err), errptr); + goto out; + } + + /* Install the trusted certificates. */ + err = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, cred); + if (err != GNUTLS_E_SUCCESS) { + RING_ERR("Could not set up credentials - %s", gnutls_strerror(err)); + goto out; + } + + /* Associate the socket with the session object and set the server name. */ + gnutls_transport_set_ptr(session, (gnutls_transport_ptr_t) (uintptr_t) sockfd); + err = gnutls_server_name_set(session, GNUTLS_NAME_DNS, host.c_str(), host.size()); + if (err != GNUTLS_E_SUCCESS) { + RING_ERR("Could not set server name - %s", gnutls_strerror(err)); + goto out; + } + + /* Establish the connection. */ + err = gnutls_handshake(session); + if (err != GNUTLS_E_SUCCESS) { + RING_ERR("Handshake failed - %s", gnutls_strerror(err)); + goto out; + } + /* Obtain the server certificate chain. The server certificate + * itself is stored in the first element of the array. */ + certs = gnutls_certificate_get_peers(session, &certslen); + if (certs == nullptr || certslen == 0) { + RING_ERR("Could not obtain peer certificate - %s", gnutls_strerror(err)); + goto out; + } + + /* Validate the certificate chain. */ + err = gnutls_certificate_verify_peers2(session, &status); + if (err != GNUTLS_E_SUCCESS) { + RING_ERR("Could not verify the certificate chain - %s", gnutls_strerror(err)); + goto out; + } + if (status != 0) { + gnutls_datum_t msg; +#if GNUTLS_VERSION_AT_LEAST_3_1_4 + int type = gnutls_certificate_type_get(session); + err = gnutls_certificate_verification_status_print(status, type, &out, 0); +#else + err = -1; +#endif + if (err == 0) { + RING_ERR("Certificate validation failed - %s\n", msg.data); + gnutls_free(msg.data); + goto out; + } else { + RING_ERR("Certificate validation failed with code 0x%x.", status); + goto out; + } + } + + /* Match the peer certificate against the hostname. + * We can only obtain a set of DER-encoded certificates from the + * session object, so we have to re-parse the peer certificate into + * a certificate object. */ + + err = gnutls_x509_crt_init(&cert); + if (err != GNUTLS_E_SUCCESS) { + RING_ERR("Could not init certificate - %s", gnutls_strerror(err)); + goto out; + } + + /* The peer certificate is the first certificate in the list. */ + err = gnutls_x509_crt_import(cert, certs, GNUTLS_X509_FMT_PEM); + if (err != GNUTLS_E_SUCCESS) + err = gnutls_x509_crt_import(cert, certs, GNUTLS_X509_FMT_DER); + if (err != GNUTLS_E_SUCCESS) { + RING_ERR("Could not read peer certificate - %s", gnutls_strerror(err)); + goto out; + } + /* Finally check if the hostnames match. */ + err = gnutls_x509_crt_check_hostname(cert, host.c_str()); + if (err == 0) { + RING_ERR("Hostname %s does not match certificate.", host.c_str()); + goto out; + } + + /* Try sending and receiving some data through. */ + snprintf(buf, sizeof(buf), "GET / HTTP/1.0\r\nHost: %s\r\n\r\n", host.c_str()); + err = gnutls_record_send(session, buf, strlen(buf)); + if (err < 0) { + RING_ERR("Send failed - %s", gnutls_strerror(err)); + goto out; + } + err = gnutls_record_recv(session, buf, sizeof(buf)); + if (err < 0) { + RING_ERR("Recv failed - %s", gnutls_strerror(err)); + goto out; + } + + RING_DBG("Hostname %s seems to point to a valid server.", host.c_str()); + res = 0; +out: + if (session) { + gnutls_bye(session, GNUTLS_SHUT_RDWR); + gnutls_deinit(session); + } + if (cert) + gnutls_x509_crt_deinit(cert); + if (cred) + gnutls_certificate_free_credentials(cred); + close(sockfd); + return res; +} +#endif + +/** + * Check if the Validator have access to a private key + */ +TlsValidator::CheckResult TlsValidator::hasPrivateKey() +{ + if (privateKeyFound_) + return TlsValidator::CheckResult(CheckValues::PASSED, ""); + + try { + dht::crypto::PrivateKey key_tmp(certificateContent_); + } catch (const std::exception& e) { + return CheckResult(CheckValues::FAILED, e.what()); + } + + RING_DBG("Key from %s seems valid.", certificatePath_.c_str()); + return CheckResult(CheckValues::PASSED, ""); +} + +/** + * Check if the certificate is not expired + * + * The double negative is used because all boolean checks need to have + * a consistent return value semantic + * + * @fixme Handle both "with ca" and "without ca" case + */ +TlsValidator::CheckResult TlsValidator::notExpired() +{ + // time_t expirationTime = gnutls_x509_crt_get_expiration_time(cert); + return TlsValidator::CheckResult(compareToCa() & GNUTLS_CERT_EXPIRED + ? CheckValues::FAILED:CheckValues::PASSED, ""); +} + +/** + * If the activation value is in the past + * + * @fixme Handle both "with ca" and "without ca" case + */ +TlsValidator::CheckResult TlsValidator::activated() +{ + // time_t activationTime = gnutls_x509_crt_get_activation_time(cert); + return TlsValidator::CheckResult(compareToCa() & GNUTLS_CERT_NOT_ACTIVATED + ? CheckValues::FAILED:CheckValues::PASSED, ""); +} + +/** + * If the algorithm used to sign the certificate is considered weak by modern + * standard + */ +TlsValidator::CheckResult TlsValidator::strongSigning() +{ + // Doesn't seem to have the same value as + // certtool --infile /home/etudiant/Téléchargements/mynsauser.pem --key-inf + // TODO figure out why + return TlsValidator::CheckResult(compareToCa() & GNUTLS_CERT_INSECURE_ALGORITHM + ? CheckValues::FAILED:CheckValues::PASSED, ""); +} + +/** + * The certificate is not self signed + */ +TlsValidator::CheckResult TlsValidator::notSelfSigned() +{ + return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, ""); +} + +/** + * The provided key can be used along with the certificate + */ +TlsValidator::CheckResult TlsValidator::keyMatch() +{ + // TODO encrypt and decrypt a small string to check + return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, ""); +} + +TlsValidator::CheckResult TlsValidator::privateKeyStoragePermissions() +{ + struct stat statbuf; + int err = stat(privateKeyPath_.c_str(), &statbuf); + if (err) + return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, ""); + + return TlsValidator::CheckResult( + (statbuf.st_mode & S_IFREG) && /* Regular file only */ + /* READ WRITE EXECUTE */ + /* Owner */ ((statbuf.st_mode & S_IRUSR) /* write is not relevant */ && (statbuf.st_mode ^ S_IXUSR)) + /* Group */ && ((statbuf.st_mode ^ S_IRGRP) && (statbuf.st_mode ^ S_IWGRP) && (statbuf.st_mode ^ S_IXGRP)) + /* Other */ && ((statbuf.st_mode ^ S_IROTH) && (statbuf.st_mode ^ S_IWOTH) && (statbuf.st_mode ^ S_IXOTH)) + ? CheckValues::FAILED:CheckValues::PASSED, ""); +} + +TlsValidator::CheckResult TlsValidator::publicKeyStoragePermissions() +{ + struct stat statbuf; + int err = stat(certificatePath_.c_str(), &statbuf); + if (err) + return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, ""); + + return TlsValidator::CheckResult( + (statbuf.st_mode & S_IFREG) && /* Regular file only */ + /* READ WRITE EXECUTE */ + /* Owner */ ((statbuf.st_mode & S_IRUSR) /* write is not relevant */ && (statbuf.st_mode ^ S_IXUSR)) + /* Group */ && ((statbuf.st_mode ^ S_IRGRP) && (statbuf.st_mode ^ S_IWGRP) && (statbuf.st_mode ^ S_IXGRP)) + /* Other */ && ((statbuf.st_mode ^ S_IROTH) && (statbuf.st_mode ^ S_IWOTH) && (statbuf.st_mode ^ S_IXOTH)) + ? CheckValues::FAILED:CheckValues::PASSED, ""); +} + +TlsValidator::CheckResult TlsValidator::privateKeyDirectoryPermissions() +{ + struct stat statbuf; + int err = stat(certificatePath_.c_str(), &statbuf); + if (err) + return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, ""); + + return TlsValidator::CheckResult( + /* READ WRITE EXECUTE */ + /* Owner */ ((statbuf.st_mode & S_IRUSR) /* write is not relevant */ && (statbuf.st_mode & S_IXUSR)) + /* Group */ && ((statbuf.st_mode ^ S_IRGRP) && (statbuf.st_mode ^ S_IWGRP) && (statbuf.st_mode ^ S_IXGRP)) + /* Other */ && ((statbuf.st_mode ^ S_IROTH) && (statbuf.st_mode ^ S_IWOTH) && (statbuf.st_mode ^ S_IXOTH)) + ? CheckValues::FAILED:CheckValues::PASSED, ""); +} + +TlsValidator::CheckResult TlsValidator::publicKeyDirectoryPermissions() +{ + struct stat statbuf; + int err = stat(certificatePath_.c_str(), &statbuf); + if (err) + return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, ""); + + return TlsValidator::CheckResult( + /* READ WRITE EXECUTE */ + /* Owner */ ((statbuf.st_mode & S_IRUSR) /* write is not relevant */ && (statbuf.st_mode & S_IXUSR)) + /* Group */ && ((statbuf.st_mode ^ S_IRGRP) && (statbuf.st_mode ^ S_IWGRP) && (statbuf.st_mode ^ S_IXGRP)) + /* Other */ && ((statbuf.st_mode ^ S_IROTH) && (statbuf.st_mode ^ S_IWOTH) && (statbuf.st_mode ^ S_IXOTH)) + ? CheckValues::FAILED:CheckValues::PASSED, ""); +} + +/** + * Certificate should be located in specific path on some operating systems + */ +TlsValidator::CheckResult TlsValidator::privateKeyStorageLocation() +{ + // TODO + return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, ""); +} + +/** + * Certificate should be located in specific path on some operating systems + */ +TlsValidator::CheckResult TlsValidator::publicKeyStorageLocation() +{ + // TODO + return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, ""); +} + +/** + * SELinux provide additional key protection mechanism + */ +TlsValidator::CheckResult TlsValidator::privateKeySelinuxAttributes() +{ + // TODO + return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, ""); +} + +/** + * SELinux provide additional key protection mechanism + */ +TlsValidator::CheckResult TlsValidator::publicKeySelinuxAttributes() +{ + // TODO + return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, ""); +} + +/** + * If the key need decryption + * + * Double factor authentication is recommended + */ +TlsValidator::CheckResult TlsValidator::requirePrivateKeyPassword() +{ + // TODO + return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, ""); +} +/** + * The CA and certificate provide conflicting ownership information + */ +TlsValidator::CheckResult TlsValidator::expectedOwner() +{ + return TlsValidator::CheckResult(compareToCa() & GNUTLS_CERT_UNEXPECTED_OWNER + ? CheckValues::FAILED : CheckValues::PASSED, ""); +} + +/** + * The file has been found + */ +TlsValidator::CheckResult TlsValidator::exist() +{ + return TlsValidator::CheckResult(certificateFound_ ? CheckValues::PASSED : CheckValues::FAILED, ""); +} + +/** + * The certificate is invalid compared to the authority + * + * @todo Handle case when there is facultative authority, such as DHT + */ +TlsValidator::CheckResult TlsValidator::valid() +{ + // TODO this is wrong + return TlsValidator::CheckResult(compareToCa() & GNUTLS_CERT_INVALID + ? CheckValues::FAILED:CheckValues::PASSED, ""); +} + +/** + * The provided authority is invalid + */ +TlsValidator::CheckResult TlsValidator::validAuthority() +{ + // TODO Merge with either above or bellow + return TlsValidator::CheckResult((!caCert_) || (compareToCa() & GNUTLS_CERT_SIGNER_NOT_FOUND) + // ^--- When no authority is present, then it is not invalid, it is not there at all + ? CheckValues::FAILED:CheckValues::PASSED, ""); +} + +/** + * Check if the authority match the certificate + */ +TlsValidator::CheckResult TlsValidator::authorityMatch() +{ + return TlsValidator::CheckResult(compareToCa() & GNUTLS_CERT_SIGNER_NOT_CA + ? CheckValues::FAILED : CheckValues::PASSED, ""); +} + +/** + * When an account require an authority known by the system (like /usr/share/ssl/certs) + * then the whole chain of trust need be to checked + * + * @fixme port crypto_cert_load_trusted + * @fixme add account settings + * @todo implement the check + */ +TlsValidator::CheckResult TlsValidator::knownAuthority() +{ + // TODO Ring need a new boolean account setting "require trusted authority" or something defaulting to true + // using GNUTLS_CERT_SIGNER_NOT_FOUND is a temporary placeholder as it is close enough + return TlsValidator::CheckResult(compareToCa() & GNUTLS_CERT_SIGNER_NOT_FOUND + ? CheckValues::FAILED : CheckValues::PASSED, ""); +} + +/** + * Check if the certificate has been revoked + */ +TlsValidator::CheckResult TlsValidator::notRevoked() +{ + return TlsValidator::CheckResult( + (compareToCa() & GNUTLS_CERT_REVOKED) || (compareToCa() & GNUTLS_CERT_REVOCATION_DATA_ISSUED_IN_FUTURE) + ? CheckValues::FAILED : CheckValues::PASSED, ""); +} + +/** + * A certificate authority has been provided + */ +bool TlsValidator::hasCa() const +{ + return caCert_ != nullptr and caCert_->certificateFound_; +} + +// +// Certificate details +// + +// TODO gnutls_x509_crl_get_this_update + +/** + * An hexadecimal representation of the signature + */ +TlsValidator::CheckResult TlsValidator::getPublicSignature() +{ + size_t resultSize = sizeof(copy_buffer); + int err = gnutls_x509_crt_get_signature(x509crt_.cert, copy_buffer, &resultSize); + return checkBinaryError(err, copy_buffer, resultSize); +} + +/** + * Return the certificate version + */ +TlsValidator::CheckResult TlsValidator::getVersionNumber() +{ + int version = gnutls_x509_crt_get_version(x509crt_.cert); + if (version < 0) + return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, ""); + + std::ostringstream convert; + convert << version; + + return TlsValidator::CheckResult(CheckValues::NUMBER, convert.str()); +} + +/** + * Return the certificate serial number + */ +TlsValidator::CheckResult TlsValidator::getSerialNumber() +{ +// gnutls_x509_crl_iter_crt_serial +// gnutls_x509_crt_get_authority_key_gn_serial + size_t resultSize = sizeof(copy_buffer); + int err = gnutls_x509_crt_get_serial(x509crt_.cert, copy_buffer, &resultSize); + return checkBinaryError(err, copy_buffer, resultSize); +} + +/** + * If the certificate is not self signed, return the issuer + */ +TlsValidator::CheckResult TlsValidator::getIssuer() +{ + size_t resultSize = sizeof(copy_buffer); + int err = gnutls_x509_crt_get_issuer_unique_id(x509crt_.cert, copy_buffer, &resultSize); + return checkError(err, copy_buffer, resultSize); +} + +/** + * The algorithm used to sign the certificate details (rather than the certificate itself) + */ +TlsValidator::CheckResult TlsValidator::getSubjectKeyAlgorithm() +{ + gnutls_pk_algorithm_t algo = (gnutls_pk_algorithm_t) gnutls_x509_crt_get_pk_algorithm( + x509crt_.cert, nullptr); + + if (algo < 0) + return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, ""); + + const char* name = gnutls_pk_get_name(algo); + + if (!name) + return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, ""); + + return TlsValidator::CheckResult(CheckValues::CUSTOM, name); +} + +/** + * The 'CN' section of a DN (RFC4514) + */ +TlsValidator::CheckResult TlsValidator::getCN() +{ + // TODO split, cache + size_t resultSize = sizeof(copy_buffer); + int err = gnutls_x509_crt_get_dn(x509crt_.cert, copy_buffer, &resultSize); + return checkError(err, copy_buffer, resultSize); +} + +/** + * The 'N' section of a DN (RFC4514) + */ +TlsValidator::CheckResult TlsValidator::getN() +{ + // TODO split, cache + size_t resultSize = sizeof(copy_buffer); + int err = gnutls_x509_crt_get_dn(x509crt_.cert, copy_buffer, &resultSize); + return checkError(err, copy_buffer, resultSize); +} + +/** + * The 'O' section of a DN (RFC4514) + */ +TlsValidator::CheckResult TlsValidator::getO() +{ + // TODO split, cache + size_t resultSize = sizeof(copy_buffer); + int err = gnutls_x509_crt_get_dn(x509crt_.cert, copy_buffer, &resultSize); + return checkError(err, copy_buffer, resultSize); +} + +/** + * Return the algorithm used to sign the Key + * + * For example: RSA + */ +TlsValidator::CheckResult TlsValidator::getSignatureAlgorithm() +{ + gnutls_sign_algorithm_t algo = (gnutls_sign_algorithm_t) gnutls_x509_crt_get_signature_algorithm(x509crt_.cert); + + if (algo < 0) + return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, ""); + + const char* algoName = gnutls_sign_get_name(algo); + return TlsValidator::CheckResult(CheckValues::CUSTOM, algoName); +} + +/** + *Compute the key fingerprint + * + * This need to be used along with getSha1Fingerprint() to avoid collisions + */ +TlsValidator::CheckResult TlsValidator::getMd5Fingerprint() +{ + size_t resultSize = sizeof(copy_buffer); + int err = gnutls_x509_crt_get_fingerprint(x509crt_.cert, GNUTLS_DIG_MD5, copy_buffer, &resultSize); + return checkBinaryError(err, copy_buffer, resultSize); +} + +/** + * Compute the key fingerprint + * + * This need to be used along with getMd5Fingerprint() to avoid collisions + */ +TlsValidator::CheckResult TlsValidator::getSha1Fingerprint() +{ + size_t resultSize = sizeof(copy_buffer); + int err = gnutls_x509_crt_get_fingerprint(x509crt_.cert, GNUTLS_DIG_SHA1, copy_buffer, &resultSize); + return checkBinaryError(err, copy_buffer, resultSize); +} + +/** + * Return an hexadecimal identifier + */ +TlsValidator::CheckResult TlsValidator::getPublicKeyId() +{ + static unsigned char unsigned_copy_buffer[4096]; + size_t resultSize = sizeof(unsigned_copy_buffer); + int err = gnutls_x509_crt_get_key_id(x509crt_.cert,0,unsigned_copy_buffer,&resultSize); + + // TODO check for GNUTLS_E_SHORT_MEMORY_BUFFER and increase the buffer size + // TODO get rid of the cast, display a HEX or something, need research + + return checkBinaryError(err, (char*) unsigned_copy_buffer, resultSize); +} +// gnutls_x509_crt_get_authority_key_id + +/** + * If the certificate is not self signed, return the issuer DN (RFC4514) + */ +TlsValidator::CheckResult TlsValidator::getIssuerDN() +{ + size_t resultSize = sizeof(copy_buffer); + int err = gnutls_x509_crt_get_issuer_dn(x509crt_.cert, copy_buffer, &resultSize); + return checkError(err, copy_buffer, resultSize); +} + +/** + * Get the expiration date + * + * @todo Move to "certificateDetails()" method once completed + */ +TlsValidator::CheckResult TlsValidator::getExpirationDate() +{ + if (not certificateFound_) + return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, ""); + + time_t expiration = gnutls_x509_crt_get_expiration_time(x509crt_.cert); + + return formatDate(expiration); +} + +/** + * Get the activation date + * + * @todo Move to "certificateDetails()" method once completed + */ +TlsValidator::CheckResult TlsValidator::getActivationDate() +{ + if (not certificateFound_) + return TlsValidator::CheckResult(CheckValues::UNSUPPORTED, ""); + + time_t expiration = gnutls_x509_crt_get_activation_time(x509crt_.cert); + + return formatDate(expiration); +} + +/** + * The expected outgoing server domain + * + * @todo Move to "certificateDetails()" method once completed + * @todo extract information for the certificate + */ +TlsValidator::CheckResult TlsValidator::outgoingServer() +{ + // TODO + return TlsValidator::CheckResult(CheckValues::CUSTOM, ""); +} + + +} //namespace ring diff --git a/src/sip/tlsvalidator.h b/src/sip/tlsvalidator.h new file mode 100644 index 0000000000..563164364b --- /dev/null +++ b/src/sip/tlsvalidator.h @@ -0,0 +1,276 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * + * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> + * Vittorio Giovara <vittorio.giovara@savoirfairelinux.com> + * Emmanuel Lepage <emmanuel.lepage@savoirfairelinux.com> + * Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef TLS_VALIDATOR_H +#define TLS_VALIDATOR_H + +#include "enumclass_utils.h" + +#include <opendht/crypto.h> + +#include <string> +#include <vector> + +namespace ring { namespace tls { +class GnuTlsGlobalInit; +}} // namespace ring::tls + +namespace ring { + +class TlsValidatorException : public std::runtime_error { + public: + TlsValidatorException(const std::string& str) : std::runtime_error(str) {}; +}; + +class TlsValidator { + +public: + /** + * @enum CertificateCheck All validation fields + * + */ + enum class CertificateCheck { + HAS_PRIVATE_KEY , /** This certificate has a build in private key */ + EXPIRED , /** This certificate is past its expiration date */ + STRONG_SIGNING , /** This certificate has been signed with a brute-force-able method */ + NOT_SELF_SIGNED , /** This certificate has been self signed */ + KEY_MATCH , /** The public and private keys provided don't match */ + PRIVATE_KEY_STORAGE_PERMISSION , /** The file hosting the private key isn't correctly secured */ + PUBLIC_KEY_STORAGE_PERMISSION , /** The file hosting the public key isn't correctly secured */ + PRIVATE_KEY_DIRECTORY_PERMISSIONS , /** The folder storing the private key isn't correctly secured */ + PUBLIC_KEY_DIRECTORY_PERMISSIONS , /** The folder storing the public key isn't correctly secured */ + PRIVATE_KEY_STORAGE_LOCATION , /** Some operating systems have extra policies for certificate storage */ + PUBLIC_KEY_STORAGE_LOCATION , /** Some operating systems have extra policies for certificate storage */ + PRIVATE_KEY_SELINUX_ATTRIBUTES , /** Some operating systems require keys to have extra attributes */ + PUBLIC_KEY_SELINUX_ATTRIBUTES , /** Some operating systems require keys to have extra attributes */ + EXIST , /** The certificate file doesn't exist or is not accessible */ + VALID , /** The file is not a certificate */ + VALID_AUTHORITY , /** The claimed authority did not sign the certificate */ + KNOWN_AUTHORITY , /** Some operating systems provide a list of trusted authorities, use it */ + NOT_REVOKED , /** The certificate has been revoked by the authority */ + AUTHORITY_MISMATCH , /** The certificate and authority mismatch */ + UNEXPECTED_OWNER , /** The certificate has an expected owner */ + NOT_ACTIVATED , /** The certificate has not been activated yet */ + COUNT__, + }; + + /** + * @enum CertificateDetails Informative fields about a certificate + */ + enum class CertificateDetails { + EXPIRATION_DATE , /** The certificate expiration date */ + ACTIVATION_DATE , /** The certificate activation date */ + REQUIRE_PRIVATE_KEY_PASSWORD , /** Does the private key require a password */ + PUBLIC_SIGNATURE , + VERSION_NUMBER , + SERIAL_NUMBER , + ISSUER , + SUBJECT_KEY_ALGORITHM , + CN , + N , + O , + SIGNATURE_ALGORITHM , + MD5_FINGERPRINT , + SHA1_FINGERPRINT , + PUBLIC_KEY_ID , + ISSUER_DN , + NEXT_EXPECTED_UPDATE_DATE , + OUTGOING_SERVER , /** The hostname/outgoing server used for this certificate */ + COUNT__ + }; + + /** + * @enum CheckValuesType Categories of possible values for each CertificateCheck + */ + enum class CheckValuesType { + BOOLEAN, + ISO_DATE, + CUSTOM, + NUMBER, + COUNT__, + }; + + /** + * @enum CheckValue possible values for check + * + * All boolean check use PASSED when the test result is positive and + * FAILED when it is negative. All new check need to keep this convention + * or ::isValid() result will become unrepresentative of the real state. + * + * CUSTOM should be avoided when possible. This enum can be extended when + * new validated types are required. + */ + enum class CheckValues { + PASSED , /** Equivalent of a boolean "true" */ + FAILED , /** Equivalent of a boolean "false" */ + UNSUPPORTED, /** The operating system doesn't support or require the check */ + ISO_DATE , /** The check value is an ISO 8601 date YYYY-MM-DD[TH24:MM:SS+00:00] */ + CUSTOM , /** The check value cannot be represented with a finite set of values */ + NUMBER , + COUNT__, + }; + + /** + * @typedef CheckResult A validated and unvalidated result pair + * + * The CheckValue is the most important value of the pair. The string + * can either be the value of a CheckValues::CUSTOM result or an + * error code (where applicable). + */ + using CheckResult = std::pair<CheckValues, std::string>; + + /** + * Create a TlsValidator for a given certificate + * @param certificate The certificate path + * @param privatekey An optional private key file path + */ + TlsValidator(const std::string& certificate, + const std::string& privatekey = ""); + + TlsValidator(const std::vector<uint8_t>& certificate_raw); + + ~TlsValidator(); + + bool hasCa() const; + + bool isValid(bool verbose = false); + + // Security checks + CheckResult hasPrivateKey(); + CheckResult notExpired(); + CheckResult strongSigning(); + CheckResult notSelfSigned(); + CheckResult keyMatch(); + CheckResult privateKeyStoragePermissions(); + CheckResult publicKeyStoragePermissions(); + CheckResult privateKeyDirectoryPermissions(); + CheckResult publicKeyDirectoryPermissions(); + CheckResult privateKeyStorageLocation(); + CheckResult publicKeyStorageLocation(); + CheckResult privateKeySelinuxAttributes(); + CheckResult publicKeySelinuxAttributes(); + CheckResult exist(); + CheckResult valid(); + CheckResult validAuthority(); + CheckResult knownAuthority(); + CheckResult notRevoked(); + CheckResult authorityMatch(); + CheckResult expectedOwner(); + CheckResult activated(); + + // Certificate details + CheckResult getExpirationDate(); + CheckResult getActivationDate(); + CheckResult requirePrivateKeyPassword(); + CheckResult getPublicSignature(); + CheckResult getVersionNumber(); + CheckResult getSerialNumber(); + CheckResult getIssuer(); + CheckResult getSubjectKeyAlgorithm(); + CheckResult getCN(); + CheckResult getN(); + CheckResult getO(); + CheckResult getSignatureAlgorithm(); + CheckResult getMd5Fingerprint(); + CheckResult getSha1Fingerprint(); + CheckResult getPublicKeyId(); + CheckResult getIssuerDN(); + CheckResult outgoingServer(); + + void setCaTlsValidator(const TlsValidator& validator); + + std::map<std::string,std::string> getSerializedChecks(); + + std::map<std::string,std::string> getSerializedDetails(); + +private: + + // Enum class names + static const EnumClassNames<CertificateCheck> CertificateCheckNames; + + static const EnumClassNames<CertificateDetails> CertificateDetailsNames; + + static const EnumClassNames<const CheckValuesType> CheckValuesTypeNames; + + static const EnumClassNames<CheckValues> CheckValuesNames; + + /** + * Map check to their check method + */ + static const CallbackMatrix1D<CertificateCheck, TlsValidator, CheckResult> checkCallback; + + /** + * Map check to their getter method + */ + static const CallbackMatrix1D<CertificateDetails, TlsValidator, CheckResult> getterCallback; + + /** + * Valid values for each categories + */ + static const Matrix2D<CheckValuesType , CheckValues , bool> acceptedCheckValuesResult; + + static const Matrix1D<CertificateCheck, CheckValuesType> enforcedCheckType; + + std::unique_ptr<tls::GnuTlsGlobalInit> gtlsGIG_; + std::string certificatePath_; + std::string privateKeyPath_; + std::vector<uint8_t> certificateContent_; + std::vector<uint8_t> privateKeyContent_; + + dht::crypto::Certificate x509crt_; + + bool certificateFound_; + bool privateKeyFound_ {false}; + TlsValidator* caCert_ {nullptr}; + bool caChecked_ {false}; + unsigned int caValidationOutput_; + + mutable char copy_buffer[4096]; + + /** + * Helper method to convert a CheckResult into a std::string + */ + std::string getStringValue(const CertificateCheck check, const CheckResult result); + + // Helper + unsigned int compareToCa(); + +public: +#if 0 // TODO reimplement this method. do not use it as it + /** + * Verify that the local hostname points to a valid SSL server by + * establishing a connection to it and by validating its certificate. + * + * @param host the DNS domain address that the certificate should feature + * @return 0 if success, -1 otherwise + */ + static int verifyHostnameCertificate(const std::string& host, + const uint16_t port); +#endif + + +}; // TlsValidator + +} // namespace ring + +#endif diff --git a/src/string_utils.cpp b/src/string_utils.cpp new file mode 100644 index 0000000000..98a9351ad4 --- /dev/null +++ b/src/string_utils.cpp @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2014-2015 Savoir-Faire Linux Inc. + * + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "string_utils.h" +#include <sstream> +#include <cctype> +#include <algorithm> + +namespace ring { + +std::string +trim(const std::string &s) +{ + auto wsfront = std::find_if_not(s.cbegin(),s.cend(), [](int c){return std::isspace(c);}); + return std::string(wsfront, std::find_if_not(s.rbegin(),std::string::const_reverse_iterator(wsfront), [](int c){return std::isspace(c);}).base()); +} + +std::vector<std::string> +split_string(const std::string &s, char delim) +{ + std::vector<std::string> result; + std::string token; + std::istringstream ss(s); + + while (std::getline(ss, token, delim)) + if (not token.empty()) + result.emplace_back(token); + return result; +} + +std::vector<unsigned> +split_string_to_unsigned(const std::string &s, char delim) +{ + std::vector<unsigned> result; + std::string token; + std::istringstream ss(s); + + while (std::getline(ss, token, delim)) + if (not token.empty()) + result.emplace_back(std::stoi(token)); + return result; +} + +} // namespace ring diff --git a/src/string_utils.h b/src/string_utils.h new file mode 100644 index 0000000000..0e6bb3dcbd --- /dev/null +++ b/src/string_utils.h @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2014-2015 Savoir-Faire Linux Inc. + * + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef STRING_UTILS_H +#define STRING_UTILS_H + +#include <string> +#include <vector> + +#ifdef __ANDROID__ +#include <sstream> +#endif + +namespace ring { + +constexpr static const char* TRUE_STR = "true"; +constexpr static const char* FALSE_STR = "false"; + +#ifdef __ANDROID__ + +template <typename T> +std::string to_string(T &&value) +{ + std::ostringstream os; + + os << value; + return os.str(); +} + +#else + +template <typename T> +std::string to_string(T &&value) +{ + return std::to_string(std::forward<T>(value)); +} + +#endif + +std::string trim(const std::string &s); + +std::vector<std::string> +split_string(const std::string& s, char sep); + +std::vector<unsigned> +split_string_to_unsigned(const std::string& s, char sep); + +} // namespace ring + +#endif diff --git a/src/threadloop.cpp b/src/threadloop.cpp new file mode 100644 index 0000000000..3d1918394c --- /dev/null +++ b/src/threadloop.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2013-2015 Savoir-Faire Linux Inc. + * + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "threadloop.h" +#include "logger.h" + +namespace ring { + +void ThreadLoop::mainloop() +{ + try { + if (setup_()) { + while (running_) + process_(); + cleanup_(); + } else { + RING_ERR("setup failed"); + } + } catch (const ThreadLoopException &e) { + RING_ERR("%s", e.what()); + } +} + +ThreadLoop::ThreadLoop(const std::function<bool()> &setup, + const std::function<void()> &process, + const std::function<void()> &cleanup) + : setup_(setup), process_(process), cleanup_(cleanup) +{} + +ThreadLoop::~ThreadLoop() +{ + if (isRunning()) { + RING_ERR("join() should be explicitly called in owner's destructor"); + join(); + } +} + +void ThreadLoop::start() +{ + if (!running_.exchange(true)) { + // a previous stop() call may be pending + if (thread_.joinable()) + thread_.join(); + thread_ = std::thread(&ThreadLoop::mainloop, this); + } else { + RING_ERR("Thread already started"); + } +} + +void ThreadLoop::stop() +{ + running_ = false; +} + +void ThreadLoop::join() +{ + stop(); + if (thread_.joinable()) + thread_.join(); +} + +void ThreadLoop::exit() +{ + stop(); + throw ThreadLoopException(); +} + +bool ThreadLoop::isRunning() const +{ + return running_; +} + +} // namespace ring diff --git a/src/threadloop.h b/src/threadloop.h new file mode 100644 index 0000000000..7ffab94c6c --- /dev/null +++ b/src/threadloop.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2013-2015 Savoir-Faire Linux Inc. + * + * Author: Guillaume Roguez <Guillaume.Roguez@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef __THREADLOOP_H__ +#define __THREADLOOP_H__ + +#include <atomic> +#include <thread> +#include <functional> +#include <stdexcept> + +namespace ring { + +// FIXME: this is ugly +// If condition A is false, print the error message in M and exit thread +#define EXIT_IF_FAIL(A, M, ...) if (!(A)) { \ + RING_ERR(M, ##__VA_ARGS__); loop_.exit(); } + +struct ThreadLoopException : public std::runtime_error { + ThreadLoopException() : std::runtime_error("ThreadLoopException") {} +}; + +class ThreadLoop { +public: + ThreadLoop(const std::function<bool()> &setup, + const std::function<void()> &process, + const std::function<void()> &cleanup); + ~ThreadLoop(); + + void start(); + + void exit(); + void stop(); + void join(); + bool isRunning() const; + +private: + // These must be provided by users of ThreadLoop + std::function<bool()> setup_; + std::function<void()> process_; + std::function<void()> cleanup_; + + void mainloop(); + + std::atomic<bool> running_ = {false}; + std::thread thread_ = {}; +}; + +} // namespace ring + +#endif // __THREADLOOP_H__ diff --git a/src/upnp/Makefile.am b/src/upnp/Makefile.am new file mode 100644 index 0000000000..353152f5ba --- /dev/null +++ b/src/upnp/Makefile.am @@ -0,0 +1,14 @@ +include $(top_srcdir)/globals.mak + +noinst_LTLIBRARIES = libupnpcontrol.la + +libupnpcontrol_la_CXXFLAGS = \ + @CXXFLAGS@ + +libupnpcontrol_la_SOURCES = \ + upnp_control.cpp \ + upnp_control.h \ + upnp_context.cpp \ + upnp_context.h \ + upnp_igd.cpp \ + upnp_igd.h diff --git a/src/upnp/upnp_context.cpp b/src/upnp/upnp_context.cpp new file mode 100644 index 0000000000..652453fe35 --- /dev/null +++ b/src/upnp/upnp_context.cpp @@ -0,0 +1,1070 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "upnp_context.h" + +#include <string> +#include <set> +#include <mutex> +#include <memory> +#include <condition_variable> +#include <random> +#include <chrono> +#include <cstdlib> // for std::free + +#if HAVE_LIBUPNP +#include <upnp/upnp.h> +#include <upnp/upnptools.h> +#endif + +#include "logger.h" +#include "ip_utils.h" +#include "upnp_igd.h" +#include "intrin.h" + +namespace ring { namespace upnp { + +/** + * This should be used to get a UPnPContext. + * It only makes sense to have one unless you have separate + * contexts for multiple internet interfaces, which is not currently + * supported. + */ +std::shared_ptr<UPnPContext> +getUPnPContext() +{ + static auto context = std::make_shared<UPnPContext>(); + return context; +} + +#if HAVE_LIBUPNP + +/* UPnP IGD definitions */ +constexpr static const char * UPNP_ROOT_DEVICE = "upnp:rootdevice"; +constexpr static const char * UPNP_IGD_DEVICE = "urn:schemas-upnp-org:device:InternetGatewayDevice:1"; +constexpr static const char * UPNP_WAN_DEVICE = "urn:schemas-upnp-org:device:WANDevice:1"; +constexpr static const char * UPNP_WANCON_DEVICE = "urn:schemas-upnp-org:device:WANConnectionDevice:1"; +constexpr static const char * UPNP_WANIP_SERVICE = "urn:schemas-upnp-org:service:WANIPConnection:1"; +constexpr static const char * UPNP_WANPPP_SERVICE = "urn:schemas-upnp-org:service:WANPPPConnection:1"; + +/* UPnP error codes */ +constexpr static int INVALID_ARGS = 402; +constexpr static const char * INVALID_ARGS_STR = "402"; +constexpr static int ARRAY_IDX_INVALID = 713; +constexpr static const char * ARRAY_IDX_INVALID_STR = "713"; +constexpr static int CONFLICT_IN_MAPPING = 718; +constexpr static const char * CONFLICT_IN_MAPPING_STR = "718"; + +/* max number of times to retry mapping if it fails due to conflict; + * there isn't much logic in picking this number... ideally not many ports should + * be mapped in a system, so a few number of random port retries should work; + * a high number of retries would indicate there might be some kind of bug or else + * incompatibility with the router; we use it to prevent an infinite loop of + * retrying to map the entry + */ +constexpr static unsigned MAX_RETRIES = 20; + +/* + * Local prototypes + */ +static std::string get_element_text(IXML_Node*); +static std::string get_first_doc_item(IXML_Document*, const char*); +static std::string get_first_element_item(IXML_Element*, const char*); +static void checkResponseError(IXML_Document*); + +static int +cp_callback(Upnp_EventType event_type, void* event, void* user_data) +{ + if (auto upnpContext = static_cast<UPnPContext*>(user_data)) + return upnpContext->handleUPnPEvents(event_type, event); + + RING_WARN("UPnP callback without UPnPContext"); + return 0; +} + +UPnPContext::UPnPContext() +{ + int upnp_err; + char* ip_address = nullptr; + unsigned short port = 0; + + /* TODO: allow user to specify interface to be used + * by selecting the IP + */ + +#ifdef UPNP_ENABLE_IPV6 + /* TODO: test if ipv6 support works properly, eg: what if router doesn't support ipv6? */ + RING_DBG("UPnP: using IPv6"); + upnp_err = UpnpInit2(0, 0); +#else + RING_DBG("UPnP: using IPv4"); + upnp_err = UpnpInit(0, 0); +#endif + if ( upnp_err != UPNP_E_SUCCESS ) { + UpnpFinish(); + throw std::runtime_error(UpnpGetErrorMessage(upnp_err)); + } + + ip_address = UpnpGetServerIpAddress(); /* do not free, it is freed by UpnpFinish() */ + port = UpnpGetServerPort(); + + RING_DBG("UPnP: initialiazed on %s:%u", ip_address, port); + + /* relax the parser to allow malformed XML text */ + ixmlRelaxParser( 1 ); + + /* Register a control point to start looking for devices right away */ + upnp_err = UpnpRegisterClient( cp_callback, this, &ctrlptHandle_ ); + if ( upnp_err != UPNP_E_SUCCESS ) { + UpnpFinish(); + throw std::runtime_error(UpnpGetErrorMessage(upnp_err)); + } + + RING_DBG("UPnP: ctrlptrHandle=%d", ctrlptHandle_); + clientRegistered_ = true; + + /* send out async searches; + * even if no account is using UPnP currently we might as well start + * gathering a list of available devices; + * we will probably receive their advertisements either way + */ + searchForIGD(); +} + +UPnPContext::~UPnPContext() +{ + /* make sure everything is unregistered, freed, and UpnpFinish() is called */ + + { + std::lock_guard<std::mutex> lock(validIGDMutex_); + for( auto const &it : validIGDs_) { + removeMappingsByLocalIPAndDescription(it.second.get(), Mapping::UPNP_DEFAULT_MAPPING_DESCRIPTION); + } + } + + if (clientRegistered_) + UpnpUnRegisterClient( ctrlptHandle_ ); + + if (deviceRegistered_) + UpnpUnRegisterRootDevice( deviceHandle_ ); + + UpnpFinish(); +} + +void +UPnPContext::searchForIGD() +{ + if (not clientRegistered_) { + RING_WARN("UPnP: Control Point not registered"); + return; + } + + /* send out search for both types, as some routers may possibly only reply to one */ + UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_ROOT_DEVICE, this); + UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_IGD_DEVICE, this); + UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_WANIP_SERVICE, this); + UpnpSearchAsync(ctrlptHandle_, SEARCH_TIMEOUT, UPNP_WANPPP_SERVICE, this); +} + +bool +UPnPContext::hasValidIGD(std::chrono::seconds timeout) +{ + if (not clientRegistered_) { + RING_WARN("UPnP: Control Point not registered"); + return false; + } + + std::unique_lock<std::mutex> lock(validIGDMutex_); + if (!validIGDCondVar_.wait_for(lock, timeout, + [this]{return not validIGDs_.empty();})) { + RING_WARN("UPnP: check for valid IGD timeout"); + return false; + } + + return not validIGDs_.empty(); +} + +/** + * chooses the IGD to use, + * assumes you already have a lock on validIGDMutex_ + */ +IGD* +UPnPContext::chooseIGD_unlocked() const +{ + if (validIGDs_.empty()) + return nullptr; + return validIGDs_.begin()->second.get(); +} + +/** + * tries to add mapping + */ +Mapping +UPnPContext::addMapping(IGD* igd, + uint16_t port_external, + uint16_t port_internal, + PortType type, + int *upnp_error) +{ + *upnp_error = -1; + + Mapping mapping{port_external, port_internal}; + + /* check if this mapping already exists + * if the mapping is the same, then we just need to increment the number of users globally + * if the mapping is not the same, then we have to return fail, as the external port is used + * for something else + * if the mapping doesn't exist, then try to add it + */ + auto globalMappings = type == PortType::UDP ? &igd->udpMappings : &igd->tcpMappings; + auto iter = globalMappings->find(port_external); + if (iter != globalMappings->end()) { + /* mapping exists with same external port */ + GlobalMapping* mapping_ptr = &iter->second; + if (*mapping_ptr == mapping) { + /* the same mapping, so nothing needs to be done */ + *upnp_error = UPNP_E_SUCCESS; + ++(mapping_ptr->users); + RING_DBG("UPnp : mapping already exists, incrementing number of users: %d", + iter->second.users); + return mapping; + } else { + /* this port is already used by a different mapping */ + RING_WARN("UPnP: cannot add a mapping with an external port which is already used by another:\n\tcurrent: %s\n\ttrying to add: %s", + mapping_ptr->toString().c_str(), mapping.toString().c_str()); + *upnp_error = CONFLICT_IN_MAPPING; + return {}; + } + } + + /* mapping doesn't exist, so try to add it */ + RING_DBG("UPnP: adding port mapping : %s", mapping.toString().c_str()); + + if(addPortMapping(igd, mapping, upnp_error)) { + /* success; add it to global list */ + globalMappings->emplace(port_external, std::move(GlobalMapping{mapping})); + return mapping; + } + return {}; +} + +static uint16_t +generateRandomPort() +{ + /* obtain a random number from hardware */ + static std::random_device rd; + /* seed the generator */ + static std::mt19937 gen(rd()); + /* define the range */ + static std::uniform_int_distribution<uint16_t> dist(Mapping::UPNP_PORT_MIN, Mapping::UPNP_PORT_MAX); + + return dist(gen);; +} + +/** + * chooses a random port that is not yet used by the daemon for UPnP + */ +uint16_t +UPnPContext::chooseRandomPort(const IGD* igd, PortType type) +{ + auto globalMappings = type == PortType::UDP ? + &igd->udpMappings : &igd->tcpMappings; + + uint16_t port = generateRandomPort(); + + /* keep generating random ports until we find one which is not used */ + while(globalMappings->find(port) != globalMappings->end()) { + port = generateRandomPort(); + } + + RING_DBG("UPnP: chose random port %u", port); + + return port; +} + +/** + * tries to add mapping from and to the port_desired + * if unique == true, makes sure the client is not using this port already + * if the mapping fails, tries other available ports until success + * + * tries to use a random port between 1024 < > 65535 if desired port fails + * + * maps port_desired to port_local; if use_same_port == true, makes sure that + * that the external and internal ports are the same + * + * returns a valid mapping on success and an invalid mapping on failure + */ +Mapping +UPnPContext::addAnyMapping(uint16_t port_desired, + uint16_t port_local, + PortType type, + bool use_same_port, + bool unique) +{ + /* get a lock on the igd list because we don't want the igd to be modified + * or removed from the list while using it */ + std::lock_guard<std::mutex> lock(validIGDMutex_); + IGD* igd = chooseIGD_unlocked(); + if (not igd) { + RING_WARN("UPnP: no valid IGD available"); + return {}; + } + + auto globalMappings = type == PortType::UDP ? + &igd->udpMappings : &igd->tcpMappings; + if (unique) { + /* check that port is not already used by the client */ + auto iter = globalMappings->find(port_desired); + if (iter != globalMappings->end()) { + /* port already used, we need a unique port */ + port_desired = chooseRandomPort(igd, type); + } + } + + if (use_same_port) + port_local = port_desired; + + int upnp_error; + Mapping mapping = addMapping(igd, port_desired, port_local, type, &upnp_error); + /* keep trying to add the mapping as long as the upnp error is 718 == conflicting mapping + * if adding the mapping fails for any other reason, give up + * don't try more than MAX_RETRIES to prevent infinite loops + */ + unsigned numberRetries = 0; + + while ( not mapping + and (upnp_error == CONFLICT_IN_MAPPING or upnp_error == INVALID_ARGS) + and numberRetries < MAX_RETRIES ) { + /* acceptable errors to keep trying: + * 718 : conflictin mapping + * 402 : invalid args (due to router implementation) + */ + RING_DBG("UPnP: mapping failed (conflicting entry? err = %d), trying with a different port.", + upnp_error); + /* TODO: make sure we don't try sellecting the same random port twice if it fails ? */ + port_desired = chooseRandomPort(igd, type); + if (use_same_port) + port_local = port_desired; + mapping = addMapping(igd, port_desired, port_local, type, &upnp_error); + ++numberRetries; + } + + if (not mapping and numberRetries == MAX_RETRIES) + RING_DBG("UPnP: could not add mapping after %u retries, giving up", MAX_RETRIES); + + return mapping; +} + +/** + * tries to remove the given mapping + */ +void +UPnPContext::removeMapping(const Mapping& mapping) +{ + /* get a lock on the igd list because we don't want the igd to be modified + * or removed from the list while using it */ + std::lock_guard<std::mutex> lock(validIGDMutex_); + IGD* igd = chooseIGD_unlocked(); + if (not igd) { + RING_WARN("UPnP: no valid IGD available"); + return; + } + + /* first make sure the mapping exists in the global list of the igd */ + auto globalMappings = mapping.getType() == PortType::UDP ? + &igd->udpMappings : &igd->tcpMappings; + + auto iter = globalMappings->find(mapping.getPortExternal()); + if ( iter != globalMappings->end() ) { + /* make sure its the same mapping */ + GlobalMapping *global_mapping = &iter->second; + if (mapping == *global_mapping ) { + /* now check the users */ + if (global_mapping->users > 1) { + /* more than one user, simply decrement the number */ + --(global_mapping->users); + RING_DBG("UPnP: decrementing users of mapping: %s, %d users remaining", + mapping.toString().c_str(), global_mapping->users); + } else { + /* no other users, can delete */ + RING_DBG("UPnP: removing port mapping : %s", + mapping.toString().c_str()); + deletePortMapping(igd, + mapping.getPortExternalStr(), + mapping.getTypeStr()); + globalMappings->erase(iter); + } + } else { + RING_WARN("UPnP: cannot remove mapping which doesn't match the existing one in the IGD list"); + } + } else { + RING_WARN("UPnP: cannot remove mapping which is not in the list of existing mappings of the IGD"); + } +} + +IpAddr +UPnPContext::getLocalIP() const +{ + /* get a lock on the igd list because we don't want the igd to be modified + * or removed from the list while using it */ + std::lock_guard<std::mutex> lock(validIGDMutex_); + + /* if its a valid igd, we must have already gotten the local ip */ + if (auto igd = chooseIGD_unlocked()) + return igd->localIp; + + RING_WARN("UPnP: no valid IGD available"); + return {}; +} + +IpAddr +UPnPContext::getExternalIP() const +{ + /* get a lock on the igd list because we don't want the igd to be modified + * or removed from the list while using it */ + std::lock_guard<std::mutex> lock(validIGDMutex_); + + /* if its a valid igd, we must have already gotten the external ip */ + if (auto igd = chooseIGD_unlocked()) + return igd->publicIp; + + RING_WARN("UPnP: no valid IGD available"); + return {}; +} + +/** + * Parses the device description and adds desired devices to + * relevant lists + */ +void +UPnPContext::parseDevice(IXML_Document* doc, const Upnp_Discovery* d_event) +{ + if (not doc or not d_event) + return; + + /* check to see the device type */ + std::string deviceType = get_first_doc_item(doc, "deviceType"); + if (deviceType.empty()) { + /* RING_DBG("UPnP: could not find deviceType in the description document of the device"); */ + return; + } + + if (deviceType.compare(UPNP_IGD_DEVICE) == 0) { + parseIGD(doc, d_event); + } + + /* TODO: check if its a ring device */ +} + +void +UPnPContext::parseIGD(IXML_Document* doc, const Upnp_Discovery* d_event) +{ + if (not doc or not d_event) + return; + + /* check the UDN to see if its already in our device list(s) + * if it is, then update the device advertisement timeout (expiration) + */ + std::string UDN = get_first_doc_item(doc, "UDN"); + if (UDN.empty()) { + RING_DBG("UPnP: could not find UDN in description document of device"); + return; + } + + { + std::lock_guard<std::mutex> lock(validIGDMutex_); + auto it = validIGDs_.find(UDN); + + if (it != validIGDs_.end()) { + /* we already have this device in our list */ + /* TODO: update expiration */ + return; + } + } + + std::unique_ptr<IGD> new_igd; + int upnp_err; + + std::string friendlyName = get_first_doc_item(doc, "friendlyName"); + if (not friendlyName.empty() ) + RING_DBG("UPnP: checking new device of type IGD: '%s'", + friendlyName.c_str()); + + /* determine baseURL */ + std::string baseURL = get_first_doc_item(doc, "URLBase"); + if (baseURL.empty()) { + /* get it from the discovery event location */ + baseURL = std::string(d_event->Location); + } + + /* check if its a valid IGD: + * 1. check for IGD device... already done if this function is called + * 2. check for WAN device... skip checking for this and check for the services directly + * 3. check for WANIPConnection service or WANPPPConnection service + * 4. check if connected to Internet (if not, no point in port forwarding) + * 5. check that we can get the external IP + */ + + /* get list of services defined by serviceType */ + std::unique_ptr<IXML_NodeList, decltype(ixmlNodeList_free)&> serviceList(nullptr, ixmlNodeList_free); + serviceList.reset(ixmlDocument_getElementsByTagName(doc, "serviceType")); + + /* get list of all 'serviceType' elements */ + bool found_connected_IGD = false; + unsigned long list_length = ixmlNodeList_length(serviceList.get()); + + /* go through the 'serviceType' nodes until we find the first service of type + * WANIPConnection or WANPPPConnection which is connected to an external network */ + for (unsigned long node_idx = 0; node_idx < list_length and not found_connected_IGD; node_idx++) { + IXML_Node* serviceType_node = ixmlNodeList_item(serviceList.get(), node_idx); + std::string serviceType = get_element_text(serviceType_node); + + /* only check serviceType of WANIPConnection or WANPPPConnection */ + if (serviceType.compare(UPNP_WANIP_SERVICE) == 0 + or serviceType.compare(UPNP_WANPPP_SERVICE) == 0) { + + /* we found a correct 'serviceType', now get the parent node because + * the rest of the service definitions are siblings of 'serviceType' */ + IXML_Node* service_node = ixmlNode_getParentNode(serviceType_node); + if (service_node) { + /* perform sanity check; the parent node should be called "service" */ + if( strcmp(ixmlNode_getNodeName(service_node), "service") == 0) { + /* get the rest of the service definitions */ + + /* serviceId */ + IXML_Element* service_element = (IXML_Element*)service_node; + std::string serviceId = get_first_element_item(service_element, "serviceId"); + + /* get the relative controlURL and turn it into absolute address using the URLBase */ + std::string controlURL = get_first_element_item(service_element, "controlURL"); + if (not controlURL.empty()) { + char* absolute_url = nullptr; + upnp_err = UpnpResolveURL2(baseURL.c_str(), + controlURL.c_str(), + &absolute_url); + if (upnp_err == UPNP_E_SUCCESS) + controlURL = absolute_url; + else + RING_WARN("UPnP: error resolving absolute controlURL: %s", + UpnpGetErrorMessage(upnp_err)); + std::free(absolute_url); + } + + /* get the relative eventSubURL and turn it into absolute address using the URLBase */ + std::string eventSubURL = get_first_element_item(service_element, "eventSubURL"); + if (not eventSubURL.empty()) { + char* absolute_url = nullptr; + upnp_err = UpnpResolveURL2(baseURL.c_str(), + eventSubURL.c_str(), + &absolute_url); + if (upnp_err == UPNP_E_SUCCESS) + eventSubURL = absolute_url; + else + RING_WARN("UPnP: error resolving absolute eventSubURL: %s", + UpnpGetErrorMessage(upnp_err)); + std::free(absolute_url); + } + + /* make sure all of the services are defined + * and check if the IGD is connected to an external network */ + if (not (serviceId.empty() and controlURL.empty() and eventSubURL.empty()) ) { + /* RING_DBG("UPnP: got service info from device:\n\tserviceType: %s\n\tserviceID: %s\n\tcontrolURL: %s\n\teventSubURL: %s", + serviceType.c_str(), serviceId.c_str(), controlURL.c_str(), eventSubURL.c_str()); */ + new_igd.reset(new IGD(UDN, baseURL, friendlyName, serviceType, serviceId, controlURL, eventSubURL)); + if (isIGDConnected(new_igd.get())) { + new_igd->publicIp = getExternalIP(new_igd.get()); + if (new_igd->publicIp) { + RING_DBG("UPnP: got external IP: %s", new_igd->publicIp.toString().c_str()); + new_igd->localIp = ip_utils::getLocalAddr(pj_AF_INET()); + if (new_igd->localIp) + found_connected_IGD = true; + + } + } + } + /* TODO: subscribe to the service to get events, eg: when IP changes */ + } else + RING_WARN("UPnP: IGD \"serviceType\" parent node is not called \"service\"!"); + } else + RING_WARN("UPnP: IGD \"serviceType\" has no parent node!"); + } + } + + /* if its a valid IGD, add to list of IGDs (ideally there is only one at a time) + * subscribe to the WANIPConnection or WANPPPConnection service to receive + * updates about state changes, eg: new external IP + */ + if (found_connected_IGD) { + RING_DBG("UPnP: found a valid IGD: %s", new_igd->getBaseURL().c_str()); + + { + std::lock_guard<std::mutex> lock(validIGDMutex_); + /* delete all RING mappings first */ + removeMappingsByLocalIPAndDescription(new_igd.get(), Mapping::UPNP_DEFAULT_MAPPING_DESCRIPTION); + validIGDs_.emplace(UDN, std::move(new_igd)); + } + } +} + +static std::string +get_element_text(IXML_Node* node) +{ + std::string ret; + if (node) { + IXML_Node *textNode = ixmlNode_getFirstChild(node); + if (textNode) { + const char* value = ixmlNode_getNodeValue(textNode); + if (value) + ret = std::string(value); + } + } + return ret; +} + +static std::string +get_first_doc_item(IXML_Document* doc, const char* item) +{ + std::string ret; + + std::unique_ptr<IXML_NodeList, decltype(ixmlNodeList_free)&> + nodeList(ixmlDocument_getElementsByTagName(doc, item), ixmlNodeList_free); + if (nodeList) { + /* if there are several nodes which match the tag, we only want the first one */ + ret = get_element_text( ixmlNodeList_item(nodeList.get(), 0) ); + } + return ret; +} + +static std::string +get_first_element_item(IXML_Element* element, const char* item) +{ + std::string ret; + + std::unique_ptr<IXML_NodeList, decltype(ixmlNodeList_free)&> + nodeList(ixmlElement_getElementsByTagName(element, item), ixmlNodeList_free); + if (nodeList) { + /* if there are several nodes which match the tag, we only want the first one */ + ret = get_element_text( ixmlNodeList_item(nodeList.get(), 0) ); + } + return ret; +} +int +UPnPContext::handleUPnPEvents(Upnp_EventType event_type, void* event) +{ + switch( event_type ) + { + case UPNP_DISCOVERY_ADVERTISEMENT_ALIVE: + /* RING_DBG("UPnP: CP received a discovery advertisement"); */ + case UPNP_DISCOVERY_SEARCH_RESULT: + { + struct Upnp_Discovery* d_event = ( struct Upnp_Discovery* )event; + std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> desc_doc(nullptr, ixmlDocument_free); + int upnp_err; + + /* if (event_type != UPNP_DISCOVERY_ADVERTISEMENT_ALIVE) + RING_DBG("UPnP: CP received a discovery search result"); */ + + /* check if we are already in the process of checking this device */ + std::unique_lock<std::mutex> lock(cpDeviceMutex_); + auto it = cpDevices_.find(std::string(d_event->Location)); + + if (it == cpDevices_.end()) { + cpDevices_.emplace(std::string(d_event->Location)); + lock.unlock(); + + if (d_event->ErrCode != UPNP_E_SUCCESS) + RING_WARN("UPnP: Error in discovery event received by the CP: %s", + UpnpGetErrorMessage(d_event->ErrCode)); + + /* RING_DBG("UPnP: Control Point received discovery event from device:\n\tid: %s\n\ttype: %s\n\tservice: %s\n\tversion: %s\n\tlocation: %s\n\tOS: %s", + d_event->DeviceId, d_event->DeviceType, d_event->ServiceType, d_event->ServiceVer, d_event->Location, d_event->Os); + */ + + /* note: this thing will block until success for the system socket timeout + * unless libupnp is compile with '-disable-blocking-tcp-connections' + * in which case it will block for the libupnp specified timeout + */ + IXML_Document* desc_doc_ptr = nullptr; + upnp_err = UpnpDownloadXmlDoc( d_event->Location, &desc_doc_ptr); + desc_doc.reset(desc_doc_ptr); + if ( upnp_err != UPNP_E_SUCCESS ) { + /* the download of the xml doc has failed; this probably happened + * because the router has UPnP disabled, but is still sending + * UPnP discovery packets + * + * RING_WARN("UPnP: Error downloading device description: %s", + * UpnpGetErrorMessage(upnp_err)); + */ + } else { + parseDevice(desc_doc.get(), d_event); + } + + /* finished parsing device; remove it from know devices list, + * since next time it could be a new device with same URL + * eg: if we switch routers or if a new device with the same IP appears + */ + lock.lock(); + cpDevices_.erase(d_event->Location); + lock.unlock(); + } else { + lock.unlock(); + /* RING_DBG("UPnP: Control Point is already checking this device"); */ + } + } + break; + + case UPNP_DISCOVERY_ADVERTISEMENT_BYEBYE: + { + struct Upnp_Discovery *d_event = (struct Upnp_Discovery *)event; + + RING_DBG("UPnP: Control Point received ByeBye for device: %s", d_event->DeviceId); + + if (d_event->ErrCode != UPNP_E_SUCCESS) + RING_WARN("UPnP: Error in ByeBye received by the CP: %s", + UpnpGetErrorMessage(d_event->ErrCode)); + + /* TODO: check if its a device we care about and remove it from the relevant lists */ + } + break; + + case UPNP_EVENT_RECEIVED: + { + /* struct Upnp_Event *e_event UNUSED = (struct Upnp_Event *)event; */ + + /* RING_DBG("UPnP: Control Point event received"); */ + + /* TODO: handle event by updating any changed state variables */ + + } + break; + + case UPNP_EVENT_AUTORENEWAL_FAILED: + { + RING_WARN("UPnP: Control Point subscription auto-renewal failed"); + } + break; + + case UPNP_EVENT_SUBSCRIPTION_EXPIRED: + { + RING_DBG("UPnP: Control Point subscription expired"); + } + break; + + case UPNP_EVENT_SUBSCRIBE_COMPLETE: + /* RING_DBG("UPnP: Control Point async subscription complete"); */ + + /* TODO: check if successfull */ + + break; + + case UPNP_DISCOVERY_SEARCH_TIMEOUT: + /* this event will occur whether or not a valid IGD has been found; + * it just indicates the search timeout has been reached + * + * RING_DBG("UPnP: Control Point search timeout"); + */ + break; + + case UPNP_CONTROL_ACTION_COMPLETE: + { + struct Upnp_Action_Complete *a_event = (struct Upnp_Action_Complete *)event; + + /* RING_DBG("UPnP: Control Point async action complete"); */ + + if (a_event->ErrCode != UPNP_E_SUCCESS) + RING_WARN("UPnP: Error in action complete event: %s", + UpnpGetErrorMessage(a_event->ErrCode)); + + /* TODO: no need for any processing here, just print out results. + * Service state table updates are handled by events. */ + } + break; + + case UPNP_CONTROL_GET_VAR_COMPLETE: + { + struct Upnp_State_Var_Complete *sv_event = (struct Upnp_State_Var_Complete *)event; + + /* RING_DBG("UPnP: Control Point async get variable complete"); */ + + if (sv_event->ErrCode != UPNP_E_SUCCESS) + RING_WARN("UPnP: Error in get variable complete event: %s", + UpnpGetErrorMessage(sv_event->ErrCode)); + + /* TODO: update state variables */ + } + break; + + default: + RING_WARN("UPnP: unhandled Control Point event"); + break; + } + + return UPNP_E_SUCCESS; /* return value currently ignored by SDK */ +} + +static void +checkResponseError(IXML_Document* doc) +{ + if (not doc) + return; + + std::string errorCode = get_first_doc_item(doc, "errorCode"); + if (not errorCode.empty()) { + std::string errorDescription = get_first_doc_item(doc, "errorDescription"); + RING_WARN("UPnP: response contains error: %s : %s", + errorCode.c_str(), errorDescription.c_str()); + } +} + +bool +UPnPContext::isIGDConnected(const IGD* igd) +{ + bool connected = false; + std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> action(nullptr, ixmlDocument_free); + action.reset(UpnpMakeAction("GetStatusInfo", igd->getServiceType().c_str(), 0, nullptr)); + + std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> response(nullptr, ixmlDocument_free); + IXML_Document* response_ptr = nullptr; + int upnp_err = UpnpSendAction(ctrlptHandle_, igd->getControlURL().c_str(), + igd->getServiceType().c_str(), nullptr, action.get(), &response_ptr); + response.reset(response_ptr); + checkResponseError(response.get()); + if( upnp_err != UPNP_E_SUCCESS) { + /* TODO: if failed, should we chck if the igd is disconnected? */ + RING_WARN("UPnP: Failed to get GetStatusInfo from: %s, %d: %s", + igd->getServiceType().c_str(), upnp_err, UpnpGetErrorMessage(upnp_err)); + + return false; + } + + /* parse response */ + std::string status = get_first_doc_item(response.get(), "NewConnectionStatus"); + if (status.compare("Connected") == 0) + connected = true; + + /* response should also contain the following elements, but we don't care for now: + * "NewLastConnectionError" + * "NewUptime" + */ + return connected; +} + +IpAddr +UPnPContext::getExternalIP(const IGD* igd) +{ + std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> action(nullptr, ixmlDocument_free); + action.reset(UpnpMakeAction("GetExternalIPAddress", igd->getServiceType().c_str(), 0, nullptr)); + + std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> response(nullptr, ixmlDocument_free); + IXML_Document* response_ptr = nullptr; + int upnp_err = UpnpSendAction(ctrlptHandle_, igd->getControlURL().c_str(), + igd->getServiceType().c_str(), nullptr, action.get(), &response_ptr); + response.reset(response_ptr); + checkResponseError(response.get()); + if( upnp_err != UPNP_E_SUCCESS) { + /* TODO: if failed, should we chck if the igd is disconnected? */ + RING_WARN("UPnP: Failed to get GetExternalIPAddress from: %s, %d: %s", + igd->getServiceType().c_str(), upnp_err, UpnpGetErrorMessage(upnp_err)); + return {}; + } + + /* parse response */ + return {get_first_doc_item(response.get(), "NewExternalIPAddress")}; +} + +void +UPnPContext::removeMappingsByLocalIPAndDescription(const IGD* igd, const std::string& description) +{ + if (!igd->localIp) { + RING_DBG("UPnP: cannot determine local IP in function removeMappingsByLocalIPAndDescription()"); + return; + } + + RING_DBG("UPnP: removing all port mappings with description: \"%s\" and local ip: %s", + description.c_str(), igd->localIp.toString().c_str()); + + int entry_idx = 0; + bool done = false; + + do { + std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> action(nullptr, ixmlDocument_free); + IXML_Document* action_ptr = nullptr; + UpnpAddToAction(&action_ptr, "GetGenericPortMappingEntry", igd->getServiceType().c_str(), + "NewPortMappingIndex", std::to_string(entry_idx).c_str()); + action.reset(action_ptr); + + std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> response(nullptr, ixmlDocument_free); + IXML_Document* response_ptr = nullptr; + int upnp_err = UpnpSendAction(ctrlptHandle_, igd->getControlURL().c_str(), + igd->getServiceType().c_str(), nullptr, action.get(), &response_ptr); + response.reset(response_ptr); + if( not response and upnp_err != UPNP_E_SUCCESS) { + /* TODO: if failed, should we chck if the igd is disconnected? */ + RING_WARN("UPnP: Failed to get GetGenericPortMappingEntry from: %s, %d: %s", + igd->getServiceType().c_str(), upnp_err, UpnpGetErrorMessage(upnp_err)); + return; + } + + /* check if there is an error code */ + std::string errorCode = get_first_doc_item(response.get(), "errorCode"); + + if (errorCode.empty()) { + /* no error, prase the rest of the response */ + std::string desc_actual = get_first_doc_item(response.get(), "NewPortMappingDescription"); + std::string client_ip = get_first_doc_item(response.get(), "NewInternalClient"); + + /* check if same IP and description */ + if (IpAddr(client_ip) == igd->localIp and desc_actual.compare(description) == 0) { + /* get the rest of the needed parameters */ + std::string port_internal = get_first_doc_item(response.get(), "NewInternalPort"); + std::string port_external = get_first_doc_item(response.get(), "NewExternalPort"); + std::string protocol = get_first_doc_item(response.get(), "NewProtocol"); + + RING_DBG("UPnP: deleting entry with matching desciption and ip:\n\t%s %5s->%s:%-5s '%s'", + protocol.c_str(), port_external.c_str(), client_ip.c_str(), port_internal.c_str(), desc_actual.c_str()); + + /* delete entry */ + if (not deletePortMapping(igd, port_external, protocol)) { + /* failed to delete entry, skip it and try the next one */ + ++entry_idx; + } + /* note: in the case that the entry deletion is successfull, we do not increment the entry + * idx as the number of entries has decreased by one */ + } else + ++entry_idx; + + } else if (errorCode.compare(ARRAY_IDX_INVALID_STR) == 0 + or errorCode.compare(INVALID_ARGS_STR) == 0) { + /* 713 means there are no more entires, but some routers will return 402 instead */ + done = true; + } else { + std::string errorDescription = get_first_doc_item(response.get(), "errorDescription"); + RING_WARN("UPnP: GetGenericPortMappingEntry returned with error: %s: %s", + errorCode.c_str(), errorDescription.c_str()); + done = true; + } + } while(not done); +} + +bool +UPnPContext::deletePortMapping(const IGD* igd, const std::string& port_external, const std::string& protocol) +{ + std::string action_name{"DeletePortMapping"}; + std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> action(nullptr, ixmlDocument_free); + IXML_Document* action_ptr = nullptr; + UpnpAddToAction(&action_ptr, action_name.c_str(), igd->getServiceType().c_str(), + "NewRemoteHost", ""); + UpnpAddToAction(&action_ptr, action_name.c_str(), igd->getServiceType().c_str(), + "NewExternalPort", port_external.c_str()); + UpnpAddToAction(&action_ptr, action_name.c_str(), igd->getServiceType().c_str(), + "NewProtocol", protocol.c_str()); + action.reset(action_ptr); + + std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> response(nullptr, ixmlDocument_free); + IXML_Document* response_ptr = nullptr; + int upnp_err = UpnpSendAction(ctrlptHandle_, igd->getControlURL().c_str(), + igd->getServiceType().c_str(), nullptr, action.get(), &response_ptr); + response.reset(response_ptr); + if( upnp_err != UPNP_E_SUCCESS) { + /* TODO: if failed, should we check if the igd is disconnected? */ + RING_WARN("UPnP: Failed to get %s from: %s, %d: %s", action_name.c_str(), + igd->getServiceType().c_str(), upnp_err, UpnpGetErrorMessage(upnp_err)); + return false; + } + /* check if there is an error code */ + std::string errorCode = get_first_doc_item(response.get(), "errorCode"); + if (not errorCode.empty()) { + std::string errorDescription = get_first_doc_item(response.get(), "errorDescription"); + RING_WARN("UPnP: %s returned with error: %s: %s", + action_name.c_str(), errorCode.c_str(), errorDescription.c_str()); + return false; + } + return true; +} + +bool +UPnPContext::addPortMapping(const IGD* igd, const Mapping& mapping, int* error_code) +{ + *error_code = UPNP_E_SUCCESS; + + std::string action_name{"AddPortMapping"}; + std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> action(nullptr, ixmlDocument_free); + IXML_Document* action_ptr = nullptr; + UpnpAddToAction(&action_ptr, action_name.c_str(), igd->getServiceType().c_str(), + "NewRemoteHost", ""); + UpnpAddToAction(&action_ptr, action_name.c_str(), igd->getServiceType().c_str(), + "NewExternalPort", mapping.getPortExternalStr().c_str()); + UpnpAddToAction(&action_ptr, action_name.c_str(), igd->getServiceType().c_str(), + "NewProtocol", mapping.getTypeStr().c_str()); + UpnpAddToAction(&action_ptr, action_name.c_str(), igd->getServiceType().c_str(), + "NewInternalPort", mapping.getPortInternalStr().c_str()); + UpnpAddToAction(&action_ptr, action_name.c_str(), igd->getServiceType().c_str(), + "NewInternalClient", igd->localIp.toString().c_str()); + UpnpAddToAction(&action_ptr, action_name.c_str(), igd->getServiceType().c_str(), + "NewEnabled", "1"); + UpnpAddToAction(&action_ptr, action_name.c_str(), igd->getServiceType().c_str(), + "NewPortMappingDescription", mapping.getDescription().c_str()); + /* for now assume lease duration is always infinite */ + UpnpAddToAction(&action_ptr, action_name.c_str(), igd->getServiceType().c_str(), + "NewLeaseDuration", "0"); + action.reset(action_ptr); + + std::unique_ptr<IXML_Document, decltype(ixmlDocument_free)&> response(nullptr, ixmlDocument_free); + IXML_Document* response_ptr = nullptr; + int upnp_err = UpnpSendAction(ctrlptHandle_, igd->getControlURL().c_str(), + igd->getServiceType().c_str(), nullptr, action.get(), &response_ptr); + response.reset(response_ptr); + if( not response and upnp_err != UPNP_E_SUCCESS) { + /* TODO: if failed, should we chck if the igd is disconnected? */ + RING_WARN("UPnP: Failed to %s from: %s, %d: %s", action_name.c_str(), + igd->getServiceType().c_str(), upnp_err, UpnpGetErrorMessage(upnp_err)); + *error_code = -1; /* make sure to -1 since we didn't get a response */ + return false; + } + + /* check if there is an error code */ + std::string errorCode = get_first_doc_item(response.get(), "errorCode"); + if (not errorCode.empty()) { + std::string errorDescription = get_first_doc_item(response.get(), "errorDescription"); + RING_WARN("UPnP: %s returned with error: %s: %s", + action_name.c_str(), errorCode.c_str(), errorDescription.c_str()); + *error_code = std::stoi(errorCode); + return false; + } + return true; +} + +#endif /* HAVE_LIBUPNP */ + +}} // namespace ring::upnp diff --git a/src/upnp/upnp_context.h b/src/upnp/upnp_context.h new file mode 100644 index 0000000000..13578945ca --- /dev/null +++ b/src/upnp/upnp_context.h @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef UPNP_CONTEXT_H_ +#define UPNP_CONTEXT_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <set> +#include <map> +#include <mutex> +#include <memory> +#include <condition_variable> +#include <chrono> +#include <atomic> + +#if HAVE_LIBUPNP +#include <upnp/upnp.h> +#include <upnp/upnptools.h> +#endif + +#include "noncopyable.h" +#include "upnp_igd.h" + +namespace ring { +class IpAddr; +} + +namespace ring { namespace upnp { + +class UPnPContext { + +public: + constexpr static unsigned SEARCH_TIMEOUT {30}; + + +#if HAVE_LIBUPNP + UPnPContext(); + ~UPnPContext(); + + /** + * Returns 'true' if there is at least one valid (connected) IGD. + * @param timeout Time to wait until a valid IGD is found. + * If timeout is not given or 0, the function pool (non-blocking). + */ + bool hasValidIGD(std::chrono::seconds timeout = {}); + + /** + * tries to add mapping from and to the port_desired + * if unique == true, makes sure the client is not using this port already + * if the mapping fails, tries other available ports until success + * + * tries to use a random port between 1024 < > 65535 if desired port fails + * + * maps port_desired to port_local; if use_same_port == true, makes sure + * that the external and internal ports are the same + * + * returns a valid mapping on success and an invalid mapping on failure + */ + Mapping addAnyMapping(uint16_t port_desired, + uint16_t port_local, + PortType type, + bool use_same_port, + bool unique); + + /** + * tries to remove the given mapping + */ + void removeMapping(const Mapping& mapping); + + /** + * tries to get the external ip of the router + */ + IpAddr getExternalIP() const; + + + /** + * get our local ip + */ + IpAddr getLocalIP() const; + + /** + * callback function for the UPnP client (control point) + * all UPnP events received by the client are processed here + */ + int handleUPnPEvents(Upnp_EventType event_type, void* event); + +#else + /* use default constructor and destructor */ + UPnPContext() = default; + ~UPnPContext() = default; +#endif + +private: + NON_COPYABLE(UPnPContext); + +#if HAVE_LIBUPNP + + /** + * UPnP devices typically send out several discovery + * packets at the same time. libupnp creates a separate event + * for each discovery packet which is processed in the threadpool, + * even if the multiple discovery packets are received from the + * same IP at the same time. In order to prevent trying + * to download and parse the device description from the + * same location in multiple threads at the same time, we + * keep track from which URL(s) we are in the process of downloading + * and parsing the device description in this set. + * + * The main purspose of this is to prevent blocking multiple + * threads when trying to download the description from an + * unresponsive device (the timeout can be several seconds) + * + * The mutex is to access the set in a thread safe manner + */ + + std::set<std::string> cpDevices_; + std::mutex cpDeviceMutex_; + + /** + * control and device handles; + * set by the SDK once each is registered + */ + UpnpClient_Handle ctrlptHandle_ {-1}; + UpnpDevice_Handle deviceHandle_ {-1}; + + /** + * keep track if we've successfully registered + * a client and/ore device + */ + std::atomic_bool clientRegistered_ {false}; + bool deviceRegistered_ {false}; + + /** + * map of valid IGDs - IGDs which have the correct services and are connected + * to some external network (have an external IP) + * + * the UDN string is used to uniquely identify the IGD + * + * the mutex is used to access these lists and IGDs in a thread-safe manner + */ + std::map<std::string, std::unique_ptr<IGD>> validIGDs_; + mutable std::mutex validIGDMutex_; + std::condition_variable validIGDCondVar_; + + /** + * chooses the IGD to use (currently selects the first one in the map) + * assumes you already have a lock on igd_mutex_ + */ + IGD* chooseIGD_unlocked() const; + + /* sends out async search for IGD */ + void searchForIGD(); + + /** + * Parses the device description and adds desired devices to + * relevant lists + */ + void parseDevice(IXML_Document* doc, const Upnp_Discovery* d_event); + + void parseIGD(IXML_Document* doc, const Upnp_Discovery* d_event); + + /* tries to add mapping, assumes you alreayd have lock on igd_mutex_ */ + Mapping addMapping(IGD* igd, + uint16_t port_external, + uint16_t port_internal, + PortType type, + int *upnp_error); + + uint16_t chooseRandomPort(const IGD* igd, PortType type); + + /* these functions directly create UPnP actions + * and make synchronous UPnP control point calls + * they assume you have a lock on the igd_mutex_ */ + bool isIGDConnected(const IGD* igd); + + IpAddr getExternalIP(const IGD* igd); + + void removeMappingsByLocalIPAndDescription(const IGD* igd, + const std::string& description); + + bool deletePortMapping(const IGD* igd, + const std::string& port_external, + const std::string& protocol); + + bool addPortMapping(const IGD* igd, + const Mapping& mapping, + int* error_code); + +#endif /* HAVE_LIBUPNP */ + +}; + +/** + * This should be used to get a UPnPContext. + * It only makes sense to have one unless you have separate + * contexts for multiple internet interfaces, which is not currently + * supported. + */ +std::shared_ptr<UPnPContext> getUPnPContext(); + +}} // namespace ring::upnp + +#endif /* UPNP_CONTEXT_H_ */ diff --git a/src/upnp/upnp_control.cpp b/src/upnp/upnp_control.cpp new file mode 100644 index 0000000000..ffcf933fa5 --- /dev/null +++ b/src/upnp/upnp_control.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "upnp_control.h" + +#include <memory> + +#include "logger.h" +#include "ip_utils.h" +#include "upnp_context.h" +#include "upnp_igd.h" + +namespace ring { namespace upnp { + +Controller::Controller() +{ + try { + upnpContext_ = getUPnPContext(); + } catch (std::runtime_error& e) { + RING_ERR("UPnP context error: %s", e.what()); + } +} + +Controller::~Controller() +{ + /* remove all mappings */ + removeMappings(); +} + +bool +Controller::hasValidIGD(std::chrono::seconds timeout) +{ +#if HAVE_LIBUPNP + return upnpContext_ and upnpContext_->hasValidIGD(timeout); +#endif + return false; +} + +bool +Controller::addAnyMapping(uint16_t port_desired, + uint16_t port_local, + PortType type, + bool use_same_port, + bool unique, + uint16_t *port_used) +{ +#if HAVE_LIBUPNP + if (not upnpContext_) + return false; + + Mapping mapping = upnpContext_->addAnyMapping(port_desired, port_local, type, + use_same_port, unique); + if (mapping) { + auto usedPort = mapping.getPortExternal(); + if (port_used) + *port_used = usedPort; + + /* add to map */ + auto& instanceMappings = type == PortType::UDP ? udpMappings_ : tcpMappings_; + instanceMappings.emplace(usedPort, std::move(mapping)); + return true; + } +#endif + return false; +} + +bool +Controller::addAnyMapping(uint16_t port_desired, + PortType type, + bool unique, + uint16_t *port_used) +{ + return addAnyMapping(port_desired, port_desired, type, true, unique, + port_used); +} + +void +Controller::removeMappings(PortType type) { +#if HAVE_LIBUPNP + if (not upnpContext_) + return; + + auto& instanceMappings = type == PortType::UDP ? udpMappings_ : tcpMappings_; + for (auto iter = instanceMappings.begin(); iter != instanceMappings.end(); ){ + auto& mapping = iter->second; + upnpContext_->removeMapping(mapping); + iter = instanceMappings.erase(iter); + } +#endif +} +void +Controller::removeMappings() +{ +#if HAVE_LIBUPNP + removeMappings(PortType::UDP); + removeMappings(PortType::TCP); +#endif +} + +IpAddr +Controller::getLocalIP() const +{ +#if HAVE_LIBUPNP + if (upnpContext_) + return upnpContext_->getLocalIP(); +#endif + return {}; // empty address +} + +IpAddr +Controller::getExternalIP() const +{ +#if HAVE_LIBUPNP + if (upnpContext_) + return upnpContext_->getExternalIP(); +#endif + return {}; // empty address +} + +}} // namespace ring::upnp diff --git a/src/upnp/upnp_control.h b/src/upnp/upnp_control.h new file mode 100644 index 0000000000..612456e689 --- /dev/null +++ b/src/upnp/upnp_control.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef UPNP_H_ +#define UPNP_H_ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <memory> +#include <chrono> + +#include "noncopyable.h" +#include "upnp_igd.h" + +namespace ring { +class IpAddr; +} + +namespace ring { namespace upnp { + +class UPnPContext; + +class Controller { +public: + typedef std::function<void(bool)> IGDFoundCallback; + + /* constructor */ + Controller(); + /* destructor */ + ~Controller(); + + /** + * Return whether or not this controller has a valid IGD. + * @param timeout Time to wait until a valid IGD is found. + * If timeout is not given or 0, the function pool (non-blocking). + */ + bool hasValidIGD(std::chrono::seconds timeout = {}); + + /** + * tries to add mapping from and to the port_desired + * if unique == true, makes sure the client is not using this port already + * if the mapping fails, tries other available ports until success + * + * tries to use a random port between 1024 < > 65535 if desired port fails + * + * maps port_desired to port_local; if use_same_port == true, makes sure that + * that the extranl and internal ports are the same + */ + bool addAnyMapping(uint16_t port_desired, + uint16_t port_local, + PortType type, + bool use_same_port, + bool unique, + uint16_t *port_used); + + /** + * addAnyMapping with the local port being the same as the external port + */ + bool addAnyMapping(uint16_t port_desired, + PortType type, + bool unique, + uint16_t *port_used); + + /** + * removes all mappings added by this instance + */ + void removeMappings(); + + /** + * tries to get the external ip of the IGD (router) + */ + IpAddr getExternalIP() const; + + /** + * tries to get the local ip of the IGD (router) + */ + IpAddr getLocalIP() const; + +private: + + /** + * All UPnP commands require an initialized upnpContext + */ + std::shared_ptr<UPnPContext> upnpContext_; + + /** + * list of mappings created by this instance + * the key is the external port number, as there can only be one mapping + * at a time for each external port + */ + PortMapLocal udpMappings_; + PortMapLocal tcpMappings_; + + /** + * Try to remove all mappings of the given type + */ + void removeMappings(PortType type); +}; + +}} // namespace ring::upnp + +#endif /* UPNP_H_ */ diff --git a/src/upnp/upnp_igd.cpp b/src/upnp/upnp_igd.cpp new file mode 100644 index 0000000000..dfe0a410c9 --- /dev/null +++ b/src/upnp/upnp_igd.cpp @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "upnp_igd.h" + +namespace ring { namespace upnp { + +/* move constructor and operator */ +Mapping::Mapping(Mapping&& other) + : port_external_(other.port_external_) + , port_internal_(other.port_internal_) + , type_(other.type_) + , description_(std::move(other.description_)) +{ + other.port_external_ = 0; + other.port_internal_ = 0; +} + +Mapping& Mapping::operator=(Mapping&& other) +{ + if (this != &other) { + port_external_ = other.port_external_; + other.port_external_ = 0; + port_internal_ = other.port_internal_; + other.port_internal_ = 0; + type_ = other.type_; + description_ = std::move(other.description_); + } + return *this; +} + +bool operator== (Mapping &cMap1, Mapping &cMap2) +{ + /* we don't compare the description because it doesn't change the function of the + * mapping; we don't compare the IGD because for now we assume that we always + * use the same one and that all mappings are active + */ + return (cMap1.port_external_ == cMap2.port_external_ && + cMap1.port_internal_ == cMap2.port_internal_ && + cMap1.type_ == cMap2.type_); +} + +bool operator!= (Mapping &cMap1, Mapping &cMap2) +{ + return !(cMap1 == cMap2); +} + +}} // namespace ring::upnp diff --git a/src/upnp/upnp_igd.h b/src/upnp/upnp_igd.h new file mode 100644 index 0000000000..d27572b660 --- /dev/null +++ b/src/upnp/upnp_igd.h @@ -0,0 +1,193 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Stepan Salenikovich <stepan.salenikovich@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef UPNP_IGD_H_ +#define UPNP_IGD_H_ + +#include <string> +#include <map> + +#include "noncopyable.h" +#include "ip_utils.h" + +namespace ring { namespace upnp { + +enum class PortType {UDP,TCP}; + +/* defines a UPnP port mapping */ +class Mapping { +public: + constexpr static const char * UPNP_DEFAULT_MAPPING_DESCRIPTION = "RING"; + /* TODO: what should the port range really be? + * Should it be the ephemeral ports as defined by the system? + */ + constexpr static uint16_t UPNP_PORT_MIN = 1024; + constexpr static uint16_t UPNP_PORT_MAX = 65535; + + Mapping( + uint16_t port_external = 0, + uint16_t port_internal = 0, + PortType type = PortType::UDP, + std::string description = UPNP_DEFAULT_MAPPING_DESCRIPTION) + : port_external_(port_external) + , port_internal_(port_internal) + , type_(type) + , description_(description) + {}; + + /* move constructor and operator */ + Mapping(Mapping&&); + Mapping& operator=(Mapping&&); + + ~Mapping() = default; + + friend bool operator== (Mapping &cRedir1, Mapping &cRedir2); + friend bool operator!= (Mapping &cRedir1, Mapping &cRedir2); + + uint16_t getPortExternal() const { return port_external_; }; + std::string getPortExternalStr() const { return std::to_string(port_external_); }; + uint16_t getPortInternal() const { return port_internal_; }; + std::string getPortInternalStr() const { return std::to_string(port_internal_); }; + PortType getType() const { return type_; }; + std::string getTypeStr() const { return type_ == PortType::UDP ? "UDP" : "TCP"; } + std::string getDescription() const { return description_; }; + + std::string toString() const { + return getPortExternalStr() + ":" + getPortInternalStr() + ", " + getTypeStr(); + }; + + bool isValid() const { + return port_external_ == 0 or port_internal_ == 0 ? false : true; + }; + + inline operator bool() const { + return isValid(); + } + +private: + NON_COPYABLE(Mapping); + +protected: + uint16_t port_external_; + uint16_t port_internal_; + PortType type_; /* UPD or TCP */ + std::string description_; +}; + +/** + * GlobalMapping is like a mapping, but it tracks the number of global users, + * ie: the number of upnp:Controller which are using this mapping + * this is usually only relevant for accounts (not calls) as multiple SIP accounts + * can use the same SIP port and we don't want to delete a mapping from the router + * if other accounts are using it + */ +class GlobalMapping : public Mapping { +public: + /* number of users of this mapping; + * this is only relevant when multiple accounts are using the same SIP port */ + unsigned users; + GlobalMapping(const Mapping& mapping, unsigned users = 1) + : Mapping(mapping.getPortExternal() + , mapping.getPortInternal() + , mapping.getType() + , mapping.getDescription()) + , users(users) + {}; +}; + +/* subclasses to make it easier to differentiate and cast maps of port mappings */ +class PortMapLocal : public std::map<uint16_t, Mapping> {}; +class PortMapGlobal : public std::map<uint16_t, GlobalMapping> {}; + +/* defines a UPnP capable Internet Gateway Device (a router) */ +class IGD { +public: + + /* device address seen by IGD */ + IpAddr localIp; + + /* external IP of IGD; can change */ + IpAddr publicIp; + + /* port mappings associated with this IGD */ + PortMapGlobal udpMappings; + PortMapGlobal tcpMappings; + + /* constructors */ + IGD() {} + IGD(std::string UDN, + std::string baseURL, + std::string friendlyName, + std::string serviceType, + std::string serviceId, + std::string controlURL, + std::string eventSubURL) + : UDN_(UDN) + , baseURL_(baseURL) + , friendlyName_(friendlyName) + , serviceType_(serviceType) + , serviceId_(serviceId) + , controlURL_(controlURL) + , eventSubURL_(eventSubURL) + {} + + /* move constructor and operator */ + IGD(IGD&&) = default; + IGD& operator=(IGD&&) = default; + + ~IGD() = default; + + const std::string& getUDN() const { return UDN_; }; + const std::string& getBaseURL() const { return baseURL_; }; + const std::string& getFriendlyName() const { return friendlyName_; }; + const std::string& getServiceType() const { return serviceType_; }; + const std::string& getServiceId() const { return serviceId_; }; + const std::string& getControlURL() const { return controlURL_; }; + const std::string& getEventSubURL() const { return eventSubURL_; }; + +private: + NON_COPYABLE(IGD); + + /* root device info */ + std::string UDN_ {}; /* used to uniquely identify this UPnP device */ + std::string baseURL_ {}; + std::string friendlyName_ {}; + + /* port forwarding service info */ + std::string serviceType_ {}; + std::string serviceId_ {}; + std::string controlURL_ {}; + std::string eventSubURL_ {}; + +}; + +}} // namespace ring::upnp + +#endif /* UPNP_IGD_H_ */ diff --git a/src/utf8_utils.cpp b/src/utf8_utils.cpp new file mode 100644 index 0000000000..d9b8dbdaec --- /dev/null +++ b/src/utf8_utils.cpp @@ -0,0 +1,308 @@ +/* + * Copyright (C) 1999 Tom Tromey + * Copyright (C) 2000 Red Hat, Inc. + * Copyright (C) 2014-2015 Savoir-Faire Linux Inc. + * + * Author: Pascal Potvin <pascal.potvin@extenway.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + + +#include <cstring> +#include <cassert> +#include "utf8_utils.h" + +/* + * The LIKELY and UNLIKELY macros let the programmer give hints to + * the compiler about the expected result of an expression. Some compilers + * can use this information for optimizations. + */ +#if defined(__GNUC__) && (__GNUC__ > 2) && defined(__OPTIMIZE__) +#define LIKELY(expr) (__builtin_expect (expr, 1)) +#define UNLIKELY(expr) (__builtin_expect (expr, 0)) +#else +#define LIKELY(expr) (expr) +#define UNLIKELY(expr) (expr) +#endif + + +/* + * Check whether a Unicode (5.2) char is in a valid range. + * + * The first check comes from the Unicode guarantee to never encode + * a point above 0x0010ffff, since UTF-16 couldn't represent it. + * + * The second check covers surrogate pairs (category Cs). + * + * @param Char the character + */ +#define UNICODE_VALID(Char) \ + ((Char) < 0x110000 && \ + (((Char) & 0xFFFFF800) != 0xD800)) + +#define CONTINUATION_CHAR \ + if ((*(unsigned char *)p & 0xc0) != 0x80) /* 10xxxxxx */ \ + goto error; \ + val <<= 6; \ + val |= (*(unsigned char *)p) & 0x3f; + +namespace ring { + +static const char * +fast_validate(const char *str) +{ + char32_t val = 0; + char32_t min = 0; + const char *p; + + for (p = str; *p; p++) { + if (*(unsigned char *)p < 128) + /* done */; + else { + const char *last; + + last = p; + + if ((*(unsigned char *)p & 0xe0) == 0xc0) { /* 110xxxxx */ + if (UNLIKELY((*(unsigned char *)p & 0x1e) == 0)) + goto error; + + p++; + + if (UNLIKELY((*(unsigned char *)p & 0xc0) != 0x80)) /* 10xxxxxx */ + goto error; + } else { + if ((*(unsigned char *)p & 0xf0) == 0xe0) { /* 1110xxxx */ + min = (1 << 11); + val = *(unsigned char *)p & 0x0f; + goto TWO_REMAINING; + } else if ((*(unsigned char *)p & 0xf8) == 0xf0) { /* 11110xxx */ + min = (1 << 16); + val = *(unsigned char *)p & 0x07; + } else + goto error; + + p++; + CONTINUATION_CHAR; +TWO_REMAINING: + p++; + CONTINUATION_CHAR; + p++; + CONTINUATION_CHAR; + + if (UNLIKELY(val < min)) + goto error; + + if (UNLIKELY(!UNICODE_VALID(val))) + goto error; + } + + continue; + +error: + return last; + } + } + + return p; +} + +static const char * +fast_validate_len(const char *str, ssize_t max_len) +{ + char32_t val = 0; + char32_t min = 0; + const char *p; + + assert(max_len >= 0); + + for (p = str; ((p - str) < max_len) && *p; p++) { + if (*(unsigned char *)p < 128) + /* done */; + else { + const char *last; + + last = p; + + if ((*(unsigned char *)p & 0xe0) == 0xc0) { /* 110xxxxx */ + if (UNLIKELY(max_len - (p - str) < 2)) + goto error; + + if (UNLIKELY((*(unsigned char *)p & 0x1e) == 0)) + goto error; + + p++; + + if (UNLIKELY((*(unsigned char *)p & 0xc0) != 0x80)) /* 10xxxxxx */ + goto error; + } else { + if ((*(unsigned char *)p & 0xf0) == 0xe0) { /* 1110xxxx */ + if (UNLIKELY(max_len - (p - str) < 3)) + goto error; + + min = (1 << 11); + val = *(unsigned char *)p & 0x0f; + goto TWO_REMAINING; + } else if ((*(unsigned char *)p & 0xf8) == 0xf0) { /* 11110xxx */ + if (UNLIKELY(max_len - (p - str) < 4)) + goto error; + + min = (1 << 16); + val = *(unsigned char *)p & 0x07; + } else + goto error; + + p++; + CONTINUATION_CHAR; +TWO_REMAINING: + p++; + CONTINUATION_CHAR; + p++; + CONTINUATION_CHAR; + + if (UNLIKELY(val < min)) + goto error; + + if (UNLIKELY(!UNICODE_VALID(val))) + goto error; + } + + continue; + +error: + return last; + } + } + + return p; +} + +/** + * utf8_validate_c_str: + * @str: a pointer to character data + * @max_len: max bytes to validate, or -1 to go until NULL + * @end: return location for end of valid data + * + * Validates UTF-8 encoded text. @str is the text to validate; + * if @str is nul-terminated, then @max_len can be -1, otherwise + * @max_len should be the number of bytes to validate. + * If @end is non-%NULL, then the end of the valid range + * will be stored there (i.e. the start of the first invalid + * character if some bytes were invalid, or the end of the text + * being validated otherwise). + * + * Note that utf8_validate() returns %false if @max_len is + * positive and any of the @max_len bytes are nul. + * + * Returns true if all of @str was valid. Dbus requires valid UTF-8 as input; + * sip packets should also be encoded in utf8; so data read from a file or the + * network should be checked with utf8_validate() before doing anything else + * with it. + * + * Returns: true if the text was valid UTF-8 + */ +bool +utf8_validate_c_str(const char *str, ssize_t max_len, const char **end) +{ + const char *p; + + if (max_len < 0) + p = fast_validate(str); + else + p = fast_validate_len(str, max_len); + + if (end) + *end = p; + + if ((max_len >= 0 && p != str + max_len) || + (max_len < 0 && *p != '\0')) + return false; + else + return true; +} + +bool +utf8_validate(const std::string & str) +{ + const char *p; + + p = fast_validate(str.c_str()); + + return (*p == '\0'); +} + +std::string +utf8_make_valid(const std::string & name) +{ + ssize_t remaining_bytes = name.size(); + ssize_t valid_bytes; + const char *remainder = name.c_str(); + const char *invalid; + char *str = NULL; + char *pos; + + while (remaining_bytes != 0) { + if (utf8_validate_c_str(remainder, remaining_bytes, &invalid)) + break; + + valid_bytes = invalid - remainder; + + if (str == NULL) + // If every byte is replaced by U+FFFD, max(strlen(string)) == 3 * name.size() + str = new char[3 * remaining_bytes]; + + pos = str; + + strncpy(pos, remainder, valid_bytes); + pos += valid_bytes; + + /* append U+FFFD REPLACEMENT CHARACTER */ + pos[0] = '\357'; + pos[1] = '\277'; + pos[2] = '\275'; + + pos += 3; + + remaining_bytes -= valid_bytes + 1; + remainder = invalid + 1; + } + + if (str == NULL) + return std::string(name); + + strncpy(pos, remainder, remaining_bytes); + pos += remaining_bytes; + + std::string answer(str, pos - str); + assert(utf8_validate_c_str(answer.c_str(), -1, NULL)); + + delete[] str; + + return answer; +} + +} // namespace ring diff --git a/src/utf8_utils.h b/src/utf8_utils.h new file mode 100644 index 0000000000..c48422a7a4 --- /dev/null +++ b/src/utf8_utils.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 1999 Tom Tromey + * Copyright (C) 2000 Red Hat, Inc. + * Copyright (C) 2014-2015 Savoir-Faire Linux Inc. + * + * Author: Pascal Potvin <pascal.potvin@extenway.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef H_UTF8_UTILS +#define H_UTF8_UTILS + +#include <cstdlib> +#include <string> + +namespace ring { + +/** + * utf8_validate: + * + * Validates UTF-8 encoded text. @str is the text to validate; + * + * Returns true if all of @str was valid. Dbus requires valid UTF-8 as input; + * sip packets should also be encoded in utf8; so data read from a file or the + * network should be checked with utf8_validate() before doing anything else + * with it. + * + * Returns: true if the text was valid UTF-8 + */ + +bool +utf8_validate(const std::string & str); + +/** + * utf8_make_valid: + * @name: a pointer to a nul delimited string. + * + * Transforms a unknown c_string into a pretty utf8 encoded std::string. + * Every unreadable or invalid byte will be transformed into U+FFFD + * (REPLACEMENT CHARACTER). + * + * Returns: a valid utf8 string. + */ +std::string +utf8_make_valid(const std::string & name); + +} // namespace ring + +#endif // H_UTF8_UTILS diff --git a/test/.gitignore b/test/.gitignore new file mode 100644 index 0000000000..e600a6bd4d --- /dev/null +++ b/test/.gitignore @@ -0,0 +1,4 @@ +test +cppunitresults.xml +im:testfile1.txt +im:testfile2.txt diff --git a/test/Makefile.am b/test/Makefile.am new file mode 100644 index 0000000000..3ccfc38bc6 --- /dev/null +++ b/test/Makefile.am @@ -0,0 +1,65 @@ +include ../globals.mak + +TESTS_ENVIRONMENT = CODECS_PATH="$(top_builddir)/src/audio/codecs" +check_PROGRAMS = test + +TESTS = run_tests.sh + +test_CXXFLAGS = -DWORKSPACE=\"$(top_srcdir)/test/\" @PTHREAD_CFLAGS@ +test_LDADD = $(top_builddir)/src/libring.la \ + $(top_builddir)/src/libring_la-logger.lo \ + @CPPUNIT_LIBS@ @YAMLCPP_LIBS@ @PJPROJECT_LIBS@ + +if BUILD_INSTANT_MESSAGING +test_LDADD += @EXPAT_LIBS@ +endif + +EXTRA_DIST = dring-sample.yml history-sample.tpl tlsSample run_tests.sh +test_SOURCES = constants.h \ + test_utils.h \ + main.cpp \ + accounttest.h \ + accounttest.cpp \ + audiocodectest.h \ + audiocodectest.cpp \ + audiolayertest.h \ + audiolayertest.cpp \ + configurationtest.h \ + configurationtest.cpp \ + historytest.h \ + historytest.cpp \ + numbercleanertest.h \ + numbercleanertest.cpp \ + siptest.h \ + siptest.cpp \ + sdptest.h \ + sdptest.cpp \ + ringbufferpooltest.h \ + ringbufferpooltest.cpp \ + resamplertest.h \ + resamplertest.cpp \ + hooktest.h \ + hooktest.cpp \ + audiobuffertest.h \ + audiobuffertest.cpp \ + iptest.h \ + iptest.cpp + +if BUILD_SDES +test_SOURCES+=sdesnegotiatortest.h \ + sdesnegotiatortest.cpp +endif + +if BUILD_INSTANT_MESSAGING +test_SOURCES+=instantmessagingtest.h \ + instantmessagingtest.cpp +endif + +if BUILD_TLS +test_SOURCES+=tlstest.h \ + tlstest.cpp +endif + +clean-local: + rm -rf cppunitresults.xml im:testfile1.txt im:testfile2.txt \ + sample_echocancel_500ms_8kHz_16bit.raw diff --git a/test/accounttest.cpp b/test/accounttest.cpp new file mode 100644 index 0000000000..e8c359c7bf --- /dev/null +++ b/test/accounttest.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Julien Bonjean <julien.bonjean@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <cppunit/extensions/HelperMacros.h> +#include <map> + +#include "account.h" +#include "account_schema.h" +#include "accounttest.h" +#include "manager.h" +#include "configurationmanager_interface.h" +#include "logger.h" + +namespace ring { namespace test { + +void AccountTest::TestAddRemove() +{ + RING_DBG("-------------------- %s --------------------\n", __PRETTY_FUNCTION__); + + auto details = DRing::getAccountTemplate(); + details[Conf::CONFIG_ACCOUNT_TYPE] = "SIP"; + details[Conf::CONFIG_ACCOUNT_ENABLE] = "false"; + details[Conf::CONFIG_LOCAL_INTERFACE] = "default"; + details[Conf::CONFIG_LOCAL_PORT] = "5060"; + + auto accountId = Manager::instance().addAccount(details); + CPPUNIT_ASSERT(not accountId.empty()); + CPPUNIT_ASSERT(Manager::instance().hasAccount(accountId)); + + Manager::instance().removeAccount(accountId); + CPPUNIT_ASSERT(!Manager::instance().hasAccount(accountId)); +} + +}} // namespace ring::test diff --git a/test/accounttest.h b/test/accounttest.h new file mode 100644 index 0000000000..1c1606e813 --- /dev/null +++ b/test/accounttest.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Julien Bonjean <julien.bonjean@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef ACCOUNTTEST_H_ +#define ACCOUNTTEST_H_ + +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +namespace ring { namespace test { + +class AccountTest : public CppUnit::TestFixture { + + CPPUNIT_TEST_SUITE(AccountTest); + CPPUNIT_TEST(TestAddRemove); + CPPUNIT_TEST_SUITE_END(); + + public: + void TestAddRemove(); +}; +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(AccountTest, "AccountTest"); +CPPUNIT_TEST_SUITE_REGISTRATION(AccountTest); + +}} // namespace ring::test + +#endif /* ACCOUNTTEST_H_ */ diff --git a/test/audiobuffertest.cpp b/test/audiobuffertest.cpp new file mode 100644 index 0000000000..f006cb8997 --- /dev/null +++ b/test/audiobuffertest.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Adrien Beraud <adrienberaud@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <string> +#include "audiobuffertest.h" +#include "audio/audiobuffer.h" +#include "logger.h" +#include "test_utils.h" + +namespace ring { namespace test { + +void AudioBufferTest::testAudioBufferConstructors() +{ + TITLE(); + + ring::AudioSample test_samples2[] = {10, 11, 12, 13, 14, 15, 16, 17}; + + AudioBuffer empty_buf(0, AudioFormat::MONO()); + CPPUNIT_ASSERT(empty_buf.frames() == 0); + CPPUNIT_ASSERT(empty_buf.channels() == 1); + CPPUNIT_ASSERT(empty_buf.getChannel(0)->size() == 0); + + AudioBuffer test_buf1(8, AudioFormat::STEREO()); + CPPUNIT_ASSERT(test_buf1.frames() == 8); + CPPUNIT_ASSERT(test_buf1.channels() == 2); + CPPUNIT_ASSERT(test_buf1.getChannel(0)->size() == 8); + CPPUNIT_ASSERT(test_buf1.getChannel(1)->size() == 8); + CPPUNIT_ASSERT(test_buf1.getChannel(2) == NULL); + + AudioBuffer test_buf3(test_samples2, 4, AudioFormat::STEREO()); + CPPUNIT_ASSERT(test_buf3.frames() == 4); + CPPUNIT_ASSERT(test_buf3.channels() == 2); + CPPUNIT_ASSERT(test_buf3.getChannel(0)->size() == 4); +} + +void AudioBufferTest::testAudioBufferMix() +{ + TITLE(); + + ring::AudioSample test_samples1[] = {18, 19, 20, 21, 22, 23, 24, 25}; + ring::AudioSample test_samples2[] = {10, 11, 12, 13, 14, 15, 16, 17, 18}; + + AudioBuffer test_buf1(test_samples1, 4, AudioFormat::STEREO()); + CPPUNIT_ASSERT(test_buf1.channels() == 2); + test_buf1.setChannelNum(1); + CPPUNIT_ASSERT(test_buf1.channels() == 1); + test_buf1.setChannelNum(2); + CPPUNIT_ASSERT(test_buf1.channels() == 2); + CPPUNIT_ASSERT(test_buf1.getChannel(1)->size() == 4); + CPPUNIT_ASSERT((*test_buf1.getChannel(1))[0] == 0); + test_buf1.setChannelNum(1); + test_buf1.setChannelNum(2, true); + CPPUNIT_ASSERT((*test_buf1.getChannel(1))[0] == test_samples1[0]); + + AudioBuffer test_buf2(0, AudioFormat::MONO()); + test_buf2.deinterleave(test_samples2, 3, 3); + CPPUNIT_ASSERT((*test_buf2.getChannel(0))[2] == test_samples2[6]); + CPPUNIT_ASSERT((*test_buf2.getChannel(1))[1] == test_samples2[4]); + CPPUNIT_ASSERT((*test_buf2.getChannel(2))[0] == test_samples2[2]); + CPPUNIT_ASSERT(test_buf2.capacity() == 9); + + ring::AudioSample *output = new ring::AudioSample[test_buf2.capacity()]; + test_buf2.interleave(output); + CPPUNIT_ASSERT(std::equal(test_samples2, test_samples2 + sizeof test_samples2 / sizeof *test_samples2, output)); + //CPPUNIT_ASSERT(std::equal(std::begin(test_samples2), std::end(test_samples2), std::begin(output))); C++11 + + test_buf1.mix(test_buf2); + CPPUNIT_ASSERT(test_buf1.channels() == 2); + CPPUNIT_ASSERT(test_buf1.frames() == 4); + CPPUNIT_ASSERT((*test_buf1.getChannel(0))[0] == test_samples1[0]+test_samples2[0]); + CPPUNIT_ASSERT((*test_buf1.getChannel(1))[0] == test_samples1[0]+test_samples2[1]); +} + + +AudioBufferTest::AudioBufferTest() : CppUnit::TestCase("Audio Buffer Tests") {} + +}} // namespace ring::test diff --git a/test/audiobuffertest.h b/test/audiobuffertest.h new file mode 100644 index 0000000000..48cf7c2668 --- /dev/null +++ b/test/audiobuffertest.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Adrien Beraud <adrienberaud@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef AUDIOBUFFER_TEST_ +#define AUDIOBUFFER_TEST_ + +// Cppunit import +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/TestCaller.h> +#include <cppunit/TestCase.h> +#include <cppunit/TestSuite.h> + +/* + * @file audiobuffertest.cpp + * @brief Regroups unit tests related to an audio buffer. + */ + +namespace ring { namespace test { + +class AudioBufferTest : public CppUnit::TestCase { + + /* + * Use cppunit library macros to add unit test the factory + */ + CPPUNIT_TEST_SUITE(AudioBufferTest); + CPPUNIT_TEST(testAudioBufferConstructors); + CPPUNIT_TEST(testAudioBufferMix); + CPPUNIT_TEST_SUITE_END(); + + public: + + AudioBufferTest(); + + void testAudioBufferConstructors(); + + void testAudioBufferMix(); +}; + +/* Register our test module */ +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(AudioBufferTest, "AudioBufferTest"); +CPPUNIT_TEST_SUITE_REGISTRATION(AudioBufferTest); + +}} // namespace ring::test + +#endif // AUDIOBUFFER_TEST_ diff --git a/test/audiocodectest.cpp b/test/audiocodectest.cpp new file mode 100644 index 0000000000..82ab0ab751 --- /dev/null +++ b/test/audiocodectest.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +/* + * ebail - 2015/02/18 + * testCodecs unit test is based on audiocodecfactory + * we are not using it anymore + * we should make this unit test work with libav + * this test is disabled for the moment + * */ +#if 0 + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "audiocodectest.h" +#include "audio/codecs/audiocodecfactory.h" +#include "plugin_manager.h" + +#include "test_utils.h" +#include "ring_types.h" // for AudioSample + +#include <cmath> +#include <climits> + +namespace ring { namespace test { + + +/* + * Detect the power of a signal for a given frequency. + * Adapted from: + * http://netwerkt.wordpress.com/2011/08/25/goertzel-filter/ + */ +static double +goertzelFilter(AudioSample *samples, double freq, unsigned N, double sample_rate) +{ + double s_prev = 0.0; + double s_prev2 = 0.0; + const double normalizedfreq = freq / sample_rate; + double coeff = 2 * cos(M_2_PI * normalizedfreq); + for (unsigned i = 0; i < N; i++) { + double s = samples[i] + coeff * s_prev - s_prev2; + s_prev2 = s_prev; + s_prev = s; + } + + return s_prev2 * s_prev2 + s_prev * s_prev - coeff * s_prev * s_prev2; +} + +void AudioCodecTest::testCodecs() +{ + TITLE(); + + PluginManager pluginMgr; + AudioCodecFactory factory(pluginMgr); + const auto payloadTypes = factory.getCodecList(); + + std::vector<std::shared_ptr<AudioCodec>> codecs; + + for (auto p : payloadTypes) + codecs.push_back(factory.getCodec(p)); + + std::vector<std::vector<AudioSample>> sine = {}; + std::vector<std::vector<AudioSample>> pcm; + + unsigned sampleRate = 0; + double referencePower = 0.0; + + for (auto c : codecs) { + + // generate the sine tone if rate has changed + if (sampleRate != c->getCurrentClockRate()) { + sampleRate = c->getCurrentClockRate(); + const unsigned nbSamples = sampleRate * 0.02; // 20 ms worth of samples + sine = {std::vector<AudioSample>(nbSamples)}; + pcm = {std::vector<AudioSample>(nbSamples)}; + + const float theta = M_2_PI * frequency_ / sampleRate; + + for (unsigned i = 0; i < nbSamples; ++i) { + sine[0][i] = SHRT_MAX * sin(theta * i); + sine[0][i] >>= 3; /* attenuate it a bit */ + } + + /* Store the raw signal's power detected at 440 Hz, this is much cheaper + * than an FFT */ + referencePower = goertzelFilter(sine[0].data(), frequency_, sine[0].size(), sampleRate); + } + + std::vector<uint8_t> data(RAW_BUFFER_SIZE); + + const size_t encodedBytes = c->encode(sine, data.data(), sine[0].size()); + + unsigned decoded = c->decode(pcm, data.data(), encodedBytes); + CPPUNIT_ASSERT(decoded == sine[0].size()); + + const auto decodedPower = goertzelFilter(pcm[0].data(), frequency_, pcm[0].size(), sampleRate); + const auto decodedRatio = decodedPower / referencePower; + CPPUNIT_ASSERT(decodedRatio > 0.0); + } +} + +}} // namespace ring::test +#endif diff --git a/test/audiocodectest.h b/test/audiocodectest.h new file mode 100644 index 0000000000..7bc7677cfc --- /dev/null +++ b/test/audiocodectest.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +/* + * @file audiocodectest.h + * @brief For every available audio codec, encode a buffer, decode it, + * and analyze the signal to ensure that it's similar to what was + * encoded. + */ + +#ifndef AUDIO_CODEC_TEST_ +#define AUDIO_CODEC_TEST_ + +// Cppunit import +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/TestCaller.h> +#include <cppunit/TestCase.h> +#include <cppunit/TestSuite.h> + +namespace ring { namespace test { + +class AudioCodecTest: public CppUnit::TestFixture { + + /* + * Use cppunit library macros to add unit test the factory + */ + CPPUNIT_TEST_SUITE(AudioCodecTest); + /* + * ebail - 2015/02/18 + * testCodecs unit test is based on audiocodecfactory + * we are not using it anymore + * we should make this unit test work with libav + * this test is disabled for the moment + * */ + //CPPUNIT_TEST(testCodecs); + CPPUNIT_TEST_SUITE_END(); + + static const short frequency_ = 440; + + public: + void testCodecs(); +}; + +/* Register our test module */ +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(AudioCodecTest, "AudioCodecTest"); +CPPUNIT_TEST_SUITE_REGISTRATION(AudioCodecTest); + +}} // namespace ring::test + +#endif // AUDIO_CODEC_TEST_ diff --git a/test/audiolayertest.cpp b/test/audiolayertest.cpp new file mode 100644 index 0000000000..88c17d0485 --- /dev/null +++ b/test/audiolayertest.cpp @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "audiolayertest.h" + +#include "logger.h" +#include "manager.h" +#include "audio/alsa/alsalayer.h" +#include "audio/pulseaudio/pulselayer.h" +#include "test_utils.h" +#include <unistd.h> + +namespace ring { namespace test { + +AudioLayerTest::AudioLayerTest() : manager_(0), pulselayer_(0), layer_(0) +{} + +void AudioLayerTest::testAudioLayerConfig() +{ + TITLE(); + + CPPUNIT_ASSERT(Manager::instance().audioPreference.getAlsaSmplrate() == 44100); + + // alsa preferences + CPPUNIT_ASSERT(Manager::instance().audioPreference.getAlsaCardin() == 0); + CPPUNIT_ASSERT(Manager::instance().audioPreference.getAlsaCardout() == 0); + CPPUNIT_ASSERT(Manager::instance().audioPreference.getAlsaCardring() == 0); + CPPUNIT_ASSERT(Manager::instance().audioPreference.getAlsaPlugin() == "default"); + + // pulseaudio preferences + CPPUNIT_ASSERT(Manager::instance().audioPreference.getPulseDevicePlayback() == "alsa_output.pci-0000_00_1b.0.analog-stereo"); + CPPUNIT_ASSERT(Manager::instance().audioPreference.getPulseDeviceRecord() == "alsa_input.pci-0000_00_1b.0.analog-stereo"); + CPPUNIT_ASSERT(Manager::instance().audioPreference.getPulseDeviceRingtone() == "alsa_output.pci-0000_00_1b.0.analog-stereo"); + + CPPUNIT_ASSERT(Manager::instance().audioPreference.getVolumemic() == 1.0); + CPPUNIT_ASSERT(Manager::instance().audioPreference.getVolumespkr() == 1.0); + + // TODO: Fix tests + //CPPUNIT_ASSERT ( (int) Manager::instance().getAudioDriver()->getSampleRate() == sampling_rate); +} + +void AudioLayerTest::testAudioLayerSwitch() +{ + TITLE(); + + bool wasAlsa = dynamic_cast<AlsaLayer*>(Manager::instance().getAudioDriver().get()) != 0; + + for (int i = 0; i < 2; i++) { + RING_DBG("iter - %i", i); + if (wasAlsa) + Manager::instance().setAudioManager(PULSEAUDIO_API_STR); + else + Manager::instance().setAudioManager(ALSA_API_STR); + + if (wasAlsa) + CPPUNIT_ASSERT(dynamic_cast<PulseLayer*>(Manager::instance().getAudioDriver().get())); + else + CPPUNIT_ASSERT(dynamic_cast<AlsaLayer*>(Manager::instance().getAudioDriver().get())); + + wasAlsa = dynamic_cast<AlsaLayer*>(Manager::instance().getAudioDriver().get()) != 0; + const struct timespec req = {0, 100000000}; + nanosleep(&req, 0); + } +} + +void AudioLayerTest::testPulseConnect() +{ + TITLE(); + + if (dynamic_cast<AlsaLayer*>(Manager::instance().getAudioDriver().get())) { + Manager::instance().setAudioManager(PULSEAUDIO_API_STR); + const struct timespec req = {0, 100000000}; + nanosleep(&req, 0); + } + + pulselayer_ = dynamic_cast<PulseLayer*>(Manager::instance().getAudioDriver().get()); + + CPPUNIT_ASSERT(pulselayer_); +} + +}} // namespace ring::test diff --git a/test/audiolayertest.h b/test/audiolayertest.h new file mode 100644 index 0000000000..adfc05a922 --- /dev/null +++ b/test/audiolayertest.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +/* + * @file audiorecorderTest.cpp + * @brief Regroups unitary tests related to the plugin manager. + */ + +#ifndef AUDIOLAYER_TEST_ +#define AUDIOLAYER_TEST_ + +// Cppunit import +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/TestCaller.h> +#include <cppunit/TestCase.h> +#include <cppunit/TestSuite.h> + +#include "noncopyable.h" + +namespace ring { + +class ManagerImpl; +class PulseLayer; + +} // namespace ring + +namespace ring { namespace test { + +class AudioLayerTest: public CppUnit::TestFixture { + + CPPUNIT_TEST_SUITE(AudioLayerTest); + CPPUNIT_TEST(testAudioLayerConfig); + //CPPUNIT_TEST(testPulseConnect); + //TODO: this test ends the test sequence when using on a alsa only system + //CPPUNIT_TEST(testAudioLayerSwitch); + CPPUNIT_TEST_SUITE_END(); + + public: + AudioLayerTest(); + void testAudioLayerConfig(); + void testPulseConnect(); + void testAudioLayerSwitch(); + + private: + NON_COPYABLE(AudioLayerTest); + + ManagerImpl* manager_; + PulseLayer* pulselayer_; + int layer_; +}; +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(AudioLayerTest, "AudioLayerTest"); +CPPUNIT_TEST_SUITE_REGISTRATION(AudioLayerTest); + +}} // namespace ring::test + +#endif // AUDIOLAYER_TEST_ diff --git a/test/configurationtest.cpp b/test/configurationtest.cpp new file mode 100644 index 0000000000..0ccc7f56e0 --- /dev/null +++ b/test/configurationtest.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "configurationtest.h" +#include "fileutils.h" +#include "config/yamlparser.h" + +namespace ring { namespace test { + +void ConfigurationTest::testNodeParse() +{ + YAML::Node node = YAML::Load("[{a: 0, b: 1, c: 2}, {a: 0, b: 1, c: 2}]"); + auto result = yaml_utils::parseVectorMap(node, {"a", "b", "c"}); + CPPUNIT_ASSERT(result[1]["b"] == "1"); +} + +void ConfigurationTest::test_expand_path(void){ + const std::string pattern_1 = "~"; + const std::string pattern_2 = "~/x"; + const std::string pattern_3 = "~foo/x"; // deliberately broken, + // tilde should not be expanded + std::string home = fileutils::get_home_dir(); + + CPPUNIT_ASSERT(fileutils::expand_path(pattern_1) == home); + CPPUNIT_ASSERT(fileutils::expand_path(pattern_2) == home.append("/x")); + CPPUNIT_ASSERT(fileutils::expand_path(pattern_3) == "~foo/x"); +} + +}} // namespace ring::test diff --git a/test/configurationtest.h b/test/configurationtest.h new file mode 100644 index 0000000000..446d9e0c42 --- /dev/null +++ b/test/configurationtest.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +/* + * @file configurationTest.cpp + * @brief Regroups unitary tests related to the user configuration. + * Check if the default configuration has been successfully loaded + */ + +#ifndef CONFIGURATION_TEST_ +#define CONFIGURATION_TEST_ + +// Cppunit import +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/TestCaller.h> +#include <cppunit/TestCase.h> +#include <cppunit/TestSuite.h> + +namespace ring { namespace test { + +class ConfigurationTest: public CppUnit::TestFixture { + + /* + * Use cppunit library macros to add unit test the factory + */ + CPPUNIT_TEST_SUITE(ConfigurationTest); + CPPUNIT_TEST(testNodeParse); + CPPUNIT_TEST(test_expand_path); + CPPUNIT_TEST_SUITE_END(); + + public: + + void testNodeParse(); + void test_expand_path(); +}; + +/* Register our test module */ +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(ConfigurationTest, "ConfigurationTest"); +CPPUNIT_TEST_SUITE_REGISTRATION(ConfigurationTest); + +}} // namespace ring::test + +#endif // CONFIGURATION_TEST_ diff --git a/test/constants.h b/test/constants.h new file mode 100644 index 0000000000..8b33136b04 --- /dev/null +++ b/test/constants.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef CONSTANTS_H_ +#define CONSTANTS_H_ + +#define HISTORY_SAMPLE WORKSPACE "history-sample.tpl" +#define HISTORY_SAMPLE_BAK HISTORY_SAMPLE ".bak" + +#define CONFIG_SAMPLE WORKSPACE "dring-sample.yml" +#define CONFIG_SAMPLE_BAK CONFIG_SAMPLE ".bak" + +#define HISTORY_SAMPLE_SIZE 3 +#define HISTORY_LIMIT 30 + +#endif /* CONSTANTS_H_ */ diff --git a/test/dring-sample.yml b/test/dring-sample.yml new file mode 100644 index 0000000000..fe76421c92 --- /dev/null +++ b/test/dring-sample.yml @@ -0,0 +1,199 @@ +--- +accounts: +- alias: SFL test + audioCodecs: 0/3/8/9/110/111/112/ + autoAnswer: false + credential: + - Account.password: 1234 + Account.realm: + Account.username: sfltest + displayName: + dtmfType: overrtp + enable: false + hasCustomUserAgent: true + hostname: localhost + id: Account:1334389473 + interface: default + keepAlive: false + mailbox: + port: 5060 + publishAddr: 0.0.0.0 + publishPort: 5060 + registrationexpire: 600 + ringtoneEnabled: true + ringtonePath: /usr/share/ring/ringtones/konga.ul + sameasLocal: true + serviceRoute: + srtp: + enable: false + keyExchange: + rtpFallback: false + stunEnabled: false + stunServer: + tls: + calist: + certificate: + ciphers: + enable: false + method: TLSv1 + password: + privateKey: + requireCertif: true + server: + timeout: 2 + tlsPort: 5061 + verifyClient: true + verifyServer: true + type: SIP + useragent: SFLphone-test + username: sfltest + videoCodecs: + - bitrate: 400 + enabled: true + name: H263-2000 + parameters: + - bitrate: 400 + enabled: true + name: H264 + parameters: profile-level-id=428014 + - bitrate: 400 + enabled: true + name: MP4V-ES + parameters: + - bitrate: 400 + enabled: true + name: VP8 + parameters: + videoEnabled: true + videoPortMax: 65534 + videoPortMin: 49152 + zrtp: + displaySas: true + displaySasOnce: false + helloHashEnabled: true + notSuppWarning: true +- alias: IP2IP + audioCodecs: 0/3/8/9/110/111/112/ + autoAnswer: false + credential: + - Account.password: + Account.realm: + Account.username: + displayName: + dtmfType: true + enable: true + hasCustomUserAgent: true + hostname: + id: IP2IP + interface: default + keepAlive: false + mailbox: + port: 5060 + publishAddr: + publishPort: 5060 + registrationexpire: 60 + ringtoneEnabled: true + ringtonePath: /usr/share/ring/ringtones/konga.ul + sameasLocal: true + serviceRoute: + srtp: + enable: false + keyExchange: sdes + rtpFallback: false + stunEnabled: false + stunServer: + tls: + calist: + certificate: + ciphers: + enable: false + method: TLSv1 + password: + privateKey: + requireCertif: true + server: + timeout: 2 + tlsPort: 5061 + verifyClient: true + verifyServer: true + type: SIP + useragent: SFLphone-test + username: + videoCodecs: + - bitrate: 400 + enabled: true + name: H263-2000 + parameters: + - bitrate: 400 + enabled: true + name: H264 + parameters: profile-level-id=428014 + - bitrate: 400 + enabled: true + name: MP4V-ES + parameters: + - bitrate: 400 + enabled: true + name: VP8 + parameters: + videoEnabled: true + videoPortMax: 65534 + videoPortMin: 49152 + zrtp: + displaySas: true + displaySasOnce: false + helloHashEnabled: true + notSuppWarning: true +preferences: + historyLimit: 30 + historyMaxCalls: 20 + md5Hash: false + order: Account:1375906657/Account:1328115463/Account:1328115393/Account:1328115062/Account:1316122317/Account:1316122284/Account:1316121900/Account:1316121889/Account:1316121691/Account:1316121662/Account:1316121661/Account:1316121654/Account:1316121611/Account:1316121607/Account:1316121605/Account:1316121602/Account:1312584532/Account:1312398082/Account:1312398066/Account:1309188361/Account:1309187807/Account:1309187723/Account:1309187670/Account:1309187609/Account:1309187081/Account:1308839853/Account:1308839662/Account:1308839447/Account:1308839359/Account:1308839335/Account:1308838875/Account:1308838713/Account:1308838236/Account:1307975440/Account:1307975347/Account:1307974800/Account:1307974672/Account:1307974527/Account:1303487773/Account:1303247743/Account:1302895321/Account:1302892836/Account:1302891834/Account:1302882519/Account:1302207377/Account:1302207262/Account:1302204136/Account:1302204108/Account:1294850905/Account:1294850775/Account:1294850618/Account:1294849651/Account:1294849602/Account:1294849310/Account:1288964768/Account:1288964603/Account:1288964434/Account:1288964141/Account:1288964134/ + portNum: 5060 + registrationexpire: 180 + searchBarDisplay: true + zoneToneChoice: North America +voipPreferences: + playDtmf: true + playTones: true + pulseLength: 250 + symmetric: true + zidFile: true +hooks: + iax2Enabled: false + numberAddPrefix: + numberEnabled: false + sipEnabled: false + urlCommand: x-www-browser + urlSipField: X-sflphone-url +audio: + alsa: + cardIn: 0 + cardOut: 0 + cardRing: 0 + plugin: default + smplRate: 44100 + alwaysRecording: false + audioApi: pulseaudio + echoCancel: false + noiseReduce: true + pulse: + devicePlayback: alsa_output.pci-0000_00_1b.0.analog-stereo + deviceRecord: alsa_input.pci-0000_00_1b.0.analog-stereo + deviceRingtone: alsa_output.pci-0000_00_1b.0.analog-stereo + recordPath: ~ + volumeMic: 1.0 + volumeSpkr: 1.0 +video: + devices: + - channel: Camera 1 + name: Integrated Camera + rate: 30 + size: 640x480 +shortcuts: + hangUp: + pickUp: + popupWindow: + toggleHold: + togglePickupHangup: +... diff --git a/test/history-sample.tpl b/test/history-sample.tpl new file mode 100644 index 0000000000..ec7fcdf0dc --- /dev/null +++ b/test/history-sample.tpl @@ -0,0 +1,30 @@ +accountid= +confid= +callid=Account:1239059899 +peer_name=Emmanuel Milou +peer_number=136 +recordfile= +timestamp_start=747638685 +timestamp_stop=747638765 +state=outgoing + +accountid=empty +confid= +callid= +peer_name=Savoir-faire Linux +peer_number=514-276-5468 +recordfile= +timestamp_start=144562000 +timestamp_stop=144562458 +state=missed + +accountid= +confid= +callid=Account:43789459478 +peer_name= +peer_number=5143848557 +recordfile= +timestamp_start=775354456 +timestamp_stop=775354987 +state=incoming + diff --git a/test/historytest.cpp b/test/historytest.cpp new file mode 100644 index 0000000000..d153ac3f81 --- /dev/null +++ b/test/historytest.cpp @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <cstdlib> + +#include "historytest.h" +#include "history/history.h" +#include "logger.h" +#include "constants.h" + +namespace ring { namespace test { + +namespace { +void restore() +{ + if (system("mv " HISTORY_SAMPLE_BAK " " HISTORY_SAMPLE) < 0) + RING_ERR("Restoration of %s failed", HISTORY_SAMPLE); +} + +void backup() +{ + if (system("cp " HISTORY_SAMPLE " " HISTORY_SAMPLE_BAK) < 0) + RING_ERR("Backup of %s failed", HISTORY_SAMPLE); +} +} + +void HistoryTest::setUp() +{ + backup(); + history_ = new History; + history_->setPath(HISTORY_SAMPLE); +} + + +void HistoryTest::test_create_path() +{ + RING_DBG("-------------------- HistoryTest::test_set_path --------------------\n"); + + std::string path(HISTORY_SAMPLE); + CPPUNIT_ASSERT(history_->path_ == path); +} + +void HistoryTest::test_load_from_file() +{ + RING_DBG("-------------------- HistoryTest::test_load_from_file --------------------\n"); + + bool res = history_->load(HISTORY_LIMIT); + CPPUNIT_ASSERT(res); +} + +void HistoryTest::test_load_items() +{ + RING_DBG("-------------------- HistoryTest::test_load_items --------------------\n"); + bool res = history_->load(HISTORY_LIMIT); + CPPUNIT_ASSERT(res); + CPPUNIT_ASSERT(history_->numberOfItems() == HISTORY_SAMPLE_SIZE); +} + +void HistoryTest::test_save_to_file() +{ + RING_DBG("-------------------- HistoryTest::test_save_to_file --------------------\n"); + CPPUNIT_ASSERT(history_->save()); +} + +void HistoryTest::test_get_serialized() +{ + RING_DBG("-------------------- HistoryTest::test_get_serialized --------------------\n"); + bool res = history_->load(HISTORY_LIMIT); + CPPUNIT_ASSERT(res); + CPPUNIT_ASSERT(history_->getSerialized().size() == HISTORY_SAMPLE_SIZE); +} + +void HistoryTest::tearDown() +{ + delete history_; + restore(); +} + +}} // namespace ring::test diff --git a/test/historytest.h b/test/historytest.h new file mode 100644 index 0000000000..b8671f8476 --- /dev/null +++ b/test/historytest.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +// Cppunit import +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/TestCaller.h> +#include <cppunit/TestCase.h> +#include <cppunit/TestSuite.h> + +// Application import +#include "noncopyable.h" + +/* + * @file historyTest.h + * @brief Regroups unitary tests related to the phone number cleanup function. + */ + +#ifndef HISTORY_TEST_ +#define HISTORY_TEST_ + +namespace ring { +class History; +} // namespace ring + +namespace ring { namespace test { + +class HistoryTest : public CppUnit::TestCase { + + /** + * Use cppunit library macros to add unit test the factory + */ + CPPUNIT_TEST_SUITE(HistoryTest); + CPPUNIT_TEST(test_create_path); + CPPUNIT_TEST(test_load_from_file); + CPPUNIT_TEST(test_load_items); + CPPUNIT_TEST(test_get_serialized); + CPPUNIT_TEST_SUITE_END(); + + public: + HistoryTest() : CppUnit::TestCase("History Tests"), history_(0) {} + + /* + * Code factoring - Common resources can be initialized here. + * This method is called by unitcpp before each test + */ + void setUp(); + + void test_create_path(); + + void test_load_from_file(); + + void test_load_items(); + + void test_save_to_file(); + + void test_get_serialized(); + + /* + * Code factoring - Common resources can be released here. + * This method is called by unitcpp after each test + */ + void tearDown(); + + private: + NON_COPYABLE(HistoryTest); + ring::History *history_; +}; + +/* Register our test module */ +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(HistoryTest, "HistoryTest"); +CPPUNIT_TEST_SUITE_REGISTRATION(HistoryTest); + + +}} // namespace ring::test + +#endif // HISTORY_TEST_ diff --git a/test/hooktest.cpp b/test/hooktest.cpp new file mode 100644 index 0000000000..1ef998148a --- /dev/null +++ b/test/hooktest.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Julien Bonjean <julien.bonjean@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "hooktest.h" +#include "hooks/urlhook.h" + +namespace ring { namespace test { + +void HookTest::RunHookWithNoArgs() +{ + CPPUNIT_ASSERT(!UrlHook::runAction("ls", "")); +} + +void HookTest::RunHookWithArgs() +{ + CPPUNIT_ASSERT(!UrlHook::runAction("ls", "-l")); +} + +}} // namespace ring::test diff --git a/test/hooktest.h b/test/hooktest.h new file mode 100644 index 0000000000..432c3f000a --- /dev/null +++ b/test/hooktest.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2013-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef HOOKTEST_H_ +#define HOOKTEST_H_ + +#include <cppunit/TestFixture.h> +#include <cppunit/extensions/HelperMacros.h> + +namespace ring { namespace test { + +class HookTest : public CppUnit::TestFixture { + + CPPUNIT_TEST_SUITE(HookTest); + CPPUNIT_TEST(RunHookWithNoArgs); + CPPUNIT_TEST(RunHookWithArgs); + CPPUNIT_TEST_SUITE_END(); + + public: + void RunHookWithNoArgs(); + void RunHookWithArgs(); +}; + +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(HookTest, "HookTest"); +CPPUNIT_TEST_SUITE_REGISTRATION(HookTest); + +}} // namespace ring::test + +#endif /* HOOKTEST_H_ */ diff --git a/test/instantmessagingtest.cpp b/test/instantmessagingtest.cpp new file mode 100644 index 0000000000..148f2d8a92 --- /dev/null +++ b/test/instantmessagingtest.cpp @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <iostream> +#include <fstream> +#include <expat.h> +#include "test_utils.h" + +#include "instantmessagingtest.h" +#include "im/instant_messaging.h" +#include "logger.h" + +#define MAXIMUM_SIZE 10 +#define DELIMITER_CHAR "\n\n" + +namespace ring { namespace InstantMessaging { namespace test { + +void InstantMessagingTest::testSaveSingleMessage() +{ + TITLE(); + std::string callID = "testfile1.txt"; + std::string filename = "im:"; + + // Open a file stream and try to write in it + CPPUNIT_ASSERT(saveMessage("Bonjour, c'est un test d'archivage de message", "Manu", callID, std::ios::out)); + + filename.append(callID); + // Read it to check it has been successfully written + std::ifstream testfile(filename.c_str(), std::ios::in); + CPPUNIT_ASSERT(testfile.is_open()); + + std::string input; + while (!testfile.eof()) { + std::string tmp; + std::getline(testfile, tmp); + input.append(tmp); + } + + testfile.close(); + CPPUNIT_ASSERT(input == "[Manu] Bonjour, c'est un test d'archivage de message"); +} + +void InstantMessagingTest::testSaveMultipleMessage() +{ + TITLE(); + + std::string callID = "testfile2.txt"; + std::string filename = "im:"; + + // Open a file stream and try to write in it + CPPUNIT_ASSERT(saveMessage("Bonjour, c'est un test d'archivage de message", "Manu", callID, std::ios::out)); + CPPUNIT_ASSERT(saveMessage("Cool", "Alex", callID, std::ios::out || std::ios::app)); + + filename.append(callID); + // Read it to check it has been successfully written + std::ifstream testfile(filename.c_str(), std::ios::in); + CPPUNIT_ASSERT(testfile.is_open()); + + std::string input; + while (!testfile.eof()) { + std::string tmp; + std::getline(testfile, tmp); + input.append(tmp); + } + + testfile.close(); + printf("%s\n", input.c_str()); + CPPUNIT_ASSERT(input == "[Manu] Bonjour, c'est un test d'archivage de message[Alex] Cool"); +} + +static inline char* duplicateString(char dst[], const char src[], size_t len) +{ + memcpy(dst, src, len); + dst[len] = 0; + return dst; +} + +static void XMLCALL startElementCallback(void *userData, const char *name, const char **atts) +{ + + std::cout << "startElement " << name << std::endl; + + int *nbEntry = (int *) userData; + + char attribute[50]; + char value[50]; + + for (const char **att = atts; *att; att += 2) { + + const char **val = att+1; + + duplicateString(attribute, *att, strlen(*att)); + std::cout << "att: " << attribute << std::endl; + + duplicateString(value, *val, strlen(*val)); + std::cout << "val: " << value << std::endl; + + if (strcmp(attribute, "uri") == 0) { + if ((strcmp(value, "sip:alex@example.com") == 0) || + (strcmp(value, "sip:manu@example.com") == 0)) + CPPUNIT_ASSERT(true); + else + CPPUNIT_ASSERT(false); + } + } + + *nbEntry += 1; +} + +static void XMLCALL +endElementCallback(void * /*userData*/, const char * /*name*/) +{} + +void InstantMessagingTest::testGenerateXmlUriList() +{ + std::cout << std::endl; + + // Create a test list with two entries + UriList list; + + UriEntry entry1; + entry1[IM_XML_URI] = "\"sip:alex@example.com\""; + + UriEntry entry2; + entry2[IM_XML_URI] = "\"sip:manu@example.com\""; + + list.push_front(entry1); + list.push_front(entry2); + + std::string buffer = generateXmlUriList(list); + CPPUNIT_ASSERT(buffer.size() != 0); + + std::cout << buffer << std::endl; + + // parse the resuling xml (further tests are performed in callbacks) + XML_Parser parser = XML_ParserCreate(NULL); + int nbEntry = 0; + XML_SetUserData(parser, &nbEntry); + XML_SetElementHandler(parser, startElementCallback, endElementCallback); + + if (XML_Parse(parser, buffer.c_str(), buffer.size(), 1) == XML_STATUS_ERROR) { + RING_ERR("%s at line %d", XML_ErrorString(XML_GetErrorCode(parser)), XML_GetCurrentLineNumber(parser)); + CPPUNIT_ASSERT(false); + } + + XML_ParserFree(parser); + CPPUNIT_ASSERT(nbEntry == 4); +} + +void InstantMessagingTest::testXmlUriListParsing() +{ + std::string xmlbuffer = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; + xmlbuffer.append("<resource-lists xmlns=\"urn:ietf:params:xml:ns:resource-lists\" xmlns:cp=\"urn:ietf:params:xml:ns:copycontrol\">"); + xmlbuffer.append("<list>"); + xmlbuffer.append("<entry uri=\"sip:alex@example.com\" cp:copyControl=\"to\" />"); + xmlbuffer.append("<entry uri=\"sip:manu@example.com\" cp:copyControl=\"to\" />"); + xmlbuffer.append("</list>"); + xmlbuffer.append("</resource-lists>"); + + + UriList list = parseXmlUriList(xmlbuffer); + CPPUNIT_ASSERT(list.size() == 2); + + // An iterator over xml attribute + UriEntry::iterator iterAttr; + + // An iterator over list entries + for (auto &entry : list) { + iterAttr = entry.find(IM_XML_URI); + + CPPUNIT_ASSERT((iterAttr->second == std::string("sip:alex@example.com")) or + (iterAttr->second == std::string("sip:manu@example.com"))); + } +} + +void InstantMessagingTest::testGetTextArea() +{ + + std::string formatedText = "--boundary Content-Type: text/plain"; + formatedText.append("Here is the text area"); + + formatedText.append("--boundary Content-Type: application/resource-lists+xml"); + formatedText.append("Content-Disposition: recipient-list"); + formatedText.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); + formatedText.append("<resource-lists xmlns=\"urn:ietf:params:xml:ns:resource-lists\" xmlns:cp=\"urn:ietf:params:xml:ns:copycontrol\">"); + formatedText.append("<list>"); + formatedText.append("<entry uri=\"sip:alex@example.com\" cp:copyControl=\"to\" />"); + formatedText.append("<entry uri=\"sip:manu@example.com\" cp:copyControl=\"to\" />"); + formatedText.append("</list>"); + formatedText.append("</resource-lists>"); + formatedText.append("--boundary--"); + + std::string message(findTextMessage(formatedText)); + RING_DBG("Message %s", message.c_str()); + + CPPUNIT_ASSERT(message == "Here is the text area"); +} + +void InstantMessagingTest::testGetUriListArea() +{ + std::string formatedText = "--boundary Content-Type: text/plain"; + formatedText.append("Here is the text area"); + + formatedText.append("--boundary Content-Type: application/resource-lists+xml"); + formatedText.append("Content-Disposition: recipient-list"); + formatedText.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); + formatedText.append("<resource-lists xmlns=\"urn:ietf:params:xml:ns:resource-lists\" xmlns:cp=\"urn:ietf:params:xml:ns:copycontrol\">"); + formatedText.append("<list>"); + formatedText.append("<entry uri=\"sip:alex@example.com\" cp:copyControl=\"to\" />"); + formatedText.append("<entry uri=\"sip:manu@example.com\" cp:copyControl=\"to\" />"); + formatedText.append("</list>"); + formatedText.append("</resource-lists>"); + formatedText.append("--boundary--"); + + std::string urilist = findTextUriList(formatedText); + + CPPUNIT_ASSERT(urilist.compare("<?xml version=\"1.0\" encoding=\"UTF-8\"?><resource-lists xmlns=\"urn:ietf:params:xml:ns:resource-lists\" xmlns:cp=\"urn:ietf:params:xml:ns:copycontrol\"><list><entry uri=\"sip:alex@example.com\" cp:copyControl=\"to\" /><entry uri=\"sip:manu@example.com\" cp:copyControl=\"to\" /></list></resource-lists>") == 0); + + std::cout << "urilist: " << urilist << std::endl; + + UriList list = parseXmlUriList(urilist); + CPPUNIT_ASSERT(list.size() == 2); + + // order may be important, for example to identify message sender + UriEntry entry = list.front(); + CPPUNIT_ASSERT(entry.size() == 2); + + UriEntry::iterator iterAttr = entry.find(IM_XML_URI); + + if (iterAttr == entry.end()) { + RING_ERR("Did not find attribute"); + CPPUNIT_ASSERT(false); + } + + std::string from = iterAttr->second; + CPPUNIT_ASSERT(from == "sip:alex@example.com"); +} + +void InstantMessagingTest::testIllFormatedMessage() +{ + bool exceptionCaught = false; + + // SHOULD BE: Content-Type: text/plain + std::string formatedText = "--boundary Content-Ty"; + formatedText.append("Here is the text area"); + + formatedText.append("--boundary Content-Type: application/resource-lists+xml"); + formatedText.append("Content-Disposition: recipient-list"); + formatedText.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); + formatedText.append("<resource-lists xmlns=\"urn:ietf:params:xml:ns:resource-lists\" xmlns:cp=\"urn:ietf:params:xml:ns:copycontrol\">"); + formatedText.append("<list>"); + formatedText.append("<entry uri=\"sip:alex@example.com\" cp:copyControl=\"to\" />"); + formatedText.append("<entry uri=\"sip:manu@example.com\" cp:copyControl=\"to\" />"); + formatedText.append("</list>"); + formatedText.append("</resource-lists>"); + formatedText.append("--boundary--"); + + try { + std::string message = findTextMessage(formatedText); + } catch (const InstantMessageException &e) { + exceptionCaught = true; + } + + CPPUNIT_ASSERT(exceptionCaught); +} + +}}} // namespace ring::InstantMessaging::test diff --git a/test/instantmessagingtest.h b/test/instantmessagingtest.h new file mode 100644 index 0000000000..2485acba5c --- /dev/null +++ b/test/instantmessagingtest.h @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +// Cppunit import +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/TestCaller.h> +#include <cppunit/TestCase.h> +#include <cppunit/TestSuite.h> + +/* + * @file instantmessagingtest.h + * @brief Unit tests related to the instant messaging module + */ + +#ifndef INSTANTMANAGER_TEST_ +#define INSTANTMANAGER_TEST_ + +namespace ring { namespace InstantMessaging { namespace test { + +class InstantMessagingTest : public CppUnit::TestCase { + CPPUNIT_TEST_SUITE(InstantMessagingTest); + CPPUNIT_TEST(testSaveSingleMessage); + CPPUNIT_TEST(testSaveMultipleMessage); + CPPUNIT_TEST(testGenerateXmlUriList); + CPPUNIT_TEST(testXmlUriListParsing); + CPPUNIT_TEST(testGetTextArea); + CPPUNIT_TEST(testGetUriListArea); + CPPUNIT_TEST(testIllFormatedMessage); + CPPUNIT_TEST_SUITE_END(); + + public: + InstantMessagingTest() : CppUnit::TestCase("Instant messaging module Tests") {} + + void testSaveSingleMessage(); + void testSaveMultipleMessage(); + void testGenerateXmlUriList(); + void testXmlUriListParsing(); + void testGetTextArea(); + void testGetUriListArea(); + void testIllFormatedMessage(); +}; + +/* Register our test module */ +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(InstantMessagingTest, "InstantMessagingTest"); +CPPUNIT_TEST_SUITE_REGISTRATION(InstantMessagingTest); + +}}} // namespace ring::InstantMessaging::test + +#endif diff --git a/test/iptest.cpp b/test/iptest.cpp new file mode 100644 index 0000000000..ca8f2ac760 --- /dev/null +++ b/test/iptest.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <string> +#include "iptest.h" +#include "ip_utils.h" +#include "logger.h" +#include "test_utils.h" + +namespace ring { namespace test { + +void IpTest::testIpAddr() +{ + TITLE(); + + IpAddr ip = {"8.8.8.8"}; + CPPUNIT_ASSERT(ip); + CPPUNIT_ASSERT(ip.toString() == "8.8.8.8"); + CPPUNIT_ASSERT(ip.getPort() == 0); + CPPUNIT_ASSERT(ip.isIpv4()); + CPPUNIT_ASSERT(not ip.isIpv6()); + CPPUNIT_ASSERT(not ip.isLoopback()); + CPPUNIT_ASSERT(not ip.isPrivate()); + CPPUNIT_ASSERT(not ip.isUnspecified()); + + IpAddr ip_2 = ip.toString(); + CPPUNIT_ASSERT(ip_2 == ip); + + ip = IpAddr(); + CPPUNIT_ASSERT(not ip); + CPPUNIT_ASSERT(ip.isUnspecified()); + CPPUNIT_ASSERT(not ip.isIpv4()); + CPPUNIT_ASSERT(not ip.isIpv6()); + CPPUNIT_ASSERT(ip.getPort() == 0); + + ip = IpAddr("8.8.8.8:42"); + CPPUNIT_ASSERT(ip); + CPPUNIT_ASSERT(ip.toString() == "8.8.8.8"); + CPPUNIT_ASSERT(ip.getPort() == 42); + + pj_sockaddr_set_port(ip.pjPtr(), 5042); + CPPUNIT_ASSERT(ip.getPort() == 5042); + +#if HAVE_IPV6 + const in6_addr native_ip = {{ + 0x3f, 0xfe, 0x05, 0x01, + 0x00, 0x08, 0x00, 0x00, + 0x02, 0x60, 0x97, 0xff, + 0xfe, 0x40, 0xef, 0xab + }}; + ip = IpAddr(native_ip); + CPPUNIT_ASSERT(ip); + CPPUNIT_ASSERT(ip == IpAddr("3ffe:0501:0008:0000:0260:97ff:fe40:efab")); + CPPUNIT_ASSERT(not ip.isIpv4()); + CPPUNIT_ASSERT(ip.isIpv6()); + + CPPUNIT_ASSERT(IpAddr::isValid("3ffe:0501:0008:0000:0260:97ff:fe40:efab")); + CPPUNIT_ASSERT(IpAddr::isValid("[3ffe:0501:0008:0000:0260:97ff:fe40:efab]")); + CPPUNIT_ASSERT(IpAddr::isValid("[3ffe:0501:0008:0000:0260:97ff:fe40:efab]:4242")); + CPPUNIT_ASSERT(IpAddr::isValid("[3ffe:501:8::260:97ff:fe40:efab]:4242")); +#endif +} + +IpTest::IpTest() : CppUnit::TestCase("IP Tests") {} + +}} // namespace ring::test diff --git a/test/iptest.h b/test/iptest.h new file mode 100644 index 0000000000..5ac72f7c57 --- /dev/null +++ b/test/iptest.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Adrien Béraud <adrien.beraud@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef IP_TEST_ +#define IP_TEST_ + +// Cppunit import +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/TestCaller.h> +#include <cppunit/TestCase.h> +#include <cppunit/TestSuite.h> + +/* + * @file audiobuffertest.cpp + * @brief Regroups unit tests related to an audio buffer. + */ + +namespace ring { namespace test { + +class IpTest : public CppUnit::TestCase { + + /* + * Use cppunit library macros to add unit test the factory + */ + CPPUNIT_TEST_SUITE(IpTest); + CPPUNIT_TEST(testIpAddr); + CPPUNIT_TEST_SUITE_END(); + + public: + + IpTest(); + + void testIpAddr(); +}; + +/* Register our test module */ +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(IpTest, "IpTest"); +CPPUNIT_TEST_SUITE_REGISTRATION(IpTest); + +}} // namespace ring::test + +#endif // IP_TEST_ diff --git a/test/main.cpp b/test/main.cpp new file mode 100644 index 0000000000..66a202f89c --- /dev/null +++ b/test/main.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Julien Bonjean <julien.bonjean@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include "logger.h" +#include "manager.h" +#include "constants.h" +#include "fileutils.h" + +#include <cstdlib> + +#include <cppunit/CompilerOutputter.h> +#include <cppunit/XmlOutputter.h> +#include <cppunit/extensions/TestFactoryRegistry.h> +#include <cppunit/ui/text/TextTestRunner.h> + +namespace { + void restore() + { + if (system("mv " CONFIG_SAMPLE_BAK " " CONFIG_SAMPLE) < 0) + RING_ERR("Restoration of %s failed", CONFIG_SAMPLE); + } + void backup() + { + if (system("cp " CONFIG_SAMPLE " " CONFIG_SAMPLE_BAK) < 0) + RING_ERR("Backup of %s failed", CONFIG_SAMPLE); + } +} + +void cleanup() +{ + int ret = system("killall sipp"); + std::cerr << "Killed all sip processes with status " << ret << std::endl; +} + +int main(int argc, char* argv[]) +{ + atexit(cleanup); + printf("\nRing Daemon Test Suite, by Savoir-Faire Linux 2004-2015\n\n"); + setConsoleLog(true); + setDebugMode(true); + ring::fileutils::FileHandle f(ring::fileutils::create_pidfile()); + if (f.fd == -1) { + fprintf(stderr, "An dring instance is already running, quitting...\n"); + return 1; + } + + int argvIndex = 1; + bool xmlOutput = false; + + if (argc > 1) { + if (strcmp("--help", argv[1]) == 0) { + argvIndex++; + + CPPUNIT_NS::Test* suite = CPPUNIT_NS::TestFactoryRegistry::getRegistry("All Tests").makeTest(); + + int testSuiteCount = suite->getChildTestCount(); + printf("Usage: test [OPTIONS] [TEST_SUITE]\n"); + printf("\nOptions:\n"); + printf(" --xml - Output results in an XML file, instead of standard output.\n"); + printf(" --debug - Debug mode\n"); + printf(" --help - Print help\n"); + printf("\nAvailable test suites:\n"); + + for (int i = 0; i < testSuiteCount; i++) { + printf(" - %s\n", suite->getChildTestAt(i)->getName().c_str()); + } + + return 0; + } else if (strcmp("--debug", argv[1]) == 0) { + argvIndex++; + + setDebugMode(true); + RING_INFO("Debug mode activated"); + + } else if (strcmp("--xml", argv[1]) == 0) { + argvIndex++; + + xmlOutput = true; + RING_INFO("Using XML output"); + } + } + + // Default test suite : all tests + std::string testSuiteName = "All Tests"; + + if (argvIndex < argc) { + testSuiteName = argv[argvIndex]; + argvIndex++; + } + + printf("\n\n=== SFLphone initialization ===\n\n"); + backup(); + ring::Manager::instance().init(CONFIG_SAMPLE); + + // Get the top level suite from the registry + printf("\n\n=== Test Suite: %s ===\n\n", testSuiteName.c_str()); + CPPUNIT_NS::Test *suite = CPPUNIT_NS::TestFactoryRegistry::getRegistry(testSuiteName).makeTest(); + + if (suite->getChildTestCount() == 0) { + RING_ERR("Invalid test suite name: %s", testSuiteName.c_str()); + restore(); + return 1; + } + + // Adds the test to the list of test to run + CppUnit::TextTestRunner runner; + runner.addTest(suite); + /* Specify XML output */ + std::ofstream outfile("cppunitresults.xml"); + + if (xmlOutput) { + CppUnit::XmlOutputter* outputter = new CppUnit::XmlOutputter(&runner.result(), outfile); + runner.setOutputter(outputter); + } else { + // Change the default outputter to a compiler error format outputter + runner.setOutputter(new CppUnit::CompilerOutputter(&runner.result(), std::cerr)); + } + + // Run the tests. + bool wasSuccessful = runner.run(); + + printf("=== Test suite ending ===\n"); + ring::Manager::instance().finish(); + + restore(); + + return wasSuccessful ? 0 : 1; +} diff --git a/test/numbercleanertest.cpp b/test/numbercleanertest.cpp new file mode 100644 index 0000000000..f2c4cb0469 --- /dev/null +++ b/test/numbercleanertest.cpp @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <cstdio> +#include <sstream> +#include <dlfcn.h> + +#include "logger.h" + +#include "numbercleanertest.h" + +#define NUMBER_TEST_1 "514 333 4444" +#define NUMBER_TEST_2 "514-333-4444" +#define NUMBER_TEST_3 "(514) 333 4444" +#define NUMBER_TEST_4 "(514)-333-4444" +#define NUMBER_TEST_5 "(514) 333-4444" +#define NUMBER_TEST_6 "514 333 4444" +#define NUMBER_TEST_7 "ext 136" +#define NUMBER_TEST_8 "514 333 4444 ext. 136" +#define NUMBER_TEST_9 "514 333 4444 ext 136" +#define NUMBER_TEST_10 "136" + +#define VALID_NUMBER "5143334444" +#define VALID_PREPENDED_NUMBER "95143334444" +#define VALID_EXTENSION "136" + +namespace ring { namespace test { + +void NumberCleanerTest::test_format_1(void) +{ + RING_DBG("-------------------- NumberCleanerTest::test_format_1 --------------------\n"); + CPPUNIT_ASSERT(NumberCleaner::clean(NUMBER_TEST_1) == VALID_NUMBER); +} + +void NumberCleanerTest::test_format_2(void) +{ + RING_DBG("-------------------- NumberCleanerTest::test_format_2 --------------------\n"); + + CPPUNIT_ASSERT(NumberCleaner::clean(NUMBER_TEST_2) == VALID_NUMBER); +} + +void NumberCleanerTest::test_format_3(void) +{ + RING_DBG("-------------------- NumberCleanerTest::test_format_3 --------------------\n"); + + CPPUNIT_ASSERT(NumberCleaner::clean(NUMBER_TEST_3) == VALID_NUMBER); +} + +void NumberCleanerTest::test_format_4(void) +{ + RING_DBG("-------------------- NumberCleanerTest::test_format_4 --------------------\n"); + + CPPUNIT_ASSERT(NumberCleaner::clean(NUMBER_TEST_4) == VALID_NUMBER); +} + +void NumberCleanerTest::test_format_5(void) +{ + RING_DBG("-------------------- NumberCleanerTest::test_format_5 --------------------\n"); + + CPPUNIT_ASSERT(NumberCleaner::clean(NUMBER_TEST_5) == VALID_NUMBER); +} + +void NumberCleanerTest::test_format_6(void) +{ + RING_DBG("-------------------- NumberCleanerTest::test_format_6 --------------------\n"); + + CPPUNIT_ASSERT(NumberCleaner::clean(NUMBER_TEST_6) == VALID_NUMBER); +} + +void NumberCleanerTest::test_format_7(void) +{ + RING_DBG("-------------------- NumberCleanerTest::test_format_7 --------------------\n"); + + CPPUNIT_ASSERT(NumberCleaner::clean(NUMBER_TEST_7) == VALID_EXTENSION); +} + +void NumberCleanerTest::test_format_8(void) +{ + RING_DBG("-------------------- NumberCleanerTest::test_format_8 --------------------\n"); + + CPPUNIT_ASSERT(NumberCleaner::clean(NUMBER_TEST_8) == VALID_NUMBER); +} + +void NumberCleanerTest::test_format_9(void) +{ + RING_DBG("-------------------- NumberCleanerTest::test_format_9 --------------------\n"); + + CPPUNIT_ASSERT(NumberCleaner::clean(NUMBER_TEST_9) == VALID_NUMBER); +} + +void NumberCleanerTest::test_format_10(void) +{ + RING_DBG("-------------------- NumberCleanerTest::test_format_10 --------------------\n"); + + CPPUNIT_ASSERT(NumberCleaner::clean(NUMBER_TEST_1, "9") == VALID_PREPENDED_NUMBER); +} + +void NumberCleanerTest::test_format_11(void) +{ + RING_DBG("-------------------- NumberCleanerTest::test_format_11 --------------------\n"); + + CPPUNIT_ASSERT(NumberCleaner::clean(NUMBER_TEST_10, "9") == VALID_EXTENSION); +} + +}} // namespace ring::test diff --git a/test/numbercleanertest.h b/test/numbercleanertest.h new file mode 100644 index 0000000000..3e60df6a3b --- /dev/null +++ b/test/numbercleanertest.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +// Cppunit import +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/TestCaller.h> +#include <cppunit/TestCase.h> +#include <cppunit/TestSuite.h> + +// Application import +#include "numbercleaner.h" +// #include "../src/conference.h" +/* + * @file numbercleanerTest.cpp + * @brief Regroups unitary tests related to the phone number cleanup function. + */ + +#ifndef _NUMBERCLEANER_TEST_ +#define _NUMBERCLEANER_TEST_ + +namespace ring { namespace test { + +class NumberCleanerTest : public CppUnit::TestCase { + + /** + * Use cppunit library macros to add unit test the factory + */ + CPPUNIT_TEST_SUITE(NumberCleanerTest); + CPPUNIT_TEST(test_format_1); + CPPUNIT_TEST(test_format_2); + CPPUNIT_TEST(test_format_3); + CPPUNIT_TEST(test_format_4); + CPPUNIT_TEST(test_format_5); + CPPUNIT_TEST(test_format_6); + /*CPPUNIT_TEST (test_format_7); + CPPUNIT_TEST (test_format_8); + CPPUNIT_TEST (test_format_9);*/ + CPPUNIT_TEST(test_format_10); + CPPUNIT_TEST_SUITE_END(); + + public: + NumberCleanerTest() : CppUnit::TestCase("Hook Manager Tests") {} + + void test_format_1(); + void test_format_2(); + void test_format_3(); + void test_format_4(); + void test_format_5(); + void test_format_6(); + void test_format_7(); + void test_format_8(); + void test_format_9(); + void test_format_10(); + void test_format_11(); +}; + +/* Register our test module */ +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(NumberCleanerTest, "NumberCleanerTest"); +CPPUNIT_TEST_SUITE_REGISTRATION(NumberCleanerTest); + +}} // namespace ring::test + +#endif diff --git a/test/resamplertest.cpp b/test/resamplertest.cpp new file mode 100644 index 0000000000..b0a1c5f88a --- /dev/null +++ b/test/resamplertest.cpp @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <iostream> +#include <iterator> +#include <algorithm> +#include <math.h> + +#include "resamplertest.h" + +namespace ring { namespace test { + +ResamplerTest::ResamplerTest() : + CppUnit::TestCase("Resampler module test"), inputBuffer(MAX_BUFFER_LENGTH, AudioFormat::MONO()), outputBuffer(MAX_BUFFER_LENGTH, AudioFormat::MONO()) +{} + +void ResamplerTest::setUp() +{ + +} + +void ResamplerTest::tearDown() +{ + +} + +namespace { + template <typename T> + void print_buffer(T &buffer) + { +#ifdef VERBOSE + std::copy(buffer.begin(), buffer.end(), + std::ostream_iterator<ring::AudioSample>(std::cout, ", ")); + std::cout << std::endl; +#endif + } +} + +void ResamplerTest::testUpsamplingRamp() +{ + // generate input samples and store them in inputBuffer + generateRamp(); + + std::cout << "Test Upsampling Ramp" << std::endl; + Resampler resampler(44100); + + performUpsampling(resampler); + + AudioBuffer tmpInputBuffer(TMP_LOWSMPLR_BUFFER_LENGTH, AudioFormat::MONO()); + AudioBuffer tmpOutputBuffer(TMP_HIGHSMPLR_BUFFER_LENGTH, AudioFormat(16000, 1)); + + tmpInputBuffer.copy(inputBuffer); + std::cout << "Input Buffer" << std::endl; + print_buffer(*tmpInputBuffer.getChannel(0)); + + tmpOutputBuffer.copy(outputBuffer); + std::cout << "Output Buffer" << std::endl; + print_buffer(*tmpOutputBuffer.getChannel(0)); +} + +void ResamplerTest::testDownsamplingRamp() +{ + generateRamp(); + + std::cout << "Test Downsampling Ramp" << std::endl; + Resampler resampler(44100); + + performDownsampling(resampler); + + AudioBuffer tmpInputBuffer(TMP_HIGHSMPLR_BUFFER_LENGTH, AudioFormat(16000, 1)); + AudioBuffer tmpOutputBuffer(TMP_LOWSMPLR_BUFFER_LENGTH, AudioFormat::MONO()); + + tmpInputBuffer.copy(inputBuffer); + std::cout << "Input Buffer" << std::endl; + print_buffer(*tmpInputBuffer.getChannel(0)); + + tmpOutputBuffer.copy(outputBuffer); + std::cout << "Output Buffer" << std::endl; + print_buffer(*tmpOutputBuffer.getChannel(0)); +} + +void ResamplerTest::testUpsamplingTriangle() +{ + generateTriangularSignal(); + + std::cout << "Test Upsampling Triangle" << std::endl; + Resampler resampler(44100); + + performUpsampling(resampler); + + AudioBuffer tmpInputBuffer(TMP_LOWSMPLR_BUFFER_LENGTH, AudioFormat::MONO()); + AudioBuffer tmpOutputBuffer(TMP_HIGHSMPLR_BUFFER_LENGTH, AudioFormat(16000, 1)); + + tmpInputBuffer.copy(inputBuffer); + std::cout << "Input Buffer" << std::endl; + print_buffer(*tmpInputBuffer.getChannel(0)); + + tmpOutputBuffer.copy(outputBuffer); + std::cout << "Output Buffer" << std::endl; + print_buffer(*tmpOutputBuffer.getChannel(0)); +} + +void ResamplerTest::testDownsamplingTriangle() +{ + generateTriangularSignal(); + + std::cout << "Test Downsampling Triangle" << std::endl; + Resampler resampler(44100); + + performDownsampling(resampler); + + AudioBuffer tmpInputBuffer(TMP_HIGHSMPLR_BUFFER_LENGTH, AudioFormat(16000, 1)); + AudioBuffer tmpOutputBuffer(TMP_LOWSMPLR_BUFFER_LENGTH, AudioFormat::MONO()); + + tmpInputBuffer.copy(inputBuffer); + std::cout << "Input Buffer" << std::endl; + print_buffer(*tmpInputBuffer.getChannel(0)); + + tmpOutputBuffer.copy(outputBuffer); + std::cout << "Output Buffer" << std::endl; + print_buffer(*tmpOutputBuffer.getChannel(0)); +} +void ResamplerTest::testUpsamplingSine() +{ + // generate input samples and store them in inputBuffer + generateSineSignal(); + + std::cout << "Test Upsampling Sine" << std::endl; + Resampler resampler(44100); + + performUpsampling(resampler); + + AudioBuffer tmpInputBuffer(TMP_LOWSMPLR_BUFFER_LENGTH, AudioFormat::MONO()); + AudioBuffer tmpOutputBuffer(TMP_HIGHSMPLR_BUFFER_LENGTH, AudioFormat(16000, 1)); + + tmpInputBuffer.copy(inputBuffer); + std::cout << "Input Buffer" << std::endl; + print_buffer(*tmpInputBuffer.getChannel(0)); + + tmpOutputBuffer.copy(outputBuffer); + std::cout << "Output Buffer" << std::endl; + print_buffer(*tmpOutputBuffer.getChannel(0)); +} + +void ResamplerTest::testDownsamplingSine() +{ + // generate input samples and store them in inputBuffer + generateSineSignal(); + + std::cout << "Test Downsampling Sine" << std::endl; + Resampler resampler(44100); + + performDownsampling(resampler); + + AudioBuffer tmpInputBuffer(TMP_HIGHSMPLR_BUFFER_LENGTH, AudioFormat(16000, 1)); + AudioBuffer tmpOutputBuffer(TMP_LOWSMPLR_BUFFER_LENGTH, AudioFormat::MONO()); + + tmpInputBuffer.copy(inputBuffer); + std::cout << "Input Buffer" << std::endl; + print_buffer(*tmpInputBuffer.getChannel(0)); + + tmpOutputBuffer.copy(outputBuffer); + std::cout << "Output Buffer" << std::endl; + print_buffer(*tmpOutputBuffer.getChannel(0)); +} + +void ResamplerTest::generateRamp() +{ + std::vector<ring::AudioSample>* buf = inputBuffer.getChannel(0); + for (size_t i = 0; i < buf->size(); ++i) + (*buf)[i] = i; +} + +void ResamplerTest::generateTriangularSignal() +{ + std::vector<ring::AudioSample>* buf = inputBuffer.getChannel(0); + for (size_t i = 0; i < buf->size(); ++i) + (*buf)[i] = i * 10; +} + +void ResamplerTest::generateSineSignal() +{ + std::vector<ring::AudioSample>* buf = inputBuffer.getChannel(0); + for (size_t i = 0; i < buf->size(); ++i) + (*buf)[i] = (ring::AudioSample) (1000.0 * sin(i)); +} + +void ResamplerTest::performUpsampling(Resampler &resampler) +{ + AudioBuffer tmpInputBuffer(TMP_LOWSMPLR_BUFFER_LENGTH, AudioFormat::MONO()); + AudioBuffer tmpOutputBuffer(TMP_HIGHSMPLR_BUFFER_LENGTH, AudioFormat(16000, 1)); + + for (size_t i = 0, j = 0; i < (inputBuffer.frames() / 2); i += tmpInputBuffer.frames(), j += tmpOutputBuffer.frames()) { + tmpInputBuffer.copy(inputBuffer, i); + resampler.resample(tmpInputBuffer, tmpOutputBuffer); + outputBuffer.copy(tmpOutputBuffer, -1, 0, j); + } +} + +void ResamplerTest::performDownsampling(Resampler &resampler) +{ + AudioBuffer tmpInputBuffer(TMP_HIGHSMPLR_BUFFER_LENGTH, AudioFormat(16000, 1)); + AudioBuffer tmpOutputBuffer(TMP_LOWSMPLR_BUFFER_LENGTH, AudioFormat::MONO()); + + for (size_t i = 0, j = 0; i < inputBuffer.frames(); i += tmpInputBuffer.frames(), j += tmpOutputBuffer.frames()) { + tmpInputBuffer.copy(inputBuffer, i); + resampler.resample(tmpInputBuffer, tmpOutputBuffer); + outputBuffer.copy(tmpOutputBuffer, -1, 0, j); + } +} + +}} // namespace ring::test diff --git a/test/resamplertest.h b/test/resamplertest.h new file mode 100644 index 0000000000..3297d35604 --- /dev/null +++ b/test/resamplertest.h @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2012-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef _RESAMPLER_TEST_ +#define _RESAMPLER_TEST_ + +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/TestCaller.h> +#include <cppunit/TestCase.h> +#include <cppunit/TestSuite.h> + +#include "audio/resampler.h" +#include "noncopyable.h" + +#define MAX_BUFFER_LENGTH 40000 +#define TMP_LOWSMPLR_BUFFER_LENGTH 160 +#define TMP_HIGHSMPLR_BUFFER_LENGTH 320 + +namespace ring { namespace test { + +class ResamplerTest : public CppUnit::TestCase { + + /** + * Use cppunit library macros to add unit test the factory + */ + CPPUNIT_TEST_SUITE(ResamplerTest); + CPPUNIT_TEST(testUpsamplingRamp); + CPPUNIT_TEST(testDownsamplingRamp); + CPPUNIT_TEST(testUpsamplingTriangle); + CPPUNIT_TEST(testDownsamplingTriangle); + CPPUNIT_TEST(testUpsamplingSine); + CPPUNIT_TEST(testDownsamplingSine); + CPPUNIT_TEST_SUITE_END(); + + public: + ResamplerTest(); + + /* + * Code factoring - Common resources can be initialized here. + * This method is called by unitcpp before each test + */ + void setUp(); + + /* + * Code factoring - Common resources can be released here. + * This method is called by unitcpp after each test + */ + void tearDown(); + + /* + * Generate a ramp and upsamples it form 8kHz to 16kHz + */ + void testUpsamplingRamp(); + + /* + * Generate a ramp and downsamples it from 16kHz to 8kHz + */ + void testDownsamplingRamp(); + + /* + * Generate a triangular signal and upsamples it from 8kHz to 16kHz + */ + void testUpsamplingTriangle(); + + /* + * Generate a triangular signal and downsamples it from 16kHz to 8kHz + */ + void testDownsamplingTriangle(); + + /* + * Generate a sine signal and upsamples it from 8kHz to 16kHz + */ + void testUpsamplingSine(); + + /* + * Generate a sine signal and downsamples it from 16kHz to 8kHz + */ + void testDownsamplingSine(); + +private: + NON_COPYABLE(ResamplerTest); + + /* + * Generate a ramp to be stored in inputBuffer + */ + void generateRamp(); + + /* + * Generate a triangular signal to be stored in inputBuffer + */ + void generateTriangularSignal(); + + /* + * Generate a sine signal to be stored in inputBuffer + */ + void generateSineSignal(); + + /* + * Perform upsampling on the whole input buffer + */ + void performUpsampling(ring::Resampler &resampler); + + /* + * Perform downsampling on the whold input buffer + */ + void performDownsampling(ring::Resampler &resampler); + + /** + * Used to store input samples + */ + ring::AudioBuffer inputBuffer; + + /** + * Used to receive output samples + */ + ring::AudioBuffer outputBuffer; +}; + +/* Register the test module */ +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(ResamplerTest, "ResamplerTest"); +CPPUNIT_TEST_SUITE_REGISTRATION(ResamplerTest); + +}} // namespace ring::test + +#endif // _RESAMPLER_TEST_ diff --git a/test/ringbufferpooltest.cpp b/test/ringbufferpooltest.cpp new file mode 100644 index 0000000000..ba76e7742c --- /dev/null +++ b/test/ringbufferpooltest.cpp @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <string> +#include <memory> + +#include "ringbufferpooltest.h" +#include "audio/ringbufferpool.h" +#include "audio/ringbuffer.h" +#include "logger.h" +#include "test_utils.h" + +namespace ring { namespace test { + +void RingBufferPoolTest::testBindUnbindBuffer() +{ + TITLE(); + + std::string test_id1 = "bind unbind 1"; + std::string test_id2 = "bind unbind 2"; + + // bind test_id1 with RingBufferPool::DEFAULT_ID (test_id1 not already created) + rbPool_->bindCallID(test_id1, RingBufferPool::DEFAULT_ID); + + // unbind test_id1 with RingBufferPool::DEFAULT_ID + rbPool_->unBindCallID(test_id1, RingBufferPool::DEFAULT_ID); + + rbPool_->bindCallID(test_id1, RingBufferPool::DEFAULT_ID); + rbPool_->bindCallID(test_id1, RingBufferPool::DEFAULT_ID); + + rbPool_->bindCallID(test_id2, RingBufferPool::DEFAULT_ID); + rbPool_->bindCallID(test_id2, RingBufferPool::DEFAULT_ID); + + // bind test_id1 with test_id2 (both testid1 and test_id2 already created) + // calling it twice not supposed to break anything + rbPool_->bindCallID(test_id1, test_id2); + rbPool_->bindCallID(test_id1, test_id2); + + rbPool_->unBindCallID(test_id1, test_id2); + rbPool_->unBindCallID(test_id1, test_id2); + + rbPool_->unBindCallID(RingBufferPool::DEFAULT_ID, test_id2); + rbPool_->unBindCallID(RingBufferPool::DEFAULT_ID, test_id2); + + rbPool_->unBindCallID(RingBufferPool::DEFAULT_ID, test_id1); + + // test unbind all function + rbPool_->bindCallID(RingBufferPool::DEFAULT_ID, test_id1); + rbPool_->bindCallID(RingBufferPool::DEFAULT_ID, test_id2); + rbPool_->bindCallID(test_id1, test_id2); + + rbPool_->unBindAll(test_id2); +} + +void RingBufferPoolTest::testGetPutData() +{ + TITLE(); + + std::string test_id = "incoming rtp session"; + + auto mainRingBuffer = rbPool_->getRingBuffer(RingBufferPool::DEFAULT_ID); + auto testRingBuffer = rbPool_->createRingBuffer(test_id); + + rbPool_->bindCallID(test_id, RingBufferPool::DEFAULT_ID); + + ring::AudioSample test_sample1 = 12; + ring::AudioSample test_sample2 = 13; + + AudioBuffer test_input1(&test_sample1, 1, AudioFormat::MONO()); + AudioBuffer test_input2(&test_sample2, 1, AudioFormat::MONO()); + AudioBuffer test_output(100, AudioFormat::MONO()); + + // get by test_id without preleminary put + CPPUNIT_ASSERT(rbPool_->getData(test_output, test_id) == 0); + + // put by RingBufferPool::DEFAULT_ID, get by test_id + mainRingBuffer->put(test_input1); + CPPUNIT_ASSERT(rbPool_->getData(test_output, test_id) == 1); + CPPUNIT_ASSERT(test_sample1 == (*test_output.getChannel(0))[0]); + + // get by RingBufferPool::DEFAULT_ID without preleminary put + CPPUNIT_ASSERT(rbPool_->getData(test_output, RingBufferPool::DEFAULT_ID) == 0); + + // put by test_id, get by RingBufferPool::DEFAULT_ID + testRingBuffer->put(test_input2); + CPPUNIT_ASSERT(rbPool_->getData(test_output, RingBufferPool::DEFAULT_ID) == 1); + CPPUNIT_ASSERT(test_sample2 == (*test_output.getChannel(0))[0]); + + rbPool_->unBindCallID(test_id, RingBufferPool::DEFAULT_ID); +} + +void RingBufferPoolTest::testGetAvailableData() +{ + TITLE(); + std::string test_id = "getData putData"; + std::string false_id = "false id"; + + auto mainRingBuffer = rbPool_->getRingBuffer(RingBufferPool::DEFAULT_ID); + auto testRingBuffer = rbPool_->createRingBuffer(test_id); + + rbPool_->bindCallID(test_id, RingBufferPool::DEFAULT_ID); + + ring::AudioSample test_sample1 = 12; + ring::AudioSample test_sample2 = 13; + + AudioBuffer test_input1(&test_sample1, 1, AudioFormat::MONO()); + AudioBuffer test_input2(&test_sample2, 1, AudioFormat::MONO()); + AudioBuffer test_output(1, AudioFormat::MONO()); + AudioBuffer test_output_large(100, AudioFormat::MONO()); + + // put by RingBufferPool::DEFAULT_ID get by test_id without preleminary put + CPPUNIT_ASSERT(rbPool_->availableForGet(test_id) == 0); + CPPUNIT_ASSERT(rbPool_->getAvailableData(test_output, test_id) == 0); + + // put by RingBufferPool::DEFAULT_ID, get by test_id + mainRingBuffer->put(test_input1); + CPPUNIT_ASSERT(rbPool_->availableForGet(test_id) == 1); + + // get by RingBufferPool::DEFAULT_ID without preliminary input + CPPUNIT_ASSERT(rbPool_->availableForGet(test_id) == 0); + CPPUNIT_ASSERT(rbPool_->getData(test_output_large, test_id) == 0); + + // put by test_id get by test_id + testRingBuffer->put(test_input2); + CPPUNIT_ASSERT(rbPool_->availableForGet(test_id) == 1); + CPPUNIT_ASSERT(rbPool_->getData(test_output_large, test_id) == 1); + CPPUNIT_ASSERT(rbPool_->availableForGet(test_id) == 0); + CPPUNIT_ASSERT((*test_output_large.getChannel(0))[0] == test_sample2); + + // get by false id + CPPUNIT_ASSERT(rbPool_->getData(test_output_large, false_id) == 0); + + rbPool_->unBindCallID(test_id, RingBufferPool::DEFAULT_ID); +} + +void RingBufferPoolTest::testDiscardFlush() +{ + TITLE(); + std::string test_id = "flush discard"; + + auto mainRingBuffer = rbPool_->getRingBuffer(RingBufferPool::DEFAULT_ID); + auto testRingBuffer = rbPool_->createRingBuffer(test_id); + + rbPool_->bindCallID(test_id, RingBufferPool::DEFAULT_ID); + + ring::AudioSample test_sample1 = 12; + AudioBuffer test_input1(&test_sample1, 1, AudioFormat::MONO()); + + testRingBuffer->put(test_input1); + rbPool_->discard(1, RingBufferPool::DEFAULT_ID); + + rbPool_->discard(1, test_id); + + mainRingBuffer->put(test_input1); + + rbPool_->discard(1, test_id); + + rbPool_->unBindCallID(test_id, RingBufferPool::DEFAULT_ID); +} + +void RingBufferPoolTest::testConference() +{ + TITLE(); + + std::string test_id1 = "participant A"; + std::string test_id2 = "participant B"; + + auto mainRingBuffer = rbPool_->getRingBuffer(RingBufferPool::DEFAULT_ID); + auto testRingBuffer1 = rbPool_->createRingBuffer(test_id1); + auto testRingBuffer2 = rbPool_->createRingBuffer(test_id2); + + // test bind Participant A with default + rbPool_->bindCallID(test_id1, RingBufferPool::DEFAULT_ID); + + // test bind Participant B with default + rbPool_->bindCallID(test_id2, RingBufferPool::DEFAULT_ID); + + // test bind Participant A with Participant B + rbPool_->bindCallID(test_id1, test_id2); + + ring::AudioSample testint = 12; + AudioBuffer testbuf(&testint, 1, AudioFormat::MONO()); + + // put data test ring buffers + mainRingBuffer->put(testbuf); + + // put data test ring buffers + testRingBuffer1->put(testbuf); + testRingBuffer2->put(testbuf); +} + +RingBufferPoolTest::RingBufferPoolTest() + : CppUnit::TestCase("Audio Layer Tests") , rbPool_(new RingBufferPool) +{} + +}} // namespace ring::test diff --git a/test/ringbufferpooltest.h b/test/ringbufferpooltest.h new file mode 100644 index 0000000000..ae48bb8e85 --- /dev/null +++ b/test/ringbufferpooltest.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef RINGBUFFERPOOL_TEST_ +#define RINGBUFFERPOOL_TEST_ + +// Cppunit import +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/TestCaller.h> +#include <cppunit/TestCase.h> +#include <cppunit/TestSuite.h> + +#include <memory> + +/* + * @file audiorecorderTest.cpp + * @brief Regroups unit tests related to the main buffer. + */ + +namespace ring { +class RingBufferPool; +} // namespace ring + +namespace ring { namespace test { + +class RingBufferPoolTest : public CppUnit::TestCase { + + /* + * Use cppunit library macros to add unit test the factory + */ + CPPUNIT_TEST_SUITE(RingBufferPoolTest); + CPPUNIT_TEST(testBindUnbindBuffer); + CPPUNIT_TEST(testGetPutData); + CPPUNIT_TEST(testDiscardFlush); + CPPUNIT_TEST(testConference); + CPPUNIT_TEST_SUITE_END(); + + public: + + RingBufferPoolTest(); + + void testBindUnbindBuffer(); + + void testGetPutData(); + + void testGetAvailableData(); + + void testDiscardFlush(); + + void testConference(); + + private: + + std::unique_ptr<ring::RingBufferPool> rbPool_; +}; + +/* Register our test module */ +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(RingBufferPoolTest, "RingBufferPoolTest"); +CPPUNIT_TEST_SUITE_REGISTRATION(RingBufferPoolTest); + +}} // namespace ring::test + +#endif // RINGBUFFERPOOL_TEST_ diff --git a/test/run_tests.sh b/test/run_tests.sh new file mode 100755 index 0000000000..9c48398b37 --- /dev/null +++ b/test/run_tests.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# print out env first for debugging +env +CODECS_PATH="../src/audio/codecs" exec ./test --xml diff --git a/test/scripts/presence_test.py b/test/scripts/presence_test.py new file mode 100755 index 0000000000..757aa5afac --- /dev/null +++ b/test/scripts/presence_test.py @@ -0,0 +1,219 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +print "\ +# --SFLPhone-- #\n\ +# #\n\ +# copyright: Savoir-Faire Linux (2013) #\n\ +# author: Patrick keroulas <patrick.keroulas@savoirfairelinux.com> #\n\ +# description: This script sends a sequence of methods to the daemon #\n\ +# through the dbus to test the presence feature of SFLPhone. #\n\ +# SET THE PARAMS IS THE 'data' SECTION OF THIS SCRIPT BEFORE #\n\ +# YOU EXECUTE IT. 'The normal mode process the tasks #\n\ +# in order while the random mode generates a random sequence #\n\ +# A Freeswitch server must be setup since it supports PUBLISH #\n\ +# requests and Asterisk doesn't. #\n\ +# The user must have 2 valid accounts. This script will #\n\ +# use the first account in the list and the IP2IP account. #\n\ +# This is a self subscribe test, set another buddy IP if needed#\n\ +#\n" + + + +import time, sys, gobject +from random import randint +import dbus, dbus.mainloop.glib +import logging, commands + + +#----------------- logger to file and stdout ------------------------------ +f = '%(asctime)s - %(name)s - %(levelname)s - %(message)s' +logfile = 'sflphone_doombot.log' +logging.basicConfig(filename=logfile,level=logging.INFO,format=f) # log to file +logger = logging.getLogger() +ch = logging.StreamHandler(sys.stdout) #log to console +ch.setLevel(logging.INFO) +formatter = logging.Formatter(f) +ch.setFormatter(formatter) +logger.addHandler(ch) + +#------------------ Initialise DBUS ------------------------------ +dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) +bus = dbus.SessionBus() +presenceManagerBus = bus.get_object('cx.ring.Ring', '/cx/ring/Ring/PresenceManager') +presenceManager = dbus.Interface(presenceManagerBus, dbus_interface='cx.ring.Ring.PresenceManager') +configurationManagerBus = bus.get_object('cx.ring.Ring', '/cx/ring/Ring/ConfigurationManager') +configurationManager = dbus.Interface(configurationManagerBus, dbus_interface='cx.ring.Ring.ConfigurationManager') + + +#------------------------- General purpose functions ---------------------------- + +#Get the first non-IP2IP account +def get_first_account(): + accounts = configurationManager.getAccountList() + for i, v in enumerate(accounts): + if v != "IP2IP": + details = configurationManager.getAccountDetails(v) + if details["Account.type"] == True or details["Account.type"] == "SIP": + return v + return "IP2IP" + +def get_account_list(): + accounts = configurationManager.getAccountList() + result = [] + for i, v in enumerate(accounts): + if v != "IP2IP": + details = configurationManager.getAccountDetails(v) + if details["Account.type"] == True or details["Account.type"] == "SIP": + result.append(v) + return result + +def registerSend(arg): + configurationManager.sendRegister(arg['acc'],arg['enable']) + logging.info('> REGISTER : '+ str(arg)) + +#------------------------- Presence functions ---------------------------- + +def presSubscribe(arg): + presenceManager.subscribeBuddy(arg['acc'],arg['buddy'],arg['flag']) + logging.info('> SUBSCRIBE to ' + str(arg)) + +def presSend(arg): + presenceManager.publish(arg['acc'],arg['status'],arg['note']) + logging.info('> PUBLISH ' + str(arg)) + +def presSubApprove(arg): + presenceManager.answerServerRequest(arg['uri'],arg['flag']) + logging.info('> APPROVE subscription from' + str(arg)) + +def newPresSubCientNotificationHandler(acc, uri, status, activity): + logging.info("< SIGNAL : Notification for acc:"+str(acc)+", from:"+str(uri)+" (status:" + str(status)+ ", "+ str(activity)+ ").") + +def newPresSubServerRequestHandler(uri): + logging.info("< SIGNAL : PresenceSubscription request from " +str(uri)) + subscriber_uri = uri + +def subcriptionStateChangedHandler(acc,uri,flag): + logging.info("< SIGNAL : new subscriptionState request for acc:"+str(acc)+" uri " +str(uri) + " flag:" + str(flag)) + +def serverErrorHandler(acc, error, msg): + logging.info("< SIGNAL : error from server:"+str(error)+" . "+str(msg)) + +def randbool(): + return bool(randint(0,1)) + +#--------------------------- Data ------------------------------- + +TEST_DURATION = 30 # in sec +acc_1 = get_account_list()[0] +print 'acc_1 : ' + str(acc_1) +acc_2 = get_account_list()[1] +print 'acc_2 : ' + str(acc_2) +IP2IP = 'IP2IP' +server_ip = '192.95.9.63' # asterisk test server + +host_user = '6001' +host_ip = '192.168.50.196' +host_uri = '<sip:'+host_user+'@'+server_ip+'>' + +buddy_uri_1 = '<sip:6001@'+server_ip+'>' +buddy_uri_2 = '<sip:6002@'+server_ip+'>' +buddy_ip = host_ip # self subscribing +buddy_ip_uri = '<sip:'+buddy_ip+'>' # IP2IP +subscriber_uri = '' + +#---------------------------- Sequence ---------------------------- + +start_time = 0 +task_count = 0 +task_N = 0 + +SEQ_MODE_NORMAL = 0 +SEQ_MODE_RANDOM = 1 +sequence_mode = SEQ_MODE_NORMAL + + +# regular test +task_list = [ + + (registerSend,{'acc':acc_1, 'enable':True}), + (presSubscribe, {'acc':acc_1,'buddy':buddy_uri_1,'flag':True}), + + (registerSend,{'acc':acc_2, 'enable':True}), + (presSubscribe, {'acc':acc_2,'buddy':buddy_uri_2,'flag':True}), + + (presSend, {'acc':acc_2,'status':randbool(),'note':buddy_uri_1+'is here!'}), + (presSend, {'acc':acc_1,'status':randbool(),'note':buddy_uri_2+'is here'}), + + (presSubscribe, {'acc':acc_1,'buddy':'<sip:6003@192.95.9.63>','flag':True}), + + (registerSend,{'acc':acc_1, 'enable':False}), + (presSubscribe, {'acc':acc_2,'buddy':buddy_uri_2,'flag':False}), + + (registerSend,{'acc':acc_2, 'enable':False}), + (presSubscribe, {'acc':acc_1,'buddy':buddy_uri_1,'flag':False}), +] + + +""" +# simple sub +task_list = [ + + (presSubscribe, {'acc':acc_2,'buddy':buddy_uri_2,'flag':True}), + (presSubscribe, {'acc':acc_2,'buddy':'<sip:6003@192.95.9.63>','flag':True}), +] +""" + +""" +# IP2IP +task_list = [ + (presSubscribe, {'acc': IP2IP,'buddy':buddy_ip_uri,'flag':True}), + (presSend, {'acc': IP2IP,'status':randbool(),'note':'This notify should not be recieved'}), + (presSubApprove, {'uri':subscriber_uri,'flag':randbool()}), + (presSend, {'acc': IP2IP,'status':randbool(),'note':'Oh yeah!'}), + (presSubscribe, {'acc': IP2IP,'buddy':buddy_ip_uri,'flag':False}), +] +""" + + +def run(): + + if sequence_mode == SEQ_MODE_NORMAL: + global task_count + task_index = task_count%task_N + task_count += 1 + if task_count == task_N+1: # one loop + return + + elif sequence_mode == SEQ_MODE_RANDOM: + task_index = randint(0,task_N-1) + + task_list[task_index][0](task_list[task_index][1]) + + if(int(time.time()-start_time) < TEST_DURATION): + gobject.timeout_add(2000, run) # const time step in ms + #gobject.timeout_add(randint(50,2000), run) # random time step in ms + else: + logging.info("Test sequence finished") + # TODO clear dbus sessio. Unfortunately stackoverflow.com is down today + + +if __name__ == '__main__': + + try: + # dbus signal monitor + presenceManagerBus.connect_to_signal("newBuddyNotification", newPresSubCientNotificationHandler, dbus_interface='cx.ring.Ring.PresenceManager') + presenceManagerBus.connect_to_signal("newServerSubscriptionRequest", newPresSubServerRequestHandler, dbus_interface='cx.ring.Ring.PresenceManager') + presenceManagerBus.connect_to_signal("subcriptionStateChanged", subcriptionStateChangedHandler, dbus_interface='cx.ring.Ring.PresenceManager') + presenceManagerBus.connect_to_signal("serverError", serverErrorHandler, dbus_interface='cx.ring.Ring.PresenceManager') + + start_time = time.time() + task_N = len(task_list) + #sequence_mode = SEQ_MODE_RANDOM + + run() + + except Exception as e: + print e + + loop = gobject.MainLoop() + loop.run() diff --git a/test/scripts/stress_test.py b/test/scripts/stress_test.py new file mode 100755 index 0000000000..cfad97e741 --- /dev/null +++ b/test/scripts/stress_test.py @@ -0,0 +1,421 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +print "\ +# --SFLphone-- #\n\ +# /¯¯¯¯\ /¯¯¯¯\ /¯¯¯¯\ /¯¯¯\_/¯¯¯\ /¯¯¯¯¯\ /¯¯¯¯\ |¯¯¯¯¯¯¯| #\n\ +# / /¯\ | / /\ \ /\ \| /¯\ /¯\ | | |¯| | | /\ | ¯¯| |¯¯ #\n\ +# / / / |/ | | | | | || | | | | | | ¯ < | | | | | | #\n\ +# / /__/ / | ¯ | ¯ || | | | | | | |¯| | | |_| | | | #\n\ +# |______/ \_____/ \____/ |_| |_| |_| \_____/ \____/ |_| #\n\ +# #\n\ +# _Version 2.0_ #\n\ +# #\n\ +# copyright: Savoir-Faire Linux (2012-2013) #\n\ +# author: Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> #\n\ +# description: This script perform stress tests to trigger rare race #\n\ +# conditions or ASSERT caused by excessive load. This script #\n\ +# should, in theory, never crash or end the sflphone daemon #\n" + +import dbus +import time +import sys +import os +from random import randint + +#---------------------------------------------------------------------# +# # +# Variables # +# # +#---------------------------------------------------------------------# + +#Initialise DBUS +bus = dbus.SessionBus() +callManagerBus = None +callManager = None +configurationManagerBus = None +configurationManager = None +instanceManagerBus = None +instanceManager = None + +#SFLphone +first_account = None +first_iax_account = None +first_account_number = None + +#GDB observer +gdbScriptPath = os.path.dirname(os.path.abspath(__file__))+"/gdb_wrapper.py" +gdbWrapperCommand = "gdb -x "+gdbScriptPath+" > /dev/null 2> /dev/null &" + +#Numbers +sip_number_1 = "7001" +sip_number_2 = "7000" +sip_unreg = "7002" + +#Messages +global_info = "" + + +#---------------------------------------------------------------------# +# # +# Initialization # +# # +#---------------------------------------------------------------------# + +#Start the GDB plugin +def start_daemon(): + #os.system(gdbWrapperCommand) + #time.sleep(10) + reInit() + +#Stop the daemon normally, do it 3 time to be sure it will unregister everyone +def stop_daemon(): + print("Stopping daemon") + try: + instanceManager.Unregister(123) + instanceManager.Unregister(123) + instanceManager.Unregister(123) + except: + #Nothing, it is normal + print("") + +#Connect DBUS +def reInit(): + try: + global callManagerBus,callManager,configurationManagerBus,configurationManager,instanceManagerBus,instanceManager + callManagerBus = bus.get_object('cx.ring.Ring', '/cx/ring/Ring/CallManager') + callManager = dbus.Interface(callManagerBus, dbus_interface='cx.ring.Ring.CallManager') + configurationManagerBus = bus.get_object('cx.ring.Ring', '/cx/ring/Ring/ConfigurationManager') + configurationManager = dbus.Interface(configurationManagerBus, dbus_interface='cx.ring.Ring.ConfigurationManager') + instanceManagerBus = bus.get_object('cx.ring.Ring', '/cx/ring/Ring/Instance') + instanceManager = dbus.Interface(instanceManagerBus , dbus_interface='cx.ring.Ring.Instance') + instanceManager.Register(123,"doombot") + global first_account,first_iax_account,first_account_number + first_account = get_first_account() + first_iax_account = get_first_iax_account() + first_account_number = get_account_number(first_account) + except dbus.exceptions.DBusException: + time.sleep(0.5) + reInit() + +#---------------------------------------------------------------------# +# # +# Tools # +# # +#---------------------------------------------------------------------# + +#Get the first non-IP2IP account +def get_first_account(): + accounts = configurationManager.getAccountList() + for i, v in enumerate(accounts): + if v != "IP2IP": + details = configurationManager.getAccountDetails(v) + if (details["Account.type"] == True or details["Account.type"] == "SIP") \ + and details['Account.registrationStatus'] == "REGISTERED": + return v + return "IP2IP" + +#Get the first IAX account +def get_first_iax_account(): + accounts = configurationManager.getAccountList() + for i, v in enumerate(accounts): + if v != "IP2IP": + details = configurationManager.getAccountDetails(v) + if details["Account.type"] != True and details["Account.type"] != "SIP": + return v + return "IP2IP" + +def get_account_number(account): + details = configurationManager.getAccountDetails(account) + return details["Account.username"] + +def answer_all_calls(): + calls = callManager.getCallList() + for i, v in enumerate(calls): + details = callManager.getCallDetails(v) + if details["CALL_STATE"] == "INCOMING": + callManager.accept(v) + +#Return true is the account is registered +def check_account_state(account): + details = configurationManager.getAccountDetails(account) + #details = {'test':1,'test2':2,'registrationStatus':3} + return details['Account.registrationStatus'] == "REGISTERED" + +#Meta test, common for all tests +def meta_test(test_func): + retCode = 0 + for y in range(0,15): + for x in range(0,10): + try: + ret = test_func() + if ret and ret['code'] > 0: + #print " \033[0;33m"+ret['error']+"\033[0m" + retCode = 1 + sys.stdout.write('X') + except dbus.exceptions.DBusException: + retCode = 1 + sys.stdout.write('X') + reInit() + sys.stdout.write('#') + sys.stdout.flush() + if retCode == 0: + sys.stdout.write(' \033[92m(Success)\033[0m\n') + else: + sys.stdout.write(' \033[91m(Failure)\033[0m\n') + return retCode + +#Add a new test +suits = {} +def add_to_suit(test_suite_name,test_name,test_func): + if not test_suite_name in suits: + suits[test_suite_name] = [] + suits[test_suite_name].append({'test_name':test_name,'test_func':test_func}) + +# Run tests +def run(): + counter = 1 + results = {} + for k in suits.keys(): + print "\n\033[1mExecuting \""+str(k)+"\" tests suit:\033[0m ("+str(counter)+"/"+str(len(suits))+")" + for i, v in enumerate(suits[k]): + #Start SFLphone + start_daemon() + sys.stdout.write(" ["+str(i+1)+"/"+str(len(suits[k]))+"] Testing \""+v['test_name']+"\": ") + sys.stdout.flush() + + #Run the test + retval = meta_test(v['test_func']) + if not k in results: + results[k] = 0 + if retval > 0: + results[k]= results[k] + 1 + + #Stop SFLphone + stop_daemon() + time.sleep(15) + try: + #Try to read the GDB wrapper report + with open('/tmp/doombotReport') as report: + print "Test results:" + print report.read() + except IOError: + print 'Report not found' + counter = counter + 1 + + #Print the test summary + print "\n\n\033[1mSummary:\033[0m" + totaltests = 0 + totalsuccess = 0 + for k in suits.keys(): + print " Suit \""+k+"\": "+str(len(suits[k])-results[k])+"/"+str(len(suits[k])) + totaltests = totaltests + len(suits[k]) + totalsuccess = totalsuccess + len(suits[k])-results[k] + + print "\nTotal: "+str(totalsuccess)+"/"+str(totaltests)+", "+str(totaltests-totalsuccess)+" failures" + + + + +#---------------------------------------------------------------------# +# # +# Unit Tests # +# # +#---------------------------------------------------------------------# + +# This unit case test the basic senario of calling asterisk/freeswitch and then hanging up +# It call itself to make the test simpler, this also test answering up as a side bonus +def stress_answer_hangup_server(): + details = configurationManager.getAccountDetails(first_account) + callManager.placeCall(first_account,sip_number_2) + time.sleep(0.05) + calls = callManager.getCallList() + + # Check if the call worked + if len(calls) < 2: + if not check_account_state(first_account): + #TODO Try to register again instead of failing + return {'code':2,'error':"Unit test \"stress_answer_hangup_server\" failed: Account went unregistered"} + else: + return {'code':1,'error':"Unit test \"stress_answer_hangup_server\" failed: Error while placing call, there is "+str(len(calls))+" calls"} + else: + #Accept the calls + for i, v in enumerate(calls): + time.sleep(0.05) + callManager.accept(v) + callManager.accept(v) + callManager.accept(v) + callManager.accept(v) + callManager.placeCall(first_account,sip_number_2) + + #Hang up + callManager.hangUp(calls[0]) + calls = callManager.getCallList() + for i, v in enumerate(calls): + callManager.hangUp(v) + return {'code':0,'error':""} +add_to_suit("Place call",'Place, answer and hangup',stress_answer_hangup_server) + + + +# This test is similar to stress_answer_hangup_server, but test using IP2IP calls +def stress_answer_hangup_IP2IP(): + callManager.placeCall(first_account,"sip:127.0.0.1") + time.sleep(0.05) + calls = callManager.getCallList() + + # Check if the call worked + if len(calls) < 2: + if not check_account_state(first_account): + #TODO Try to register again instead of failing + return {'code':2,'error':"\nUnit test \"stress_answer_hangup_server\" failed: Account went unregistered"} + else: + return {'code':1,'error':"\nUnit test \"stress_answer_hangup_server\" failed: Error while placing call, there is "+str(len(calls))+" calls"} + else: + #Accept the calls + for i, v in enumerate(calls): + time.sleep(0.05) + callManager.accept(v) + #Hang up + callManager.hangUp(calls[0]) + return {'code':0,'error':""} +add_to_suit("Place call",'Place, answer and hangup (IP2IP)',stress_answer_hangup_IP2IP) + +# Test various type of transfers between various type of calls +# Use both localhost, SIP and IAX +def stress_transfers(): + for i in range(0,50): #repeat the tests + for j in range(0,3): # alternate between IP2IP, SIP and IAX + for k in range(0,2): #alternate between classic transfer and attended one + acc1 = "" + if j == 0: + acc1 = first_account + elif j == 1: + acc1 = "IP2IP" + else: + acc1 = first_iax_account + acc2 = "" + if i%3 == 0: #Use the first loop to shuffle second account type + acc2 = first_account + global_info = "Using SIP Account" + elif i%3 == 1: + acc2 = "IP2IP" + global_info = "Using IP2IP" + else: + acc2 = first_iax_account + global_info = "Using IAX account" + #print "ACC1"+acc1+" ACC2 "+acc2+ " FIRST IAX "+ first_iax_account +" FISRT "+first_account + destination_number = "" + if acc2 == "IP2IP": + destination_number = "sip:127.0.0.1" + else: + destination_number = configurationManager.getAccountDetails(acc2)["Account.username"] + callManager.placeCall(acc1,destination_number) + second_call = None + if k == 1: + callManager.placeCall(acc1,sip_number_1) + answer_all_calls() + + if k == 1: + first_call = None + calls = callManager.getCallList() + for i, v in enumerate(calls): + if first_call == None: + first_call = v + else: + callManager.attendedTransfer(v,first_call) + else: + calls = callManager.getCallList() + for i, v in enumerate(calls): + callManager.transfer(v,destination_number) +#add_to_suit("Transfer",'Make calls and transfer them',stress_transfers) + + +# This test make as tons or calls, then hangup them all as fast as it can +def stress_concurent_calls(): + for i in range(0,5): #repeat the tests + for j in range(0,3): # alternate between IP2IP, SIP and IAX + acc1 = "" + if j == 0: + acc1 = first_account + global_info = "Using SIP" + elif j == 1: + acc1 = "IP2IP" + global_info = "Using IP2IP" + else: + acc1 = first_iax_account + global_info = "Using IAX" + acc2 = "" + if i%3 == 0: #Use the first loop to shuffle second account type + acc2 = first_account + elif i%3 == 1: + acc2 = "IP2IP" + else: + acc2 = first_iax_account + #print "ACC1"+acc1+" ACC2 "+acc2+ " FIRST IAX "+ first_iax_account +" FISRT "+first_account + destination_number = "" + if acc2 == "IP2IP": + destination_number = "sip:127.0.0.1" + else: + destination_number = configurationManager.getAccountDetails(acc2)["Account.username"] + callManager.placeCall(acc1,destination_number) + calls = callManager.getCallList() + for i, v in enumerate(calls): + callManager.hangUp(v) +add_to_suit("Place call",'Many simultanious calls (IP2IP)',stress_concurent_calls) + +# Test if SFLPhone can handle more than 50 simultanious IP2IP call over localhost +# Using localhost to save bandwidth, this is about concurent calls, not network load +#def stress_concurent_calls(): + + ## Create 50 calls + #for i in range(0,50): + #callManager.placeCall(first_account,str(randint(100000000,100000000000)),"sip:127.0.0.1") + + ##TODO check if the could is right + + ## Accept all calls that worked + #calls = callManager.getCallList() + #for i, v in enumerate(calls): + #callManager.accept(v) + + ## Hang up all calls + #for i, v in enumerate(calls): + #callManager.hangUp(v) + #return {'code':0,'error':""} +#add_to_suit("Place call",'Many simultanious calls (IP2IP)',stress_concurent_calls) + + +# Test if a call can be put and removed from hold multiple time +def stress_hold_unhold_server(): + # Hang up everything left + calls = callManager.getCallList() + for i, v in enumerate(calls): + callManager.hangUp(v) + + #Place a call + callManager.placeCall(first_account,first_account_number) + calls = callManager.getCallList() + if len(calls) < 1: + return {'code':5,'error':"\nUnit test \"stress_hold_unhold\" failed: The call is gone"} + call = calls[0] + + #Hold and unhold it + for i in range(0,10): + callManager.hold(call) + details = callManager.getCallDetails(call) + if not 'CALL_STATE' in details: + return {'code':1,'error':"\nUnit test \"stress_hold_unhold\" failed: The call is gone (hold)"} + if not details['CALL_STATE'] == "HOLD": + return {'code':2,'error':"\nUnit test \"stress_hold_unhold\" failed: The call should be on hold, but is "+details['CALL_STATE']} + callManager.unhold(call) + details = callManager.getCallDetails(call) + if not 'CALL_STATE' in details: + return {'code':3,'error':"\nUnit test \"stress_hold_unhold\" failed: The call is gone (unhold)"} + if not details['CALL_STATE'] == "CURRENT": + return {'code':4,'error':"\nUnit test \"stress_hold_unhold\" failed: The call should be current, but is "+details['CALL_STATE']} + return {'code':0,'error':""} +add_to_suit("Hold call",'Hold and unhold',stress_hold_unhold_server) + +#Run the tests +run() +#kate: space-indent off; tab-indents on; mixedindent off; indent-width 4;tab-width 4; diff --git a/test/sdesnegotiatortest.cpp b/test/sdesnegotiatortest.cpp new file mode 100644 index 0000000000..77bcd41f6a --- /dev/null +++ b/test/sdesnegotiatortest.cpp @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#include <cstddef> +#include <stdio.h> +#include <sstream> +#include <cstddef> +#include <string> +#include <cstring> +#include <math.h> +#include <dlfcn.h> +#include <iostream> +#include <sstream> + +#include "sdesnegotiatortest.h" +#include "sip/pattern.h" +#include "sip/sdes_negotiator.h" + +#include <unistd.h> +#include "test_utils.h" +#include "logger.h" + +namespace ring { namespace test { + +using std::cout; +using std::endl; + +void SdesNegotiatorTest::testTagPattern() +{ + TITLE(); + std::string subject = "a=crypto:4"; + + ring::Pattern pattern("^a=crypto:(?P<tag>[0-9]{1,9})", false); + pattern.updateSubject(subject); + + CPPUNIT_ASSERT(pattern.matches()); + CPPUNIT_ASSERT(pattern.group("tag").compare("4") == 0); +} + + +void SdesNegotiatorTest::testCryptoSuitePattern() +{ + TITLE(); + std::string subject = "AES_CM_128_HMAC_SHA1_80"; + + ring::Pattern pattern("(?P<cryptoSuite>AES_CM_128_HMAC_SHA1_80|" \ + "AES_CM_128_HMAC_SHA1_32|" \ + "F8_128_HMAC_SHA1_80|" \ + "[A-Za-z0-9_]+)", false); + pattern.updateSubject(subject); + + CPPUNIT_ASSERT(pattern.matches()); + CPPUNIT_ASSERT(pattern.group("cryptoSuite").compare("AES_CM_128_HMAC_SHA1_80") == 0); +} + + +void SdesNegotiatorTest::testKeyParamsPattern() +{ + TITLE(); + + std::string subject = "inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj|2^20|1:32"; + + ring::Pattern pattern("(?P<srtpKeyMethod>inline|[A-Za-z0-9_]+)\\:" \ + "(?P<srtpKeyInfo>[A-Za-z0-9\x2B\x2F\x3D]+)\\|" \ + "(2\\^(?P<lifetime>[0-9]+)\\|" \ + "(?P<mkiValue>[0-9]+)\\:" \ + "(?P<mkiLength>[0-9]{1,3})\\;?)?", true); + + pattern.updateSubject(subject); + + pattern.matches(); + CPPUNIT_ASSERT(pattern.group("srtpKeyMethod").compare("inline:")); + CPPUNIT_ASSERT(pattern.group("srtpKeyInfo").compare("d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj") + == 0); + CPPUNIT_ASSERT(pattern.group("lifetime").compare("20") == 0); + CPPUNIT_ASSERT(pattern.group("mkiValue").compare("1") == 0); + CPPUNIT_ASSERT(pattern.group("mkiLength").compare("32") == 0); +} + + +void SdesNegotiatorTest::testKeyParamsPatternWithoutMKI() +{ + TITLE(); + + std::string subject("inline:d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj"); + + ring::Pattern pattern("(?P<srtpKeyMethod>inline|[A-Za-z0-9_]+)\\:" \ + "(?P<srtpKeyInfo>[A-Za-z0-9\x2B\x2F\x3D]+)" \ + "(\\|2\\^(?P<lifetime>[0-9]+)\\|" \ + "(?P<mkiValue>[0-9]+)\\:" \ + "(?P<mkiLength>[0-9]{1,3})\\;?)?", true); + + pattern.updateSubject(subject); + pattern.matches(); + CPPUNIT_ASSERT(pattern.group("srtpKeyMethod").compare("inline:")); + CPPUNIT_ASSERT(pattern.group("srtpKeyInfo").compare("d0RmdmcmVCspeEc3QGZiNWpVLFJhQX1cfHAwJSoj") + == 0); +} + + +/** + * Make sure that all the fields can be extracted + * properly from the syntax. + */ +void SdesNegotiatorTest::testNegotiation() +{ + TITLE(); + + // Add a new SDES crypto line to be processed. + std::vector<std::string> remoteOffer; + remoteOffer.push_back("a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwd|2^20|1:32"); + remoteOffer.push_back("a=crypto:2 AES_CM_128_HMAC_SHA1_32 inline:NzB4d1BINUAvLEw6UzF3WSJ+PSdFcGdUJShpX1Zj|2^20|1:32"); + + // Register the local capabilities. + std::vector<ring::CryptoSuiteDefinition> localCapabilities; + + for (int i = 0; i < 3; ++i) + localCapabilities.push_back(ring::CryptoSuites[i]); + + ring::SdesNegotiator sdesnego(localCapabilities, remoteOffer); + + CPPUNIT_ASSERT(sdesnego.negotiate()); +} + +/** + * Make sure that unproperly formatted crypto lines are rejected. + */ +void SdesNegotiatorTest::testComponent() +{ + TITLE(); + + // Register the local capabilities. + std::vector<ring::CryptoSuiteDefinition> capabilities; + + // Support all the CryptoSuites + for (int i = 0; i < 3; i++) + capabilities.push_back(ring::CryptoSuites[i]); + + // Make sure that if a component is missing, negotiate will fail + std::string cryptoLine("a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:|2^20|1:32"); + std::vector<std::string> cryptoOffer; + cryptoOffer.push_back(cryptoLine); + + ring::SdesNegotiator negotiator(capabilities, cryptoOffer); + CPPUNIT_ASSERT(!negotiator.negotiate()); +} + +/** + * Make sure that most simple case does not fail. + */ +void SdesNegotiatorTest::testMostSimpleCase() +{ + TITLE(); + + // Register the local capabilities. + std::vector<ring::CryptoSuiteDefinition> capabilities; + + // Support all the CryptoSuites + for (int i = 0; i < 3; i++) + capabilities.push_back(ring::CryptoSuites[i]); + + // Make sure taht this case works (since it's default for most application) + std::string cryptoLine("a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwd"); + std::vector<std::string> cryptoOffer; + cryptoOffer.push_back(cryptoLine); + + ring::SdesNegotiator negotiator(capabilities, cryptoOffer); + + CPPUNIT_ASSERT(negotiator.negotiate()); + + CPPUNIT_ASSERT(negotiator.getCryptoSuite() == "AES_CM_128_HMAC_SHA1_80"); + CPPUNIT_ASSERT(negotiator.getKeyMethod() == "inline"); + CPPUNIT_ASSERT(negotiator.getKeyInfo() == "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwd"); + CPPUNIT_ASSERT(negotiator.getLifeTime().empty()); + CPPUNIT_ASSERT(negotiator.getMkiValue().empty()); + CPPUNIT_ASSERT(negotiator.getMkiLength().empty()); + CPPUNIT_ASSERT(negotiator.getAuthTagLength() == "80"); +} + + +void SdesNegotiatorTest::test32ByteKeyLength() +{ + TITLE(); + + // Register the local capabilities. + std::vector<ring::CryptoSuiteDefinition> capabilities; + + //Support all the CryptoSuites + for (int i = 0; i < 3; i++) + capabilities.push_back(ring::CryptoSuites[i]); + + std::string cryptoLine("a=crypto:1 AES_CM_128_HMAC_SHA1_32 inline:AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwd"); + std::vector<std::string> cryptoOffer; + cryptoOffer.push_back(cryptoLine); + + ring::SdesNegotiator negotiator(capabilities, cryptoOffer); + + CPPUNIT_ASSERT(negotiator.negotiate()); + + CPPUNIT_ASSERT(negotiator.getCryptoSuite() == "AES_CM_128_HMAC_SHA1_32"); + CPPUNIT_ASSERT(negotiator.getKeyMethod() == "inline"); + CPPUNIT_ASSERT(negotiator.getKeyInfo() == "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwd"); + CPPUNIT_ASSERT(negotiator.getLifeTime().empty()); + CPPUNIT_ASSERT(negotiator.getMkiValue().empty()); + CPPUNIT_ASSERT(negotiator.getMkiLength().empty()); + CPPUNIT_ASSERT(negotiator.getAuthTagLength() == "32"); +} + +}} // namespace ring::test diff --git a/test/sdesnegotiatortest.h b/test/sdesnegotiatortest.h new file mode 100644 index 0000000000..168d65ca71 --- /dev/null +++ b/test/sdesnegotiatortest.h @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +// Cppunit import +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/TestCaller.h> +#include <cppunit/TestCase.h> +#include <cppunit/TestSuite.h> + +#include <cstddef> +#include <stdio.h> +#include <sstream> + +#include <vector> + +// pjsip import +#include <pjsip.h> +#include <pjlib.h> +#include <pjsip_ua.h> +#include <pjlib-util.h> +#include <pjnath/stun_config.h> + +#include "noncopyable.h" + +/* + * @file sdesnegotiationTest.cpp + * @brief Regroups unitary tests related to the plugin manager. + */ + +#ifndef __SDESNEGOTIATOR_TEST_H__ +#define __SDESNEGOTIATOR_TEST_H__ + +#include "sip/sdes_negotiator.h" // for CryptoSuiteDefinition + +namespace ring { namespace test { + +class Pattern; + +class SdesNegotiatorTest : public CppUnit::TestCase { + + /* + * Use cppunit library macros to add unit test the factory + */ + CPPUNIT_TEST_SUITE(SdesNegotiatorTest); + CPPUNIT_TEST(testTagPattern); + CPPUNIT_TEST(testCryptoSuitePattern); + CPPUNIT_TEST(testKeyParamsPattern); + CPPUNIT_TEST(testKeyParamsPatternWithoutMKI); + CPPUNIT_TEST(testNegotiation); + CPPUNIT_TEST(testMostSimpleCase); + CPPUNIT_TEST(test32ByteKeyLength); + CPPUNIT_TEST_SUITE_END(); + + public: + /* + * Code factoring - Common resources can be released here. + * This method is called by unitcpp after each test + */ + void testTagPattern(); + + void testCryptoSuitePattern(); + + void testKeyParamsPattern(); + + void testKeyParamsPatternCiscoStyle(); + + void testKeyParamsPatternWithoutMKI(); + + void testNegotiation(); + + void testComponent(); + + void testMostSimpleCase(); + + void test32ByteKeyLength(); +}; + +/* Register our test module */ +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(SdesNegotiatorTest, "SdesNegotiatorTest"); +CPPUNIT_TEST_SUITE_REGISTRATION(SdesNegotiatorTest); + +}} // namespace ring::test + +#endif // __SDESNEGOTIATOR_TEST_H__ diff --git a/test/sdptest.cpp b/test/sdptest.cpp new file mode 100644 index 0000000000..5ef2a2cac5 --- /dev/null +++ b/test/sdptest.cpp @@ -0,0 +1,307 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +/* + * ebail - 2015/02/18 + * unit test is based on old SDP manager code + * this test is disabled for the moment + * */ +#if 0 +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "sdptest.h" +#include <iostream> +#include <cstring> + +#include "audio/codecs/audiocodec.h" + +namespace ring { namespace test { + +enum session_type { + REMOTE_OFFER, + LOCAL_OFFER, +}; + +static const char *sdp_answer1 = "v=0\r\n" + "o=bob 2890844730 2890844730 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 49920 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 53002 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n"; + +static const char *sdp_offer1 = "v=0\r\n" + "o=bob 2890844730 2890844730 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 49920 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 53002 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n"; + +static const char *sdp_answer2 = "v=0\r\n" + "o=bob 2890844730 2890844730 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 49920 RTP/AVP 3 97 9\r\n" + "a=rtpmap:3 GSM/8000\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "a=rtpmap:9 G722/8000\r\n" + "m=video 53002 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n"; + +static const char *sdp_offer2 = "v=0\r\n" + "o=bob 2890844730 2890844730 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.example.com\r\n" + "t=0 0\r\n" + "m=audio 49920 RTP/AVP 3 97 9\r\n" + "a=rtpmap:3 GSM/8000\r\n" + "a=rtpmap:97 iLBC/8000\r\n" + "a=rtpmap:9 G722/8000\r\n" + "m=video 53002 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n"; + +static const char *sdp_reinvite = "v=0\r\n" + "o=bob 2890844730 2890844730 IN IP4 host.example.com\r\n" + "s= \r\n" + "c=IN IP4 host.exampleReinvite.com\r\n" + "t=0 0\r\n" + "m=audio 42445 RTP/AVP 0\r\n" + "a=rtpmap:0 PCMU/8000\r\n" + "m=video 53002 RTP/AVP 32\r\n" + "a=rtpmap:32 MPV/90000\r\n"; + +static const char *const LOCALHOST = "127.0.0.1"; + +void SDPTest::setUp() +{ + session_.reset(new Sdp("123456789")); +} + +void SDPTest::tearDown() +{ + session_.reset(); +} + +void SDPTest::receiveAnswerAfterInitialOffer(const pjmedia_sdp_session* remote) +{ + CPPUNIT_ASSERT(pjmedia_sdp_neg_get_state(session_->negotiator_) == PJMEDIA_SDP_NEG_STATE_LOCAL_OFFER); + CPPUNIT_ASSERT(pjmedia_sdp_neg_set_remote_answer(session_->memPool_.get(), session_->negotiator_, remote) == PJ_SUCCESS); + CPPUNIT_ASSERT(pjmedia_sdp_neg_get_state(session_->negotiator_) == PJMEDIA_SDP_NEG_STATE_WAIT_NEGO); +} + +namespace { +std::vector<std::map<std::string, std::string> > + createVideoCodecs() { + std::vector<std::map<std::string, std::string> > videoCodecs; +#ifdef RING_VIDEO + std::map<std::string, std::string> codec; + codec["name"] = "H264"; + codec["enabled"] = "true"; + videoCodecs.push_back(codec); + codec["name"] = "H263"; + videoCodecs.push_back(codec); +#endif + return videoCodecs; + } +} + +void SDPTest::testInitialOfferFirstCodec() +{ + std::cout << "------------ SDPTest::testInitialOfferFirstCodec --------------" << std::endl; + + CPPUNIT_ASSERT(session_->getPublishedIP().empty()); + CPPUNIT_ASSERT(session_->getRemoteIP().empty()); + + std::vector<int> codecSelection; + + codecSelection.push_back(PAYLOAD_CODEC_ULAW); + codecSelection.push_back(PAYLOAD_CODEC_ALAW); + codecSelection.push_back(PAYLOAD_CODEC_G722); + + std::vector<std::map<std::string, std::string> > videoCodecs(createVideoCodecs()); + + session_->setPublishedIP(LOCALHOST); + session_->setLocalPublishedAudioPort(49567); + + session_->createOffer(codecSelection, videoCodecs); + + pjmedia_sdp_session *remoteAnswer; + pjmedia_sdp_parse(session_->memPool_.get(), (char*) sdp_answer1, strlen(sdp_answer1), &remoteAnswer); + + receiveAnswerAfterInitialOffer(remoteAnswer); + session_->startNegotiation(); + + session_->setMediaTransportInfoFromRemoteSdp(); + + CPPUNIT_ASSERT(session_->getPublishedIP() == LOCALHOST); + CPPUNIT_ASSERT(session_->getRemoteIP() == "host.example.com"); +} + +void SDPTest::testInitialAnswerFirstCodec() +{ + std::cout << "------------ SDPTest::testInitialAnswerFirstCodec -------------" << std::endl; + + CPPUNIT_ASSERT(session_->getPublishedIP().empty()); + CPPUNIT_ASSERT(session_->getRemoteIP().empty()); + + std::vector<int> codecSelection; + pjmedia_sdp_session *remoteOffer; + + codecSelection.push_back(PAYLOAD_CODEC_ULAW); + codecSelection.push_back(PAYLOAD_CODEC_ALAW); + codecSelection.push_back(PAYLOAD_CODEC_G722); + + pjmedia_sdp_parse(session_->memPool_.get(), (char*) sdp_offer1, strlen(sdp_offer1), &remoteOffer); + + session_->setPublishedIP(LOCALHOST); + session_->setLocalPublishedAudioPort(49567); + + session_->receiveOffer(remoteOffer, codecSelection, createVideoCodecs()); + + session_->startNegotiation(); + + session_->setMediaTransportInfoFromRemoteSdp(); + + CPPUNIT_ASSERT(session_->getPublishedIP() == LOCALHOST); + CPPUNIT_ASSERT(session_->getRemoteIP() == "host.example.com"); +} + +void SDPTest::testInitialOfferLastCodec() +{ + std::cout << "------------ SDPTest::testInitialOfferLastCodec --------------------" << std::endl; + + CPPUNIT_ASSERT(session_->getPublishedIP().empty()); + CPPUNIT_ASSERT(session_->getRemoteIP().empty()); + + std::vector<int> codecSelection; + + codecSelection.push_back(PAYLOAD_CODEC_ULAW); + codecSelection.push_back(PAYLOAD_CODEC_ALAW); + codecSelection.push_back(PAYLOAD_CODEC_G722); + + session_->setPublishedIP(LOCALHOST); + session_->setLocalPublishedAudioPort(49567); + + session_->createOffer(codecSelection, createVideoCodecs()); + + pjmedia_sdp_session *remoteAnswer; + pjmedia_sdp_parse(session_->memPool_.get(), (char*) sdp_answer2, strlen(sdp_answer2), &remoteAnswer); + + receiveAnswerAfterInitialOffer(remoteAnswer); + session_->startNegotiation(); + + session_->setMediaTransportInfoFromRemoteSdp(); + + CPPUNIT_ASSERT(session_->getPublishedIP() == LOCALHOST); + CPPUNIT_ASSERT(session_->getRemoteIP() == "host.example.com"); +} + +void SDPTest::testInitialAnswerLastCodec() +{ + std::cout << "------------ SDPTest::testInitialAnswerLastCodec ------------" << std::endl; + + CPPUNIT_ASSERT(session_->getPublishedIP().empty()); + CPPUNIT_ASSERT(session_->getRemoteIP().empty()); + + std::vector<int> codecSelection; + pjmedia_sdp_session *remoteOffer; + + codecSelection.push_back(PAYLOAD_CODEC_ULAW); + codecSelection.push_back(PAYLOAD_CODEC_ALAW); + codecSelection.push_back(PAYLOAD_CODEC_G722); + + pjmedia_sdp_parse(session_->memPool_.get(), (char*)sdp_offer2, strlen(sdp_offer2), &remoteOffer); + + session_->setPublishedIP(LOCALHOST); + session_->setLocalPublishedAudioPort(49567); + + session_->receiveOffer(remoteOffer, codecSelection, createVideoCodecs()); + + session_->startNegotiation(); + + session_->setMediaTransportInfoFromRemoteSdp(); + + CPPUNIT_ASSERT(session_->getPublishedIP() == LOCALHOST); + CPPUNIT_ASSERT(session_->getRemoteIP() == "host.example.com"); +} + + +void SDPTest::testReinvite() +{ + std::cout << "------------ SDPTest::testReinvite --------------------" << std::endl; + + CPPUNIT_ASSERT(session_->getPublishedIP().empty()); + CPPUNIT_ASSERT(session_->getRemoteIP().empty()); + + std::vector<int> codecSelection; + codecSelection.push_back(PAYLOAD_CODEC_ULAW); + codecSelection.push_back(PAYLOAD_CODEC_ALAW); + codecSelection.push_back(PAYLOAD_CODEC_G722); + + session_->setPublishedIP(LOCALHOST); + session_->setLocalPublishedAudioPort(49567); + + std::vector<std::map<std::string, std::string> > videoCodecs(createVideoCodecs()); + session_->createOffer(codecSelection, videoCodecs); + + pjmedia_sdp_session *remoteAnswer; + // pjmedia_sdp_parse(session_->memPool_.get(), test[0].offer_answer[0].sdp2, strlen(test[0].offer_answer[0].sdp2), &remoteAnswer); + pjmedia_sdp_parse(session_->memPool_.get(), (char*) sdp_answer1, strlen(sdp_answer1), &remoteAnswer); + + receiveAnswerAfterInitialOffer(remoteAnswer); + session_->startNegotiation(); + + session_->setMediaTransportInfoFromRemoteSdp(); + + CPPUNIT_ASSERT(session_->getPublishedIP() == LOCALHOST); + CPPUNIT_ASSERT(session_->getRemoteIP() == "host.example.com"); + std::vector<ring::AudioCodec*> codecs(session_->getSessionAudioMedia()); + ring::AudioCodec *codec = codecs[0]; + CPPUNIT_ASSERT(codec and codec->getMimeSubtype() == "PCMU"); + + pjmedia_sdp_session *reinviteOffer; + pjmedia_sdp_parse(session_->memPool_.get(), (char*) sdp_reinvite, strlen(sdp_reinvite), &reinviteOffer); + session_->receiveOffer(reinviteOffer, codecSelection, videoCodecs); + + session_->startNegotiation(); + session_->setMediaTransportInfoFromRemoteSdp(); + + CPPUNIT_ASSERT(session_->getRemoteIP() == "host.exampleReinvite.com"); +} + +}} // namespace ring::test +#endif diff --git a/test/sdptest.h b/test/sdptest.h new file mode 100644 index 0000000000..c977a2b9b6 --- /dev/null +++ b/test/sdptest.h @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + + + +/* + * @file sdptest.h + * @brief Regroups unitary tests related to the SDP session + */ + +#ifndef _SDP_TEST_ +#define _SDP_TEST_ + +// Cppunit import +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/TestCaller.h> +#include <cppunit/TestCase.h> +#include <cppunit/TestSuite.h> + +#include <exception> +#include <string> +#include <memory> + +#include <pj/pool.h> +#include <pjmedia/sdp.h> +#include <pjmedia/sdp_neg.h> +#include <pjmedia/errno.h> +#include <pjsip/sip_transport.h> +#include <pjlib.h> +#include <pjsip_ua.h> + +#include "sip/sdp.h" +#include "noncopyable.h" + +namespace ring { namespace test { +/* + * ebail - 2015/02/18 + * unit test is based on old SDP manager code + * this test is disabled for the moment + * */ +#if 0 + +class SdpSessionException : public std::exception { + public: + SdpSessionException(const std::string& str="") throw() : errstr(str) {} + + virtual ~SdpSessionException() throw() {} + + virtual const char *what() const throw() { + std::string expt("SdpSession: SdpSessionException occured: "); + expt.append(errstr); + return expt.c_str(); + } + private: + std::string errstr; +}; + + +class SDPTest : public CppUnit::TestCase { + + /** + * Use cppunit library macros to add unit test the factory + */ + CPPUNIT_TEST_SUITE(SDPTest); + CPPUNIT_TEST(testInitialOfferLastCodec); + CPPUNIT_TEST(testInitialAnswerLastCodec); + CPPUNIT_TEST(testInitialOfferLastCodec); + CPPUNIT_TEST(testInitialAnswerLastCodec); + CPPUNIT_TEST(testReinvite); + CPPUNIT_TEST_SUITE_END(); + + public: + SDPTest() : CppUnit::TestCase("SDP module Tests") {} + + /** + * Code factoring - Common resources can be initialized here. + * This method is called by unitcpp before each test + */ + void setUp(); + + /** + * Code factoring - Common resources can be released here. + * This method is called by unitcpp after each test + */ + void tearDown(); + + void testInitialOfferFirstCodec(); + + void testInitialAnswerFirstCodec(); + + void testInitialOfferLastCodec(); + + void testInitialAnswerLastCodec(); + + void testReinvite(); + + private: + NON_COPYABLE(SDPTest); + void receiveAnswerAfterInitialOffer(const pjmedia_sdp_session* remote); + + std::unique_ptr<Sdp> session_; +}; + +/* Register our test module */ +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(SDPTest, "SDPTest"); +CPPUNIT_TEST_SUITE_REGISTRATION(SDPTest); + +}} // namespace ring::test + +#endif +#endif //_SDP_TEST_ diff --git a/test/sippxml/test_1.xml b/test/sippxml/test_1.xml new file mode 100644 index 0000000000..7092639357 --- /dev/null +++ b/test/sippxml/test_1.xml @@ -0,0 +1,157 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> +<!DOCTYPE scenario SYSTEM "sipp.dtd"> + +<!-- This program is free software; you can redistribute it and/or --> +<!-- modify it under the terms of the GNU General Public License as --> +<!-- published by the Free Software Foundation; either version 2 of the --> +<!-- License, or (at your option) any later version. --> +<!-- --> +<!-- This program is distributed in the hope that it will be useful, --> +<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of --> +<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --> +<!-- GNU General Public License for more details. --> +<!-- --> +<!-- You should have received a copy of the GNU General Public License --> +<!-- along with this program; if not, write to the --> +<!-- Free Software Foundation, Inc., --> +<!-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA --> +<!-- --> +<!-- Sipp default 'uas' scenario. --> +<!-- --> + +<scenario name="UAS responder put on hold then hungup"> + + <!-- This scenario implies a second call made by the uac which implies --> + <!-- This call to be put on hold automatically --> + + <recv request="INVITE" crlf="true"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 180 Ringing + [last_Via:] + [last_From:] + [last_To:];tag=[pid]SIPpTag01[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[pid]SIPpTag01[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv request="ACK" + optional="true" + rtd="true" + crlf="true"> + </recv> + + + <!-- Here process a second invite with updated sdp, this call is placed on HOLD --> + <!-- TODO: parse in sdp: Media Attribute (a): sendonly --> + <recv request="INVITE" crlf="true"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 100 Trying + [last_Via:] + [last_From:] + [last_To:];tag=[pid]SIPpTag01[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[pid]SIPpTag01[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv request="ACK" + optional="true" + rtd="true" + crlf="true"> + </recv> + + <!-- expect to be hung up immediately --> + <recv request="BYE"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <!-- Keep the call open for a while in case the 200 is lost to be --> + <!-- able to retransmit it if we receive the BYE again. --> + <timewait milliseconds="4000"/> + + + <!-- definition of the response time repartition table (unit is ms) --> + <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/> + + <!-- definition of the call length repartition table (unit is ms) --> + <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/> + +</scenario> + diff --git a/test/sippxml/test_2.xml b/test/sippxml/test_2.xml new file mode 100644 index 0000000000..fa3b9c129e --- /dev/null +++ b/test/sippxml/test_2.xml @@ -0,0 +1,161 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> +<!DOCTYPE scenario SYSTEM "sipp.dtd"> + +<!-- This program is free software; you can redistribute it and/or --> +<!-- modify it under the terms of the GNU General Public License as --> +<!-- published by the Free Software Foundation; either version 2 of the --> +<!-- License, or (at your option) any later version. --> +<!-- --> +<!-- This program is distributed in the hope that it will be useful, --> +<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of --> +<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --> +<!-- GNU General Public License for more details. --> +<!-- --> +<!-- You should have received a copy of the GNU General Public License --> +<!-- along with this program; if not, write to the --> +<!-- Free Software Foundation, Inc., --> +<!-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA --> +<!-- --> +<!-- Sipp default 'uac' scenario. --> +<!-- --> + +<scenario name="Two simultaneous outgoing call Sipstone UAC"> + <!-- In client mode (sipp placing calls), the Call-ID MUST be --> + <!-- generated by sipp. To do so, use [call_id] keyword. --> + <send retrans="500"> + <![CDATA[ + + INVITE sip:[service]@[remote_ip]:[remote_port] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number] + To: sut <sip:[service]@[remote_ip]:[remote_port]> + Call-ID: [call_id] + CSeq: 1 INVITE + Contact: sip:sipp@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Performance Test + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv response="100" optional="true"> + </recv> + + <recv response="180" optional="true"> + </recv> + + <recv response="183" optional="true"> + </recv> + + <!-- By adding rrs="true" (Record Route Sets), the route sets --> + <!-- are saved and used for following messages sent. Useful to test --> + <!-- against stateful SIP proxies/B2BUAs. --> + <recv response="200" rtd="true"> + </recv> + + <!-- Packet lost can be simulated in any send/recv message by --> + <!-- by adding the 'lost = "10"'. Value can be [1-100] percent. --> + <send> + <![CDATA[ + + ACK sip:[service]@[remote_ip]:[remote_port] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number] + To: sut <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param] + Call-ID: [call_id] + CSeq: 1 ACK + Contact: sip:sipp@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Performance Test + Content-Length: 0 + + ]]> + </send> + + <!-- Here is where the call is put on hold --> + <recv request="INVITE" crlf="true"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 100 Trying + [last_Via:] + [last_From:] + [last_To:];tag=[pid]SIPpTag01[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[pid]SIPpTag01[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv request="ACK" + rtd="true" + crlf="true"> + </recv> + + <!-- The 'crlf' option inserts a blank line in the statistics report. --> + <send retrans="500"> + <![CDATA[ + + BYE sip:[service]@[remote_ip]:[remote_port] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number] + To: sut <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param] + Call-ID: [call_id] + CSeq: 2 BYE + Contact: sip:sipp@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Performance Test + Content-Length: 0 + + ]]> + </send> + + <recv response="200" crlf="true"> + </recv> + + <!-- definition of the response time repartition table (unit is ms) --> + <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/> + + <!-- definition of the call length repartition table (unit is ms) --> + <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/> + +</scenario> + diff --git a/test/sippxml/test_3.xml b/test/sippxml/test_3.xml new file mode 100644 index 0000000000..bae7eaa2bc --- /dev/null +++ b/test/sippxml/test_3.xml @@ -0,0 +1,187 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE scenario SYSTEM "sipp.dtd"> + + +<scenario name="UAS HOLD/OFFHOLD"> + + <!-- Receive a new call --> + + <recv request="INVITE" crlf="true"> + <action> + <ereg regexp="sendrecv" search_in="body" check_it="true" assign_to="1"/> + <log message="Media is [$1]"/> + </action> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 180 Ringing + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> +</send> + + <recv request="ACK" optional="true" rtd="true" crlf="true"> + </recv> + + <!-- This call is now on HOLD: sendonly tell to PBX to send music on hold--> + + <recv request="INVITE" crlf="true"> + <action> + <ereg regexp="sendonly" search_in="body" check_it="true" assign_to="2"/> + <log message="Media is [$2]"/> + </action> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 180 Ringing + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv request="ACK" optional="true" rtd="true" crlf="true"> + </recv> + + <!-- OFFHOLD this call --> + + <recv request="INVITE" crlf="true"> + <action> + <ereg regexp="sendrecv" search_in="body" check_it="true" assign_to="3"/> + <log message="Media is [$3]"/> + </action> + </recv> + + + <send> + <![CDATA[ + + SIP/2.0 180 Ringing + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv request="ACK" optional="true" rtd="true" crlf="true"> + </recv> + + <!-- Hangup this call --> + + <recv request="BYE"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <pause milliseconds="4000"/> + + + <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/> + + <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/> + +</scenario> diff --git a/test/sippxml/test_4.xml b/test/sippxml/test_4.xml new file mode 100644 index 0000000000..d241bfe3b6 --- /dev/null +++ b/test/sippxml/test_4.xml @@ -0,0 +1,122 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> +<!DOCTYPE scenario SYSTEM "sipp.dtd"> + +<!-- This program is free software; you can redistribute it and/or --> +<!-- modify it under the terms of the GNU General Public License as --> +<!-- published by the Free Software Foundation; either version 2 of the --> +<!-- License, or (at your option) any later version. --> +<!-- --> +<!-- This program is distributed in the hope that it will be useful, --> +<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of --> +<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --> +<!-- GNU General Public License for more details. --> +<!-- --> +<!-- You should have received a copy of the GNU General Public License --> +<!-- along with this program; if not, write to the --> +<!-- Free Software Foundation, Inc., --> +<!-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA --> +<!-- --> +<!-- Sipp default 'uac' scenario. --> +<!-- --> + +<scenario name="Basic Sipstone UAC"> + <!-- In client mode (sipp placing calls), the Call-ID MUST be --> + <!-- generated by sipp. To do so, use [call_id] keyword. -- + <send retrans="500"> + <![CDATA[ + + INVITE sip:[service]@[remote_ip]:[remote_port] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number] + To: sut <sip:[service]@[remote_ip]:[remote_port]> + Call-ID: [call_id] + CSeq: 1 INVITE + Contact: sip:sipp@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Performance Test + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv response="100" + optional="true"> + </recv> + + <recv response="180" optional="true"> + </recv> + + <recv response="183" optional="true"> + </recv> + + <!-- By adding rrs="true" (Record Route Sets), the route sets --> + <!-- are saved and used for following messages sent. Useful to test --> + <!-- against stateful SIP proxies/B2BUAs. --> + <recv response="200" rtd="true"> + <action> + <ereg regexp="a=rtpmap:0 PCMU/8000" search_in="body" check_it="true" assign_to="1" /> + <log message="Custom header is [$1]"/> + </action> + </recv> + + <!-- Packet lost can be simulated in any send/recv message by --> + <!-- by adding the 'lost = "10"'. Value can be [1-100] percent. --> + <send> + <![CDATA[ + + ACK sip:[service]@[remote_ip]:[remote_port] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number] + To: sut <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param] + Call-ID: [call_id] + CSeq: 1 ACK + Contact: sip:sipp@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Performance Test + Content-Length: 0 + + ]]> + </send> + + <!-- This delay can be customized by the -d command-line option --> + <!-- or by adding a 'milliseconds = "value"' option here. --> + <pause/> + + <!-- The 'crlf' option inserts a blank line in the statistics report. --> + <send retrans="500"> + <![CDATA[ + + BYE sip:[service]@[remote_ip]:[remote_port] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[pid]SIPpTag00[call_number] + To: sut <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param] + Call-ID: [call_id] + CSeq: 2 BYE + Contact: sip:sipp@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Performance Test + Content-Length: 0 + + ]]> + </send> + + <recv response="200" crlf="true"> + </recv> + + <!-- definition of the response time repartition table (unit is ms) --> + <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/> + + <!-- definition of the call length repartition table (unit is ms) --> + <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/> + +</scenario> + diff --git a/test/siptest.cpp b/test/siptest.cpp new file mode 100644 index 0000000000..09df660ae6 --- /dev/null +++ b/test/siptest.cpp @@ -0,0 +1,441 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +/* + * ebail - 2015/02/18 + * unit test is deprecated + * this test is disabled for the moment + * */ +#if 0 + +#include <unistd.h> +#include <cstdlib> +#include <cstdio> +#include <iostream> +#include <fstream> + +#include <pthread.h> +#include <string> + +#include "siptest.h" +#include "manager.h" +#include "sip/sipvoiplink.h" +#include "sip/sip_utils.h" +#include "sip/sipcall.h" + +// anonymous namespace +namespace { +pthread_mutex_t count_mutex; +pthread_cond_t count_nb_thread; +int counter = 0; +} + +namespace ring { namespace test { + +void *sippThreadWithCount(void *str) +{ + pthread_mutex_lock(&count_mutex); + counter++; + pthread_mutex_unlock(&count_mutex); + + std::string *command = (std::string *)(str); + + std::cout << "SIPTest: " << command << std::endl; + + // Set up the sipp instance in this thread in order to catch return value + // 0: All calls were successful + // 1: At least one call failed + // 97: exit on internal command. Calls may have been processed + // 99: Normal exit without calls processed + // -1: Fatal error + // -2: Fatal error binding a socket + int i = system(command->c_str()); + + CPPUNIT_ASSERT(i); + + pthread_mutex_lock(&count_mutex); + counter--; + + if (counter == 0) + pthread_cond_signal(&count_nb_thread); + + pthread_mutex_unlock(&count_mutex); + + pthread_exit(NULL); +} + + +void *sippThread(void *str) +{ + std::string *command = static_cast<std::string *>(str); + std::cout << "SIPTest: " << command << std::endl; + + // Set up the sipp instance in this thread in order to catch return value + // 0: All calls were successful + // 1: At least one call failed + // 97: exit on internal command. Calls may have been processed + // 99: Normal exit without calls processed + // -1: Fatal error + // -2: Fatal error binding a socket + int i = system(command->c_str()); + + std::stringstream output; + output << i; + + std::cout << "SIPTest: Command executed by system returned: " << output.str() << std::endl; + pthread_exit(NULL); +} + + +void SIPTest::setUp() +{ + pthread_mutex_lock(&count_mutex); + counter = 0; + pthread_mutex_unlock(&count_mutex); +} + +void SIPTest::tearDown() +{ + // in order to stop any currently running threads + std::cout << "SIPTest: Clean all remaining sipp instances" << std::endl; + int ret = system("killall sipp"); + + if (!ret) + std::cout << "SIPTest: Error from system call, killall sipp" << std::endl; +} + + +void SIPTest::testSimpleOutgoingIpCall() +{ + pthread_t thethread; + + // command to be executed by the thread, user agent server waiting for a call + std::string command("sipp -sn uas -i 127.0.0.1 -p 5068 -m 1 -bg"); + + int rc = pthread_create(&thethread, NULL, sippThread, &command); + + if (rc) + std::cout << "SIPTest: RING_ERR; return code from pthread_create()" << std::endl; + + std::string testaccount("IP2IP"); + std::string testcallid("callid1234"); + std::string testcallnumber("sip:test@127.0.0.1:5068"); + + CPPUNIT_ASSERT(!Manager::instance().hasCurrentCall()); + + // start a new call sending INVITE message to sipp instance + Manager::instance().outgoingCall(testaccount, testcallid, testcallnumber); + + // must sleep here until receiving 180 and 200 message from peer + sleep(2); + + CPPUNIT_ASSERT(Manager::instance().hasCurrentCall()); + CPPUNIT_ASSERT(Manager::instance().getCurrentCallId() == testcallid); + + Manager::instance().hangupCall(testcallid); + + rc = pthread_join(thethread, NULL); + + if (rc) + std::cout << "SIPTest: RING_ERR; return code from pthread_join(): " << rc << std::endl; + else + std::cout << "SIPTest: completed join with thread" << std::endl; +} + + +void SIPTest::testSimpleIncomingIpCall() +{ + pthread_t thethread; + + // command to be executed by the thread, user agent client which initiate a call and hangup + std::string command("sipp -sn uac 127.0.0.1 -i 127.0.0.1 -p 5062 -m 1i -bg"); + + int rc = pthread_create(&thethread, NULL, sippThread, &command); + + if (rc) + std::cout << "SIPTest: RING_ERR; return code from pthread_create()" << std::endl; + + // sleep a while to make sure that sipp insdtance is initialized and dring received + // the incoming invite. + sleep(2); + + CPPUNIT_ASSERT(Manager::instance().callFactory.callCount<SIPCall>() == 1); + + // Answer this call + const auto& calls = Manager::instance().callFactory.getAllCalls<SIPCall>(); + const auto call = *calls.cbegin(); + CPPUNIT_ASSERT(Manager::instance().answerCall(call->getCallId())); + + sleep(1); + + rc = pthread_join(thethread, NULL); + + if (rc) + std::cout << "SIPTest: RING_ERR; return code from pthread_join(): " << rc << std::endl; + else + std::cout << "SIPTest: completed join with thread" << std::endl; +} + + +void SIPTest::testTwoOutgoingIpCall() +{ + // This scenario expect to be put on hold before hangup + std::string firstCallCommand("sipp -sf tools/sippxml/test_1.xml -i 127.0.0.1 -p 5062 -m 1"); + + // The second call uses the default user agent scenario + std::string secondCallCommand("sipp -sn uas -i 127.0.0.1 -p 5064 -m 1"); + + pthread_t firstCallThread; + int rc = pthread_create(&firstCallThread, NULL, sippThread, &firstCallCommand); + + if (rc) + std::cout << "SIPTest: RING_ERR; return code from pthread_create()" << std::endl; + + pthread_t secondCallThread; + rc = pthread_create(&secondCallThread, NULL, sippThread, &secondCallCommand); + + if (rc) + std::cout << "SIPTest: RING_ERR; return code from pthread_create()" << std::endl; + + sleep(1); + + std::string testAccount("IP2IP"); + + std::string firstCallID("callid1234"); + std::string firstCallNumber("sip:test@127.0.0.1:5062"); + + std::string secondCallID("callid2345"); + std::string secondCallNumber("sip:test@127.0.0.1:5064"); + + CPPUNIT_ASSERT(!Manager::instance().hasCurrentCall()); + + // start a new call sending INVITE message to sipp instance + // this call should be put on hold when making the second call + Manager::instance().outgoingCall(testAccount, firstCallID, firstCallNumber); + + // must sleep here until receiving 180 and 200 message from peer + sleep(1); + + Manager::instance().outgoingCall(testAccount, secondCallID, secondCallNumber); + + sleep(1); + + Manager::instance().hangupCall(firstCallID); + + rc = pthread_join(firstCallThread, NULL); + + if (rc) + std::cout << "SIPTest: RING_ERR; return code from pthread_join(): " << rc << std::endl; + + std::cout << "SIPTest: completed join with thread" << std::endl; + + Manager::instance().hangupCall(secondCallID); + + rc = pthread_join(secondCallThread, NULL); + + if (rc) + std::cout << "SIPTest: RING_ERR; return code from pthread_join(): " << rc << std::endl; + else + std::cout << "SIPTest: completed join with thread" << std::endl; +} + +void SIPTest::testTwoIncomingIpCall() +{ + pthread_mutex_init(&count_mutex, NULL); + pthread_cond_init(&count_nb_thread, NULL); + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + // the first call is supposed to be put on hold when answering teh second incoming call + std::string firstCallCommand("sipp -sf tools/sippxml/test_2.xml 127.0.0.1 -i 127.0.0.1 -p 5064 -m 1 > testfile1.txt -bg"); + + // command to be executed by the thread, user agent client which initiate a call and hangup + std::string secondCallCommand("sipp -sn uac 127.0.0.1 -i 127.0.0.1 -p 5062 -m 1 -d 250 > testfile2.txt -bg"); + + pthread_t firstCallThread; + int rc = pthread_create(&firstCallThread, &attr, sippThreadWithCount, &firstCallCommand); + + if (rc) + std::cout << "SIPTest: RING_ERR; return code from pthread_create()" << std::endl; + + // sleep a while to make sure that sipp insdtance is initialized and dring received + // the incoming invite. + sleep(1); + + CPPUNIT_ASSERT(Manager::instance().callFactory.callCount<SIPCall>() == 1); + + // Answer this call + auto calls = Manager::instance().callFactory.getCallIDs<SIPCall>(); + auto iterCallId = calls.cbegin(); + const auto& firstCallId = *iterCallId; + CPPUNIT_ASSERT(Manager::instance().answerCall(firstCallId)); + + sleep(1); + pthread_t secondCallThread; + rc = pthread_create(&secondCallThread, &attr, sippThread, &secondCallCommand); + + if (rc) + std::cout << "SIPTest: Error; return code from pthread_create()" << std::endl; + + sleep(1); + + CPPUNIT_ASSERT(Manager::instance().callFactory.callCount<SIPCall>() == 2); + calls = Manager::instance().callFactory.getCallIDs<SIPCall>(); + iterCallId = calls.cbegin(); + if (*iterCallId == firstCallId) + ++iterCallId; + + std::string secondCallID(*iterCallId); + + CPPUNIT_ASSERT(Manager::instance().answerCall(secondCallID)); + + sleep(2); + + pthread_mutex_lock(&count_mutex); + + while (counter > 0) + pthread_cond_wait(&count_nb_thread, &count_mutex); + + pthread_mutex_unlock(&count_mutex); + + pthread_mutex_destroy(&count_mutex); + pthread_cond_destroy(&count_nb_thread); +} + + +void SIPTest::testHoldIpCall() +{ + std::string callCommand("sipp -sf tools/sippxml/test_3.xml -i 127.0.0.1 -p 5062 -m 1 -bg"); + + pthread_t callThread; + int rc = pthread_create(&callThread, NULL, sippThread, (void *)(&callCommand)); + + if (rc) + std::cout << "SIPTest: RING_ERR; return code from pthread_create(): " << rc << std::endl; + else + std::cout << "SIPTest: completed thread creation" << std::endl; + + + std::string testAccount("IP2IP"); + + std::string testCallID("callid1234"); + std::string testCallNumber("sip:test@127.0.0.1:5062"); + + Manager::instance().outgoingCall(testAccount, testCallID, testCallNumber); + + sleep(1); + + Manager::instance().onHoldCall(testCallID); + + sleep(1); + + Manager::instance().offHoldCall(testCallID); + + sleep(1); + + Manager::instance().hangupCall(testCallID); +} + +void SIPTest::testSIPURI() +{ + std::string foo("<sip:17771234567@callcentric.com>"); + sip_utils::stripSipUriPrefix(foo); + CPPUNIT_ASSERT(foo == "17771234567"); +} + +void SIPTest::testParseDisplayName() +{ + // 1st element is input, 2nd is expected output + const char *test_set[][2] = { + {"\nFrom: \"A. G. Bell\" <sip:agb@bell-telephone.com> ;tag=a48s", "A. G. Bell"}, + {"\nFrom: \"A. G. Bell2\" <sip:agb@bell-telephone.com> ;tag=a48s\r\nOtherLine: \"bla\"\r\n", "A. G. Bell2"}, + {"\nf: Anonymous <sip:c8oqz84zk7z@privacy.org>;tag=hyh8", "Anonymous"}, + {"\nFrom: \"Alejandro Perez\" <sip:1111@10.0.0.1>;tag=3a7516a63bdbo0", "Alejandro Perez"}, + {"\nFrom: \"Malformed <sip:1111@10.0.0.1>;tag=3a6a63bdbo0", ""}, + {"\nTo: <sip:1955@10.0.0.1>;tag=as6fbade41", ""}, + {"\nFrom: \"1000\" <sip:1000@sip.example.es>;tag=as775338f3", "1000"}, + {"\nFrom: 1111_9532323 <sip:1111_9532323@sip.example.es>;tag=caa3a61", "1111_9532323"}, + {"\nFrom: \"4444_953111111\" <sip:4444_111111@sip.example.es>;tag=2b00632co0", "4444_953111111"}, + {"\nFrom: <sip:6926666@4.4.4.4>;tag=4421-D9700", ""}, + {"\nFrom: <sip:pinger@sipwise.local>;tag=01f516a4", ""}, + {"\nFrom: sip:pinger@sipwise.local;tag=01f516a4", ""}, + {"\nFrom: ", ""}, + {"\nFrom: \"\xb1""Alejandro P\xc3\xa9rez\" <sip:1111@10.0.0.1>;tag=3a7516a63bdbo0", "\xef\xbf\xbd""Alejandro P\xc3\xa9rez"}, + {"\nFrom: \"Alejandro P\xc3\xa9rez\" <sip:1111@10.0.0.1>;tag=3a7516a63bdbo0", "Alejandro P\xc3\xa9rez"}, + {"\nFrom: sip:+1212555@server.example.com;tag=887s", ""}}; + + for (const auto &t : test_set) { + const std::string str(sip_utils::parseDisplayName(t[0])); + CPPUNIT_ASSERT_MESSAGE(std::string("\"") + str + "\" should be \"" + + t[1] + "\", input on next line: " + t[0], + str == t[1]); + } +} + +void SIPTest::testIncomingIpCallSdp() +{ + // command to be executed by the thread, user agent client which initiate a call and hangup + std::string command("sipp -sf tools/sippxml/test_4.xml 127.0.0.1 -i 127.0.0.1 -p 5062 -m 1i -bg"); + + pthread_t thethread; + int rc = pthread_create(&thethread, NULL, sippThread, (void *)(&command)); + + if (rc) + std::cout << "SIPTest: RING_ERR; return code from pthread_create()" << std::endl; + + // sleep a while to make sure that sipp insdtance is initialized and dring received + // the incoming invite. + sleep(2); + + CPPUNIT_ASSERT(Manager::instance().callFactory.callCount<SIPCall>() == 1); + + // Answer this call + const auto& calls = Manager::instance().callFactory.getAllCalls<SIPCall>(); + + // TODO: hmmm, should IP2IP call be stored in call list.... + CPPUNIT_ASSERT(Manager::instance().getCallList().empty()); + + // Answer this call + CPPUNIT_ASSERT(Manager::instance().answerCall((*calls.cbegin())->getCallId())); + + sleep(1); + + rc = pthread_join(thethread, NULL); + + if (rc) + std::cout << "SIPTest: RING_ERR; return code from pthread_join(): " << rc << std::endl; + else + std::cout << "SIPTest: completed join with thread" << std::endl; +} + +}} // namespace ring::test +#endif diff --git a/test/siptest.h b/test/siptest.h new file mode 100644 index 0000000000..82ccef51b8 --- /dev/null +++ b/test/siptest.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +// Cppunit import +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/TestCaller.h> +#include <cppunit/TestCase.h> +#include <cppunit/TestSuite.h> + +// Application import +#include "manager.h" + +/* + * @file siptest.h + * @brief Regroups unitary tests related to the SIP module + */ + +#ifndef _SIP_TEST_ +#define _SIP_TEST_ + +namespace ring { namespace test { + +class SIPTest : public CppUnit::TestCase { + + /** + * Use cppunit library macros to add unit test the factory + */ + CPPUNIT_TEST_SUITE(SIPTest); + CPPUNIT_TEST ( testSimpleOutgoingIpCall ); + CPPUNIT_TEST ( testParseDisplayName ); + // CPPUNIT_TEST ( testSimpleIncomingIpCall ); + // CPPUNIT_TEST ( testTwoOutgoingIpCall ); + // CPPUNIT_TEST ( testTwoIncomingIpCall ); + // CPPUNIT_TEST ( testHoldIpCall); + // CPPUNIT_TEST ( testIncomingIpCallSdp ); + CPPUNIT_TEST_SUITE_END(); + + public: + SIPTest() : CppUnit::TestCase("SIP module Tests") {} + + /* + * Code factoring - Common resources can be initialized here. + * This method is called by unitcpp before each test + */ + void setUp(); + + /* + * Code factoring - Common resources can be released here. + * This method is called by unitcpp after each test + */ + void tearDown(); + + + void testSimpleOutgoingIpCall(void); + + void testSimpleIncomingIpCall(void); + + void testTwoOutgoingIpCall(void); + + void testTwoIncomingIpCall(void); + + void testHoldIpCall(void); + + void testIncomingIpCallSdp(void); + + void testSIPURI(); + + void testParseDisplayName(); +}; + +/* Register our test module */ +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(SIPTest, "SIPTest"); +CPPUNIT_TEST_SUITE_REGISTRATION(SIPTest); + +}} // namespace ring::test + +#endif diff --git a/test/test_utils.h b/test/test_utils.h new file mode 100644 index 0000000000..5fd9d9f4d6 --- /dev/null +++ b/test/test_utils.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Tristan Matthews <tristan.matthews@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +#ifndef TEST_UTILS_H_ +#define TEST_UTILS_H_ + +#include "logger.h" + +#define TITLE() RING_DBG("Starting test..."); fflush(stderr) + +#endif // TEST_UTILS_H_ diff --git a/test/tlsSample/ca.crt b/test/tlsSample/ca.crt new file mode 100644 index 0000000000..5f5b490606 --- /dev/null +++ b/test/tlsSample/ca.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDtzCCAm+gAwIBAgIEU3EhUTANBgkqhkiG9w0BAQsFADBLMQswCQYDVQQGEwJD +QTEYMBYGA1UEChMPU2F2b2lyRmFpcmVUZXN0MREwDwYDVQQHEwhNb250cmVhbDEP +MA0GA1UECBMGUXVlYmVjMB4XDTE0MDUxMjE5MzAyOVoXDTE1MDUxMjE5MzAzM1ow +SzELMAkGA1UEBhMCQ0ExGDAWBgNVBAoTD1Nhdm9pckZhaXJlVGVzdDERMA8GA1UE +BxMITW9udHJlYWwxDzANBgNVBAgTBlF1ZWJlYzCCAVIwDQYJKoZIhvcNAQEBBQAD +ggE/ADCCAToCggExAKOMxKvsloBIWBNVTvMXTnvmXoCvSJpH0FNjJnFySUY9x7ld +p/oWdVU7SJQn1u1nKwuP27NWLX3ICRZo2ziRbBNfI0DaUcNX+sg/G2Ftzr5Z86oP +n4LOTuAaRrAHELTJafGnFdl7v5t3OAElRy7ICkQhmqdPJndvOaAREmyZXIkbn+bF +zO369/UBRF26Z9sYwwV65bZZ6eHw2wTuHXyDLKA+Qcj7HMhE4Zu1ySFtjDZYo6OD +ygPZDyOiftm+o8noZyMaLI+r3KY0W14QnGnLSSLVE+CbVErBU+lZ7LjODpn8bsa7 +hOV74r0xRux2VrWfDpIVulpm+gDxKQK1eTR7ENJKPBsfcDCblflVZHoY87TYckWW +98zGRdehSSoJhJotexIecrybZW6T3QycsbeALnECAwEAAaNDMEEwDwYDVR0TAQH/ +BAUwAwEB/zAPBgNVHQ8BAf8EBQMDBwQAMB0GA1UdDgQWBBQeTMJ8HtoXRp316pus +7AMDEDq8CDANBgkqhkiG9w0BAQsFAAOCATEAWRKyaKLC7VRVshITdvsMoleV+5ek +xH3riJmw/x1Peosu2ijehEUc+cHFRA0/wFyY93YzsmVyR6lrdL3f8KOD+k6u/s0z +hhAoYoVeZTwZ7wGN2yz8oVUcM/NlVA//DTqU/RYjm19cLKR4YIWglbPmWHNh/rI8 +vOi2IbhubsXXCgi0lL3G7Lz8//sEkxlhh3y9j1dmygtIL8rX8GlJsLZPbGELQrQF +8mHuIfMMOFMXqV9Os2gStKYpNh/to2fdE5elxGjegluDJ6wXH3Wsoth35j7woaRj +HZ2SovMUz7IPklsk1QNaRb08LxJUHVpJffsRIpJMvUFl7nxWIT/ZEaV3aY4lc0ks +PL0i952R2ddVR7O0g7hkuYT72lVE+Vw9+HrEdWDkOSnBxvKSTn7zjzmnGA== +-----END CERTIFICATE----- diff --git a/test/tlsSample/cert.crt b/test/tlsSample/cert.crt new file mode 100644 index 0000000000..7697f26082 --- /dev/null +++ b/test/tlsSample/cert.crt @@ -0,0 +1,23 @@ +-----BEGIN CERTIFICATE----- +MIIDyzCCAoOgAwIBAgIEU3EusDANBgkqhkiG9w0BAQsFADBLMQswCQYDVQQGEwJD +QTEYMBYGA1UEChMPU2F2b2lyRmFpcmVUZXN0MREwDwYDVQQHEwhNb250cmVhbDEP +MA0GA1UECBMGUXVlYmVjMB4XDTE0MDUxMjIwMjcyOFoXDTE1MDUxMjIwMjczMVow +UjELMAkGA1UEBhMCQ0ExHzAdBgNVBAoTFlNhdm9pckZhaXJlQ2VydGlmaWNhdGUx +ETAPBgNVBAcTCE1vbnRyZWFsMQ8wDQYDVQQIEwZRdWViZWMwggFSMA0GCSqGSIb3 +DQEBAQUAA4IBPwAwggE6AoIBMQCjjMSr7JaASFgTVU7zF0575l6Ar0iaR9BTYyZx +cklGPce5Xaf6FnVVO0iUJ9btZysLj9uzVi19yAkWaNs4kWwTXyNA2lHDV/rIPxth +bc6+WfOqD5+Czk7gGkawBxC0yWnxpxXZe7+bdzgBJUcuyApEIZqnTyZ3bzmgERJs +mVyJG5/mxczt+vf1AURdumfbGMMFeuW2Wenh8NsE7h18gyygPkHI+xzIROGbtckh +bYw2WKOjg8oD2Q8jon7ZvqPJ6GcjGiyPq9ymNFteEJxpy0ki1RPgm1RKwVPpWey4 +zg6Z/G7Gu4Tle+K9MUbsdla1nw6SFbpaZvoA8SkCtXk0exDSSjwbH3Awm5X5VWR6 +GPO02HJFlvfMxkXXoUkqCYSaLXsSHnK8m2Vuk90MnLG3gC5xAgMBAAGjUDBOMAwG +A1UdEwEB/wQCMAAwHQYDVR0OBBYEFB5Mwnwe2hdGnfXqm6zsAwMQOrwIMB8GA1Ud +IwQYMBaAFB5Mwnwe2hdGnfXqm6zsAwMQOrwIMA0GCSqGSIb3DQEBCwUAA4IBMQAP +W4x5JU7vBBWgJdpqy5YMr9yu9HICwrUpUC4uq7GmhFs91mJpSAu1MIb9c1Iy4XBX +cJV65R30NIJY+tBxxss+DP458UwSjBHRs2aFsBMkJOpzXmjlUl3QifoLCbLtPYv0 +Q741zCU5k9c2qzSNoEfxoKugP2J31aVNsfl4NMxuC7xsrXGtuJK3yp8MIIXdFTq8 +lTrSZ1nltQxdU/Tg8nlKT1ikzew9cI6BmPyWXzHoVaP6PJruYUZsVyGX+408JFvO +tqyY6XiSn4jpTgDkz743youZTdB85qlnc2AIGOTWnmwEjqjR13GpuQTY/i0c6C8Z +R1rSKjmCujH2rbnPnk7qRt7qSgsyC/lm3NLC7BtAeLxqCyOtbjhfsbIadHALoAlh +85PWqpJyTXqvuihJFdu9 +-----END CERTIFICATE----- diff --git a/test/tlsSample/certwithkey.pem b/test/tlsSample/certwithkey.pem new file mode 100644 index 0000000000..6dc6e28ce1 --- /dev/null +++ b/test/tlsSample/certwithkey.pem @@ -0,0 +1,35 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQCpHOrS9GBJFtH0hhT0/vM2Bh1vAiB3d4L4E4u9IXphzg/F4K/d +0MvgxWrQY38HRQtqj+CfczWqCpVivQsQCVvSB2YVckbgotFF6j1qdP3wzGex64WY +TEiZabJSCWGAnUtWCgvhCBRL0PzuaDfMAttN5qRNYZsgqWT1KhiyECCHCwIDAQAB +AoGBAI/w4xbnlkTfvZkpvxDBKHYlQkxIPSYyfTF2QYybwDUbVWWR118v5zjMEByL +c4XWiN8M054kS9sN4xUF3zKpJJMABaDVs+Mr7tDF8l+j767L730DYdAX8yH37nrK +H+myW7hvBwuYjssEiybgPpVfiVd48DiN9K/aIbr74tLSvYuRAkEA1p20d+gmeUJS +eYbpOusYGHAVBwM7xuWyrwyEcewzXxgZRhVzUTcP4T2Uk7iQRMqW/wDTaEpxGVtb +DFh/eot0dQJBAMm5ADFfvfQ0DVkSodZCc9UCQ80eTtrvL+xnGdEpKDARWwYbHM6P +wBx5VIJelj4mMmkfUPjqMHs47k3R5luunX8CQHkrFr3wAvDJQhk80ychnjwF48lO +yQwVmVl9XrWXHrXcvEA2UiITTVLk2qLAPCuqma+lPraN+ObRDkmdGXQNkhUCQBwg +cYhw4xmT0HClCm/HoEMJJ88H9NLOGw6Jaa4iYPoi0WBOk3uxy4Ws3T24Vpaf2NOT +jETI0q27yG6NX+NUu50CQGYj8frp0aDMO7Fxnz4dODuBeVpiT5UH+YcaEaan4plJ +Sgn2eo7jHktyEXVGmmemIJbpPiJmQT549RcX+M9c4iQ= +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDUzCCATsCAQEwDQYJKoZIhvcNAQEFBQAwOjEcMBoGA1UEAxMTQXN0ZXJpc2sg +UHJpdmF0ZSBDQTEaMBgGA1UEChMRQXN0ZXJpc2sgdGVzdCBQQlgwHhcNMTQwMzA1 +MTk0MTU2WhcNMTUwMzA1MTk0MTU2WjApMQ0wCwYDVQQDEwRwY2FsMRgwFgYDVQQK +Ew9Bc3RlcmlzayBTZXJ2ZXIwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKkc +6tL0YEkW0fSGFPT+8zYGHW8CIHd3gvgTi70hemHOD8Xgr93Qy+DFatBjfwdFC2qP +4J9zNaoKlWK9CxAJW9IHZhVyRuCi0UXqPWp0/fDMZ7HrhZhMSJlpslIJYYCdS1YK +C+EIFEvQ/O5oN8wC203mpE1hmyCpZPUqGLIQIIcLAgMBAAEwDQYJKoZIhvcNAQEF +BQADggIBACBcy6n+S2SIOdCTiMr7A7xcHQB2pTHVgnp0twqxWyYmun3k+uIyTweE +IKCaZXvnz0+kW5Oo6dDcTZaXZkRhLdDWCXYcWGE2fDA16ZwUXUBRpf8t1wrssEud +m+eBP7DQ8sNmUBuTiSZtFqyY4HYN426+ehbocByU7A7iRQ41dXKJJktNAUg5DQwh +bK4sWU4ktIWrQnlSKLe8O+1FSv3WqDgXeDcBhC49zgFEUy9BDUKmfnnCk1cLSmTq +8sGtYVcV8NbD5Cp9+uOf8/rQ3TndPLdS4RQy7QEEdI+ZOuVoMwd05rYQH9sWfQxy +WOTRuXqVOE5SsTy5yyRVI0lQ+71UoykaQeUsS9GNmVEfd0cAVSqI6T/58JEuesm8 +QlDFRtnAmBeqmGtGE62W68W3DjgeO7LlDMmOKtevWAH1pSUK6EgCph4tFOc2Csts +Ckve/t/WLoioEFubpWGNZ2LUe+qp4PcBsySAc3eUjjd8/e47BDn44L1DPfbzWE4i +QphIEwnsRjNqkrCKUYnvNjQtDTng8X/syeRJdX/2m4XICD5+DkawHfNW0nUs5MIk +wQ+DBESGDJRQymj14LVcQObuslZrkcPyCqf2O2hGP1Zvc+6SvQpjsEHUFZVMixli +Qwq9G4UcQDEmzR1ivmELX7tkpUOFdyIDlf6UvaIkSRpV0+EnShUo +-----END CERTIFICATE----- diff --git a/test/tlsSample/corruptedkey.pem b/test/tlsSample/corruptedkey.pem new file mode 100644 index 0000000000..9352ff819b --- /dev/null +++ b/test/tlsSample/corruptedkey.pem @@ -0,0 +1,29 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIFfQIBAAKCATEAo4zEq+yWgEhYE1VO8xdOe+ZegK9ImkfQU2MmcXJJRj3HuV2n ++hZ1VTtIlCfW7WcrC4/bs1YtfcgJFmjbOJFsE18jQNpRw1f6yD8bYW3Ovlnzqg+f +gs5O4BpGsAcQtMlp8acV2Xu/m3c4ASVHLsgKRCGap08md285oBESbJlciRuf5sXM +7fr39QFEXbpn2xjDBXrltlnp4fDbBO4dfIMsoD5ByPscyEThm7XJIW2MNlijo4PK +A9kPI6J+2b6jyehnIxosj6vcpjRbXhCcactJItUT4JtUSsFT6VnsuM4OmfxuxruE +5XvivTFG7HZWtZ8OkhW6Wmb6APEpArV5NHsQ0ko8Gx9wMJuV+VVkehjztNhyRZb3 +zMZF16FJKgmEmi17Eh5yvJtlbpPdDJyxt4AucQIDAQABAoIBMAPbCLPo4u9pRTJH +e5H9haskFixAdZy1frLIjpp0EEgoorG7BH+/0cpNmHttqQJ1wS0Hmi1TJcVmVtiB +ZlGiFAMs5f7cZrsNMutPmYylcw9HvZfPwHnr9S4RUINL11f7u5Sn1LsI0M6Qc/CK +kseDyy2/qcalprz6c4puFLrVMuuen9YvriWVquWv+h4AISwrl+ESU9+QTzaUTSUu +CCwyNXECgZkAyhiZTp/xziLtNfiYQMRZk6OYk2IW5co0t8P1jo+VDaKehFuFhQsx +6hNG6k2n3dA5MeZK4O+n5I0SGkc+MYyAWOtI0q7IiSFUn9PMwZXO91rBDG5VxFpT +srD5m86BQhfmMqpeWQgAXT/ZM1q4me4gtg9PAyJDVHLs5MV49szcH2YAVzWCLJ7e +/o9Xne7jOhUGRVzGpwZ8gP0CgZkAzywuNe+K51nFED+WSHmSw73eqxmFsdm7FHg0 +O9RykMT3vfXx1fMPQU01BbsA0w+NH/lAtgwxSSzHZPAAOYl6iB29BXErbqBpJsOz +1afqXlEAiQcA3uOgPWMWnORcLfdwvze8lndHYvQ6bu85CoPl/mv+PBNp9fJLvxMr +Xr1V8DPYUeWTPr8IOSDQ8qWXO5s/wtqOZxynR4UCgZkAtcV+Tqupz+C2Pu8D8m0o +rJRerWFyeuhulkLY4IrgY806io3PACVbldAU3rVOIUIhz3H1M3AbMIR6HcyqceKP +pmqpq2Wt1Tl3ZfIYnp3h87VbIZUz61HbKkPnnm4U8J+SW0vFZIq/2R+OepREqZ73 +KQmH5aexq4la7UF4VXNZIZya/dWQ6hVTON8wzUqKcjlq1IRn4N8CqPkCgZkAhW6t +LKvlyO/2jmqF/pPexRerLIQ5qbzUU3DAI7DHQRq7favEYMo1jxeNCO3SsB2aBXfi +B95+d+TRX998JZK4SqbssW6cTJogX4k4cGm/95MwRxEtrGDrOd5yGAa0oB54jY/2 +dOrKHZ+zavPEx1MAdQdVLZhLbM7rnltYWaKUkSqzLrwwQ/+B6lmKel3Qs+FgBb4C +bcL0sokCgZkAnKmzmzSLb1Hg2fMVXP+xtQJIwv2/2eDcOXfC+X5qoNK/KxSXF/co +U16nAVY4VwOrYLjUuOh1N8yhwj+7DrTx8F2Q4sWvoXcbBmJv46fJlsVMByuyiWPv +4q3rPEf1eQ6MBj0D9iY+JtvJmtPT0vQQa6O2gl2qFlbmlc8PCSj4hI5d19ePcQlg +ypRMPut1kF4EDNV6vcrnukA= +-----END RSA PRIVATE KEY----- diff --git a/test/tlsSample/expired.crt b/test/tlsSample/expired.crt new file mode 100644 index 0000000000..59e7b84e3b --- /dev/null +++ b/test/tlsSample/expired.crt @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDDTCCAcWgAwIBAgIEU3FIpjANBgkqhkiG9w0BAQsFADAAMB4XDTE0MDUxMjIy +MTgxNVoXDTE0MDUxMzIyMTgxN1owADCCAVIwDQYJKoZIhvcNAQEBBQADggE/ADCC +AToCggExAKOMxKvsloBIWBNVTvMXTnvmXoCvSJpH0FNjJnFySUY9x7ldp/oWdVU7 +SJQn1u1nKwuP27NWLX3ICRZo2ziRbBNfI0DaUcNX+sg/G2Ftzr5Z86oPn4LOTuAa +RrAHELTJafGnFdl7v5t3OAElRy7ICkQhmqdPJndvOaAREmyZXIkbn+bFzO369/UB +RF26Z9sYwwV65bZZ6eHw2wTuHXyDLKA+Qcj7HMhE4Zu1ySFtjDZYo6ODygPZDyOi +ftm+o8noZyMaLI+r3KY0W14QnGnLSSLVE+CbVErBU+lZ7LjODpn8bsa7hOV74r0x +Rux2VrWfDpIVulpm+gDxKQK1eTR7ENJKPBsfcDCblflVZHoY87TYckWW98zGRdeh +SSoJhJotexIecrybZW6T3QycsbeALnECAwEAAaMvMC0wDAYDVR0TAQH/BAIwADAd +BgNVHQ4EFgQUHkzCfB7aF0ad9eqbrOwDAxA6vAgwDQYJKoZIhvcNAQELBQADggEx +AGmzz8Ta1v1MUYzUk1DE03rAG4Sh9Nyr7Jesj8jtWSFho+RzQISKAY7AfayGflmQ +NJndYp0H3B6AvhujRptNRQ0ybI/p4wV0PSVCBT8jGf83d3fCOZOC/45neg9KUVTJ +5Xtap5knIvQRCCfLIrAAUgYgjhOxfCAB56rDyRK6zEJ+lC2AJpweRMtXTGI/CbDl +BS7knAE30h6NChWLIjDFRvrLjupPMArrTOg92DGrdUsZF8OqisO/Eba0j+LE17q+ +jb3Ga3fW828CWJUQZNi1LuQCSRnyS90+yv0cK7oeyp2BAaTVDT0AY+VSMD5v+2Cb +Wv/J5lITKF3ees0x3NNquMcHvYfEqNfqFc1bubYut8isKwM67JVgCnvUPwKEm4fh +E8NXJ7eYiHS1/RCRqA8g/ZY= +-----END CERTIFICATE----- diff --git a/test/tlsSample/fake.crt b/test/tlsSample/fake.crt new file mode 100644 index 0000000000000000000000000000000000000000..c66393ae02dd83a704ee7038ed9fe7b7d79d6c54 GIT binary patch literal 4546 zcmeAS@N?(olHy`uVBq!ia0y~yU@!n-4mJh`hH$2z?F<YIY)RhkE)4%caKYZ?lNlHo zI14-?iy0VruY)k7lg8`{1_lQ95>H=O_Pg9-qS|JMKTgeHU=Z~8ba4#HxVLpy<pi1V z+T-@`zZyQjH~rg%dyA)<Zc_E$q!VekA?2jmjbj3xhY!wR5mu-V==2p`CS#zY*s`dh zfm3avLZ>IkzEh0Nx;Kv<i<mZ1|Ky~!rFSQ8(av0YRqoc+IAd$uyMI3&e|_`trBlaL z*00Lm_58)ZzjgQDzpqiQFxQ^gQnIQ*uR&>nL<G|sR;vc9DOyZCEi;(d8V(<jJ}~=$ zhJmBi({p!%H7)M^Wbah^ksPK}$-IH}M#J;9hravP$m?`_s5qV2Cdf2rkGI-v*QZ4a zDiT6VY+3|%H{56NpX4`}bwRRaiR_R1fCaXk?dfawiT;qf(D2$d(5~~FS(xhdix+P> zRc3PTdp_~x(*Q5QO*w}YXLqjD<de5Px6k?Ro{Kj-i#{dH;&b9*v{#z@>y;(P|BGBA zdD~PpKfHAKAUq{1KT_<e>d`g(OH^NnuT4oZIy|p=ZprsAMW4PX%zR_w^y;?uQUl+~ zu2V&~n44}~l+1aSxAe{HSI>UlU0+m{(ed)73`Zf`4%v#fl2f(nT^;2rHA<O`@r?T# zUQ3<qp0=s(DHo6PrMuNV%r8xx)7=btnKf0NI9g`ZXD=1;(zH2$_;byoiyWtK-QH2* z$#i?0Mad5h{XL&nePh;?XM3kM_f@sE>Je)JJH9<F@8g@M)ciVp+rYOw-uAnV^5bNS z%I`9X_qBdFo&H|Sqp8_<d}q~LDZQ-h$Bpy<njdyPoG?rIfbNoD{a6pq$GI*34AqU> zmi@AN_wTd+hUX6y<!<hqn|b`;_p42Z;ypebN_XNAF?eh7=KZT>^#v2o*L*qbleT$| zlvEc(c|hP_wU;5{ohm&%dzo3%TJxE=J+^rJS}*$WtnTTbk9^+$)a8F>&S|CtUu~8M zcxVQCX$K4VWqPK^J-7X0^F_$%!7jav9#htu=cFYSbsgXKo<Y%w<D=oFmB-Jww?sJn z;3$wdzVh~_k5bdNZvQyh{eSzM$;&^yd6_e9t9H17jg{B^myEi5-apoCegFHpXw5z6 zRtL6yMRp?jd!A`Yy%ziU_x}(5376Q-Ci(6C5T^PpI3tBa$dmI8%d~ZIg@&sqd%mmx zkghO8aG!<T<M@B??Dl;({~iz)b0%2-U5eH8!=L~Dyt2_rAyr|M#sj1CpXwgmDHaX% zD%@c08=A^`db-MsS1&~m{9d`@pXJLdH-6SCOl;}OVyqEdY8MwAdGgrT*FRqB&zo`Z zWZ@@~qe(U&5B%Me{#I()rpRgPw<IN{A5Xozz9}<HcWP6C$hBDRg&X#*Tfubx>ipka zOJ=kOI8J0bx+JmS$qml1m@AFBQD44)cb~9nX4k9ZD^`5t{+DoObDe9c@&|!+*|p!2 zw|_iRygxOkNNA~u<?)Biv&{>?c0E0!oOo;2tPfKUeXd=%_m9!HlI500MtuADT~#j> z8cqp4a3k`BkAtu6kuIixsR=i=BrGhsU*9R<KJno1yLoYW`@f0>x%W*vn>JnP$m}J- z?A(63_nr2>f3)i&|DNw`-{fqLYz$tW_UQX37wh}~FWKJty3+dR(Y|yGTazW44}Mq7 zJI?=Z_w-|_rw<w|SjOn1ec%Se^~=}W1Qnh*d$VuRk2dhC+}G|T{qA_b%_;Tme>>fH z<y0n4X<V=5slqLCt08Fnwd>b|L*veE-d$%eoXhi|Z+4!FRA+!lf8lNRd*|=h{oZ|J zn(5*MPn&c0HLo7e`0Bf1h24VP7o4>HFLRgZhJ0wBRK(FL7Qke4-ksy*fd>Zb-`XD# zT^-gKWcXlq>-&?Ia({CbRkQUY&rR0f<8fjemtp^kmaW=)GdF&_`dTmj@U7E7?o8kB zWg_IL+4{ON=Jvfa>2{wK3+^^;J+R4i?VAbD>JIud91_fk6|?wt=%n8)we`!bGFPlr zTq3%!*QH4CWr)K<mm<>*CUU(=5^U+mkEj2faX#78^Y0?55P?*g;}83C<(F89J^i@6 zjA6p*rv*0szt`>Wy6Sx^UDd$XSMu_;^{i5&7YuxT)$i|LAT)U<&&`;=RiBIkRtfEh zR9<ks^v9|lEB8%%crmc3Z?STdfQI>c?NbF6YC#%HXUv%MXq|t3*YmQsi|P{+%xbuH z>{}dqKZ>Dk<;#@FMx|>yy*$E87@tlJh}xHMkKu!D|KGcJ*F;xxR?OqfQJT0Vb;1_2 z2Rqk9ZR>G%?>pzMvu^hB&$Y{z85tiE@0*bOIbHtNvAzF)_;x)qJhbfK^lk5-NbB$6 zdU#=tFnh=mgDp&~rz9Nn6WSHym^S`6<i+9me&gdUre<RAb~0*AbKuz_a(dCSO_MG= zA2#0qu|{51wNg<+Le9#a@&5P7OP9`^jW+xEbN_#<`kyb#>UO*?wb9&K${l9L)7zBE zdVN{hqeI`{7#vBms*26{*~NS}Q}0~_)4b~pS3DMo+|Q4=bm`2(#qOrLTue#}7EaBn z)qW|wT!2yjxz+;<zUg}L=l0uw^Om!p(B=^m9nCgvyK_LS^aV|hrL0k%4veQS*#!kM zm-cdNCwP1-E#16j?bZjz;T{W^?=N4vuu=KAzXJD%Uv{UyE5tQD{`uzRn<HQM6bA@6 zZP@>NL!ZVnQ|%o*H4YrIdP^6wPCcfxcFC3JmoCh-7HVu0i?~zf*Q8zZ|H#Q;lWTff z0lB>_LK?2Q4)dMZN@m|m3}g-oYZln^<r$+|)0!BEgWqdo&L{spC2E!P>)xJr$AxP; zUS?lry3A4J<e+%@%KC{b*3H$4PX7>4VWt>iRy=3YoW}|!7P-?8ercHW$Rhdi;|I?I zOMO?Ys?yMF&X7qe{KnUJskSO$`iXqYQ{G)WKP%r>n0(PPK`rsDXPQCx1Vi<eqUU-Y z1g&{Ow_0tP7MgYR*0fjm*(Mg|6iv#o;0cH>NYc4{<a14i*Me8G7jEL3H9aH4TT4Oc z>Fa)bSCf?U&W#Hcf4<qqe<?1W*-x*b+|R_2C-%h5XEh65A3W&`KC%DSr)_In*XLg| z4OUsFDfL7^{dq#9nnR8n>nf*9k(*@pFN%ux^V0PRG@LSJV|IMk^KbG;c%)|RT)j!- zT<q-iN5r`H{K-lBWUHHUkF%(OW6en&t#e!c^-62Je5NRL`FyXq<5i`I{hU$Ln@pPG zoV#@!IU8Qa@HumZT(^tW-KF<rnd0=T{F+*=1v9Gazexn|yi=((=h3vju)OH0Ek0q| z3M>`()+lmC?g$VUtvNcg?Pca)pQQ;_s~2CtvEteP9~wP8hZ5{H=1<6d{NyvkWFd=A zfjP3$%T45*=UOy*Er0Mv^2OV>9JLooUXD^r{J(s>aPF@0IlJ<m^Nxq-?P&X|`*rh{ z4|{H&Xt^-)<fbj0dk?%Uw$$A+`?^P}n(0%&`B&bTzH6M9r#tz%V87c*AAjaA-U__} zU-)^0gCjfjRb(tVgclbS==aROYI4G_aq`jgyKO$aUj9;E)_VEEFvq`N4qTI7sjWYE z>4Yg!8y3`EDV=yp<EPz|;$I(_t{4X&G-Pz&{pIIO$6EQ*hfI2#8h5E)Xr1z9&WpuM z7s~Bf5ckq*!9qD(X5r+5F9JOLP8Nq&PF}v_`@!}N&w>)qY4y7OxTZUM1F!Oo&mEC2 zEex#T=GQkbS{8FTtwf?v_M(@S<qQk%m(0I5-`nEm$nl|8>h)`jvQHrkWhz6N9(&xp zUHph;ZMXROYf5ztwf6&SFYj5rd)p(S4Y_rnf8VY7`7mCHzx~|p2{AW!bo|cW<8}G) z_k*Plnm*2-&es=O|F61y_0NNAXQ#CK%#dkc$*EhA78IPWu%JKjjIOt*+AWKUCmD{7 z(^U;v_cXr0Y;NG-Tl#gUnylP~*r_5?6O(-V+3UA=ACJFPdv9gaYwf&u8IP)O-}Cm# zO6QAKyT8BGc)N;l3s3X2O-XfI-n!jYZ&(+$Ie3=abKMszxxKwh`oA?yu|2Ti;E4&| zuBFUQIfb_VCfBYNJqr0hXIAWinT)xTnu$3qTG<CD{8)E7`+QFN>9(&^rcRk79Az6n zaZUV@W7pWCPM_YfXrtKsH<Hs#&0n6Mx@hg9DW@V@(&MWl-RI3)_cLt%<wd6YaXbem z-g~U@bbVtxcjbf37K!)rGK;pX;IK~Fzm#p}<rWQAcPm9HcMiw;c=_@_U!ND+zW?J< zAtxJ}dZ+U2)0$78M0-*`mwfwr-eu#O$?31Bz5emx?EB{6<Ii2z6rX;ns_Xm4wt~Y) zS)Qlib5vu=D~1i_TC<N9eh!&7EpEe3flBt>#r$bHZYEL>zg!U5UjI(-)6YMZ?Q6X< zO+=c`?<*I+|Lf@FIcHPSrx^H7u73B2rMGk|x1G6MZ_-S$#k-<qnCh1P{(9)l#zj^9 zUbSgCNt1M?*CpIP==}U`sPAQkl-n(b6qPOqw>TyGh#Xc{{xbK~^H*P|Crcb_IyJlP z;IE~e`@Y>+zauHrdSQY=fK319SBsX~Y<e8|$4N+MP0hU0)%vL)&Pe`ZXLIbxpXisX zKJmy5$r*h-ya9oCC(WG|6&VxbQeA9pYrEIDM#L$BU0PJ}_A$PjEj-Ny7CshI&QCsn znfK=LfrHL_-tX!amw9vSu%cLa=!YMt*Kb_5msOEv-uvM2SXb{_S%16#ht_<onZIjS zRG4n`vCI1JSTxx^m*+7p*pqcGCZkyB0k_1l7w7Kpcgigl?Uoj2zZoaADff_K_XJrb znN6KH%q9lR@X&Cvkz2lKU0;UEp?~Z0-u+m;+tSu#%Y!Gk!n>B1Zk_UE`OW$j*~{2J zPF=Kh;k-$kHqHEgXd=h<oI59!t;<9Leg@liMDVp+Klgc-VtT{p;pC$Rc@}k_rtEn3 z>9obY`~3oEIOH~^_ZOTvwk_w)1Xl|gMX7G(Dd%t8u+ZQC%_`00n5*ttiJ(XG+i#zA zk+%Q$^RUUa>w)pXhd1lLe>U&^9se||Zb6NLzoF7M{ywu~Yj1Nr^`GTmXIECJ1bZjz zH}-jKX501h9^5LO{`p0)^@iNp1r{y2Q7t;cr?+kN6+UfZ=p%gEz<`HYGoh%Y<;)L@ zZMn%N+dfb2&sDv7tLgQw&mw|O51xFk`S|+&f^F;OJ&(VCxI)eOUZKSNQyVhxMxC1@ zn|yuVUY6Z+#3%dl>h3uhSny)v!udgge=lB~IMM5P@y?gGzF)An=}~;j)9bX&_rxX> zp>1_rbGrjHTnaNaA9}FdKRH?6`mpo){U<Ia@0}tpD#+JbXv=@Sv{0sf-s9RgHMRV| ztNIj|UWi>WB`0S4v^~#1%@!|joMQRF>(a`5+w)3dGMbYGKZvZ`y=m>%)@;3K=l3Ur zdHVZ=y4^PDa^K#vK}Y!XCKIM*otYj>CP*x6oL)XtN<O(LscB_rVUgdwNsE-UPMtPb zqVl+k$KNOEmg1*%-ep$Zxi@vP^OvrEF0nhVlGi6$<cGq`EBW{9njgogW;Kd8xI`8j zM(5vXO7yVemsicX-Qr|6O;RIa){M5sM5!4;N=qEr3=e&_i81$AGj`Rp?tT8-<FuPc z=j-1Ve)Cqo)G57sRVOawaI*Ehh0EWo955-ceR<`**e&(y-@bOW|75&kyWbbz4V8}A zYvXzUC1cF>qXs-V-oKktXGyemDym)*=x~Z(bKtwJO}ziy*y~46J}>;FH}84Vge#%p zh81Zo?ep*SO+MW?rS{-DpI=V1Yu@L~4xN9W_fGqB(W@ch#Yb07-J%!gV<MDhWq!8g z+(82#oy(U@CKaiPtV)nS_}S*uy08N#yo(-k?0a9h=fBDNcenk^7oHM%)P6wRbNRV_ zzjLn@zhntuuMuCm%}cY>b#ciSlj5hdu5K~cJ7%Eb7uRoVbAEPawd(}$HwRvd8W`A| z@m|0G;JxN{iP<xbl*S4y65hdl!`bTT-;dIFxfgF_{K2<D<i7r)($1+<F5J7~v1n!I zvbHF%M@Mf=lhfZd?{tdV;ggq{OTNo3etv|Ljh&6Vc`eTc{+iyBSNH$D|DRQmxbzy+ zb>{FzSF~#O3*Dce`+(Q^eOy(Xfxb}H3~d)N&HF4|_1|tdT0Pw>EqG+NNX@=hp_3W{ z>{$&{4+Jf+`@!AEqSolR!1{)Tz1$viN2{g(0#=`o{m;$kdB;Cv+mD+J3=9mOu6{1- HoD!M<5}&*4 literal 0 HcmV?d00001 diff --git a/test/tlsSample/keyonly.pem b/test/tlsSample/keyonly.pem new file mode 100644 index 0000000000..ba2fc011aa --- /dev/null +++ b/test/tlsSample/keyonly.pem @@ -0,0 +1,32 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIFfQIBAAKCATEAo4zEq+yWgEhYE1VO8xdOe+ZegK9ImkfQU2MmcXJJRj3HuV2n ++hZ1VTtIlCfW7WcrC4/bs1YtfcgJFmjbOJFsE18jQNpRw1f6yD8bYW3Ovlnzqg+f +gs5O4BpGsAcQtMlp8acV2Xu/m3c4ASVHLsgKRCGap08md285oBESbJlciRuf5sXM +7fr39QFEXbpn2xjDBXrltlnp4fDbBO4dfIMsoD5ByPscyEThm7XJIW2MNlijo4PK +A9kPI6J+2b6jyehnIxosj6vcpjRbXhCcactJItUT4JtUSsFT6VnsuM4OmfxuxruE +5XvivTFG7HZWtZ8OkhW6Wmb6APEpArV5NHsQ0ko8Gx9wMJuV+VVkehjztNhyRZb3 +zMZF16FJKgmEmi17Eh5yvJtlbpPdDJyxt4AucQIDAQABAoIBMAPbCLPo4u9pRTJH +e5H9haskFixAdZy1frLIjpp0EEgoorG7BH+/0cpNmHttqQJ1wS0Hmi1TJcVmVtiB +ZlGiFAMs5f7cZrsNMutPmYylcw9HvZfPwHnr9S4RUINL11f7u5Sn1LsI0M6Qc/CK ++W+f5aYPiL7S/dwo8hnU7TNENktBLHbbSOZ/WSMcgYkYIOSoY8iYShwPaJUb99fY +0RgdY+ehoC+cswm65p9S5B/emjVtZU6Zb45L+LVmMHMiAxRR//Wpl1GzgG+wHmcu +KZ+a6oyCCdIoT5Mf7otdUBL+aTV13K8oW3vt+TdlA7EEPU3f6dsctB7H/kmE0jHh +kseDyy2/qcalprz6c4puFLrVMuuen9YvriWVquWv+h4AISwrl+ESU9+QTzaUTSUu +CCwyNXECgZkAyhiZTp/xziLtNfiYQMRZk6OYk2IW5co0t8P1jo+VDaKehFuFhQsx +6hNG6k2n3dA5MeZK4O+n5I0SGkc+MYyAWOtI0q7IiSFUn9PMwZXO91rBDG5VxFpT +srD5m86BQhfmMqpeWQgAXT/ZM1q4me4gtg9PAyJDVHLs5MV49szcH2YAVzWCLJ7e +/o9Xne7jOhUGRVzGpwZ8gP0CgZkAzywuNe+K51nFED+WSHmSw73eqxmFsdm7FHg0 +O9RykMT3vfXx1fMPQU01BbsA0w+NH/lAtgwxSSzHZPAAOYl6iB29BXErbqBpJsOz +1afqXlEAiQcA3uOgPWMWnORcLfdwvze8lndHYvQ6bu85CoPl/mv+PBNp9fJLvxMr +Xr1V8DPYUeWTPr8IOSDQ8qWXO5s/wtqOZxynR4UCgZkAtcV+Tqupz+C2Pu8D8m0o +rJRerWFyeuhulkLY4IrgY806io3PACVbldAU3rVOIUIhz3H1M3AbMIR6HcyqceKP +pmqpq2Wt1Tl3ZfIYnp3h87VbIZUz61HbKkPnnm4U8J+SW0vFZIq/2R+OepREqZ73 +KQmH5aexq4la7UF4VXNZIZya/dWQ6hVTON8wzUqKcjlq1IRn4N8CqPkCgZkAhW6t +LKvlyO/2jmqF/pPexRerLIQ5qbzUU3DAI7DHQRq7favEYMo1jxeNCO3SsB2aBXfi +B95+d+TRX998JZK4SqbssW6cTJogX4k4cGm/95MwRxEtrGDrOd5yGAa0oB54jY/2 +dOrKHZ+zavPEx1MAdQdVLZhLbM7rnltYWaKUkSqzLrwwQ/+B6lmKel3Qs+FgBb4C +bcL0sokCgZkAnKmzmzSLb1Hg2fMVXP+xtQJIwv2/2eDcOXfC+X5qoNK/KxSXF/co +U16nAVY4VwOrYLjUuOh1N8yhwj+7DrTx8F2Q4sWvoXcbBmJv46fJlsVMByuyiWPv +4q3rPEf1eQ6MBj0D9iY+JtvJmtPT0vQQa6O2gl2qFlbmlc8PCSj4hI5d19ePcQlg +ypRMPut1kF4EDNV6vcrnukA= +-----END RSA PRIVATE KEY----- diff --git a/test/tlstest.cpp b/test/tlstest.cpp new file mode 100644 index 0000000000..dbbbadf434 --- /dev/null +++ b/test/tlstest.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +/* + * ebail - 2015/02/18 + * unit test is based on old SDP manager code + * this test is disabled for the moment + * */ +#if 0 + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "tlstest.h" +#include "account.h" +#include "test_utils.h" +#include "logger.h" + +#include "sip/tlsvalidator.h" + +namespace ring { namespace test { + +void TlsTest::testKey() +{ + TITLE(); + + const char *validKey = WORKSPACE "tlsSample/keyonly.pem"; + const char *validCertWithKey = WORKSPACE "tlsSample/certwithkey.pem"; + const char *corruptedKey = WORKSPACE "tlsSample/corruptedkey.pem"; + + CPPUNIT_ASSERT(containsPrivateKey(validKey) == 0); + + CPPUNIT_ASSERT(containsPrivateKey(validCertWithKey) == 0); + + CPPUNIT_ASSERT(containsPrivateKey(corruptedKey) != 0); +} + +void TlsTest::testCertificate() +{ + TITLE(); + + const char *validCa = WORKSPACE "tlsSample/ca.crt"; + const char *validCertificate = WORKSPACE "tlsSample/cert.crt"; + const char *fakeCertificate = WORKSPACE "tlsSample/fake.crt"; + const char *expiredCertificate = WORKSPACE "tlsSample/expired.crt"; + + CPPUNIT_ASSERT(certificateIsValid(NULL, validCa) == 0); + + CPPUNIT_ASSERT(certificateIsValid(validCa, validCertificate) == 0); + + // This is a png + CPPUNIT_ASSERT(certificateIsValid(NULL, fakeCertificate) != 0); + + // This would need a CA to be valid + CPPUNIT_ASSERT(certificateIsValid(NULL, validCertificate) != 0); + + // This is an invalid CA + CPPUNIT_ASSERT(certificateIsValid(validCertificate, validCertificate) != 0); + + // This certificate is expired + CPPUNIT_ASSERT(certificateIsValid(NULL, expiredCertificate) != 0); +} + +}} // namespace ring::test +#endif diff --git a/test/tlstest.h b/test/tlstest.h new file mode 100644 index 0000000000..eb5c5e3082 --- /dev/null +++ b/test/tlstest.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2004-2015 Savoir-Faire Linux Inc. + * Author: Alexandre Lision <alexandre.lision@savoirfairelinux.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this program, or any covered work, by linking or + * combining it with the OpenSSL project's OpenSSL library (or a + * modified version of that library), containing parts covered by the + * terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. + * grants you additional permission to convey the resulting work. + * Corresponding Source for a non-source form of such a combination + * shall include the source code for the parts of OpenSSL used as well + * as that of the covered work. + */ + +/* + * @file tsltest.cpp + * @brief Tests TLS setup. + * Check if certificate is valid + */ + +#ifndef TLS_TEST_ +#define TLS_TEST_ + +// Cppunit import +#include <cppunit/extensions/HelperMacros.h> +#include <cppunit/TestCaller.h> +#include <cppunit/TestCase.h> +#include <cppunit/TestSuite.h> + +namespace ring { namespace test { + +class TlsTest: public CppUnit::TestFixture { + + /* + * Use cppunit library macros to add unit test the factory + */ + CPPUNIT_TEST_SUITE(TlsTest); + CPPUNIT_TEST(testKey); + CPPUNIT_TEST(testCertificate); + CPPUNIT_TEST(testHostname); + CPPUNIT_TEST_SUITE_END(); + + public: + void testKey(); + void testCertificate(); + void testHostname(); +}; +/* Register our test module */ +CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(TlsTest, "TlsTest"); +CPPUNIT_TEST_SUITE_REGISTRATION(TlsTest); + +}} // namespace ring::test + +#endif // TLS_TEST_ diff --git a/test/waveSample/M1F1-int16WE-AFsp.wav b/test/waveSample/M1F1-int16WE-AFsp.wav new file mode 100644 index 0000000000000000000000000000000000000000..6df1c305809b5cee7a8fa4b07e28ee4dcbe524cb GIT binary patch literal 47134 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 H{{II6AUsA{ literal 0 HcmV?d00001 diff --git a/tools/.gitignore b/tools/.gitignore new file mode 100644 index 0000000000..52d9ad73ba --- /dev/null +++ b/tools/.gitignore @@ -0,0 +1,27 @@ +# build system +build-system/launchpad/daemon +!build-system/launchpad/daemon/debian +build-system/launchpad/gnome +!build-system/launchpad/gnome/debian +build-system/launchpad/kde +!build-system/launchpad/kde/debian +build-system/launchpad/*.dsc +build-system/launchpad/*.changes +build-system/launchpad/*.tar.gz +build-system/launchpad/*.build +build-system/launchpad/*.upload + +# telify +mozilla-telify-sflphone/debian/mozilla-telify-sflphone +mozilla-telify-sflphone/tmp/ +mozilla-telify-sflphone/*-stamp +*.dsc +*.tar.gz +*.deb +*.build +*.changes +mozilla-telify-sflphone/debian/control +mozilla-telify-sflphone/debian/mozilla-telify-sflphone.links +mozilla-telify-sflphone/debian* +build-system/launchpad/mozilla-telify-sflphone/* + diff --git a/tools/asterisk/extensions.conf b/tools/asterisk/extensions.conf new file mode 100644 index 0000000000..7c92a2d2f1 --- /dev/null +++ b/tools/asterisk/extensions.conf @@ -0,0 +1,838 @@ +; extensions.conf - the Asterisk dial plan +; +; Static extension configuration file, used by +; the pbx_config module. This is where you configure all your +; inbound and outbound calls in Asterisk. +; +; This configuration file is reloaded +; - With the "dialplan reload" command in the CLI +; - With the "reload" command (that reloads everything) in the CLI + +; +; The "General" category is for certain variables. +; +[general] +; +; If static is set to no, or omitted, then the pbx_config will rewrite +; this file when extensions are modified. Remember that all comments +; made in the file will be lost when that happens. +; +; XXX Not yet implemented XXX +; +static=yes +; +; if static=yes and writeprotect=no, you can save dialplan by +; CLI command "dialplan save" too +; +writeprotect=no +; +; If autofallthrough is set, then if an extension runs out of +; things to do, it will terminate the call with BUSY, CONGESTION +; or HANGUP depending on Asterisk's best guess. This is the default. +; +; If autofallthrough is not set, then if an extension runs out of +; things to do, Asterisk will wait for a new extension to be dialed +; (this is the original behavior of Asterisk 1.0 and earlier). +; +;autofallthrough=no +; +; +; +; If extenpatternmatchnew is set (true, yes, etc), then a new algorithm that uses +; a Trie to find the best matching pattern is used. In dialplans +; with more than about 20-40 extensions in a single context, this +; new algorithm can provide a noticeable speedup. +; With 50 extensions, the speedup is 1.32x +; with 88 extensions, the speedup is 2.23x +; with 138 extensions, the speedup is 3.44x +; with 238 extensions, the speedup is 5.8x +; with 438 extensions, the speedup is 10.4x +; With 1000 extensions, the speedup is ~25x +; with 10,000 extensions, the speedup is 374x +; Basically, the new algorithm provides a flat response +; time, no matter the number of extensions. +; +; By default, the old pattern matcher is used. +; +; ****This is a new feature! ********************* +; The new pattern matcher is for the brave, the bold, and +; the desperate. If you have large dialplans (more than about 50 extensions +; in a context), and/or high call volume, you might consider setting +; this value to "yes" !! +; Please, if you try this out, and are forced to return to the +; old pattern matcher, please report your reasons in a bug report +; on https://issues.asterisk.org. We have made good progress in providing +; something compatible with the old matcher; help us finish the job! +; +; This value can be switched at runtime using the cli command "dialplan set extenpatternmatchnew true" +; or "dialplan set extenpatternmatchnew false", so you can experiment to your hearts content. +; +;extenpatternmatchnew=no +; +; If clearglobalvars is set, global variables will be cleared +; and reparsed on a dialplan reload, or Asterisk reload. +; +; If clearglobalvars is not set, then global variables will persist +; through reloads, and even if deleted from the extensions.conf or +; one of its included files, will remain set to the previous value. +; +; NOTE: A complication sets in, if you put your global variables into +; the AEL file, instead of the extensions.conf file. With clearglobalvars +; set, a "reload" will often leave the globals vars cleared, because it +; is not unusual to have extensions.conf (which will have no globals) +; load after the extensions.ael file (where the global vars are stored). +; So, with "reload" in this particular situation, first the AEL file will +; clear and then set all the global vars, then, later, when the extensions.conf +; file is loaded, the global vars are all cleared, and then not set, because +; they are not stored in the extensions.conf file. +; +clearglobalvars=no +; +; User context is where entries from users.conf are registered. The +; default value is 'default' +; +;userscontext=default +; +; You can include other config files, use the #include command +; (without the ';'). Note that this is different from the "include" command +; that includes contexts within other contexts. The #include command works +; in all asterisk configuration files. +;#include "filename.conf" +;#include <filename.conf> +;#include filename.conf +; +; You can execute a program or script that produces config files, and they +; will be inserted where you insert the #exec command. The #exec command +; works on all asterisk configuration files. However, you will need to +; activate them within asterisk.conf with the "execincludes" option. They +; are otherwise considered a security risk. +;#exec /opt/bin/build-extra-contexts.sh +;#exec /opt/bin/build-extra-contexts.sh --foo="bar" +;#exec </opt/bin/build-extra-contexts.sh --foo="bar"> +;#exec "/opt/bin/build-extra-contexts.sh --foo=\"bar\"" +; + +; The "Globals" category contains global variables that can be referenced +; in the dialplan with the GLOBAL dialplan function: +; ${GLOBAL(VARIABLE)} +; ${${GLOBAL(VARIABLE)}} or ${text${GLOBAL(VARIABLE)}} or any hybrid +; Unix/Linux environmental variables can be reached with the ENV dialplan +; function: ${ENV(VARIABLE)} +; +[globals] +CONSOLE=Console/dsp ; Console interface for demo +;CONSOLE=DAHDI/1 +;CONSOLE=Phone/phone0 +IAXINFO=guest ; IAXtel username/password +;IAXINFO=myuser:mypass +TRUNK=DAHDI/G2 ; Trunk interface +; +; Note the 'G2' in the TRUNK variable above. It specifies which group (defined +; in chan_dahdi.conf) to dial, i.e. group 2, and how to choose a channel to use +; in the specified group. The four possible options are: +; +; g: select the lowest-numbered non-busy DAHDI channel +; (aka. ascending sequential hunt group). +; G: select the highest-numbered non-busy DAHDI channel +; (aka. descending sequential hunt group). +; r: use a round-robin search, starting at the next highest channel than last +; time (aka. ascending rotary hunt group). +; R: use a round-robin search, starting at the next lowest channel than last +; time (aka. descending rotary hunt group). +; +TRUNKMSD=1 ; MSD digits to strip (usually 1 or 0) +;TRUNK=IAX2/user:pass@provider + +;FREENUMDOMAIN=mydomain.com ; domain to send on outbound + ; freenum calls (uses outbound-freenum + ; context) + +; +; WARNING WARNING WARNING WARNING +; If you load any other extension configuration engine, such as pbx_ael.so, +; your global variables may be overridden by that file. Please take care to +; use only one location to set global variables, and you will likely save +; yourself a ton of grief. +; WARNING WARNING WARNING WARNING +; +; Any category other than "General" and "Globals" represent +; extension contexts, which are collections of extensions. +; +; Extension names may be numbers, letters, or combinations +; thereof. If an extension name is prefixed by a '_' +; character, it is interpreted as a pattern rather than a +; literal. In patterns, some characters have special meanings: +; +; X - any digit from 0-9 +; Z - any digit from 1-9 +; N - any digit from 2-9 +; [1235-9] - any digit in the brackets (in this example, 1,2,3,5,6,7,8,9) +; . - wildcard, matches anything remaining (e.g. _9011. matches +; anything starting with 9011 excluding 9011 itself) +; ! - wildcard, causes the matching process to complete as soon as +; it can unambiguously determine that no other matches are possible +; +; For example, the extension _NXXXXXX would match normal 7 digit dialings, +; while _1NXXNXXXXXX would represent an area code plus phone number +; preceded by a one. +; +; Each step of an extension is ordered by priority, which must always start +; with 1 to be considered a valid extension. The priority "next" or "n" means +; the previous priority plus one, regardless of whether the previous priority +; was associated with the current extension or not. The priority "same" or "s" +; means the same as the previously specified priority, again regardless of +; whether the previous entry was for the same extension. Priorities may be +; immediately followed by a plus sign and another integer to add that amount +; (most useful with 's' or 'n'). Priorities may then also have an alias, or +; label, in parentheses after their name which can be used in goto situations. +; +; Contexts contain several lines, one for each step of each extension. One may +; include another context in the current one as well, optionally with a date +; and time. Included contexts are included in the order they are listed. +; Switches may also be included within a context. The order of matching within +; a context is always exact extensions, pattern match extensions, includes, and +; switches. Includes are always processed depth-first. So for example, if you +; would like a switch "A" to match before context "B", simply put switch "A" in +; an included context "C", where "C" is included in your original context +; before "B". +; +;[context] +;exten => someexten,{priority|label{+|-}offset}[(alias)],application(arg1,arg2,...) +; +; Timing list for includes is +; +; <time range>,<days of week>,<days of month>,<months>[,<timezone>] +; +; Note that ranges may be specified to wrap around the ends. Also, minutes are +; fine-grained only down to the closest even minute. +; +;include => daytime,9:00-17:00,mon-fri,*,* +;include => weekend,*,sat-sun,*,* +;include => weeknights,17:02-8:58,mon-fri,*,* +; +; ignorepat can be used to instruct drivers to not cancel dialtone upon receipt +; of a particular pattern. The most commonly used example is of course '9' +; like this: +; +;ignorepat => 9 +; +; so that dialtone remains even after dialing a 9. Please note that ignorepat +; only works with channels which receive dialtone from the PBX, such as DAHDI, +; Phone, and VPB. Other channels, such as SIP and MGCP, which generate their +; own dialtone and converse with the PBX only after a number is complete, are +; generally unaffected by ignorepat (unless DISA or another method is used to +; generate a dialtone after answering the channel). +; + +; +; Sample entries for extensions.conf +; +; +[dundi-e164-canonical] +;include => stdexten +; +; List canonical entries here +; +;exten => 12564286000,1,Gosub(6000,stdexten(IAX2/foo)) +;exten => 12564286000,n,Goto(default,s,1) ; exited Voicemail +;exten => _125642860XX,1,Dial(IAX2/otherbox/${EXTEN:7}) + +[dundi-e164-customers] +; +; If you are an ITSP or Reseller, list your customers here. +; +;exten => _12564286000,1,Dial(SIP/customer1) +;exten => _12564286001,1,Dial(IAX2/customer2) + +[dundi-e164-via-pstn] +; +; If you are freely delivering calls to the PSTN, list them here +; +;exten => _1256428XXXX,1,Dial(DAHDI/G2/${EXTEN:7}) ; Expose all of 256-428 +;exten => _1256325XXXX,1,Dial(DAHDI/G2/${EXTEN:7}) ; Ditto for 256-325 + +[dundi-e164-local] +; +; Context to put your dundi IAX2 or SIP user in for +; full access +; +include => dundi-e164-canonical +include => dundi-e164-customers +include => dundi-e164-via-pstn + +[dundi-e164-switch] +; +; Just a wrapper for the switch +; +switch => DUNDi/e164 + +[dundi-e164-lookup] +; +; Locally to lookup, try looking for a local E.164 solution +; then try DUNDi if we don't have one. +; +include => dundi-e164-local +include => dundi-e164-switch +; +; DUNDi can also be implemented as a Macro instead of using +; the Local channel driver. +; +[macro-dundi-e164] +; +; ARG1 is the extension to Dial +; +; Extension "s" is not a wildcard extension that matches "anything". +; In macros, it is the start extension. In most other cases, +; you have to goto "s" to execute that extension. +; +; For wildcard matches, see above - all pattern matches start with +; an underscore. +exten => s,1,Goto(${ARG1},1) +include => dundi-e164-lookup + +; +; Here are the entries you need to participate in the IAXTEL +; call routing system. Most IAXTEL numbers begin with 1-700, but +; there are exceptions. For more information, and to sign +; up, please go to www.gnophone.com or www.iaxtel.com +; +[iaxtel700] +exten => _91700XXXXXXX,1,Dial(IAX2/${GLOBAL(IAXINFO)}@iaxtel.com/${EXTEN:1}@iaxtel) + +; +; The SWITCH statement permits a server to share the dialplan with +; another server. Use with care: Reciprocal switch statements are not +; allowed (e.g. both A -> B and B -> A), and the switched server needs +; to be on-line or else dialing can be severly delayed. +; +[iaxprovider] +;switch => IAX2/user:[key]@myserver/mycontext + +[trunkint] +; +; International long distance through trunk +; +exten => _9011.,1,Macro(dundi-e164,${EXTEN:4}) +exten => _9011.,n,Dial(${GLOBAL(TRUNK)}/${FILTER(0-9,${EXTEN:${GLOBAL(TRUNKMSD)}})}) + +[trunkld] +; +; Long distance context accessed through trunk +; +exten => _91NXXNXXXXXX,1,Macro(dundi-e164,${EXTEN:1}) +exten => _91NXXNXXXXXX,n,Dial(${GLOBAL(TRUNK)}/${EXTEN:${GLOBAL(TRUNKMSD)}}) + +[trunklocal] +; +; Local seven-digit dialing accessed through trunk interface +; +exten => _9NXXXXXX,1,Dial(${GLOBAL(TRUNK)}/${EXTEN:${GLOBAL(TRUNKMSD)}}) + +[trunktollfree] +; +; Long distance context accessed through trunk interface +; +exten => _91800NXXXXXX,1,Dial(${GLOBAL(TRUNK)}/${EXTEN:${GLOBAL(TRUNKMSD)}}) +exten => _91888NXXXXXX,1,Dial(${GLOBAL(TRUNK)}/${EXTEN:${GLOBAL(TRUNKMSD)}}) +exten => _91877NXXXXXX,1,Dial(${GLOBAL(TRUNK)}/${EXTEN:${GLOBAL(TRUNKMSD)}}) +exten => _91866NXXXXXX,1,Dial(${GLOBAL(TRUNK)}/${EXTEN:${GLOBAL(TRUNKMSD)}}) + +[international] +; +; Master context for international long distance +; +ignorepat => 9 +include => longdistance +include => trunkint + +[longdistance] +; +; Master context for long distance +; +ignorepat => 9 +include => local +include => trunkld + +[local] +; +; Master context for local, toll-free, and iaxtel calls only +; +ignorepat => 9 +include => default +include => trunklocal +include => iaxtel700 +include => trunktollfree +include => iaxprovider + +;Include parkedcalls (or the context you define in features conf) +;to enable call parking. +include => parkedcalls +; +; You can use an alternative switch type as well, to resolve +; extensions that are not known here, for example with remote +; IAX switching you transparently get access to the remote +; Asterisk PBX +; +; switch => IAX2/user:password@bigserver/local +; +; An "lswitch" is like a switch but is literal, in that +; variable substitution is not performed at load time +; but is passed to the switch directly (presumably to +; be substituted in the switch routine itself) +; +; lswitch => Loopback/12${EXTEN}@othercontext +; +; An "eswitch" is like a switch but the evaluation of +; variable substitution is performed at runtime before +; being passed to the switch routine. +; +; eswitch => IAX2/context@${CURSERVER} + +; The following two contexts are a template to enable the ability to dial +; ISN numbers. For more information about what an ISN number is, please see +; http://www.freenum.org. +; +; This is the dialing hook. use: +; include => outbound-freenum + +[outbound-freenum] +; We'll add more digits as needed. The purpose is to dial things +; like extension numbers at domains (ITAD number) so we're matching +; on lengths of 1 through 6 prior to the separator (the asterisk [*]) +; +exten => _X*X!,1,Goto(outbound-freenum2,${EXTEN},1) +exten => _XX*X!,1,Goto(outbound-freenum2,${EXTEN},1) +exten => _XXX*X!,1,Goto(outbound-freenum2,${EXTEN},1) +exten => _XXXX*X!,1,Goto(outbound-freenum2,${EXTEN},1) +exten => _XXXXX*X!,1,Goto(outbound-freenum2,${EXTEN},1) +exten => _XXXXXX*X!,1,Goto(outbound-freenum2,${EXTEN},1) + +[outbound-freenum2] +; This is the handler which performs the dialing logic. It is called +; from the [outbound-freenum] context +; +exten => _X!,1,Verbose(2,Performing ISN lookup for ${EXTEN}) +same => n,Set(SUFFIX=${CUT(EXTEN,*,2-)}) ; make sure the suffix is all digits as well +same => n,GotoIf($["${FILTER(0-9,${SUFFIX})}" != "${SUFFIX}"]?fn-CONGESTION,1) + ; filter out bad characters per the README-SERIOUSLY.best-practices.txt document +same => n,Set(TIMEOUT(absolute)=10800) +same => n,Set(isnresult=${ENUMLOOKUP(${EXTEN},sip,,1,freenum.org)}) ; perform our lookup with freenum.org +same => n,GotoIf($["${isnresult}" != ""]?from) +same => n,Set(DIALSTATUS=CONGESTION) +same => n,Goto(fn-CONGESTION,1) +same => n(from),Set(__SIPFROMUSER=${CALLERID(num)}) +same => n,GotoIf($["${GLOBAL(FREENUMDOMAIN)}" = ""]?dial) ; check if we set the FREENUMDOMAIN global variable in [global] +same => n,Set(__SIPFROMDOMAIN=${GLOBAL(FREENUMDOMAIN)}) ; if we did set it, then we'll use it for our outbound dialing domain +same => n(dial),Dial(SIP/${isnresult},40) +same => n,Goto(fn-${DIALSTATUS},1) + +exten => fn-BUSY,1,Busy() + +exten => _f[n]-.,1,NoOp(ISN: ${DIALSTATUS}) +same => n,Congestion() + +[macro-trunkdial] +; +; Standard trunk dial macro (hangs up on a dialstatus that should +; terminate call) +; ${ARG1} - What to dial +; +exten => s,1,Dial(${ARG1}) +exten => s,n,Goto(s-${DIALSTATUS},1) +exten => s-NOANSWER,1,Hangup +exten => s-BUSY,1,Hangup +exten => _s-.,1,NoOp + +[stdexten] +; +; Standard extension subroutine: +; ${EXTEN} - Extension +; ${ARG1} - Device(s) to ring +; ${ARG2} - Optional context in Voicemail +; +; Note that the current version will drop through to the next priority in the +; case of their pressing '#'. This gives more flexibility in what do to next: +; you can prompt for a new extension, or drop the call, or send them to a +; general delivery mailbox, or... +; +; The use of the LOCAL() function is purely for convenience. Any variable +; initially declared as LOCAL() will disappear when the innermost Gosub context +; in which it was declared returns. Note also that you can declare a LOCAL() +; variable on top of an existing variable, and its value will revert to its +; previous value (before being declared as LOCAL()) upon Return. +; +exten => _X.,50000(stdexten),NoOp(Start stdexten) +exten => _X.,n,Set(LOCAL(ext)=${EXTEN}) +exten => _X.,n,Set(LOCAL(dev)=${ARG1}) +exten => _X.,n,Set(LOCAL(cntx)=${ARG2}) +exten => _X.,n,Set(LOCAL(mbx)=${ext}${IF($[!${ISNULL(${cntx})}]?@${cntx})}) +exten => _X.,n,Dial(${dev},20) ; Ring the interface, 20 seconds maximum +exten => _X.,n,Goto(stdexten-${DIALSTATUS},1) ; Jump based on status (NOANSWER,BUSY,CHANUNAVAIL,CONGESTION,ANSWER) + +exten => stdexten-NOANSWER,1,Voicemail(${mbx},u) ; If unavailable, send to voicemail w/ unavail announce +exten => stdexten-NOANSWER,n,Return() ; If they press #, return to start + +exten => stdexten-BUSY,1,Voicemail(${mbx},b) ; If busy, send to voicemail w/ busy announce +exten => stdexten-BUSY,n,Return() ; If they press #, return to start + +exten => _stde[x]te[n]-.,1,Goto(stdexten-NOANSWER,1) ; Treat anything else as no answer + +exten => a,1,VoicemailMain(${mbx}) ; If they press *, send the user into VoicemailMain +exten => a,n,Return() + +[stdPrivacyexten] +; +; Standard extension subroutine: +; ${ARG1} - Extension +; ${ARG2} - Device(s) to ring +; ${ARG3} - Optional DONTCALL context name to jump to (assumes the s,1 extension-priority) +; ${ARG4} - Optional TORTURE context name to jump to (assumes the s,1 extension-priority)` +; ${ARG5} - Context in voicemail (if empty, then "default") +; +; See above note in stdexten about priority handling on exit. +; +exten => _X.,60000(stdPrivacyexten),NoOp(Start stdPrivacyexten) +exten => _X.,n,Set(LOCAL(ext)=${ARG1}) +exten => _X.,n,Set(LOCAL(dev)=${ARG2}) +exten => _X.,n,Set(LOCAL(dontcntx)=${ARG3}) +exten => _X.,n,Set(LOCAL(tortcntx)=${ARG4}) +exten => _X.,n,Set(LOCAL(cntx)=${ARG5}) + +exten => _X.,n,Set(LOCAL(mbx)="${ext}"$["${cntx}" ? "@${cntx}" :: ""]) +exten => _X.,n,Dial(${dev},20,p) ; Ring the interface, 20 seconds maximum, call screening + ; option (or use P for databased call _X.creening) +exten => _X.,n,Goto(stdexten-${DIALSTATUS},1) ; Jump based on status (NOANSWER,BUSY,CHANUNAVAIL,CONGESTION,ANSWER) + +exten => stdexten-NOANSWER,1,Voicemail(${mbx},u) ; If unavailable, send to voicemail w/ unavail announce +exten => stdexten-NOANSWER,n,NoOp(Finish stdPrivacyexten NOANSWER) +exten => stdexten-NOANSWER,n,Return() ; If they press #, return to start + +exten => stdexten-BUSY,1,Voicemail(${mbx},b) ; If busy, send to voicemail w/ busy announce +exten => stdexten-BUSY,n,NoOp(Finish stdPrivacyexten BUSY) +exten => stdexten-BUSY,n,Return() ; If they press #, return to start + +exten => stdexten-DONTCALL,1,Goto(${dontcntx},s,1) ; Callee chose to send this call to a polite "Don't call again" script. + +exten => stdexten-TORTURE,1,Goto(${tortcntx},s,1) ; Callee chose to send this call to a telemarketer torture script. + +exten => _stde[x]te[n]-.,1,Goto(stdexten-NOANSWER,1) ; Treat anything else as no answer + +exten => a,1,VoicemailMain(${mbx}) ; If they press *, send the user into VoicemailMain +exten => a,n,Return + +[macro-page]; +; +; Paging macro: +; +; Check to see if SIP device is in use and DO NOT PAGE if they are +; +; ${ARG1} - Device to page + +exten => s,1,ChanIsAvail(${ARG1},s) ; s is for ANY call +exten => s,n,GoToIf($[${AVAILSTATUS} = "1"]?autoanswer:fail) +exten => s,n(autoanswer),Set(_ALERT_INFO="RA") ; This is for the PolyComs +exten => s,n,SIPAddHeader(Call-Info: Answer-After=0) ; This is for the Grandstream, Snoms, and Others +exten => s,n,NoOp() ; Add others here and Post on the Wiki!!!! +exten => s,n,Dial(${ARG1}) +exten => s,n(fail),Hangup + + +[demo] +include => stdexten +; +; We start with what to do when a call first comes in. +; +exten => s,1,Wait(1) ; Wait a second, just for fun +exten => s,n,Answer ; Answer the line +exten => s,n,Set(TIMEOUT(digit)=5) ; Set Digit Timeout to 5 seconds +exten => s,n,Set(TIMEOUT(response)=10) ; Set Response Timeout to 10 seconds +exten => s,n(restart),BackGround(demo-congrats) ; Play a congratulatory message +exten => s,n(instruct),BackGround(demo-instruct) ; Play some instructions +exten => s,n,WaitExten ; Wait for an extension to be dialed. + +exten => 2,1,BackGround(demo-moreinfo) ; Give some more information. +exten => 2,n,Goto(s,instruct) + +exten => 3,1,Set(LANGUAGE()=fr) ; Set language to french +exten => 3,n,Goto(s,restart) ; Start with the congratulations + +exten => 1000,1,Goto(default,s,1) +; +; We also create an example user, 1234, who is on the console and has +; voicemail, etc. +; +exten => 1234,1,Playback(transfer,skip) ; "Please hold while..." + ; (but skip if channel is not up) +exten => 1234,n,Gosub(${EXTEN},stdexten(${GLOBAL(CONSOLE)})) +exten => 1234,n,Goto(default,s,1) ; exited Voicemail + +exten => 1235,1,Voicemail(1234,u) ; Right to voicemail + +exten => 1236,1,Dial(Console/dsp) ; Ring forever +exten => 1236,n,Voicemail(1234,b) ; Unless busy + +; +; # for when they're done with the demo +; +exten => #,1,Playback(demo-thanks) ; "Thanks for trying the demo" +exten => #,n,Hangup ; Hang them up. + +; +; A timeout and "invalid extension rule" +; +exten => t,1,Goto(#,1) ; If they take too long, give up +exten => i,1,Playback(invalid) ; "That's not valid, try again" + +; +; Create an extension, 500, for dialing the +; Asterisk demo. +; +exten => 500,1,Playback(demo-abouttotry); Let them know what's going on +exten => 500,n,Dial(IAX2/guest@pbx.digium.com/s@default) ; Call the Asterisk demo +exten => 500,n,Playback(demo-nogo) ; Couldn't connect to the demo site +exten => 500,n,Goto(s,6) ; Return to the start over message. + +; +; Create an extension, 600, for evaluating echo latency. +; +exten => 600,1,Playback(demo-echotest) ; Let them know what's going on +exten => 600,n,Echo ; Do the echo test +exten => 600,n,Playback(demo-echodone) ; Let them know it's over +exten => 600,n,Goto(s,6) ; Start over + +; +; You can use the Macro Page to intercom a individual user +exten => 76245,1,Macro(page,SIP/Grandstream1) +; or if your peernames are the same as extensions +exten => _7XXX,1,Macro(page,SIP/${EXTEN}) +; +; +; System Wide Page at extension 7999 +; +exten => 7999,1,Set(TIMEOUT(absolute)=60) +exten => 7999,2,Page(Local/Grandstream1@page&Local/Xlite1@page&Local/1234@page/n,d) + +; Give voicemail at extension 8500 +; +exten => 8500,1,VoicemailMain +exten => 8500,n,Goto(s,6) +; +; Here's what a phone entry would look like (IXJ for example) +; +;exten => 1265,1,Dial(Phone/phone0,15) +;exten => 1265,n,Goto(s,5) + +; +; The page context calls up the page macro that sets variables needed for auto-answer +; It is in is own context to make calling it from the Page() application as simple as +; Local/{peername}@page +; +[page] +exten => _X.,1,Macro(page,SIP/${EXTEN}) + +;[mainmenu] +; +; Example "main menu" context with submenu +; +;exten => s,1,Answer +;exten => s,n,Background(thanks) ; "Thanks for calling press 1 for sales, 2 for support, ..." +;exten => s,n,WaitExten +;exten => 1,1,Goto(submenu,s,1) +;exten => 2,1,Hangup +;include => default +; +;[submenu] +;exten => s,1,Ringing ; Make them comfortable with 2 seconds of ringback +;exten => s,n,Wait,2 +;exten => s,n,Background(submenuopts) ; "Thanks for calling the sales department. Press 1 for steve, 2 for..." +;exten => s,n,WaitExten +;exten => 1,1,Goto(default,steve,1) +;exten => 2,1,Goto(default,mark,2) + +[default] +; +; By default we include the demo. In a production system, you +; probably don't want to have the demo there. +; +include => demo + +exten => 100,1,Dial(SIP/100) +exten => 200,1,Dial(SIP/200) +exten => 300,1,Dial(SIP/300) + +; +; An extension like the one below can be used for FWD, Nikotel, sipgate etc. +; Note that you must have a [sipprovider] section in sip.conf +; +;exten => _41X.,1,Dial(SIP/${FILTER(0-9,${EXTEN:2})}@sipprovider,,r) + +; Real extensions would go here. Generally you want real extensions to be +; 4 or 5 digits long (although there is no such requirement) and start with a +; single digit that is fairly large (like 6 or 7) so that you have plenty of +; room to overlap extensions and menu options without conflict. You can alias +; them with names, too, and use global variables + +;exten => 6245,hint,SIP/Grandstream1&SIP/Xlite1(Joe Schmoe) ; Channel hints for presence +;exten => 6245,1,Dial(SIP/Grandstream1,20,rt) ; permit transfer +;exten => 6245,n(dial),Dial(${HINT},20,rtT) ; Use hint as listed +;exten => 6245,n,Voicemail(6245,u) ; Voicemail (unavailable) +;exten => 6245,s+1,Hangup ; s+1, same as n +;exten => 6245,dial+101,Voicemail(6245,b) ; Voicemail (busy) +;exten => 6361,1,Dial(IAX2/JaneDoe,,rm) ; ring without time limit +;exten => 6389,1,Dial(MGCP/aaln/1@192.168.0.14) +;exten => 6390,1,Dial(JINGLE/caller/callee) ; Dial via jingle using labels +;exten => 6391,1,Dial(JINGLE/asterisk@digium.com/mogorman@astjab.org) ;Dial via jingle using asterisk as the transport and calling mogorman. +;exten => 6394,1,Dial(Local/6275/n) ; this will dial ${MARK} + +;exten => 6275,1,Gosub(${EXTEN},stdexten(${MARK})) + ; assuming ${MARK} is something like DAHDI/2 +;exten => 6275,n,Goto(default,s,1) ; exited Voicemail +;exten => mark,1,Goto(6275,1) ; alias mark to 6275 +;exten => 6536,1,Gosub(${EXTEN},stdexten(${WIL})) + ; Ditto for wil +;exten => 6536,n,Goto(default,s,1) ; exited Voicemail +;exten => wil,1,Goto(6236,1) + +;If you want to subscribe to the status of a parking space, this is +;how you do it. Subscribe to extension 6600 in sip, and you will see +;the status of the first parking lot with this extensions' help +;exten => 6600,hint,park:701@parkedcalls +;exten => 6600,1,noop +; +; Some other handy things are an extension for checking voicemail via +; voicemailmain +; +;exten => 8500,1,VoicemailMain +;exten => 8500,n,Hangup +; +; Or a conference room (you'll need to edit meetme.conf to enable this room) +; +;exten => 8600,1,Meetme(1234) +; +; Or playing an announcement to the called party, as soon it answers +; +;exten = 8700,1,Dial(${MARK},30,A(/path/to/my/announcemsg)) +; + +; example of a compartmentalized company called "acme" +; +; this is the context that your incoming IAX/SIP trunk dumps you in... +;[acme-incoming] +;exten => s,1,Wait(1) +;exten => s,n,Answer() +;exten => s,n(menu),Playback(acme/vm-brief-menu) +;exten => s,n(exten),Background(vm-enter-num-to-call) +;exten => s,n,WaitExten(5) +;exten => s,n(goodbye),Playback(vm-goodbye) +;exten => s,n(end),Hangup() +; +;include => acme-extens +; +;exten => i,1,Playback(vm-invalid) +;exten => i,n,Goto(s,exten) ; optionally, transfer to operator +; +;exten => t,1,Goto(s,goodbye) +; +; this is the context our internal SIP hardphones use (see sip.conf) +; +;[acme-internal] +;exten => s,1,Answer() +;exten => s,n(exten),Background(vm-enter-num-to-call) +;exten => s,n,WaitExten(5) +;exten => s,n(goodbye),Playback(vm-goodbye) +;exten => s,n(end),Hangup() +; +;include => trunkint +;include => trunkld +;include => trunklocal +; +;include => acme-extens +; +; you can test what your system sounds like to outside callers by dialing this +;exten => 777,1,DISA(no-password,acme-incoming) +; +; grouping of acme's extensions... never used directly, always included. +; +;[acme-extens] +;include => stdexten +;exten => 111,1,Gosub(111,stdexten(SIP/pete_1,acme)) +;exten => 111,n,Goto(s,exten) +; +;exten => 112,1,Gosub(112,stdexten(SIP/nancy_1,acme)) +;exten => 112,n,Goto(s,end) +; +; end of acme example + +; +; Time context: you can patch this in via the following. +; +; [acme-internal] +; ... +; exten => 777,1,Gosub(time) +; exten => 777,n,Hangup() +; +; ... +; include => time +; +; Note: if you're geographically spread out, you can have SIP extensions +; specify their own local timezone in sip.conf as: +; +; [boi] +; type=friend +; context=acme-internal +; callerid="Boise Ofc. <2083451111>" +; ... +; ; use system-wide default timezone of MST7MDT +; +; [lws] +; type=friend +; context=acme-internal +; callerid="Lewiston Ofc. <2087431111>" +; ... +; setvar=timezone=PST8PDT +; +; "timezone" isn't a 'reserved' name in any way, and other places where +; the timezone is significant (e.g. calls to "SayUnixTime()", etc) will +; require modification as well. Note that voicemail.conf already has +; a mechanism for timezones. +; + +[time] +exten => _X.,30000(time),NoOp(Time: ${EXTEN} ${timezone}) +exten => _X.,n,Wait(0.25) +exten => _X.,n,Answer() +; the amount of delay is set for English; you may need to adjust this time +; for other languages if there's no pause before the synchronizing beep. +exten => _X.,n,Set(FUTURETIME=$[${EPOCH} + 12]) +exten => _X.,n,SayUnixTime(${FUTURETIME},Zulu,HNS) +exten => _X.,n,SayPhonetic(z) +; use the timezone associated with the extension (sip only), or system-wide +; default if one hasn't been set. +exten => _X.,n,SayUnixTime(${FUTURETIME},${timezone},HNS) +exten => _X.,n,Playback(spy-local) +exten => _X.,n,WaitUntil(${FUTURETIME}) +exten => _X.,n,Playback(beep) +exten => _X.,n,Return() + +; +; ANI context: use in the same way as "time" above +; + +[ani] +exten => _X.,40000(ani),NoOp(ANI: ${EXTEN}) +exten => _X.,n,Wait(0.25) +exten => _X.,n,Answer() +exten => _X.,n,Playback(vm-from) +exten => _X.,n,SayDigits(${CALLERID(ani)}) +exten => _X.,n,Wait(1.25) +exten => _X.,n,SayDigits(${CALLERID(ani)}) ; playback again in case of missed digit +exten => _X.,n,Return() + +; For more information on applications, just type "core show applications" at your +; friendly Asterisk CLI prompt. +; +; "core show application <command>" will show details of how you +; use that particular application in this file, the dial plan. +; "core show functions" will list all dialplan functions +; "core show function <COMMAND>" will show you more information about +; one function. Remember that function names are UPPER CASE. diff --git a/tools/asterisk/keys/500.crt b/tools/asterisk/keys/500.crt new file mode 100644 index 0000000000..ebb4a7539d --- /dev/null +++ b/tools/asterisk/keys/500.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDYTCCAUkCAQEwDQYJKoZIhvcNAQEFBQAwQDEcMBoGA1UEAxMTQXN0ZXJpc2sg +UHJpdmF0ZSBDQTEgMB4GA1UEChMXVGVzdCBTYXZvaXItRmFpcmUgTGludXgwHhcN +MTIwNDI3MTMyNzM2WhcNMTMwNDI3MTMyNzM2WjAxMRIwEAYDVQQDEwkxMjcuMC4w +LjExGzAZBgNVBAoTElNhdm9pci1GYWlyZSBMaW51eDCBnzANBgkqhkiG9w0BAQEF +AAOBjQAwgYkCgYEA0Ht4X+Iitm1akm/m7YRKh2uh14xHyTNbpQDDLfIS4qzhzl1Z +n+WflXxoGN2WFUj1lBtI2tbM3rbg702eoj4WzkNQY4ipNjKcdoMmZ9fJT3IYa8GI +XBL8CMjS9aqNBc3yJ8jT8Y4IhDhGiZoUyL7DblsXbGVH6m5xP9uf9dEA7JsCAwEA +ATANBgkqhkiG9w0BAQUFAAOCAgEAl845XKxQTKmqNAE2vRbWNsKWuOUU0biDmNYr +mc/63vPt3yNA+rNaLD3dfkJq+qQfVMfo47eUfcikq5YE6/vazUAWyE6jrWoMIPrL +ylt0lgX6f0b19LyECBlrfJ61BpjGBxmqAbWNmMzxv85e0f98ANWg2JkQgUGrwpW1 +NY1zr8v7m3U+JWQgBb0yhXvlyLmAHGpHvyaU9jKxKXHa2d/69rs+3Y3C82/eCJAM +ZMp0BLKUxXG1EkSBkLs/2Ag3AGPWTUi5a4HT4LlS1zpigFISdUsICNfsfQ9W5Nry +2EYpsPooh6GTeKqFUtWloA6AgARWAMxCMouXsHg1RpITdxFreWB58sQtW9vfKkrd +Qzj3NJPwaa2HkP6XJ8kulaPGboS/CFYFT5brIHBnZQcuy/mdHMTAXgB0mwW1hajy +A7LUFBgRBZNYWLRTQ9Mc4K0QlS4Nc0tgel6KkLhxz8jdigp/qMahC54krz/vG8zJ +a6TOmQelgIxCdRhyaocrsilXrfkY61BOCO6FiBQ3V4LDHMXr3Pz+Gc3J+9MIdxSC +YqLTrZbvrW9uufOPjslK3MPeD1Eughscfj513wMF2tuvfChBJ/nwIJ3etM3Q5i3b +OnoYHipWxGDj0JDOTkqd9GbZmhS34qQWIUMOZ9oNC+rWHOnM5PR2j8eVQOGQAQCt +fXLPgfc= +-----END CERTIFICATE----- diff --git a/tools/asterisk/keys/500.csr b/tools/asterisk/keys/500.csr new file mode 100644 index 0000000000..86a11bf429 --- /dev/null +++ b/tools/asterisk/keys/500.csr @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBcDCB2gIBADAxMRIwEAYDVQQDEwkxMjcuMC4wLjExGzAZBgNVBAoTElNhdm9p +ci1GYWlyZSBMaW51eDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0Ht4X+Ii +tm1akm/m7YRKh2uh14xHyTNbpQDDLfIS4qzhzl1Zn+WflXxoGN2WFUj1lBtI2tbM +3rbg702eoj4WzkNQY4ipNjKcdoMmZ9fJT3IYa8GIXBL8CMjS9aqNBc3yJ8jT8Y4I +hDhGiZoUyL7DblsXbGVH6m5xP9uf9dEA7JsCAwEAAaAAMA0GCSqGSIb3DQEBBQUA +A4GBAFIQ8XAzHEZ16KCtV0EkePMArJt/xgSZ1Gydw/F/Wxoxevjq5Ft6A9AZYBWJ +aWLO0lJ2eqJikhdXXL75suBhDMcs4CKPJyl/LvDbNds5ua9UixLwQV7sOB4IzpEJ +dfLO1U1/WPc+7LpCwTurP1sXHSrHWKZj0EEygZq+DVQNoGaw +-----END CERTIFICATE REQUEST----- diff --git a/tools/asterisk/keys/500.key b/tools/asterisk/keys/500.key new file mode 100644 index 0000000000..39dcde1a56 --- /dev/null +++ b/tools/asterisk/keys/500.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDQe3hf4iK2bVqSb+bthEqHa6HXjEfJM1ulAMMt8hLirOHOXVmf +5Z+VfGgY3ZYVSPWUG0ja1szetuDvTZ6iPhbOQ1BjiKk2Mpx2gyZn18lPchhrwYhc +EvwIyNL1qo0FzfInyNPxjgiEOEaJmhTIvsNuWxdsZUfqbnE/25/10QDsmwIDAQAB +AoGBALZAKYPQgL3vLK0067AY5Loraiiu9hY6MlQ1LWqd4sqLjT5EttOj/XTFc47B +LrFevWgCzhaYjjHntw0bUqDMHEwRWTRP6RGLn4geDnA/LzjSka1fUbAi2hvbPWQF +Uz98Kidpi/Z1LSx0hW3hG+aZIbouRQzx7eEXUBpdwMQuePrZAkEA9FkxnkkgQY8y +6hGLPvOLvzb/s9FnpYpTcXyBPBf9SwfJTuBg4kABIRtXOzdlTqmOcVrG5+hMCC/V +IALDJQrXLQJBANpsc8L3eBig02FJT4R0+oE60UWq+1wufKBgmNGtWFZ5aM3hR8Sp +GvAEWd1O/F1j7sr5rNO0PviZiReIgmxFr+cCQQCHaH5Et0V2z0Jp0DsYMaL53iKp +pZwIcrV3KIX9pVWqpK/8U/+codd+X0Zh/HrZssDLNIERtvubddZnnOBDwNQpAkEA +tTuLidggE/9NpMlZa0RMnnGZNr86NTB1Q/Uil8fHJmkyprEoBWty6HgTwGdLSooi +ltQ3rKlAHrH2aEpiPUhNPQJARWuoMI7fayI1wLOjWXu23rXXP6ObAGpJDuPLM7aB +oGmRYVVe9eMe1SfNuNib1fw5I4GSLLhEe0qzoCZ6jv3L6g== +-----END RSA PRIVATE KEY----- diff --git a/tools/asterisk/keys/500.pem b/tools/asterisk/keys/500.pem new file mode 100644 index 0000000000..179f66ad74 --- /dev/null +++ b/tools/asterisk/keys/500.pem @@ -0,0 +1,36 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDQe3hf4iK2bVqSb+bthEqHa6HXjEfJM1ulAMMt8hLirOHOXVmf +5Z+VfGgY3ZYVSPWUG0ja1szetuDvTZ6iPhbOQ1BjiKk2Mpx2gyZn18lPchhrwYhc +EvwIyNL1qo0FzfInyNPxjgiEOEaJmhTIvsNuWxdsZUfqbnE/25/10QDsmwIDAQAB +AoGBALZAKYPQgL3vLK0067AY5Loraiiu9hY6MlQ1LWqd4sqLjT5EttOj/XTFc47B +LrFevWgCzhaYjjHntw0bUqDMHEwRWTRP6RGLn4geDnA/LzjSka1fUbAi2hvbPWQF +Uz98Kidpi/Z1LSx0hW3hG+aZIbouRQzx7eEXUBpdwMQuePrZAkEA9FkxnkkgQY8y +6hGLPvOLvzb/s9FnpYpTcXyBPBf9SwfJTuBg4kABIRtXOzdlTqmOcVrG5+hMCC/V +IALDJQrXLQJBANpsc8L3eBig02FJT4R0+oE60UWq+1wufKBgmNGtWFZ5aM3hR8Sp +GvAEWd1O/F1j7sr5rNO0PviZiReIgmxFr+cCQQCHaH5Et0V2z0Jp0DsYMaL53iKp +pZwIcrV3KIX9pVWqpK/8U/+codd+X0Zh/HrZssDLNIERtvubddZnnOBDwNQpAkEA +tTuLidggE/9NpMlZa0RMnnGZNr86NTB1Q/Uil8fHJmkyprEoBWty6HgTwGdLSooi +ltQ3rKlAHrH2aEpiPUhNPQJARWuoMI7fayI1wLOjWXu23rXXP6ObAGpJDuPLM7aB +oGmRYVVe9eMe1SfNuNib1fw5I4GSLLhEe0qzoCZ6jv3L6g== +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDYTCCAUkCAQEwDQYJKoZIhvcNAQEFBQAwQDEcMBoGA1UEAxMTQXN0ZXJpc2sg +UHJpdmF0ZSBDQTEgMB4GA1UEChMXVGVzdCBTYXZvaXItRmFpcmUgTGludXgwHhcN +MTIwNDI3MTMyNzM2WhcNMTMwNDI3MTMyNzM2WjAxMRIwEAYDVQQDEwkxMjcuMC4w +LjExGzAZBgNVBAoTElNhdm9pci1GYWlyZSBMaW51eDCBnzANBgkqhkiG9w0BAQEF +AAOBjQAwgYkCgYEA0Ht4X+Iitm1akm/m7YRKh2uh14xHyTNbpQDDLfIS4qzhzl1Z +n+WflXxoGN2WFUj1lBtI2tbM3rbg702eoj4WzkNQY4ipNjKcdoMmZ9fJT3IYa8GI +XBL8CMjS9aqNBc3yJ8jT8Y4IhDhGiZoUyL7DblsXbGVH6m5xP9uf9dEA7JsCAwEA +ATANBgkqhkiG9w0BAQUFAAOCAgEAl845XKxQTKmqNAE2vRbWNsKWuOUU0biDmNYr +mc/63vPt3yNA+rNaLD3dfkJq+qQfVMfo47eUfcikq5YE6/vazUAWyE6jrWoMIPrL +ylt0lgX6f0b19LyECBlrfJ61BpjGBxmqAbWNmMzxv85e0f98ANWg2JkQgUGrwpW1 +NY1zr8v7m3U+JWQgBb0yhXvlyLmAHGpHvyaU9jKxKXHa2d/69rs+3Y3C82/eCJAM +ZMp0BLKUxXG1EkSBkLs/2Ag3AGPWTUi5a4HT4LlS1zpigFISdUsICNfsfQ9W5Nry +2EYpsPooh6GTeKqFUtWloA6AgARWAMxCMouXsHg1RpITdxFreWB58sQtW9vfKkrd +Qzj3NJPwaa2HkP6XJ8kulaPGboS/CFYFT5brIHBnZQcuy/mdHMTAXgB0mwW1hajy +A7LUFBgRBZNYWLRTQ9Mc4K0QlS4Nc0tgel6KkLhxz8jdigp/qMahC54krz/vG8zJ +a6TOmQelgIxCdRhyaocrsilXrfkY61BOCO6FiBQ3V4LDHMXr3Pz+Gc3J+9MIdxSC +YqLTrZbvrW9uufOPjslK3MPeD1Eughscfj513wMF2tuvfChBJ/nwIJ3etM3Q5i3b +OnoYHipWxGDj0JDOTkqd9GbZmhS34qQWIUMOZ9oNC+rWHOnM5PR2j8eVQOGQAQCt +fXLPgfc= +-----END CERTIFICATE----- diff --git a/tools/asterisk/keys/600.crt b/tools/asterisk/keys/600.crt new file mode 100644 index 0000000000..32e6b9c937 --- /dev/null +++ b/tools/asterisk/keys/600.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDZjCCAU4CAQEwDQYJKoZIhvcNAQEFBQAwQDEcMBoGA1UEAxMTQXN0ZXJpc2sg +UHJpdmF0ZSBDQTEgMB4GA1UEChMXVGVzdCBTYXZvaXItRmFpcmUgTGludXgwHhcN +MTIwNDI3MTMzMDI2WhcNMTMwNDI3MTMzMDI2WjA2MRcwFQYDVQQDEw4xOTIuMTY4 +LjUwLjE3MjEbMBkGA1UEChMSU2F2b2lyLUZhaXJlIExpbnV4MIGfMA0GCSqGSIb3 +DQEBAQUAA4GNADCBiQKBgQDQsm0AjqfDmwoCGcGNz4jgU54/yz4pWip1A2OlHUYX +XOxKoFJQPjMSRUVuVw0ZpdL3phw4GyL7CNtAbALRg6qKLj6Sf5ZCn8sFnmsVrW+v +Md0sYnf+b/u8KQyJdpuga9vKWn31e6P3RsdGVZBhSkx28V7/5iFF6TpZDhM+Ql3N +FQIDAQABMA0GCSqGSIb3DQEBBQUAA4ICAQBnCugdLIWsZrHtv3jEC1D3RVume58O +z98q3OVap3ZxfSo5V9I1iU5W6t4wz/YP7QmGFbThFkE4Sj4CQGmKJMh9oEwBqzuY +wuPjfgymrBk7yKy0XCkVDZ+q4yidPd1tS8WMQlklnb0TR4bNB07C2QAQoKb50fLm +K5GycOfNb5sjAl790ugM5z7QW912XCHkF1/ymTcCso+rK8vAaO9tkpk6l22igIEE +Pq1W/OLkm5u+u2HSISxdozXj4keK/0kuvzVnTuvqtwftESJaA4mfehE6Z5dW/AlU +dLyZnZIUEQmLN2zET2E5rABNLAeRCKCyPITmj8/v9yr10MDI3I/BZXM3BI4QgqoS +JOhNFCeRFtyy/bzBAh7o6tq9YEohvVztjFwCZWthDiPPKxTO6HnTlUFDbG3Gv9Wb +BP/f/v52Tm7LvY4jHo5LFILaF/HXHRrryCkdw3wjjtpAkl5zGh+E0C4yeqmdjsP9 +bH9a9mAa1N3LgtL52cxadfCacoH3oI+FwymOdnf6+tyv+D6BZWtX5t4WINVkdGuD +RzUZi08VH9Vd3d0qjzO7Vve028w/cM3QE+si4SVHHfV08a4zIGOTP5edJYJ58QWC +jWajYyTw1mhCzSEFofxw29aDO5cXw1lKmjCjoVi+Ae3tKUyhPTK1+9e0MAsR+cLG +OKKct2tGWeuGHA== +-----END CERTIFICATE----- diff --git a/tools/asterisk/keys/600.csr b/tools/asterisk/keys/600.csr new file mode 100644 index 0000000000..47395498bc --- /dev/null +++ b/tools/asterisk/keys/600.csr @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBdTCB3wIBADA2MRcwFQYDVQQDEw4xOTIuMTY4LjUwLjE3MjEbMBkGA1UEChMS +U2F2b2lyLUZhaXJlIExpbnV4MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDQ +sm0AjqfDmwoCGcGNz4jgU54/yz4pWip1A2OlHUYXXOxKoFJQPjMSRUVuVw0ZpdL3 +phw4GyL7CNtAbALRg6qKLj6Sf5ZCn8sFnmsVrW+vMd0sYnf+b/u8KQyJdpuga9vK +Wn31e6P3RsdGVZBhSkx28V7/5iFF6TpZDhM+Ql3NFQIDAQABoAAwDQYJKoZIhvcN +AQEFBQADgYEAForeBGuC/JjnLQ7/aafWGZZJWkGqqTtkO8ksuMfm09Hy0viyMzJr +DXcCE8lGNukB8NXWkl7khLnuxNHCsbFsbpzI9dx1b6GiKI+95U+Nu9HvHh4o7Vmv +7O4kt75M/HtM15nXbKEGiYmRLcJKWkenQUEasVMfp/giZx7hjaQO10I= +-----END CERTIFICATE REQUEST----- diff --git a/tools/asterisk/keys/600.key b/tools/asterisk/keys/600.key new file mode 100644 index 0000000000..50c2934016 --- /dev/null +++ b/tools/asterisk/keys/600.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDQsm0AjqfDmwoCGcGNz4jgU54/yz4pWip1A2OlHUYXXOxKoFJQ +PjMSRUVuVw0ZpdL3phw4GyL7CNtAbALRg6qKLj6Sf5ZCn8sFnmsVrW+vMd0sYnf+ +b/u8KQyJdpuga9vKWn31e6P3RsdGVZBhSkx28V7/5iFF6TpZDhM+Ql3NFQIDAQAB +AoGAIJOZfDrIaTosR8OpeO9qWEn1K9QX8fCHLBjJVx7IsCDrKYL5Fll/M1zox56D +BvvhgJLHWOKzhSgOwXGaxwWcewkrhy4UbQ7+8wm/LdgT8nKU7OyU+oKAz/6+bFX3 +M9aA8cKOwDQCrZvKCHDPJzab71HAe8uWA54T798S3HyFOgkCQQDsFiST3XH6ortD +YLQIwIl+LUCeb9UpksjitYvtjA+CANPpjdpPASNpTNVoS7adecfU01cC6spw/1EF +7izTkm23AkEA4kzbL/LLDdwD3pBFvA/tu+kwcfRDtryRymiHz4qKg7xtHphAV3Sx +K2nRs0WRJ/br1CE3kz1PEvFcVsDUyf2bkwJBALqFnAx+zohYfV70TgkEJRzdH8qN +THp2D+Sdzpm1KKNriAFkI3B708BkBc9K0lKEXo8VEg+p9Jtl/FuVGzFk5O0CQA6k +Cko/2wM6iMWNb/WK0kal/4xf0UGxUX1W5fJ3dB6xwh2InCEMW6oDXp3KkmmTgA5p +V78e6E7BbsfuEdY/oiECQBlR/WfYeoZpHueRwx8M3jICOR5WBjmu7c+rLjaEvGCB +kXhsM8UrMHncz1fNHDLgLcPneiw+tReeuKVHBSDYwBg= +-----END RSA PRIVATE KEY----- diff --git a/tools/asterisk/keys/600.pem b/tools/asterisk/keys/600.pem new file mode 100644 index 0000000000..b2d4d0cc32 --- /dev/null +++ b/tools/asterisk/keys/600.pem @@ -0,0 +1,36 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXAIBAAKBgQDQsm0AjqfDmwoCGcGNz4jgU54/yz4pWip1A2OlHUYXXOxKoFJQ +PjMSRUVuVw0ZpdL3phw4GyL7CNtAbALRg6qKLj6Sf5ZCn8sFnmsVrW+vMd0sYnf+ +b/u8KQyJdpuga9vKWn31e6P3RsdGVZBhSkx28V7/5iFF6TpZDhM+Ql3NFQIDAQAB +AoGAIJOZfDrIaTosR8OpeO9qWEn1K9QX8fCHLBjJVx7IsCDrKYL5Fll/M1zox56D +BvvhgJLHWOKzhSgOwXGaxwWcewkrhy4UbQ7+8wm/LdgT8nKU7OyU+oKAz/6+bFX3 +M9aA8cKOwDQCrZvKCHDPJzab71HAe8uWA54T798S3HyFOgkCQQDsFiST3XH6ortD +YLQIwIl+LUCeb9UpksjitYvtjA+CANPpjdpPASNpTNVoS7adecfU01cC6spw/1EF +7izTkm23AkEA4kzbL/LLDdwD3pBFvA/tu+kwcfRDtryRymiHz4qKg7xtHphAV3Sx +K2nRs0WRJ/br1CE3kz1PEvFcVsDUyf2bkwJBALqFnAx+zohYfV70TgkEJRzdH8qN +THp2D+Sdzpm1KKNriAFkI3B708BkBc9K0lKEXo8VEg+p9Jtl/FuVGzFk5O0CQA6k +Cko/2wM6iMWNb/WK0kal/4xf0UGxUX1W5fJ3dB6xwh2InCEMW6oDXp3KkmmTgA5p +V78e6E7BbsfuEdY/oiECQBlR/WfYeoZpHueRwx8M3jICOR5WBjmu7c+rLjaEvGCB +kXhsM8UrMHncz1fNHDLgLcPneiw+tReeuKVHBSDYwBg= +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDZjCCAU4CAQEwDQYJKoZIhvcNAQEFBQAwQDEcMBoGA1UEAxMTQXN0ZXJpc2sg +UHJpdmF0ZSBDQTEgMB4GA1UEChMXVGVzdCBTYXZvaXItRmFpcmUgTGludXgwHhcN +MTIwNDI3MTMzMDI2WhcNMTMwNDI3MTMzMDI2WjA2MRcwFQYDVQQDEw4xOTIuMTY4 +LjUwLjE3MjEbMBkGA1UEChMSU2F2b2lyLUZhaXJlIExpbnV4MIGfMA0GCSqGSIb3 +DQEBAQUAA4GNADCBiQKBgQDQsm0AjqfDmwoCGcGNz4jgU54/yz4pWip1A2OlHUYX +XOxKoFJQPjMSRUVuVw0ZpdL3phw4GyL7CNtAbALRg6qKLj6Sf5ZCn8sFnmsVrW+v +Md0sYnf+b/u8KQyJdpuga9vKWn31e6P3RsdGVZBhSkx28V7/5iFF6TpZDhM+Ql3N +FQIDAQABMA0GCSqGSIb3DQEBBQUAA4ICAQBnCugdLIWsZrHtv3jEC1D3RVume58O +z98q3OVap3ZxfSo5V9I1iU5W6t4wz/YP7QmGFbThFkE4Sj4CQGmKJMh9oEwBqzuY +wuPjfgymrBk7yKy0XCkVDZ+q4yidPd1tS8WMQlklnb0TR4bNB07C2QAQoKb50fLm +K5GycOfNb5sjAl790ugM5z7QW912XCHkF1/ymTcCso+rK8vAaO9tkpk6l22igIEE +Pq1W/OLkm5u+u2HSISxdozXj4keK/0kuvzVnTuvqtwftESJaA4mfehE6Z5dW/AlU +dLyZnZIUEQmLN2zET2E5rABNLAeRCKCyPITmj8/v9yr10MDI3I/BZXM3BI4QgqoS +JOhNFCeRFtyy/bzBAh7o6tq9YEohvVztjFwCZWthDiPPKxTO6HnTlUFDbG3Gv9Wb +BP/f/v52Tm7LvY4jHo5LFILaF/HXHRrryCkdw3wjjtpAkl5zGh+E0C4yeqmdjsP9 +bH9a9mAa1N3LgtL52cxadfCacoH3oI+FwymOdnf6+tyv+D6BZWtX5t4WINVkdGuD +RzUZi08VH9Vd3d0qjzO7Vve028w/cM3QE+si4SVHHfV08a4zIGOTP5edJYJ58QWC +jWajYyTw1mhCzSEFofxw29aDO5cXw1lKmjCjoVi+Ae3tKUyhPTK1+9e0MAsR+cLG +OKKct2tGWeuGHA== +-----END CERTIFICATE----- diff --git a/tools/asterisk/keys/asterisk.crt b/tools/asterisk/keys/asterisk.crt new file mode 100644 index 0000000000..088853a120 --- /dev/null +++ b/tools/asterisk/keys/asterisk.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDZjCCAU4CAQEwDQYJKoZIhvcNAQEFBQAwQDEcMBoGA1UEAxMTQXN0ZXJpc2sg +UHJpdmF0ZSBDQTEgMB4GA1UEChMXVGVzdCBTYXZvaXItRmFpcmUgTGludXgwHhcN +MTIwNDE2MjEwNzU3WhcNMTMwNDE2MjEwNzU3WjA2MRIwEAYDVQQDEwkxMjcuMC4w +LjExIDAeBgNVBAoTF1Rlc3QgU2F2b2lyLUZhaXJlIExpbnV4MIGfMA0GCSqGSIb3 +DQEBAQUAA4GNADCBiQKBgQCdxgF7K/c/esDIl4NEqlOPghD1UNDejt0miKrkCGmn +xnWauKY45me+LfRmHqsmFz8F2657/B0Xh1cLD46jYC7vwRrFC7vkkxXaf7/EjLJj +qNFUl0yjKrBSZOii3goxeekdDFv7MNqZn2dxaJL18jEtbXUBrFBZ5sVN2ftmWqU2 +QwIDAQABMA0GCSqGSIb3DQEBBQUAA4ICAQCGBJTTqtPXDZmqdjp1tt+LiC9Jc4fs +6ounWAiHKgyboVGCk8ouDeNhbHnAqxkjAFzFPiuhuiYnKHQcOZ71YHHbMO8Mo8xV +BvT1KGlDSg/BfRkLMzFea3kIomSXUxPPqHx+EVD7HpsZVQ+4LNwAdg+C3S/9JaNE +7KWY4dXuaIsS51uiNhxwSKuD7Mqp25FH/cIVl6D9m+4l6RHRDSleISv/PKEh4400 +hM4dUe4K7fCziTI0DDDz1WMGrlGg9pq2PGqqMaNg7tog7wyEq5VDZc0CYggEfMO8 +3BK6FLrVOAf6FHOHlzhX/YpFhouaJtIQ+Ke8/X4j8O48nhJN+qvUovEvGRBYF1JC +NSsH26dAYvFMzwLxc9QIEn/35ygOpH7FV3eTIryZ4qgulQznKtUF8jsnhkQR8S1N +vTKCr2JkevxU1c24mmBvd+NUYU9JL9BAoAPyCcSzvQf3imnJ3nMhRAG9c0C/7JAI +IzmRVYBvoeTCYJdEctQX/Y0SaDque2JJVWhGkeeneJirh1LGwH5yEstFp7Rvus+t +MERVZOupPx9NRc6STfd3TLtNqPuhU5AoLknxtvpyBeWCiwCL1+/NNSAXdUDO0MrF +UFfAOdKWORTL8p0ZrVcy7N3UwK+P2bkjhrz+3PWh2airczYIycEXeDvmT20/6X/A +1jHWfYG7WWPECA== +-----END CERTIFICATE----- diff --git a/tools/asterisk/keys/asterisk.csr b/tools/asterisk/keys/asterisk.csr new file mode 100644 index 0000000000..8d6f41a562 --- /dev/null +++ b/tools/asterisk/keys/asterisk.csr @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBdTCB3wIBADA2MRIwEAYDVQQDEwkxMjcuMC4wLjExIDAeBgNVBAoTF1Rlc3Qg +U2F2b2lyLUZhaXJlIExpbnV4MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCd +xgF7K/c/esDIl4NEqlOPghD1UNDejt0miKrkCGmnxnWauKY45me+LfRmHqsmFz8F +2657/B0Xh1cLD46jYC7vwRrFC7vkkxXaf7/EjLJjqNFUl0yjKrBSZOii3goxeekd +DFv7MNqZn2dxaJL18jEtbXUBrFBZ5sVN2ftmWqU2QwIDAQABoAAwDQYJKoZIhvcN +AQEFBQADgYEAnMwWYJhhXUF2pu9YPxrNX7D3/EatZNugzFR1e/BCusH6LXUaGKbD +ionzVVjkxCBEysK8B4Dcrw1+65mHAQehs3TesaEfZ3ykf9rC8cPY+kwzsqL96H3z +OOTfz6XuhANUKkSwD3coqauGwIubCCsE2qiBYib5OJwfchbDsIcIBdc= +-----END CERTIFICATE REQUEST----- diff --git a/tools/asterisk/keys/asterisk.key b/tools/asterisk/keys/asterisk.key new file mode 100644 index 0000000000..3d5bbcb1b6 --- /dev/null +++ b/tools/asterisk/keys/asterisk.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQCdxgF7K/c/esDIl4NEqlOPghD1UNDejt0miKrkCGmnxnWauKY4 +5me+LfRmHqsmFz8F2657/B0Xh1cLD46jYC7vwRrFC7vkkxXaf7/EjLJjqNFUl0yj +KrBSZOii3goxeekdDFv7MNqZn2dxaJL18jEtbXUBrFBZ5sVN2ftmWqU2QwIDAQAB +AoGASC+NA+70u+2NAPoZjDQl8TYATk1Ak2NoGbZUAes7dBDgQ/8RxlzcwG3EMWj8 +w3vFUQfXCFEselRo5d2jVGqwbjemjynm9S0bzRdlM2zW3ORv8mKZkKRNVmr5QlJo +TD9+HbPCFSPEOOYQ2EDITBXvti2Ch16GNgVLZXd+fwePsdkCQQDR8o3HbkteyZsC +OTi14El04LEE2JLCtPeeeqNYgZw9NrLuS/QInBX0PCN08DY3RemmWsH4/KqFhS/t +xSMIKJLHAkEAwGGokL0+d1F2eIdQ8BkcpF+AUM7kwSpzTQ2oRSnK+6vj341YAESf +H4Nki54QHBQiHuFQBiDOCuonX/CBnLCEpQJAByjpcuKtCVeAxMukxncWqji7cLne +D2vSggIWrf8FkATch0np0Z1ZFlIyt1s1zh7BQB4aPV6IhjMrlkVB05ZmowJAcZT2 +9cWVbNLe1Fhn8+mPnIh59LvCGT3b50FJ+NOs8RvSJPmJXFcnb26e3UOMFVfZsUur +eILDw3PtnVoc3ArntQJANA/uYKQtNcFuDHPR7mYpLLcAxNW1pNh5Ynq5tSivoXsu +ED3RTExmskv2sXsjE69K75JD823zdVu0mUmAkYMQiQ== +-----END RSA PRIVATE KEY----- diff --git a/tools/asterisk/keys/asterisk.pem b/tools/asterisk/keys/asterisk.pem new file mode 100644 index 0000000000..2c43c07fae --- /dev/null +++ b/tools/asterisk/keys/asterisk.pem @@ -0,0 +1,36 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICWwIBAAKBgQCdxgF7K/c/esDIl4NEqlOPghD1UNDejt0miKrkCGmnxnWauKY4 +5me+LfRmHqsmFz8F2657/B0Xh1cLD46jYC7vwRrFC7vkkxXaf7/EjLJjqNFUl0yj +KrBSZOii3goxeekdDFv7MNqZn2dxaJL18jEtbXUBrFBZ5sVN2ftmWqU2QwIDAQAB +AoGASC+NA+70u+2NAPoZjDQl8TYATk1Ak2NoGbZUAes7dBDgQ/8RxlzcwG3EMWj8 +w3vFUQfXCFEselRo5d2jVGqwbjemjynm9S0bzRdlM2zW3ORv8mKZkKRNVmr5QlJo +TD9+HbPCFSPEOOYQ2EDITBXvti2Ch16GNgVLZXd+fwePsdkCQQDR8o3HbkteyZsC +OTi14El04LEE2JLCtPeeeqNYgZw9NrLuS/QInBX0PCN08DY3RemmWsH4/KqFhS/t +xSMIKJLHAkEAwGGokL0+d1F2eIdQ8BkcpF+AUM7kwSpzTQ2oRSnK+6vj341YAESf +H4Nki54QHBQiHuFQBiDOCuonX/CBnLCEpQJAByjpcuKtCVeAxMukxncWqji7cLne +D2vSggIWrf8FkATch0np0Z1ZFlIyt1s1zh7BQB4aPV6IhjMrlkVB05ZmowJAcZT2 +9cWVbNLe1Fhn8+mPnIh59LvCGT3b50FJ+NOs8RvSJPmJXFcnb26e3UOMFVfZsUur +eILDw3PtnVoc3ArntQJANA/uYKQtNcFuDHPR7mYpLLcAxNW1pNh5Ynq5tSivoXsu +ED3RTExmskv2sXsjE69K75JD823zdVu0mUmAkYMQiQ== +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDZjCCAU4CAQEwDQYJKoZIhvcNAQEFBQAwQDEcMBoGA1UEAxMTQXN0ZXJpc2sg +UHJpdmF0ZSBDQTEgMB4GA1UEChMXVGVzdCBTYXZvaXItRmFpcmUgTGludXgwHhcN +MTIwNDE2MjEwNzU3WhcNMTMwNDE2MjEwNzU3WjA2MRIwEAYDVQQDEwkxMjcuMC4w +LjExIDAeBgNVBAoTF1Rlc3QgU2F2b2lyLUZhaXJlIExpbnV4MIGfMA0GCSqGSIb3 +DQEBAQUAA4GNADCBiQKBgQCdxgF7K/c/esDIl4NEqlOPghD1UNDejt0miKrkCGmn +xnWauKY45me+LfRmHqsmFz8F2657/B0Xh1cLD46jYC7vwRrFC7vkkxXaf7/EjLJj +qNFUl0yjKrBSZOii3goxeekdDFv7MNqZn2dxaJL18jEtbXUBrFBZ5sVN2ftmWqU2 +QwIDAQABMA0GCSqGSIb3DQEBBQUAA4ICAQCGBJTTqtPXDZmqdjp1tt+LiC9Jc4fs +6ounWAiHKgyboVGCk8ouDeNhbHnAqxkjAFzFPiuhuiYnKHQcOZ71YHHbMO8Mo8xV +BvT1KGlDSg/BfRkLMzFea3kIomSXUxPPqHx+EVD7HpsZVQ+4LNwAdg+C3S/9JaNE +7KWY4dXuaIsS51uiNhxwSKuD7Mqp25FH/cIVl6D9m+4l6RHRDSleISv/PKEh4400 +hM4dUe4K7fCziTI0DDDz1WMGrlGg9pq2PGqqMaNg7tog7wyEq5VDZc0CYggEfMO8 +3BK6FLrVOAf6FHOHlzhX/YpFhouaJtIQ+Ke8/X4j8O48nhJN+qvUovEvGRBYF1JC +NSsH26dAYvFMzwLxc9QIEn/35ygOpH7FV3eTIryZ4qgulQznKtUF8jsnhkQR8S1N +vTKCr2JkevxU1c24mmBvd+NUYU9JL9BAoAPyCcSzvQf3imnJ3nMhRAG9c0C/7JAI +IzmRVYBvoeTCYJdEctQX/Y0SaDque2JJVWhGkeeneJirh1LGwH5yEstFp7Rvus+t +MERVZOupPx9NRc6STfd3TLtNqPuhU5AoLknxtvpyBeWCiwCL1+/NNSAXdUDO0MrF +UFfAOdKWORTL8p0ZrVcy7N3UwK+P2bkjhrz+3PWh2airczYIycEXeDvmT20/6X/A +1jHWfYG7WWPECA== +-----END CERTIFICATE----- diff --git a/tools/asterisk/keys/ca.cfg b/tools/asterisk/keys/ca.cfg new file mode 100644 index 0000000000..f362441845 --- /dev/null +++ b/tools/asterisk/keys/ca.cfg @@ -0,0 +1,10 @@ +[req] +distinguished_name = req_distinguished_name +prompt = no + +[req_distinguished_name] +CN=Asterisk Private CA +O=Test Savoir-Faire Linux + +[ext] +basicConstraints=CA:TRUE diff --git a/tools/asterisk/keys/ca.crt b/tools/asterisk/keys/ca.crt new file mode 100644 index 0000000000..bce248b454 --- /dev/null +++ b/tools/asterisk/keys/ca.crt @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIE/DCCAuQCCQCHJx9CtI2lVDANBgkqhkiG9w0BAQUFADBAMRwwGgYDVQQDExNB +c3RlcmlzayBQcml2YXRlIENBMSAwHgYDVQQKExdUZXN0IFNhdm9pci1GYWlyZSBM +aW51eDAeFw0xMjA0MTYyMTA3NTJaFw0xMzA0MTYyMTA3NTJaMEAxHDAaBgNVBAMT +E0FzdGVyaXNrIFByaXZhdGUgQ0ExIDAeBgNVBAoTF1Rlc3QgU2F2b2lyLUZhaXJl +IExpbnV4MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEArD4kMmNp/EmP +aqsg9/UWviUXPHjze8TpJfFB4eNJy7HsBHAJSyfrf8NU9Bv8zWsPFs27GKC1EH+N +i+KIqmz5LZuh6e3q9QwC1ii17ruTZBAAxZXUcH1uX13MhJtaKEvT1pnnGFAA7eRT +VIvN27R6uTVzBsJKe+qEwxr5ozN5g3JqpM0yF+lQtUWTw9g7JQnw4a/78/qDSnHm +DnBb8zDccAZiyyhPAzUunrlEIAfTZObrx8xbQRNuRbv55jgcBLwqVUbr0b9Tvx5y +lM/jqrCYQee1ayNpaHLqYgk8/e2zicM1NBwTLSTITwaaDIJL/YHkDo13UAhGZ3D/ +P9tIGCcGea4zoVhzkakCfSHMR+WUad4Y2sG9wR46S5SDuVUQgrEGgMBFykMYfSCL +Xs8h6flD5BfaE4FKeQcwfZvaFxieV/5hW6y7splL10pRbmsOleK1rn+aoA9b2Lo7 +HFLbaDMjkOOTGsAxlWMJcbOoPKzsX+4VsUU7/dAMK8QDMGuJOBZ3uX5ESIHj23qv +oZ3KmreW/VsAlU7J0n7ElQIEbBuqcw/xPZfJpG364Tm9V560JXmNKIlsGbUsT3zA +NV68EOZlcSXEGOIr8adC7geHtL+cJ8TejiUIFjDWIcJaU0Gg6nFeyY1jhBnEBVxm +Qg9KT2vKloSlCNyMki0kwXld2l48SG8CAwEAATANBgkqhkiG9w0BAQUFAAOCAgEA +VkmvTg1DbX0/70A+UijAk+GwJpOak3FaKwgF1zFu1zVQGOnPXxeOa2B7cffgXxSq +CNccHB3yZyU0skxRkRLjyHRTgtObvH3KmEDMUSBlH06hKzOGbnhhNFkJGmXSEN/Y +4QytlApqP3Ugpu931xtS3JDfSwwQCbdoUU5Jnn4pRgcGXZI9jTP8wz9w3YVvFUn3 +7bF5WEwtbtMh9Umtx5EpZa9CFAnABY+qU18kucHwu7wtucIaEyPdTT9I0H8Wzn0P +6elhzJlhru/xaekhZU+Qk1kDwHndLUUfl6/KpLO7gYKqUyAW6rGjgKGuT/0O3Iw6 +yVsz/zmvkUYK6lCMLDY8Kva3Gc0QmRxnO/GcdwAd3WP85V6Nh5bx1Vrsjlro9oZ7 +AZOsUP6ghmNFBGQUl9UsFUNJjYrpZtr8AwYzY4sa46a7nEK1Pe9QxMgGMPXGSwT3 +0PYVXPABgpgEJJTBnejRi7/xsr60qLrEqPlnAWvq8/g6MrEVz2OUPK3c/pBS9xMf +pgo+adR3zgKHtfE7Ud+Vlw19ASZy/5P7pqkFUuCqOKziJMYSTq92SGqry1svPlqo +YvpTNBhgCv4WrgdcgceRMOONNyeo55lR417VtY0wiE6pkzcwU5m1O0MceYndk5Tq +f1fpnAn3GkgKbphiPEU7megH4rdjt2XUa+N/QiRzjyU= +-----END CERTIFICATE----- diff --git a/tools/asterisk/keys/ca.key b/tools/asterisk/keys/ca.key new file mode 100644 index 0000000000..723a1be63c --- /dev/null +++ b/tools/asterisk/keys/ca.key @@ -0,0 +1,54 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: DES-EDE3-CBC,2C7C01E8719BBC40 + +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 +-----END RSA PRIVATE KEY----- diff --git a/tools/asterisk/keys/testphone1.crt b/tools/asterisk/keys/testphone1.crt new file mode 100644 index 0000000000..c4bdbc7b83 --- /dev/null +++ b/tools/asterisk/keys/testphone1.crt @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDZjCCAU4CAQEwDQYJKoZIhvcNAQEFBQAwQDEcMBoGA1UEAxMTQXN0ZXJpc2sg +UHJpdmF0ZSBDQTEgMB4GA1UEChMXVGVzdCBTYXZvaXItRmFpcmUgTGludXgwHhcN +MTIwNDE2MjExMDQ3WhcNMTMwNDE2MjExMDQ3WjA2MRIwEAYDVQQDEwkxMjcuMC4w +LjExIDAeBgNVBAoTF1Rlc3QgU2F2b2lyLUZhaXJlIExpbnV4MIGfMA0GCSqGSIb3 +DQEBAQUAA4GNADCBiQKBgQDcD0nVaHoNVpEVLTyN0JOVuEDUZovaMK/XXmQYnP1M +v9Ru5fU4977DpPQdjqmTSv+VD83QGbUnORdjDSgK8vAmWLnZxJJxxF1pTpbKpKDp +FO9f74xhh/ekJCWPqfeJqRbjJ5rhXqqbWPtjuI1vXgwBL7MdWQVAuzgz9+kHITxj +tQIDAQABMA0GCSqGSIb3DQEBBQUAA4ICAQCqs3JJxwXqiRksCf7fGA6mt19Kp6EJ +kYv7tA2xO0qR6dHX5pE6IQ2bk22jlIHsg269oX4XAtg1OiGMCyLOfrDOve86+qlW +7+sARNmtDXHT1kQPa8FJbwmdS5Gl4PXzFrRrB6hDlBBtKu5CEMkHuscjpnt9BPA5 +RvXhb3MREs9n0VM0R8hhtgiEVPOLKZpfASSLU8iiTurBdybSAb1hPLRwJF+PIak8 +pohqbEo2lNZq/OdxEIvOnNO7UISfYcojmFFUatPgl35O3hTRA6/RIkPtdXOtTqzX +agZDgHvn2tnkmSPfO9Zx/8KKa9u1XyXBdNXjdoq0cEaKv7sYCKzPGPbuvoemSbmz +6u1CrZY+IkFDPLe5mkUBMzNFuHYpy5Xfafs8zo2F+NEpHHKT4PhVFWKQ+FEFoRgi +P70oA0mXtjoSIhcZiYkbwV5algr7aWP1pAO7A6mjT4SQpk/LSN96eBvy5Llkgety +Fl+TZizCKMN+PVELgMjQwkHGNDFCyrxGS9j5SSvYj8lIIqlqyOkpsLarfK73JEfZ +Ad52SK+2/6LamoQbouwVVx7grDEtP+jfqYlr4Mrif8IgoDw9vHSVtlDZ6o5kJh7y +ZWQz0EDQJXlxnKb4UkftilOUoDPinc1nkRvk6s/DQ6JrHDKYaVyHckjj5xEiqzuP +USftzxBPTlP4dA== +-----END CERTIFICATE----- diff --git a/tools/asterisk/keys/testphone1.csr b/tools/asterisk/keys/testphone1.csr new file mode 100644 index 0000000000..7599b6114a --- /dev/null +++ b/tools/asterisk/keys/testphone1.csr @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBdTCB3wIBADA2MRIwEAYDVQQDEwkxMjcuMC4wLjExIDAeBgNVBAoTF1Rlc3Qg +U2F2b2lyLUZhaXJlIExpbnV4MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDc +D0nVaHoNVpEVLTyN0JOVuEDUZovaMK/XXmQYnP1Mv9Ru5fU4977DpPQdjqmTSv+V +D83QGbUnORdjDSgK8vAmWLnZxJJxxF1pTpbKpKDpFO9f74xhh/ekJCWPqfeJqRbj +J5rhXqqbWPtjuI1vXgwBL7MdWQVAuzgz9+kHITxjtQIDAQABoAAwDQYJKoZIhvcN +AQEFBQADgYEAyqOCNC90ONkad55VRJDnxxhGDtOHlmy/87+5XF09luqF2i2aRjV5 +sY8fkHmkcMH0ppMTakrTxLsfJUJ6k5CCg3zXRzUB8Eg89gmPQuPEbBWEXm5ahZIS +ceJUoOTSfNAfiYaTTaap2dCTb4ENrOyyu6WMbH22FBgr/OQrRB9NLS8= +-----END CERTIFICATE REQUEST----- diff --git a/tools/asterisk/keys/testphone1.key b/tools/asterisk/keys/testphone1.key new file mode 100644 index 0000000000..8562655775 --- /dev/null +++ b/tools/asterisk/keys/testphone1.key @@ -0,0 +1,15 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDcD0nVaHoNVpEVLTyN0JOVuEDUZovaMK/XXmQYnP1Mv9Ru5fU4 +977DpPQdjqmTSv+VD83QGbUnORdjDSgK8vAmWLnZxJJxxF1pTpbKpKDpFO9f74xh +h/ekJCWPqfeJqRbjJ5rhXqqbWPtjuI1vXgwBL7MdWQVAuzgz9+kHITxjtQIDAQAB +AoGAEJIaJjbK0gxNuoGAiNFG+8Q3JYdfSpvV8erKsMvJiyj6zysDzzlgpQzb0Qn4 +HP5NxqS9A8mbyYtUBoJsHH70tFrBufsr/ewWL9W/o5SuLOVk4mtGl5aALQSa9OO8 +ue/UwOG5Wnbggdl/RqNlAtqROPfraKmK7RJxHU1MqIUs27UCQQDz2p+y5O8N+NH8 +LRDMK9Y/pk1KAKVwj20escn+Km9NThjAeF6ejhP8RcAXUdxyxPQmktc74udnGlfk +RqVa68u7AkEA5wVDq8xEaEO08g7F6msiuqImnCfvfn5j52sVXbLEh4fOlHX3gfep +B0QURCLrsq28ACHaD+AtXzVEr5IaTOi/TwJBALw4PlXdwOre6G2l9zYwi+F7ImMB +VrEn84jin8+vv1NC+XXuMtJdRe3NhLQ7OlXX0b/ITZtqy0PYoIiRQuaH5CsCQQDB +tUBQxS523o7SiGCbdsngBCaruTCvt/q9CKUZs9PmcJFfGqs2Zxtr5EG6AC3x3ItO +8ROPTEG/G0NElBVJd78xAkEAmHAZd/+5qcBlz7BLLcD1Rc598gRxaOnMNkjHw15i +KOyDNgk5m4OtZ5npbTJDrEW1wjw912ga0jb2J/urCl1X4A== +-----END RSA PRIVATE KEY----- diff --git a/tools/asterisk/keys/testphone1.pem b/tools/asterisk/keys/testphone1.pem new file mode 100644 index 0000000000..f704ed61e7 --- /dev/null +++ b/tools/asterisk/keys/testphone1.pem @@ -0,0 +1,36 @@ +-----BEGIN RSA PRIVATE KEY----- +MIICXgIBAAKBgQDcD0nVaHoNVpEVLTyN0JOVuEDUZovaMK/XXmQYnP1Mv9Ru5fU4 +977DpPQdjqmTSv+VD83QGbUnORdjDSgK8vAmWLnZxJJxxF1pTpbKpKDpFO9f74xh +h/ekJCWPqfeJqRbjJ5rhXqqbWPtjuI1vXgwBL7MdWQVAuzgz9+kHITxjtQIDAQAB +AoGAEJIaJjbK0gxNuoGAiNFG+8Q3JYdfSpvV8erKsMvJiyj6zysDzzlgpQzb0Qn4 +HP5NxqS9A8mbyYtUBoJsHH70tFrBufsr/ewWL9W/o5SuLOVk4mtGl5aALQSa9OO8 +ue/UwOG5Wnbggdl/RqNlAtqROPfraKmK7RJxHU1MqIUs27UCQQDz2p+y5O8N+NH8 +LRDMK9Y/pk1KAKVwj20escn+Km9NThjAeF6ejhP8RcAXUdxyxPQmktc74udnGlfk +RqVa68u7AkEA5wVDq8xEaEO08g7F6msiuqImnCfvfn5j52sVXbLEh4fOlHX3gfep +B0QURCLrsq28ACHaD+AtXzVEr5IaTOi/TwJBALw4PlXdwOre6G2l9zYwi+F7ImMB +VrEn84jin8+vv1NC+XXuMtJdRe3NhLQ7OlXX0b/ITZtqy0PYoIiRQuaH5CsCQQDB +tUBQxS523o7SiGCbdsngBCaruTCvt/q9CKUZs9PmcJFfGqs2Zxtr5EG6AC3x3ItO +8ROPTEG/G0NElBVJd78xAkEAmHAZd/+5qcBlz7BLLcD1Rc598gRxaOnMNkjHw15i +KOyDNgk5m4OtZ5npbTJDrEW1wjw912ga0jb2J/urCl1X4A== +-----END RSA PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIDZjCCAU4CAQEwDQYJKoZIhvcNAQEFBQAwQDEcMBoGA1UEAxMTQXN0ZXJpc2sg +UHJpdmF0ZSBDQTEgMB4GA1UEChMXVGVzdCBTYXZvaXItRmFpcmUgTGludXgwHhcN +MTIwNDE2MjExMDQ3WhcNMTMwNDE2MjExMDQ3WjA2MRIwEAYDVQQDEwkxMjcuMC4w +LjExIDAeBgNVBAoTF1Rlc3QgU2F2b2lyLUZhaXJlIExpbnV4MIGfMA0GCSqGSIb3 +DQEBAQUAA4GNADCBiQKBgQDcD0nVaHoNVpEVLTyN0JOVuEDUZovaMK/XXmQYnP1M +v9Ru5fU4977DpPQdjqmTSv+VD83QGbUnORdjDSgK8vAmWLnZxJJxxF1pTpbKpKDp +FO9f74xhh/ekJCWPqfeJqRbjJ5rhXqqbWPtjuI1vXgwBL7MdWQVAuzgz9+kHITxj +tQIDAQABMA0GCSqGSIb3DQEBBQUAA4ICAQCqs3JJxwXqiRksCf7fGA6mt19Kp6EJ +kYv7tA2xO0qR6dHX5pE6IQ2bk22jlIHsg269oX4XAtg1OiGMCyLOfrDOve86+qlW +7+sARNmtDXHT1kQPa8FJbwmdS5Gl4PXzFrRrB6hDlBBtKu5CEMkHuscjpnt9BPA5 +RvXhb3MREs9n0VM0R8hhtgiEVPOLKZpfASSLU8iiTurBdybSAb1hPLRwJF+PIak8 +pohqbEo2lNZq/OdxEIvOnNO7UISfYcojmFFUatPgl35O3hTRA6/RIkPtdXOtTqzX +agZDgHvn2tnkmSPfO9Zx/8KKa9u1XyXBdNXjdoq0cEaKv7sYCKzPGPbuvoemSbmz +6u1CrZY+IkFDPLe5mkUBMzNFuHYpy5Xfafs8zo2F+NEpHHKT4PhVFWKQ+FEFoRgi +P70oA0mXtjoSIhcZiYkbwV5algr7aWP1pAO7A6mjT4SQpk/LSN96eBvy5Llkgety +Fl+TZizCKMN+PVELgMjQwkHGNDFCyrxGS9j5SSvYj8lIIqlqyOkpsLarfK73JEfZ +Ad52SK+2/6LamoQbouwVVx7grDEtP+jfqYlr4Mrif8IgoDw9vHSVtlDZ6o5kJh7y +ZWQz0EDQJXlxnKb4UkftilOUoDPinc1nkRvk6s/DQ6JrHDKYaVyHckjj5xEiqzuP +USftzxBPTlP4dA== +-----END CERTIFICATE----- diff --git a/tools/asterisk/keys/tmp.cfg b/tools/asterisk/keys/tmp.cfg new file mode 100644 index 0000000000..f9d4477e97 --- /dev/null +++ b/tools/asterisk/keys/tmp.cfg @@ -0,0 +1,7 @@ +[req] +distinguished_name = req_distinguished_name +prompt = no + +[req_distinguished_name] +CN=127.0.0.1 +O=Test Savoir-Faire Linux diff --git a/tools/asterisk/sip.conf b/tools/asterisk/sip.conf new file mode 100644 index 0000000000..5ec53dfb87 --- /dev/null +++ b/tools/asterisk/sip.conf @@ -0,0 +1,1378 @@ +; +; SIP Configuration example for Asterisk +; +; Note: Please read the security documentation for Asterisk in order to +; understand the risks of installing Asterisk with the sample +; configuration. If your Asterisk is installed on a public +; IP address connected to the Internet, you will want to learn +; about the various security settings BEFORE you start +; Asterisk. +; +; Especially note the following settings: +; - allowguest (default enabled) +; - permit/deny - IP address filters +; - contactpermit/contactdeny - IP address filters for registrations +; - context - Which set of services you offer various users +; +; SIP dial strings +;----------------------------------------------------------- +; In the dialplan (extensions.conf) you can use several +; syntaxes for dialing SIP devices. +; SIP/devicename +; SIP/username@domain (SIP uri) +; SIP/username[:password[:md5secret[:authname[:transport]]]]@host[:port] +; SIP/devicename/extension +; SIP/devicename/extension/IPorHost +; SIP/username@domain//IPorHost +; +; +; Devicename +; devicename is defined as a peer in a section below. +; +; username@domain +; Call any SIP user on the Internet +; (Don't forget to enable DNS SRV records if you want to use this) +; +; devicename/extension +; If you define a SIP proxy as a peer below, you may call +; SIP/proxyhostname/user or SIP/user@proxyhostname +; where the proxyhostname is defined in a section below +; This syntax also works with ATA's with FXO ports +; +; SIP/username[:password[:md5secret[:authname]]]@host[:port] +; This form allows you to specify password or md5secret and authname +; without altering any authentication data in config. +; Examples: +; +; SIP/*98@mysipproxy +; SIP/sales:topsecret::account02@domain.com:5062 +; SIP/12345678::bc53f0ba8ceb1ded2b70e05c3f91de4f:myname@192.168.0.1 +; +; IPorHost +; The next server for this call regardless of domain/peer +; +; All of these dial strings specify the SIP request URI. +; In addition, you can specify a specific To: header by adding an +; exclamation mark after the dial string, like +; +; SIP/sales@mysipproxy!sales@edvina.net +; +; A new feature for 1.8 allows one to specify a host or IP address to use +; when routing the call. This is typically used in tandem with func_srv if +; multiple methods of reaching the same domain exist. The host or IP address +; is specified after the third slash in the dialstring. Examples: +; +; SIP/devicename/extension/IPorHost +; SIP/username@domain//IPorHost +; +; CLI Commands +; ------------------------------------------------------------- +; Useful CLI commands to check peers/users: +; sip show peers Show all SIP peers (including friends) +; sip show registry Show status of hosts we register with +; +; sip set debug on Show all SIP messages +; +; sip reload Reload configuration file +; sip show settings Show the current channel configuration +; +;------- Naming devices ------------------------------------------------------ +; +; When naming devices, make sure you understand how Asterisk matches calls +; that come in. +; 1. Asterisk checks the SIP From: address username and matches against +; names of devices with type=user +; The name is the text between square brackets [name] +; 2. Asterisk checks the From: addres and matches the list of devices +; with a type=peer +; 3. Asterisk checks the IP address (and port number) that the INVITE +; was sent from and matches against any devices with type=peer +; +; Don't mix extensions with the names of the devices. Devices need a unique +; name. The device name is *not* used as phone numbers. Phone numbers are +; anything you declare as an extension in the dialplan (extensions.conf). +; +; When setting up trunks, make sure there's no risk that any From: username +; (caller ID) will match any of your device names, because then Asterisk +; might match the wrong device. +; +; Note: The parameter "username" is not the username and in most cases is +; not needed at all. Check below. In later releases, it's renamed +; to "defaultuser" which is a better name, since it is used in +; combination with the "defaultip" setting. +;----------------------------------------------------------------------------- + +; ** Old configuration options ** +; The "call-limit" configuation option is considered old is replaced +; by new functionality. To enable callcounters, you use the new +; "callcounter" setting (for extension states in queue and subscriptions) +; You are encouraged to use the dialplan groupcount functionality +; to enforce call limits instead of using this channel-specific method. +; You can still set limits per device in sip.conf or in a database by using +; "setvar" to set variables that can be used in the dialplan for various limits. + +[general] +context=default ; Default context for incoming calls +;allowguest=no ; Allow or reject guest calls (default is yes) + ; If your Asterisk is connected to the Internet + ; and you have allowguest=yes + ; you want to check which services you offer everyone + ; out there, by enabling them in the default context (see below). +;match_auth_username=yes ; if available, match user entry using the + ; 'username' field from the authentication line + ; instead of the From: field. +allowoverlap=no ; Disable overlap dialing support. (Default is yes) +;allowtransfer=no ; Disable all transfers (unless enabled in peers or users) + ; Default is enabled. The Dial() options 't' and 'T' are not + ; related as to whether SIP transfers are allowed or not. +;realm=mydomain.tld ; Realm for digest authentication + ; defaults to "asterisk". If you set a system name in + ; asterisk.conf, it defaults to that system name + ; Realms MUST be globally unique according to RFC 3261 + ; Set this to your host name or domain name +;domainsasrealm=no ; Use domans list as realms + ; You can serve multiple Realms specifying several + ; 'domain=...' directives (see below). + ; In this case Realm will be based on request 'From'/'To' header + ; and should match one of domain names. + ; Otherwise default 'realm=...' will be used. + +; With the current situation, you can do one of four things: +; a) Listen on a specific IPv4 address. Example: bindaddr=192.0.2.1 +; b) Listen on a specific IPv6 address. Example: bindaddr=2001:db8::1 +; c) Listen on the IPv4 wildcard. Example: bindaddr=0.0.0.0 +; d) Listen on the IPv4 and IPv6 wildcards. Example: bindaddr=:: +; (You can choose independently for UDP, TCP, and TLS, by specifying different values for +; "udpbindaddr", "tcpbindaddr", and "tlsbindaddr".) +; (Note that using bindaddr=:: will show only a single IPv6 socket in netstat. +; IPv4 is supported at the same time using IPv4-mapped IPv6 addresses.) +; +; You may optionally add a port number. (The default is port 5060 for UDP and TCP, 5061 +; for TLS). +; IPv4 example: bindaddr=0.0.0.0:5062 +; IPv6 example: bindaddr=[::]:5062 +; +; The address family of the bound UDP address is used to determine how Asterisk performs +; DNS lookups. In cases a) and c) above, only A records are considered. In case b), only +; AAAA records are considered. In case d), both A and AAAA records are considered. Note, +; however, that Asterisk ignores all records except the first one. In case d), when both A +; and AAAA records are available, either an A or AAAA record will be first, and which one +; depends on the operating system. On systems using glibc, AAAA records are given +; priority. + +udpbindaddr=0.0.0.0:5062 ; IP address to bind UDP listen socket to (0.0.0.0 binds to all) + ; Optionally add a port number, 192.168.1.1:5062 (default is port 5060) + +; When a dialog is started with another SIP endpoint, the other endpoint +; should include an Allow header telling us what SIP methods the endpoint +; implements. However, some endpoints either do not include an Allow header +; or lie about what methods they implement. In the former case, Asterisk +; makes the assumption that the endpoint supports all known SIP methods. +; If you know that your SIP endpoint does not provide support for a specific +; method, then you may provide a comma-separated list of methods that your +; endpoint does not implement in the disallowed_methods option. Note that +; if your endpoint is truthful with its Allow header, then there is no need +; to set this option. This option may be set in the general section or may +; be set per endpoint. If this option is set both in the general section and +; in a peer section, then the peer setting completely overrides the general +; setting (i.e. the result is *not* the union of the two options). +; +; Note also that while Asterisk currently will parse an Allow header to learn +; what methods an endpoint supports, the only actual use for this currently +; is for determining if Asterisk may send connected line UPDATE requests. Its +; use may be expanded in the future. +; +; disallowed_methods = UPDATE + +; +; Note that the TCP and TLS support for chan_sip is currently considered +; experimental. Since it is new, all of the related configuration options are +; subject to change in any release. If they are changed, the changes will +; be reflected in this sample configuration file, as well as in the UPGRADE.txt file. +; +tcpenable=no ; Enable server for incoming TCP connections (default is no) +tcpbindaddr=0.0.0.0 ; IP address for TCP server to bind to (0.0.0.0 binds to all interfaces) + ; Optionally add a port number, 192.168.1.1:5062 (default is port 5060) + +tlsenable=yes ; Enable server for incoming TLS (secure) connections (default is no) +tlsbindaddr=0.0.0.0:5061 ; IP address for TLS server to bind to (0.0.0.0) binds to all interfaces) + ; Optionally add a port number, 192.168.1.1:5063 (default is port 5061) + ; Remember that the IP address must match the common name (hostname) in the + ; certificate, so you don't want to bind a TLS socket to multiple IP addresses. + ; For details how to construct a certificate for SIP see + ; http://tools.ietf.org/html/draft-ietf-sip-domain-certs + +;tcpauthtimeout = 30 ; tcpauthtimeout specifies the maximum number + ; of seconds a client has to authenticate. If + ; the client does not authenticate beofre this + ; timeout expires, the client will be + ; disconnected. (default: 30 seconds) + +;tcpauthlimit = 100 ; tcpauthlimit specifies the maximum number of + ; unauthenticated sessions that will be allowed + ; to connect at any given time. (default: 100) + +;srvlookup=yes ; Enable DNS SRV lookups on outbound calls + ; Note: Asterisk only uses the first host + ; in SRV records + ; Disabling DNS SRV lookups disables the + ; ability to place SIP calls based on domain + ; names to some other SIP users on the Internet + ; Specifying a port in a SIP peer definition or + ; when dialing outbound calls will supress SRV + ; lookups for that peer or call. + +;pedantic=yes ; Enable checking of tags in headers, + ; international character conversions in URIs + ; and multiline formatted headers for strict + ; SIP compatibility (defaults to "yes") + +; See https://wiki.asterisk.org/wiki/display/AST/IP+Quality+of+Service for a description of these parameters. +;tos_sip=cs3 ; Sets TOS for SIP packets. +;tos_audio=ef ; Sets TOS for RTP audio packets. +;tos_video=af41 ; Sets TOS for RTP video packets. +;tos_text=af41 ; Sets TOS for RTP text packets. + +;cos_sip=3 ; Sets 802.1p priority for SIP packets. +;cos_audio=5 ; Sets 802.1p priority for RTP audio packets. +;cos_video=4 ; Sets 802.1p priority for RTP video packets. +;cos_text=3 ; Sets 802.1p priority for RTP text packets. + +;maxexpiry=3600 ; Maximum allowed time of incoming registrations + ; and subscriptions (seconds) +;minexpiry=60 ; Minimum length of registrations/subscriptions (default 60) +;defaultexpiry=120 ; Default length of incoming/outgoing registration +;mwiexpiry=3600 ; Expiry time for outgoing MWI subscriptions +;maxforwards=70 ; Setting for the SIP Max-Forwards: header (loop prevention) + ; Default value is 70 +;qualifyfreq=60 ; Qualification: How often to check for the host to be up in seconds + ; and reported in milliseconds with sip show settings. + ; Set to low value if you use low timeout for NAT of UDP sessions + ; Default: 60 +;qualifygap=100 ; Number of milliseconds between each group of peers being qualified + ; Default: 100 +;qualifypeers=1 ; Number of peers in a group to be qualified at the same time + ; Default: 1 +;notifymimetype=text/plain ; Allow overriding of mime type in MWI NOTIFY +;buggymwi=no ; Cisco SIP firmware doesn't support the MWI RFC + ; fully. Enable this option to not get error messages + ; when sending MWI to phones with this bug. +;mwi_from=asterisk ; When sending MWI NOTIFY requests, use this setting in + ; the From: header as the "name" portion. Also fill the + ; "user" portion of the URI in the From: header with this + ; value if no fromuser is set + ; Default: empty +;vmexten=voicemail ; dialplan extension to reach mailbox sets the + ; Message-Account in the MWI notify message + ; defaults to "asterisk" + +; Codec negotiation +; +; When Asterisk is receiving a call, the codec will initially be set to the +; first codec in the allowed codecs defined for the user receiving the call +; that the caller also indicates that it supports. But, after the caller +; starts sending RTP, Asterisk will switch to using whatever codec the caller +; is sending. +; +; When Asterisk is placing a call, the codec used will be the first codec in +; the allowed codecs that the callee indicates that it supports. Asterisk will +; *not* switch to whatever codec the callee is sending. +; +;preferred_codec_only=yes ; Respond to a SIP invite with the single most preferred codec + ; rather than advertising all joint codec capabilities. This + ; limits the other side's codec choice to exactly what we prefer. + +;disallow=all ; First disallow all codecs +;allow=ulaw ; Allow codecs in order of preference +;allow=ilbc ; see https://wiki.asterisk.org/wiki/display/AST/RTP+Packetization + ; for framing options +; +; This option specifies a preference for which music on hold class this channel +; should listen to when put on hold if the music class has not been set on the +; channel with Set(CHANNEL(musicclass)=whatever) in the dialplan, and the peer +; channel putting this one on hold did not suggest a music class. +; +; This option may be specified globally, or on a per-user or per-peer basis. +; +;mohinterpret=default +; +; This option specifies which music on hold class to suggest to the peer channel +; when this channel places the peer on hold. It may be specified globally or on +; a per-user or per-peer basis. +; +;mohsuggest=default +; +;parkinglot=plaza ; Sets the default parking lot for call parking + ; This may also be set for individual users/peers + ; Parkinglots are configured in features.conf +;language=en ; Default language setting for all users/peers + ; This may also be set for individual users/peers +;relaxdtmf=yes ; Relax dtmf handling +;trustrpid = no ; If Remote-Party-ID should be trusted +;sendrpid = yes ; If Remote-Party-ID should be sent (defaults to no) +;sendrpid = rpid ; Use the "Remote-Party-ID" header + ; to send the identity of the remote party + ; This is identical to sendrpid=yes +;sendrpid = pai ; Use the "P-Asserted-Identity" header + ; to send the identity of the remote party +;rpid_update = no ; In certain cases, the only method by which a connected line + ; change may be immediately transmitted is with a SIP UPDATE request. + ; If communicating with another Asterisk server, and you wish to be able + ; transmit such UPDATE messages to it, then you must enable this option. + ; Otherwise, we will have to wait until we can send a reinvite to + ; transmit the information. +;prematuremedia=no ; Some ISDN links send empty media frames before + ; the call is in ringing or progress state. The SIP + ; channel will then send 183 indicating early media + ; which will be empty - thus users get no ring signal. + ; Setting this to "yes" will stop any media before we have + ; call progress (meaning the SIP channel will not send 183 Session + ; Progress for early media). Default is "yes". Also make sure that + ; the SIP peer is configured with progressinband=never. + ; + ; In order for "noanswer" applications to work, you need to run + ; the progress() application in the priority before the app. + +;progressinband=never ; If we should generate in-band ringing always + ; use 'never' to never use in-band signalling, even in cases + ; where some buggy devices might not render it + ; Valid values: yes, no, never Default: never +;useragent=Asterisk PBX ; Allows you to change the user agent string + ; The default user agent string also contains the Asterisk + ; version. If you don't want to expose this, change the + ; useragent string. +;promiscredir = no ; If yes, allows 302 or REDIR to non-local SIP address + ; Note that promiscredir when redirects are made to the + ; local system will cause loops since Asterisk is incapable + ; of performing a "hairpin" call. +;usereqphone = no ; If yes, ";user=phone" is added to uri that contains + ; a valid phone number +;dtmfmode = rfc2833 ; Set default dtmfmode for sending DTMF. Default: rfc2833 + ; Other options: + ; info : SIP INFO messages (application/dtmf-relay) + ; shortinfo : SIP INFO messages (application/dtmf) + ; inband : Inband audio (requires 64 kbit codec -alaw, ulaw) + ; auto : Use rfc2833 if offered, inband otherwise + +;compactheaders = yes ; send compact sip headers. +; +;videosupport=yes ; Turn on support for SIP video. You need to turn this + ; on in this section to get any video support at all. + ; You can turn it off on a per peer basis if the general + ; video support is enabled, but you can't enable it for + ; one peer only without enabling in the general section. + ; If you set videosupport to "always", then RTP ports will + ; always be set up for video, even on clients that don't + ; support it. This assists callfile-derived calls and + ; certain transferred calls to use always use video when + ; available. [yes|NO|always] + +;maxcallbitrate=384 ; Maximum bitrate for video calls (default 384 kb/s) + ; Videosupport and maxcallbitrate is settable + ; for peers and users as well +;callevents=no ; generate manager events when sip ua + ; performs events (e.g. hold) +;authfailureevents=no ; generate manager "peerstatus" events when peer can't + ; authenticate with Asterisk. Peerstatus will be "rejected". +;alwaysauthreject = yes ; When an incoming INVITE or REGISTER is to be rejected, + ; for any reason, always reject with an identical response + ; equivalent to valid username and invalid password/hash + ; instead of letting the requester know whether there was + ; a matching user or peer for their request. This reduces + ; the ability of an attacker to scan for valid SIP usernames. + ; This option is set to "yes" by default. + +;auth_options_requests = yes ; Enabling this option will authenticate OPTIONS requests just like + ; INVITE requests are. By default this option is disabled. + +;g726nonstandard = yes ; If the peer negotiates G726-32 audio, use AAL2 packing + ; order instead of RFC3551 packing order (this is required + ; for Sipura and Grandstream ATAs, among others). This is + ; contrary to the RFC3551 specification, the peer _should_ + ; be negotiating AAL2-G726-32 instead :-( +;outboundproxy=proxy.provider.domain ; send outbound signaling to this proxy, not directly to the devices +;outboundproxy=proxy.provider.domain:8080 ; send outbound signaling to this proxy, not directly to the devices +;outboundproxy=proxy.provider.domain,force ; Send ALL outbound signalling to proxy, ignoring route: headers +;outboundproxy=tls://proxy.provider.domain ; same as '=proxy.provider.domain' except we try to connect with tls +;outboundproxy=192.0.2.1 ; IPv4 address literal (default port is 5060) +;outboundproxy=2001:db8::1 ; IPv6 address literal (default port is 5060) +;outboundproxy=192.168.0.2.1:5062 ; IPv4 address literal with explicit port +;outboundproxy=[2001:db8::1]:5062 ; IPv6 address literal with explicit port +; ; (could also be tcp,udp) - defining transports on the proxy line only +; ; applies for the global proxy, otherwise use the transport= option +;matchexternaddrlocally = yes ; Only substitute the externaddr or externhost setting if it matches + ; your localnet setting. Unless you have some sort of strange network + ; setup you will not need to enable this. + +;dynamic_exclude_static = yes ; Disallow all dynamic hosts from registering + ; as any IP address used for staticly defined + ; hosts. This helps avoid the configuration + ; error of allowing your users to register at + ; the same address as a SIP provider. + +;contactdeny=0.0.0.0/0.0.0.0 ; Use contactpermit and contactdeny to +;contactpermit=172.16.0.0/255.255.0.0 ; restrict at what IPs your users may + ; register their phones. + +;engine=asterisk ; RTP engine to use when communicating with the device + +; +; If regcontext is specified, Asterisk will dynamically create and destroy a +; NoOp priority 1 extension for a given peer who registers or unregisters with +; us and have a "regexten=" configuration item. +; Multiple contexts may be specified by separating them with '&'. The +; actual extension is the 'regexten' parameter of the registering peer or its +; name if 'regexten' is not provided. If more than one context is provided, +; the context must be specified within regexten by appending the desired +; context after '@'. More than one regexten may be supplied if they are +; separated by '&'. Patterns may be used in regexten. +; +;regcontext=sipregistrations +;regextenonqualify=yes ; Default "no" + ; If you have qualify on and the peer becomes unreachable + ; this setting will enforce inactivation of the regexten + ; extension for the peer + +; The shrinkcallerid function removes '(', ' ', ')', non-trailing '.', and '-' not +; in square brackets. For example, the caller id value 555.5555 becomes 5555555 +; when this option is enabled. Disabling this option results in no modification +; of the caller id value, which is necessary when the caller id represents something +; that must be preserved. This option can only be used in the [general] section. +; By default this option is on. +; +;shrinkcallerid=yes ; on by default + + +;use_q850_reason = no ; Default "no" + ; Set to yes add Reason header and use Reason header if it is available. +; +;------------------------ TLS settings ------------------------------------------------------------ +tlscertfile=/etc/asterisk/keys/asterisk.pem ; Certificate file (*.pem format only) to use for TLS connections + ; default is to look for "asterisk.pem" in current directory + +; tlsprivatekey=/etc/asterisk/keys/asterisk.key ; Private key file (*.pem format only) for TLS connections. + ; If no tlsprivatekey is specified, tlscertfile is searched for + ; for both public and private key. + +; tlscafile=/etc/asterisk/keys/ca.crt +; If the server your connecting to uses a self signed certificate +; you should have their certificate installed here so the code can +; verify the authenticity of their certificate. + +; tlscapath=/etc/asterisk/keys/ +; A directory full of CA certificates. The files must be named with +; the CA subject name hash value. +; (see man SSL_CTX_load_verify_locations for more info) + +; tlsdontverifyserver=[yes|no] +; If set to yes, don't verify the servers certificate when acting as +; a client. If you don't have the server's CA certificate you can +; set this and it will connect without requiring tlscafile to be set. +; Default is no. + +; tlscipher=ALL +; A string specifying which SSL ciphers to use or not use +; A list of valid SSL cipher strings can be found at: +; http://www.openssl.org/docs/apps/ciphers.html#CIPHER_STRINGS +; +; tlsclientmethod=tlsv1 ; values include tlsv1, sslv3, sslv2. + ; Specify protocol for outbound client connections. + ; If left unspecified, the default is sslv2. +; +;--------------------------- SIP timers ---------------------------------------------------- +; These timers are used primarily in INVITE transactions. +; The default for Timer T1 is 500 ms or the measured run-trip time between +; Asterisk and the device if you have qualify=yes for the device. +; +;t1min=100 ; Minimum roundtrip time for messages to monitored hosts + ; Defaults to 100 ms +;timert1=500 ; Default T1 timer + ; Defaults to 500 ms or the measured round-trip + ; time to a peer (qualify=yes). +;timerb=32000 ; Call setup timer. If a provisional response is not received + ; in this amount of time, the call will autocongest + ; Defaults to 64*timert1 + +;--------------------------- RTP timers ---------------------------------------------------- +; These timers are currently used for both audio and video streams. The RTP timeouts +; are only applied to the audio channel. +; The settings are settable in the global section as well as per device +; +;rtptimeout=60 ; Terminate call if 60 seconds of no RTP or RTCP activity + ; on the audio channel + ; when we're not on hold. This is to be able to hangup + ; a call in the case of a phone disappearing from the net, + ; like a powerloss or grandma tripping over a cable. +;rtpholdtimeout=300 ; Terminate call if 300 seconds of no RTP or RTCP activity + ; on the audio channel + ; when we're on hold (must be > rtptimeout) +;rtpkeepalive=<secs> ; Send keepalives in the RTP stream to keep NAT open + ; (default is off - zero) + +;--------------------------- SIP Session-Timers (RFC 4028)------------------------------------ +; SIP Session-Timers provide an end-to-end keep-alive mechanism for active SIP sessions. +; This mechanism can detect and reclaim SIP channels that do not terminate through normal +; signaling procedures. Session-Timers can be configured globally or at a user/peer level. +; The operation of Session-Timers is driven by the following configuration parameters: +; +; * session-timers - Session-Timers feature operates in the following three modes: +; originate : Request and run session-timers always +; accept : Run session-timers only when requested by other UA +; refuse : Do not run session timers in any case +; The default mode of operation is 'accept'. +; * session-expires - Maximum session refresh interval in seconds. Defaults to 1800 secs. +; * session-minse - Minimum session refresh interval in seconds. Defualts to 90 secs. +; * session-refresher - The session refresher (uac|uas). Defaults to 'uas'. +; +;session-timers=originate +;session-expires=600 +;session-minse=90 +;session-refresher=uas +; +;--------------------------- SIP DEBUGGING --------------------------------------------------- +;sipdebug = yes ; Turn on SIP debugging by default, from + ; the moment the channel loads this configuration +;recordhistory=yes ; Record SIP history by default + ; (see sip history / sip no history) +;dumphistory=yes ; Dump SIP history at end of SIP dialogue + ; SIP history is output to the DEBUG logging channel + + +;--------------------------- STATUS NOTIFICATIONS (SUBSCRIPTIONS) ---------------------------- +; You can subscribe to the status of extensions with a "hint" priority +; (See extensions.conf.sample for examples) +; chan_sip support two major formats for notifications: dialog-info and SIMPLE +; +; You will get more detailed reports (busy etc) if you have a call counter enabled +; for a device. +; +; If you set the busylevel, we will indicate busy when we have a number of calls that +; matches the busylevel treshold. +; +; For queues, you will need this level of detail in status reporting, regardless +; if you use SIP subscriptions. Queues and manager use the same internal interface +; for reading status information. +; +; Note: Subscriptions does not work if you have a realtime dialplan and use the +; realtime switch. +; +;allowsubscribe=no ; Disable support for subscriptions. (Default is yes) +;subscribecontext = default ; Set a specific context for SUBSCRIBE requests + ; Useful to limit subscriptions to local extensions + ; Settable per peer/user also +;notifyringing = no ; Control whether subscriptions already INUSE get sent + ; RINGING when another call is sent (default: yes) +;notifyhold = yes ; Notify subscriptions on HOLD state (default: no) + ; Turning on notifyringing and notifyhold will add a lot + ; more database transactions if you are using realtime. +;notifycid = yes ; Control whether caller ID information is sent along with + ; dialog-info+xml notifications (supported by snom phones). + ; Note that this feature will only work properly when the + ; incoming call is using the same extension and context that + ; is being used as the hint for the called extension. This means + ; that it won't work when using subscribecontext for your sip + ; user or peer (if subscribecontext is different than context). + ; This is also limited to a single caller, meaning that if an + ; extension is ringing because multiple calls are incoming, + ; only one will be used as the source of caller ID. Specify + ; 'ignore-context' to ignore the called context when looking + ; for the caller's channel. The default value is 'no.' Setting + ; notifycid to 'ignore-context' also causes call-pickups attempted + ; via SNOM's NOTIFY mechanism to set the context for the call pickup + ; to PICKUPMARK. +;callcounter = yes ; Enable call counters on devices. This can be set per + ; device too. + +;----------------------------------------- T.38 FAX SUPPORT ---------------------------------- +; +; This setting is available in the [general] section as well as in device configurations. +; Setting this to yes enables T.38 FAX (UDPTL) on SIP calls; it defaults to off. +; +; t38pt_udptl = yes ; Enables T.38 with FEC error correction. +; t38pt_udptl = yes,fec ; Enables T.38 with FEC error correction. +; t38pt_udptl = yes,redundancy ; Enables T.38 with redundancy error correction. +; t38pt_udptl = yes,none ; Enables T.38 with no error correction. +; +; In some cases, T.38 endpoints will provide a T38FaxMaxDatagram value (during T.38 setup) that +; is based on an incorrect interpretation of the T.38 recommendation, and results in failures +; because Asterisk does not believe it can send T.38 packets of a reasonable size to that +; endpoint (Cisco media gateways are one example of this situation). In these cases, during a +; T.38 call you will see warning messages on the console/in the logs from the Asterisk UDPTL +; stack complaining about lack of buffer space to send T.38 FAX packets. If this occurs, you +; can set an override (globally, or on a per-device basis) to make Asterisk ignore the +; T38FaxMaxDatagram value specified by the other endpoint, and use a configured value instead. +; This can be done by appending 'maxdatagram=<value>' to the t38pt_udptl configuration option, +; like this: +; +; t38pt_udptl = yes,fec,maxdatagram=400 ; Enables T.38 with FEC error correction and overrides +; ; the other endpoint's provided value to assume we can +; ; send 400 byte T.38 FAX packets to it. +; +; FAX detection will cause the SIP channel to jump to the 'fax' extension (if it exists) +; based one or more events being detected. The events that can be detected are an incoming +; CNG tone or an incoming T.38 re-INVITE request. +; +; faxdetect = yes ; Default 'no', 'yes' enables both CNG and T.38 detection +; faxdetect = cng ; Enables only CNG detection +; faxdetect = t38 ; Enables only T.38 detection +; +;----------------------------------------- OUTBOUND SIP REGISTRATIONS ------------------------ +; Asterisk can register as a SIP user agent to a SIP proxy (provider) +; Format for the register statement is: +; register => [peer?][transport://]user[@domain][:secret[:authuser]]@host[:port][/extension][~expiry] +; +; +; +; domain is either +; - domain in DNS +; - host name in DNS +; - the name of a peer defined below or in realtime +; The domain is where you register your username, so your SIP uri you are registering to +; is username@domain +; +; If no extension is given, the 's' extension is used. The extension needs to +; be defined in extensions.conf to be able to accept calls from this SIP proxy +; (provider). +; +; A similar effect can be achieved by adding a "callbackextension" option in a peer section. +; this is equivalent to having the following line in the general section: +; +; register => username:secret@host/callbackextension +; +; and more readable because you don't have to write the parameters in two places +; (note that the "port" is ignored - this is a bug that should be fixed). +; +; Note that a register= line doesn't mean that we will match the incoming call in any +; other way than described above. If you want to control where the call enters your +; dialplan, which context, you want to define a peer with the hostname of the provider's +; server. If the provider has multiple servers to place calls to your system, you need +; a peer for each server. +; +; Beginning with Asterisk version 1.6.2, the "user" portion of the register line may +; contain a port number. Since the logical separator between a host and port number is a +; ':' character, and this character is already used to separate between the optional "secret" +; and "authuser" portions of the line, there is a bit of a hoop to jump through if you wish +; to use a port here. That is, you must explicitly provide a "secret" and "authuser" even if +; they are blank. See the third example below for an illustration. +; +; +; Examples: +; +;register => 1234:password@mysipprovider.com +; +; This will pass incoming calls to the 's' extension +; +; +;register => 2345:password@sip_proxy/1234 +; +; Register 2345 at sip provider 'sip_proxy'. Calls from this provider +; connect to local extension 1234 in extensions.conf, default context, +; unless you configure a [sip_proxy] section below, and configure a +; context. +; Tip 1: Avoid assigning hostname to a sip.conf section like [provider.com] +; Tip 2: Use separate inbound and outbound sections for SIP providers +; (instead of type=friend) if you have calls in both directions +; +;register => 3456@mydomain:5082::@mysipprovider.com +; +; Note that in this example, the optional authuser and secret portions have +; been left blank because we have specified a port in the user section +; +;register => tls://username:xxxxxx@sip-tls-proxy.example.org +; +; The 'transport' part defaults to 'udp' but may also be 'tcp' or 'tls'. +; Using 'udp://' explicitly is also useful in case the username part +; contains a '/' ('user/name'). + +;registertimeout=20 ; retry registration calls every 20 seconds (default) +;registerattempts=10 ; Number of registration attempts before we give up + ; 0 = continue forever, hammering the other server + ; until it accepts the registration + ; Default is 0 tries, continue forever + +;----------------------------------------- OUTBOUND MWI SUBSCRIPTIONS ------------------------- +; Asterisk can subscribe to receive the MWI from another SIP server and store it locally for retrieval +; by other phones. At this time, you can only subscribe using UDP as the transport. +; Format for the mwi register statement is: +; mwi => user[:secret[:authuser]]@host[:port]/mailbox +; +; Examples: +;mwi => 1234:password@mysipprovider.com/1234 +;mwi => 1234:password@myportprovider.com:6969/1234 +;mwi => 1234:password:authuser@myauthprovider.com/1234 +;mwi => 1234:password:authuser@myauthportprovider.com:6969/1234 +; +; MWI received will be stored in the 1234 mailbox of the SIP_Remote context. It can be used by other phones by following the below: +; mailbox=1234@SIP_Remote +;----------------------------------------- NAT SUPPORT ------------------------ +; +; WARNING: SIP operation behind a NAT is tricky and you really need +; to read and understand well the following section. +; +; When Asterisk is behind a NAT device, the "local" address (and port) that +; a socket is bound to has different values when seen from the inside or +; from the outside of the NATted network. Unfortunately this address must +; be communicated to the outside (e.g. in SIP and SDP messages), and in +; order to determine the correct value Asterisk needs to know: +; +; + whether it is talking to someone "inside" or "outside" of the NATted network. +; This is configured by assigning the "localnet" parameter with a list +; of network addresses that are considered "inside" of the NATted network. +; IF LOCALNET IS NOT SET, THE EXTERNAL ADDRESS WILL NOT BE SET CORRECTLY. +; Multiple entries are allowed, e.g. a reasonable set is the following: +; +; localnet=192.168.0.0/255.255.0.0 ; RFC 1918 addresses +; localnet=10.0.0.0/255.0.0.0 ; Also RFC1918 +; localnet=172.16.0.0/12 ; Another RFC1918 with CIDR notation +; localnet=169.254.0.0/255.255.0.0 ; Zero conf local network +; +; + the "externally visible" address and port number to be used when talking +; to a host outside the NAT. This information is derived by one of the +; following (mutually exclusive) config file parameters: +; +; a. "externaddr = hostname[:port]" specifies a static address[:port] to +; be used in SIP and SDP messages. +; The hostname is looked up only once, when [re]loading sip.conf . +; If a port number is not present, use the port specified in the "udpbindaddr" +; (which is not guaranteed to work correctly, because a NAT box might remap the +; port number as well as the address). +; This approach can be useful if you have a NAT device where you can +; configure the mapping statically. Examples: +; +; externaddr = 12.34.56.78 ; use this address. +; externaddr = 12.34.56.78:9900 ; use this address and port. +; externaddr = mynat.my.org:12600 ; Public address of my nat box. +; externtcpport = 9900 ; The externally mapped tcp port, when Asterisk is behind a static NAT or PAT. +; ; externtcpport will default to the externaddr or externhost port if either one is set. +; externtlsport = 12600 ; The externally mapped tls port, when Asterisk is behind a static NAT or PAT. +; ; externtlsport port will default to the RFC designated port of 5061. +; +; b. "externhost = hostname[:port]" is similar to "externaddr" except +; that the hostname is looked up every "externrefresh" seconds +; (default 10s). This can be useful when your NAT device lets you choose +; the port mapping, but the IP address is dynamic. +; Beware, you might suffer from service disruption when the name server +; resolution fails. Examples: +; +; externhost=foo.dyndns.net ; refreshed periodically +; externrefresh=180 ; change the refresh interval +; +; Note that at the moment all these mechanism work only for the SIP socket. +; The IP address discovered with externaddr/externhost is reused for +; media sessions as well, but the port numbers are not remapped so you +; may still experience problems. +; +; NOTE 1: in some cases, NAT boxes will use different port numbers in +; the internal<->external mapping. In these cases, the "externaddr" and +; "externhost" might not help you configure addresses properly. +; +; NOTE 2: when using "externaddr" or "externhost", the address part is +; also used as the external address for media sessions. Thus, the port +; information in the SDP may be wrong! +; +; In addition to the above, Asterisk has an additional "nat" parameter to +; address NAT-related issues in incoming SIP or media sessions. +; In particular, depending on the 'nat= ' settings described below, Asterisk +; may override the address/port information specified in the SIP/SDP messages, +; and use the information (sender address) supplied by the network stack instead. +; However, this is only useful if the external traffic can reach us. +; The following settings are allowed (both globally and in individual sections): +; +; nat = no ; Default. Use rport if the remote side says to use it. +; nat = force_rport ; Force rport to always be on. +; nat = yes ; Force rport to always be on and perform comedia RTP handling. +; nat = comedia ; Use rport if the remote side says to use it and perform comedia RTP handling. +; +; 'comedia RTP handling' refers to the technique of sending RTP to the port that the +; the other endpoint's RTP arrived from, and means 'connection-oriented media'. This is +; only partially related to RFC 4145 which was referred to as COMEDIA while it was in +; draft form. This method is used to accomodate endpoints that may be located behind +; NAT devices, and as such the port number they tell Asterisk to send RTP packets to +; for their media streams is not actual port number that will be used on the nearer +; side of the NAT. +; +; In addition to these settings, Asterisk *always* uses 'symmetric RTP' mode as defined by +; RFC 4961; Asterisk will always send RTP packets from the same port number it expects +; to receive them on. +; +; The IP address used for media (audio, video, and text) in the SDP can also be overridden by using +; the media_address configuration option. This is only applicable to the general section and +; can not be set per-user or per-peer. +; +; media_address = 172.16.42.1 +; +; Through the use of the res_stun_monitor module, Asterisk has the ability to detect when the +; perceived external network address has changed. When the stun_monitor is installed and +; configured, chan_sip will renew all outbound registrations when the monitor detects any sort +; of network change has occurred. By default this option is enabled, but only takes effect once +; res_stun_monitor is configured. If res_stun_monitor is enabled and you wish to not +; generate all outbound registrations on a network change, use the option below to disable +; this feature. +; +; subscribe_network_change_event = yes ; on by default + +;----------------------------------- MEDIA HANDLING -------------------------------- +; By default, Asterisk tries to re-invite media streams to an optimal path. If there's +; no reason for Asterisk to stay in the media path, the media will be redirected. +; This does not really work well in the case where Asterisk is outside and the +; clients are on the inside of a NAT. In that case, you want to set directmedia=nonat. +; +;directmedia=yes ; Asterisk by default tries to redirect the + ; RTP media stream to go directly from + ; the caller to the callee. Some devices do not + ; support this (especially if one of them is behind a NAT). + ; The default setting is YES. If you have all clients + ; behind a NAT, or for some other reason want Asterisk to + ; stay in the audio path, you may want to turn this off. + + ; This setting also affect direct RTP + ; at call setup (a new feature in 1.4 - setting up the + ; call directly between the endpoints instead of sending + ; a re-INVITE). + + ; Additionally this option does not disable all reINVITE operations. + ; It only controls Asterisk generating reINVITEs for the specific + ; purpose of setting up a direct media path. If a reINVITE is + ; needed to switch a media stream to inactive (when placed on + ; hold) or to T.38, it will still be done, regardless of this + ; setting. Note that direct T.38 is not supported. + +;directmedia=nonat ; An additional option is to allow media path redirection + ; (reinvite) but only when the peer where the media is being + ; sent is known to not be behind a NAT (as the RTP core can + ; determine it based on the apparent IP address the media + ; arrives from). + +;directmedia=update ; Yet a third option... use UPDATE for media path redirection, + ; instead of INVITE. This can be combined with 'nonat', as + ; 'directmedia=update,nonat'. It implies 'yes'. + +;directrtpsetup=yes ; Enable the new experimental direct RTP setup. This sets up + ; the call directly with media peer-2-peer without re-invites. + ; Will not work for video and cases where the callee sends + ; RTP payloads and fmtp headers in the 200 OK that does not match the + ; callers INVITE. This will also fail if directmedia is enabled when + ; the device is actually behind NAT. + +;directmediadeny=0.0.0.0/0 ; Use directmediapermit and directmediadeny to restrict +;directmediapermit=172.16.0.0/16; which peers should be able to pass directmedia to each other + ; (There is no default setting, this is just an example) + ; Use this if some of your phones are on IP addresses that + ; can not reach each other directly. This way you can force + ; RTP to always flow through asterisk in such cases. + +;ignoresdpversion=yes ; By default, Asterisk will honor the session version + ; number in SDP packets and will only modify the SDP + ; session if the version number changes. This option will + ; force asterisk to ignore the SDP session version number + ; and treat all SDP data as new data. This is required + ; for devices that send us non standard SDP packets + ; (observed with Microsoft OCS). By default this option is + ; off. + +;sdpsession=Asterisk PBX ; Allows you to change the SDP session name string, (s=) + ; Like the useragent parameter, the default user agent string + ; also contains the Asterisk version. +;sdpowner=root ; Allows you to change the username field in the SDP owner string, (o=) + ; This field MUST NOT contain spaces +;encryption=no ; Whether to offer SRTP encrypted media (and only SRTP encrypted media) + ; on outgoing calls to a peer. Calls will fail with HANGUPCAUSE=58 if + ; the peer does not support SRTP. Defaults to no. + +;----------------------------------------- REALTIME SUPPORT ------------------------ +; For additional information on ARA, the Asterisk Realtime Architecture, +; please read https://wiki.asterisk.org/wiki/display/AST/Realtime+Database+Configuration +; +;rtcachefriends=yes ; Cache realtime friends by adding them to the internal list + ; just like friends added from the config file only on a + ; as-needed basis? (yes|no) + +;rtsavesysname=yes ; Save systemname in realtime database at registration + ; Default= no + +;rtupdate=yes ; Send registry updates to database using realtime? (yes|no) + ; If set to yes, when a SIP UA registers successfully, the ip address, + ; the origination port, the registration period, and the username of + ; the UA will be set to database via realtime. + ; If not present, defaults to 'yes'. Note: realtime peers will + ; probably not function across reloads in the way that you expect, if + ; you turn this option off. +;rtautoclear=yes ; Auto-Expire friends created on the fly on the same schedule + ; as if it had just registered? (yes|no|<seconds>) + ; If set to yes, when the registration expires, the friend will + ; vanish from the configuration until requested again. If set + ; to an integer, friends expire within this number of seconds + ; instead of the registration interval. + +;ignoreregexpire=yes ; Enabling this setting has two functions: + ; + ; For non-realtime peers, when their registration expires, the + ; information will _not_ be removed from memory or the Asterisk database + ; if you attempt to place a call to the peer, the existing information + ; will be used in spite of it having expired + ; + ; For realtime peers, when the peer is retrieved from realtime storage, + ; the registration information will be used regardless of whether + ; it has expired or not; if it expires while the realtime peer + ; is still in memory (due to caching or other reasons), the + ; information will not be removed from realtime storage + +;----------------------------------------- SIP DOMAIN SUPPORT ------------------------ +; Incoming INVITE and REFER messages can be matched against a list of 'allowed' +; domains, each of which can direct the call to a specific context if desired. +; By default, all domains are accepted and sent to the default context or the +; context associated with the user/peer placing the call. +; REGISTER to non-local domains will be automatically denied if a domain +; list is configured. +; +; Domains can be specified using: +; domain=<domain>[,<context>] +; Examples: +; domain=myasterisk.dom +; domain=customer.com,customer-context +; +; In addition, all the 'default' domains associated with a server should be +; added if incoming request filtering is desired. +; autodomain=yes +; +; To disallow requests for domains not serviced by this server: +; allowexternaldomains=no + +;domain=mydomain.tld,mydomain-incoming + ; Add domain and configure incoming context + ; for external calls to this domain +;domain=1.2.3.4 ; Add IP address as local domain + ; You can have several "domain" settings +;allowexternaldomains=no ; Disable INVITE and REFER to non-local domains + ; Default is yes +;autodomain=yes ; Turn this on to have Asterisk add local host + ; name and local IP to domain list. + +; fromdomain=mydomain.tld ; When making outbound SIP INVITEs to + ; non-peers, use your primary domain "identity" + ; for From: headers instead of just your IP + ; address. This is to be polite and + ; it may be a mandatory requirement for some + ; destinations which do not have a prior + ; account relationship with your server. + +;------------------------------ Advice of Charge CONFIGURATION -------------------------- +; snom_aoc_enabled = yes; ; This options turns on and off support for sending AOC-D and + ; AOC-E to snom endpoints. This option can be used both in the + ; peer and global scope. The default for this option is off. + + +;------------------------------ JITTER BUFFER CONFIGURATION -------------------------- +; jbenable = yes ; Enables the use of a jitterbuffer on the receiving side of a + ; SIP channel. Defaults to "no". An enabled jitterbuffer will + ; be used only if the sending side can create and the receiving + ; side can not accept jitter. The SIP channel can accept jitter, + ; thus a jitterbuffer on the receive SIP side will be used only + ; if it is forced and enabled. + +; jbforce = no ; Forces the use of a jitterbuffer on the receive side of a SIP + ; channel. Defaults to "no". + +; jbmaxsize = 200 ; Max length of the jitterbuffer in milliseconds. + +; jbresyncthreshold = 1000 ; Jump in the frame timestamps over which the jitterbuffer is + ; resynchronized. Useful to improve the quality of the voice, with + ; big jumps in/broken timestamps, usually sent from exotic devices + ; and programs. Defaults to 1000. + +; jbimpl = fixed ; Jitterbuffer implementation, used on the receiving side of a SIP + ; channel. Two implementations are currently available - "fixed" + ; (with size always equals to jbmaxsize) and "adaptive" (with + ; variable size, actually the new jb of IAX2). Defaults to fixed. + +; jbtargetextra = 40 ; This option only affects the jb when 'jbimpl = adaptive' is set. + ; The option represents the number of milliseconds by which the new jitter buffer + ; will pad its size. the default is 40, so without modification, the new + ; jitter buffer will set its size to the jitter value plus 40 milliseconds. + ; increasing this value may help if your network normally has low jitter, + ; but occasionally has spikes. + +; jblog = no ; Enables jitterbuffer frame logging. Defaults to "no". +;----------------------------------------------------------------------------------- + +[authentication] +; Global credentials for outbound calls, i.e. when a proxy challenges your +; Asterisk server for authentication. These credentials override +; any credentials in peer/register definition if realm is matched. +; +; This way, Asterisk can authenticate for outbound calls to other +; realms. We match realm on the proxy challenge and pick an set of +; credentials from this list +; Syntax: +; auth = <user>:<secret>@<realm> +; auth = <user>#<md5secret>@<realm> +; Example: +;auth=mark:topsecret@digium.com +; +; You may also add auth= statements to [peer] definitions +; Peer auth= override all other authentication settings if we match on realm + +;------------------------------------------------------------------------------ +; DEVICE CONFIGURATION +; +; The SIP channel has two types of devices, the friend and the peer. +; * The type=friend is a device type that accepts both incoming and outbound calls, +; where Asterisk match on the From: username on incoming calls. +; (A synonym for friend is "user"). This is a type you use for your local +; SIP phones. +; * The type=peer also handles both incoming and outbound calls. On inbound calls, +; Asterisk only matches on IP/port, not on names. This is mostly used for SIP +; trunks. +; +; For device names, we recommend using only a-z, numerics (0-9) and underscore +; +; For local phones, type=friend works most of the time +; +; If you have one-way audio, you probably have NAT problems. +; If Asterisk is on a public IP, and the phone is inside of a NAT device +; you will need to configure nat option for those phones. +; Also, turn on qualify=yes to keep the nat session open +; +; Configuration options available +; -------------------- +; context +; callingpres +; permit +; deny +; secret +; md5secret +; remotesecret +; transport +; dtmfmode +; directmedia +; nat +; callgroup +; pickupgroup +; language +; allow +; disallow +; insecure +; trustrpid +; progressinband +; promiscredir +; useclientcode +; accountcode +; setvar +; callerid +; amaflags +; callcounter +; busylevel +; allowoverlap +; allowsubscribe +; allowtransfer +; ignoresdpversion +; subscribecontext +; template +; videosupport +; maxcallbitrate +; rfc2833compensate +; mailbox +; session-timers +; session-expires +; session-minse +; session-refresher +; t38pt_usertpsource +; regexten +; fromdomain +; fromuser +; host +; port +; qualify +; defaultip +; defaultuser +; rtptimeout +; rtpholdtimeout +; sendrpid +; outboundproxy +; rfc2833compensate +; callbackextension +; registertrying +; timert1 +; timerb +; qualifyfreq +; t38pt_usertpsource +; contactpermit ; Limit what a host may register as (a neat trick +; contactdeny ; is to register at the same IP as a SIP provider, +; ; then call oneself, and get redirected to that +; ; same location). +; directmediapermit +; directmediadeny +; unsolicited_mailbox +; use_q850_reason +; maxforwards +; encryption + +;[sip_proxy] +; For incoming calls only. Example: FWD (Free World Dialup) +; We match on IP address of the proxy for incoming calls +; since we can not match on username (caller id) +;type=peer +;context=from-fwd +;host=fwd.pulver.com + +;[sip_proxy-out] +;type=peer ; we only want to call out, not be called +;remotesecret=guessit ; Our password to their service +;defaultuser=yourusername ; Authentication user for outbound proxies +;fromuser=yourusername ; Many SIP providers require this! +;fromdomain=provider.sip.domain +;host=box.provider.com +;transport=udp,tcp ; This sets the default transport type to udp for outgoing, and will +; ; accept both tcp and udp. The default transport type is only used for +; ; outbound messages until a Registration takes place. During the +; ; peer Registration the transport type may change to another supported +; ; type if the peer requests so. + +;usereqphone=yes ; This provider requires ";user=phone" on URI +;callcounter=yes ; Enable call counter +;busylevel=2 ; Signal busy at 2 or more calls +;outboundproxy=proxy.provider.domain ; send outbound signaling to this proxy, not directly to the peer +;port=80 ; The port number we want to connect to on the remote side + ; Also used as "defaultport" in combination with "defaultip" settings + +;--- sample definition for a provider +;[provider1] +;type=peer +;host=sip.provider1.com +;fromuser=4015552299 ; how your provider knows you +;remotesecret=youwillneverguessit ; The password we use to authenticate to them +;secret=gissadetdu ; The password they use to contact us +;callbackextension=123 ; Register with this server and require calls coming back to this extension +;transport=udp,tcp ; This sets the transport type to udp for outgoing, and will +; ; accept both tcp and udp. Default is udp. The first transport +; ; listed will always be used for outgoing connections. +;unsolicited_mailbox=4015552299 ; If the remote SIP server sends an unsolicited MWI NOTIFY message the new/old +; ; message count will be stored in the configured virtual mailbox. It can be used +; ; by any device supporting MWI by specifying <configured value>@SIP_Remote as the +; ; mailbox. + +; +; Because you might have a large number of similar sections, it is generally +; convenient to use templates for the common parameters, and add them +; the the various sections. Examples are below, and we can even leave +; the templates uncommented as they will not harm: + +[basic-options](!) ; a template + dtmfmode=rfc2833 + context=from-office + type=friend + +[natted-phone](!,basic-options) ; another template inheriting basic-options + nat=yes + directmedia=no + host=dynamic + +[public-phone](!,basic-options) ; another template inheriting basic-options + nat=no + directmedia=yes + +[my-codecs](!) ; a template for my preferred codecs + disallow=all + allow=ilbc + allow=g729 + allow=gsm + allow=g723 + allow=ulaw + +[ulaw-phone](!) ; and another one for ulaw-only + disallow=all + allow=ulaw + +; and finally instantiate a few phones +; +; [2133](natted-phone,my-codecs) +; secret = peekaboo +; [2134](natted-phone,ulaw-phone) +; secret = not_very_secret +; [2136](public-phone,ulaw-phone) +; secret = not_very_secret_either +; ... +; + +; Standard configurations not using templates look like this: +; +;[grandstream1] +;type=friend +;context=from-sip ; Where to start in the dialplan when this phone calls +;callerid=John Doe <1234> ; Full caller ID, to override the phones config + ; on incoming calls to Asterisk +;host=192.168.0.23 ; we have a static but private IP address + ; No registration allowed +;nat=no ; there is not NAT between phone and Asterisk +;directmedia=yes ; allow RTP voice traffic to bypass Asterisk +;dtmfmode=info ; either RFC2833 or INFO for the BudgeTone +;call-limit=1 ; permit only 1 outgoing call and 1 incoming call at a time + ; from the phone to asterisk (deprecated) + ; 1 for the explicit peer, 1 for the explicit user, + ; remember that a friend equals 1 peer and 1 user in + ; memory + ; There is no combined call counter for a "friend" + ; so there's currently no way in sip.conf to limit + ; to one inbound or outbound call per phone. Use + ; the group counters in the dial plan for that. + ; +;mailbox=1234@default ; mailbox 1234 in voicemail context "default" +;disallow=all ; need to disallow=all before we can use allow= +;allow=ulaw ; Note: In user sections the order of codecs + ; listed with allow= does NOT matter! +;allow=alaw +;allow=g723.1 ; Asterisk only supports g723.1 pass-thru! +;allow=g729 ; Pass-thru only unless g729 license obtained +;callingpres=allowed_passed_screen ; Set caller ID presentation + ; See README.callingpres for more information + +;[xlite1] +; Turn off silence suppression in X-Lite ("Transmit Silence"=YES)! +; Note that Xlite sends NAT keep-alive packets, so qualify=yes is not needed +;type=friend +;regexten=1234 ; When they register, create extension 1234 +;callerid="Jane Smith" <5678> +;host=dynamic ; This device needs to register +;nat=yes ; X-Lite is behind a NAT router +;directmedia=no ; Typically set to NO if behind NAT +;disallow=all +;allow=gsm ; GSM consumes far less bandwidth than ulaw +;allow=ulaw +;allow=alaw +;mailbox=1234@default,1233@default ; Subscribe to status of multiple mailboxes +;registertrying=yes ; Send a 100 Trying when the device registers. + +;[snom] +;type=friend ; Friends place calls and receive calls +;context=from-sip ; Context for incoming calls from this user +;secret=blah +;subscribecontext=localextensions ; Only allow SUBSCRIBE for local extensions +;language=de ; Use German prompts for this user +;host=dynamic ; This peer register with us +;dtmfmode=inband ; Choices are inband, rfc2833, or info +;defaultip=192.168.0.59 ; IP used until peer registers +;mailbox=1234@context,2345 ; Mailbox(-es) for message waiting indicator +;subscribemwi=yes ; Only send notifications if this phone + ; subscribes for mailbox notification +;vmexten=voicemail ; dialplan extension to reach mailbox + ; sets the Message-Account in the MWI notify message + ; defaults to global vmexten which defaults to "asterisk" +;disallow=all +;allow=ulaw ; dtmfmode=inband only works with ulaw or alaw! + + +;[polycom] +;type=friend ; Friends place calls and receive calls +;context=from-sip ; Context for incoming calls from this user +;secret=blahpoly +;host=dynamic ; This peer register with us +;dtmfmode=rfc2833 ; Choices are inband, rfc2833, or info +;defaultuser=polly ; Username to use in INVITE until peer registers +;defaultip=192.168.40.123 + ; Normally you do NOT need to set this parameter +;disallow=all +;allow=ulaw ; dtmfmode=inband only works with ulaw or alaw! +;progressinband=no ; Polycom phones don't work properly with "never" + + +;[pingtel] +;type=friend +;secret=blah +;host=dynamic +;insecure=port ; Allow matching of peer by IP address without + ; matching port number +;insecure=invite ; Do not require authentication of incoming INVITEs +;insecure=port,invite ; (both) +;qualify=1000 ; Consider it down if it's 1 second to reply + ; Helps with NAT session + ; qualify=yes uses default value +;qualifyfreq=60 ; Qualification: How often to check for the + ; host to be up in seconds + ; Set to low value if you use low timeout for + ; NAT of UDP sessions +; +; Call group and Pickup group should be in the range from 0 to 63 +; +;callgroup=1,3-4 ; We are in caller groups 1,3,4 +;pickupgroup=1,3-5 ; We can do call pick-p for call group 1,3,4,5 +;defaultip=192.168.0.60 ; IP address to use if peer has not registered +;deny=0.0.0.0/0.0.0.0 ; ACL: Control access to this account based on IP address +;permit=192.168.0.60/255.255.255.0 +;permit=192.168.0.60/24 ; we can also use CIDR notation for subnet masks +;permit=2001:db8::/32 ; IPv6 ACLs can be specified if desired. IPv6 ACLs + ; apply only to IPv6 addresses, and IPv4 ACLs apply + ; only to IPv4 addresses. + +;[cisco1] +;type=friend +;secret=blah +;qualify=200 ; Qualify peer is no more than 200ms away +;nat=yes ; This phone may be natted + ; Send SIP and RTP to the IP address that packet is + ; received from instead of trusting SIP headers +;host=dynamic ; This device registers with us +;directmedia=no ; Asterisk by default tries to redirect the + ; RTP media stream (audio) to go directly from + ; the caller to the callee. Some devices do not + ; support this (especially if one of them is + ; behind a NAT). +;defaultip=192.168.0.4 ; IP address to use until registration +;defaultuser=goran ; Username to use when calling this device before registration + ; Normally you do NOT need to set this parameter +;setvar=CUSTID=5678 ; Channel variable to be set for all calls from or to this device +;setvar=ATTENDED_TRANSFER_COMPLETE_SOUND=beep ; This channel variable will + ; cause the given audio file to + ; be played upon completion of + ; an attended transfer. + +;[pre14-asterisk] +;type=friend +;secret=digium +;host=dynamic +;rfc2833compensate=yes ; Compensate for pre-1.4 DTMF transmission from another Asterisk machine. + ; You must have this turned on or DTMF reception will work improperly. +;t38pt_usertpsource=yes ; Use the source IP address of RTP as the destination IP address for UDPTL packets + ; if the nat option is enabled. If a single RTP packet is received Asterisk will know the + ; external IP address of the remote device. If port forwarding is done at the client side + ; then UDPTL will flow to the remote device. + +[100] +type=friend +host=dynamic +username=100 +secret=password +canreinvite=no +allow=all + +[200] +type=friend +host=dynamic +username=200 +secret=password +canreinvite=no +allow=all + +[300] +type=friend +host=dynamic +username=300 +canreinvite=no +allow=all + +[400] +type=friend +host=dynamic +username=400 +canreinvite=no +allow=all + +[testphone1] +context=default +type=friend +secret=savoirfairelinux +host=dynamic +insecure=invite,port +dtmfmode=rfc2833 +transport=tls +allow=all +nat=yes diff --git a/tools/build-system/README.launchpad b/tools/build-system/README.launchpad new file mode 100644 index 0000000000..aa0861b033 --- /dev/null +++ b/tools/build-system/README.launchpad @@ -0,0 +1,10 @@ +To push packages for a new Ubuntu distribution on launchpad, you'll need to +modify the following files: + +tools/build-system/launchpad/dput.conf +tools/build-system/setenv.sh + +See commit 1b19e3869aa5e4632b8b043f47024297218ed2c5 for an example. + +In addition, you'll have to modify the sflphone-package-manager job on Jenkins, specifically +Configure->Build->Execute Shell (add new distro). diff --git a/tools/build-system/build_tarball.sh b/tools/build-system/build_tarball.sh new file mode 100755 index 0000000000..d34e2b6b6c --- /dev/null +++ b/tools/build-system/build_tarball.sh @@ -0,0 +1,76 @@ +#!/bin/bash +# +# Script to build the source tarball for distribution on sflphone.org +# Inclusion of KDE is a requirement. Run get-kde.sh to have it. +# +# Author: Francois Marier <francois@debian.org> + + +# Exit on error +set -o errexit + +# This is an environment variable provided by Jenkins. It points to the repository's root +cd ${WORKSPACE} + +if [ ! -e daemon/configure.ac ] ; then + echo "This script must be run in the root directory of the sflphone repository" >&2 + exit 1 +fi + +if [ $# -ne 1 ] ; then + echo "Usage: $(basename $0) SOFTWARE_VERSION_NUMBER" >&2 + exit 2 +fi + +if [ ! -d kde ] ; then + echo 'No "kde" directory. Make sure get-kde.sh ran at some point.' >&2 + exit 1 +fi + +# Use the version fed by launch-build-machine-jenkins.sh +SOFTWARE_VERSION=$1 +BUILDDIR=sflphone-$SOFTWARE_VERSION + +if [ -e $BUILDDIR ] ; then + echo "The build directory ($BUILDDIR) already exists. Delete it first." >&2 + exit 3 +fi + +# Populate the tarball directory +mkdir $BUILDDIR +SRCITEMS=$(echo *) +# Exclude existing tarballs from the created tarball +SRCITEMS=${SRCITEMS//*.tar.gz} +# ${SRCITEMS//$BUILDDIR} is used to remove $BUILDDIR from $SRCITEMS +# See bash parameter expansion +cp -r ${SRCITEMS//$BUILDDIR} $BUILDDIR/ + +pushd $BUILDDIR +# No dash in Version: +sed /^Version/s/[0-9].*/${SOFTWARE_VERSION%%-*}/ tools/build-system/rpm/sflphone.spec > sflphone.spec + +# Remove unwanted files +rm -rf lang/ +rm -rf tools/ +rm -rf .git/ +find -name .gitignore -delete +find -name .project -type f -delete +find -name .cproject -type f -delete +find -name .settings -type d -exec rm -rf {} + + +# Generate the configure files +pushd daemon +./autogen.sh +find -name \*.spec -delete +popd + +pushd gnome +NOCONFIGURE=1 ./autogen.sh +popd + +find -name autom4te.cache -type d -exec rm -rf {} + +find -name *.in~ -type f -delete +popd # builddir + +tar zcf sflphone-${SOFTWARE_VERSION}.tar.gz sflphone-${SOFTWARE_VERSION} +rm -rf $BUILDDIR diff --git a/tools/build-system/get-kde.sh b/tools/build-system/get-kde.sh new file mode 100755 index 0000000000..25630c2721 --- /dev/null +++ b/tools/build-system/get-kde.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# Get the KDE client +# To get all files, you have to create the tarball from scratch, +# then extract files from it. The directory is renamed "kde". +# $WORKSPACE is declared in setenv.sh +set -o errexit +source $(dirname $0)/setenv.sh +cd "$WORKSPACE" +baseurl='https://projects.kde.org/projects' +config_uri='/playground/network/sflphone-kde/repository/revisions/master/raw/data/config.ini' +createtarball_uri='/kde/kdesdk/kde-dev-scripts/repository/revisions/master/raw/createtarball/create_tarball.rb' + +set -x + +# timeout in seconds +let -i timeout=300 +let -i timestamp=$(date +%s) +while ! curl --fail --remote-name ${baseurl}${config_uri} +do + if [ $(date +%s) -gt $(( $timestamp + $timeout)) ]; then + break + fi + sleep 15 +done +let -i timestamp=$(date +%s) +while ! curl --fail --remote-name ${baseurl}${createtarball_uri} +do + if [ $(date +%s) -gt $(( $timestamp + $timeout)) ]; then + break + fi + sleep 15 +done + +ruby create_tarball.rb --noaccount --application sflphone-kde +rm -rf kde +rm -rf sflphone-kde-*.tar.* +rm create_tarball.rb config.ini +mv sflphone-kde-* kde diff --git a/tools/build-system/hudson-sflphone-script.sh b/tools/build-system/hudson-sflphone-script.sh new file mode 100755 index 0000000000..fc63181c7d --- /dev/null +++ b/tools/build-system/hudson-sflphone-script.sh @@ -0,0 +1,260 @@ +#!/bin/bash -e +# +# Copyright (C) 2004-2015 Savoir-Faire Linux Inc. +# +# Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +# +# Additional permission under GNU GPL version 3 section 7: +# +# If you modify this program, or any covered work, by linking or +# combining it with the OpenSSL project's OpenSSL library (or a +# modified version of that library), containing parts covered by the +# terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. +# grants you additional permission to convey the resulting work. +# Corresponding Source for a non-source form of such a combination +# shall include the source code for the parts of OpenSSL used as well +# as that of the covered work. +# + +# Script used by Hudson continious integration server to build SFLphone + +XML_RESULTS="cppunitresults.xml" +TEST=0 +BUILD= +CODE_ANALYSIS=0 +DOXYGEN=0 +#daemon opts +DOPTS="--prefix=/usr" +#gnome opts +GOPTS="--prefix=/usr --enable-video" + +#compiler defaults +export CC=gcc +export CXX=g++ + +CONFIGDIR=~/.config +SFLCONFDIR=${CONFIGDIR}/sflphone + +function exit_clean { + popd + exit $1 +} + +function run_code_analysis { + # Check if cppcheck is installed on the system + if [ `which cppcheck &>/dev/null ; echo $?` -ne 1 ] ; then + pushd src + cppcheck . --enable=all --xml --inline-suppr 2> cppcheck-report.xml + popd + fi +} + + +function gen_doxygen { + # Check if doxygen is installed on the system + if [ `which doxygen &>/dev/null ; echo $?` -ne 1 ] ; then + pushd doc/doxygen + doxygen core-doc.cfg.in + popd + fi +} + +function launch_functional_test_daemon { + # Run the python functional tests for the daemon + + # make sure no other instance are currently running + killall sflphoned + killall sipp + + # make sure the configuration directory created + CONFDIR=~/.config + SFLCONFDIR=${CONFDIR}/sflphone + + eval `dbus-launch --auto-syntax` + + if [ ! -d ${CONFDIR} ]; then + mkdir ${CONFDIR} + fi + + if [ ! -d ${SFLCONFDIR} ]; then + mkdir ${SFLCONFDIR} + fi + + # make sure the most recent version of the configuration + # is installed + pushd tools/pysflphone + cp -f sflphoned.functest.yml ${SFLCONFDIR} + popd + + # launch sflphone daemon, wait some time for + # dbus registration to complete + pushd daemon + ./src/sflphoned & + sleep 3 + popd + + # launch the test script + pushd tools/pysflphone + nosetests --with-xunit test_sflphone_dbus_interface.py + popd +} + +function build_contrib { + if [ -d contrib ] ; then + pushd contrib + mkdir -p native + pushd native + ../bootstrap + # list dependencies that aren't detected by contrib + make list + + # FIXME: this is very slow but it's the best we can do until we migrate + # to a builder with more up to date packages + if [ "$DEBUG_CONTRIB" != "" ]; then + make + else + make -j + fi + popd + else + # We're on 1.4.x + pushd libs + ./compile_pjsip.sh + fi + popd +} + +function build_daemon { + pushd daemon + + # Build dependencies first + build_contrib + + # Run static analysis code tool + if [ $CODE_ANALYSIS == 1 ]; then + run_code_analysis + fi + + # Compile the daemon + ./autogen.sh || exit_clean 1 + #FIXME: this is a temporary hack around linking failure on jenkins + ./configure $DOPTS + make clean + make -j + # Remove the previous XML test file + rm -rf $XML_RESULTS + # Compile unit tests + make check + popd +} + +function build_gnome { + # Compile the plugins + pushd plugins + ./autogen.sh || exit_clean 1 + ./configure $GOPTS + make -j + popd + + # Compile the client + pushd gnome + ./autogen.sh || exit_clean 1 + ./configure $GOPTS + make clean + make -j 1 + make check + popd +} + +function build_kde { + # Compile the KDE client + pushd kde + mkdir -p build + cd build + cmake ../ + make -j + popd +} + + +if [ "$#" -eq 0 ]; then # Script needs at least one command-line argument. + echo "$0 accepts the following options: + -b select 'daemon' or 'gnome' component + -v enable video support + -c use clang compiler + -a run static code analysis after build + -t run unit tests after build + -m disable most optional options" + exit $E_OPTERR +fi + +pushd "$(git rev-parse --show-toplevel)" +git clean -f -d -x + +while getopts ":b: t a v c" opt; do + case $opt in + b) + echo "-b is set with option $OPTARG" >&2 + if [ ! -d $OPTARG ] + then + echo "$OPTARG directory is missing, exiting" + exit_clean $E_OPTERR + fi + BUILD=$OPTARG + ;; + t) + echo "-t is set, unit tests will be run after build" >&2 + TEST=1 + ;; + a) + echo "-a is set, static code analysis will be run after build" >&2 + CODE_ANALYSIS=1 + ;; + v) + echo "-v is set, video support is disabled" >&2 + DOPTS="--disable-video $DOPTS" + ;; + m) + echo "-m is set, disabling dbus, video, iax, nm and pulse" >&2 + DOPTS="--disable-video --without-iax --without-dbus --without-pulse --without-networkmanager $DOPTS" + ;; + c) + echo "-c is set, clang compiler is used" >&2 + export CC=clang + export CXX=clang++ + DOPTS="--without-dbus $DOPTS" + ;; + \?) + echo "Invalid option: -$OPTARG" >&2 + exit_clean 1 + ;; + :) + echo "Option -$OPTARG requires an argument." >&2 + exit_clean 1 + ;; + esac +done + +# Call appropriate build function, with parameters if needed +build_$BUILD + +if [ $TEST == 1 ]; then + launch_functional_test_daemon +fi + +# SUCCESS +exit_clean 0 diff --git a/tools/build-system/launch-build-machine-jenkins.sh b/tools/build-system/launch-build-machine-jenkins.sh new file mode 100755 index 0000000000..5f606af6ff --- /dev/null +++ b/tools/build-system/launch-build-machine-jenkins.sh @@ -0,0 +1,316 @@ +#!/bin/bash +# run with --help for documentation + +set -x +set -e + +#Check dependencies + +for cmd in curl ruby git svn +do + if ! command -v $cmd; then + echo "$cmd is missing" >&2 + exit 1 + fi +done + +source $(dirname $0)/setenv.sh + +./$(dirname $0)/get-kde.sh + +IS_RELEASE= +VERSION_INDEX="1" +IS_KDE_CLIENT= +DO_PUSH=1 +DO_LOGGING=1 +DO_UPLOAD=1 +SNAPSHOT_TAG=`date +%Y%m%d` +TAG_NAME_PREFIX= +VERSION_NUMBER="1.4.2" + +LAUNCHPAD_PACKAGES=("sflphone-daemon" "sflphone-kde" "sflphone-gnome" "sflphone-plugins" "sflphone-daemon-video" "sflphone-gnome-video") + +cat << EOF +_________________________ +| SFLPhone build system | +------------------------- +EOF + + +for PARAMETER in $* +do + case ${PARAMETER} in + --help) + echo + echo "Options :" + echo " --skip-push" + echo " --skip-upload" + echo " --kde-client" + echo " --no-logging" + echo " --release" + echo " --version-index=[1,2,...]" + echo + exit 0;; + --skip-push) + unset DO_PUSH;; + --skip-upload) + unset DO_UPLOAD;; + --kde-client) + IS_KDE_CLIENT=1;; + --no-logging) + unset DO_LOGGING;; + --release) + IS_RELEASE=1;; + --tag=*) + TAG=(${PARAMETER##*=});; + --version-index=*) + VERSION_INDEX=(${PARAMETER##*=});; + *) + echo "Unknown parameter : ${PARAMETER}" + exit -1;; + esac +done + +######################### +# LAUNCHPAD +######################### + +# change to working directory +cd ${LAUNCHPAD_DIR} + +if [ "$?" -ne "0" ]; then + echo " !! Cannot cd to launchpad directory" + exit -1 +fi + +# logging +if [ ${DO_LOGGING} ]; then + + rm -f ${ROOT_DIR}/packaging.log >/dev/null 2>&1 + + # open file descriptor + exec 3<> ${ROOT_DIR}/packaging.log + + # redirect outputs (stdout & stderr) + exec 1>&3 + exec 2>&3 +fi + +if [ ${RELEASE_MODE} ]; then + echo "Release mode" +else + echo "Snapshot mode" +fi + +if [ ${IS_KDE_CLIENT} ]; then + TAG_NAME_PREFIX="kde." +fi + +######################### +# COMMON PART +######################### + +cd ${REFERENCE_REPOSITORY} + +echo "Update reference sources" +git checkout . && git checkout -f master && git pull + +# Get the version +if [ -n "$TAG" ]; then + CURRENT_RELEASE_TAG_NAME="$TAG" +else + CURRENT_RELEASE_TAG_NAME=`git describe --tags --abbrev=0` +fi + +PREVIOUS_RELEASE_TAG_NAME=`git describe --tags --abbrev=0 ${CURRENT_RELEASE_TAG_NAME}^` +CURRENT_RELEASE_COMMIT_HASH=`git show --pretty=format:"%H" -s ${CURRENT_RELEASE_TAG_NAME} | tail -n 1` +PREVIOUS_RELEASE_COMMIT_HASH=`git show --pretty=format:"%H" -s ${PREVIOUS_RELEASE_TAG_NAME} | tail -n 1` +CURRENT_COMMIT=`git show --pretty=format:"%H" -s | tail -n 1` +CURRENT_RELEASE_TYPE=${CURRENT_RELEASE_TAG_NAME##*.} +PREVIOUS_RELEASE_TYPE=${PREVIOUS_RELEASE_TAG_NAME##*.} + +if [ ${IS_KDE_CLIENT} ]; then + CURRENT_RELEASE_VERSION=${CURRENT_RELEASE_TAG_NAME%.*} + CURRENT_RELEASE_VERSION=${CURRENT_RELEASE_VERSION#*.} + PREVIOUS_VERSION=${PREVIOUS_RELEASE_TAG_NAME%.*} + PREVIOUS_VERSION=${PREVIOUS_VERSION#*.} +else + CURRENT_RELEASE_VERSION=${CURRENT_RELEASE_TAG_NAME} + PREVIOUS_VERSION=${PREVIOUS_RELEASE_TAG_NAME} +fi + + +echo "Retrieve build info" +# retrieve info we may need +if [ ${IS_KDE_CLIENT} ]; then + TAG_NAME_PREFIX="kde." + LAUNCHPAD_PACKAGES=( "sflphone-kde" ) +fi + + +cd ${LAUNCHPAD_DIR} + +COMMIT_HASH_BEGIN="" +COMMIT_HASH_END="" +SOFTWARE_VERSION="" +LAUNCHPAD_CONF_PREFIX="" + +if [ ${IS_RELEASE} ]; then + SOFTWARE_VERSION="${CURRENT_RELEASE_VERSION}" + COMMIT_HASH_BEGIN="${PREVIOUS_RELEASE_COMMIT_HASH}" + LAUNCHPAD_CONF_PREFIX="sflphone" +else + SOFTWARE_VERSION="${VERSION_NUMBER}-rc${SNAPSHOT_TAG}" + COMMIT_HASH_BEGIN="${CURRENT_RELEASE_COMMIT_HASH}" + LAUNCHPAD_CONF_PREFIX="sflphone-nightly" +fi + +VERSION="${SOFTWARE_VERSION}~ppa${VERSION_INDEX}~SYSTEM" + +echo "Clean build directory" +git clean -f -x ${LAUNCHPAD_DIR}/* >/dev/null +git checkout ${LAUNCHPAD_DIR} + +# If release, checkout the latest tag +if [ ${IS_RELEASE} ]; then + git checkout ${CURRENT_RELEASE_TAG_NAME} + + # When we need to apply an emergency patch for the release builds + # This should only be used to temporarily patch packaging tools, not + # daemon/client code (or anything else that build_tarball would grab). + if [ -d /tmp/sflphone_release_patch ]; then + echo "Applying patch(es) to packaging tools..." + git apply --verbose /tmp/sflphone_release_patch/* + rm -rf /tmp/sflphone_release_patch + REQUIRE_RESET=1 + fi +fi + +get_dir_name() { + case $1 in + sflphone-daemon) + echo daemon + ;; + sflphone-daemon-video) + echo daemon + ;; + sflphone-plugins) + echo plugins + ;; + sflphone-gnome) + echo gnome + ;; + sflphone-gnome-video) + echo gnome + ;; + sflphone-kde) + echo kde + ;; + *) + exit 1 + ;; + esac +} + +# Looping over the packages +for LAUNCHPAD_PACKAGE in ${LAUNCHPAD_PACKAGES[*]} +do + echo " Package: ${LAUNCHPAD_PACKAGE}" + + echo " --> Clean old sources" + git clean -f -x ${LAUNCHPAD_DIR}/${LAUNCHPAD_PACKAGE}/* >/dev/null + + DEBIAN_DIR="${LAUNCHPAD_DIR}/${LAUNCHPAD_PACKAGE}/debian" + + echo " --> Retrieve new sources" + DIRNAME=`get_dir_name ${LAUNCHPAD_PACKAGE}` + cp -r ${REFERENCE_REPOSITORY}/${DIRNAME}/* ${LAUNCHPAD_DIR}/${LAUNCHPAD_PACKAGE} + + echo " --> Update software version number (${SOFTWARE_VERSION})" + echo "${SOFTWARE_VERSION}" > ${LAUNCHPAD_DIR}/${LAUNCHPAD_PACKAGE}/VERSION + + echo " --> Update debian changelog" + +cat << END > ${WORKING_DIR}/sfl-git-dch.conf +WORKING_DIR="${REFERENCE_REPOSITORY}" +SOFTWARE="${LAUNCHPAD_PACKAGE}" +VERSION="${VERSION}" +DISTRIBUTION="SYSTEM" +CHANGELOG_FILE="${DEBIAN_DIR}/changelog" +COMMIT_HASH_BEGIN="${COMMIT_HASH_BEGIN}" +COMMIT_HASH_END="${COMMIT_HASH_END}" +IS_RELEASE=${IS_RELEASE} +export DEBFULLNAME="Emmanuel Milou" +export DEBEMAIL="emmanuel.milou@savoirfairelinux.com" +export EDITOR="echo" +END + + ${WORKING_DIR}/sfl-git-dch-2.sh ${WORKING_DIR}/sfl-git-dch.conf ${REFERENCE_REPOSITORY}/${DIRNAME}/ + if [ "$?" -ne "0" ]; then + echo "!! Cannot update debian changelogs" + exit -1 + fi + + if [ "${LAUNCHPAD_PACKAGE}" == "sflphone-kde" ]; then + version_kde=$(echo ${VERSION} | grep -e '[0-9]*\.[0-9.]*' -o | head -n1) + sed -i -e "s/Standards-Version: [0-9.A-Za-z]*/Standards-Version: ${version_kde}/" ${LAUNCHPAD_DIR}/${LAUNCHPAD_PACKAGE}/debian/control + tar -C ${LAUNCHPAD_DIR}/ -cjf ${LAUNCHPAD_DIR}/sflphone-kde_${version_kde}.orig.tar.bz2 ${LAUNCHPAD_PACKAGE} + fi + + rm -f ${WORKING_DIR}/sfl-git-dch.conf >/dev/null 2>&1 + + cd ${LAUNCHPAD_DIR} + + cp ${DEBIAN_DIR}/changelog ${DEBIAN_DIR}/changelog.generic + + for LAUNCHPAD_DISTRIBUTION in ${LAUNCHPAD_DISTRIBUTIONS[*]} + do + + LOCAL_VERSION="${SOFTWARE_VERSION}~ppa${VERSION_INDEX}~${LAUNCHPAD_DISTRIBUTION}" + + cp ${DEBIAN_DIR}/changelog.generic ${DEBIAN_DIR}/changelog + + sed -i "s/SYSTEM/${LAUNCHPAD_DISTRIBUTION}/g" ${DEBIAN_DIR}/changelog + + cd ${LAUNCHPAD_DIR}/${LAUNCHPAD_PACKAGE} + if [ "${DIRNAME}" == "daemon" ]; then + if [ -d contrib ]; then + mkdir -p contrib/native + pushd contrib/native + ../bootstrap + # only fetch it, don't build it + make iax + make dht + else + pushd libs + #./compile_pjsip.sh #This script should not attempt to compile + fi + popd + fi + if [ "${LAUNCHPAD_PACKAGE}" != "sflphone-kde" ]; then + ./autogen.sh + fi + debuild -S -sa -kF5362695 + cd ${LAUNCHPAD_DIR} + + if [ ${DO_UPLOAD} ] ; then + dput -f --debug --no-upload-log -c ${LAUNCHPAD_DIR}/dput.conf ${LAUNCHPAD_CONF_PREFIX}-${LAUNCHPAD_DISTRIBUTION} ${LAUNCHPAD_PACKAGE}_${LOCAL_VERSION}_source.changes + fi + done + + cp ${DEBIAN_DIR}/changelog.generic ${DEBIAN_DIR}/changelog +done + +# Archive source tarball for Debian maintainer +# and for RPM package building +${WORKING_DIR}/build_tarball.sh ${SOFTWARE_VERSION} + +# Undo any modifications caused by temporary patches +if [ "$REQUIRE_RESET" == "1" ]; then + git reset --hard +fi + +# close file descriptor +exec 3>&- + +exit 0 diff --git a/tools/build-system/launchpad/dput.conf b/tools/build-system/launchpad/dput.conf new file mode 100644 index 0000000000..8d6fe9d5cb --- /dev/null +++ b/tools/build-system/launchpad/dput.conf @@ -0,0 +1,27 @@ +[sflphone-trusty] +fqdn = ppa.launchpad.net +method = ftp +incoming = ~savoirfairelinux/ppa/ubuntu/trusty +login = anonymous +allow_unsigned_uploads = 0 + +[sflphone-utopic] +fqdn = ppa.launchpad.net +method = ftp +incoming = ~savoirfairelinux/ppa/ubuntu/utopic +login = anonymous +allow_unsigned_uploads = 0 + +[sflphone-nightly-trusty] +fqdn = ppa.launchpad.net +method = ftp +incoming = ~savoirfairelinux/sflphone-nightly/ubuntu/trusty +login = anonymous +allow_unsigned_uploads = 0 + +[sflphone-nightly-utopic] +fqdn = ppa.launchpad.net +method = ftp +incoming = ~savoirfairelinux/sflphone-nightly/ubuntu/utopic +login = anonymous +allow_unsigned_uploads = 0 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/changelog b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/changelog new file mode 100644 index 0000000000..17f7f40a39 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/changelog @@ -0,0 +1,13 @@ +mozilla-telify-sflphone (1.0) unstable; urgency=low + + [ Julien Bonjean ] + * Package update + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Fri, 21 Apr 2010 19:51:54 +0100 + +mozilla-telify-sflphone (0.4.7.3) unstable; urgency=low + + [ Julien Bonjean ] + * Package creation + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Fri, 20 Nov 2009 19:51:54 +0100 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/compat b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/compat new file mode 100644 index 0000000000..7f8f011eb7 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/compat @@ -0,0 +1 @@ +7 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/control b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/control new file mode 100644 index 0000000000..e0bbb912d3 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/control @@ -0,0 +1,19 @@ +Source: mozilla-telify-sflphone +Section: web +Priority: optional +Maintainer: Julien Bonjean <julien.bonjean@savoirfairelinux.com> +Uploaders: Julien Bonjean <julien.bonjean@savoirfairelinux.com> +Build-Depends: debhelper (>= 7), unzip +Homepage: http://www.sflphone.org +Standards-Version: 3.8.3 +DM-Upload-Allowed: yes + +Package: mozilla-telify-sflphone +Depends: firefox-gnome-support, sflphone-gnome +Architecture: all +Description: This package provides telify firefox plugin and handler for SFLphone. + Telify recognizes phone numbers on web pages and converts them to clickable links. + Additionally, any text can be selected and handled as a phone number (including + vanity number conversion) by selecting the corresponding context menu item. + http://www.codepad.de/en/software/firefox-add-ons/telify.html + diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/control.debian b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/control.debian new file mode 100644 index 0000000000..77147717cc --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/control.debian @@ -0,0 +1,19 @@ +Source: mozilla-telify-sflphone +Section: web +Priority: optional +Maintainer: Julien Bonjean <julien.bonjean@savoirfairelinux.com> +Uploaders: Julien Bonjean <julien.bonjean@savoirfairelinux.com> +Build-Depends: debhelper (>= 7), unzip +Homepage: http://www.sflphone.org +Standards-Version: 3.8.3 +DM-Upload-Allowed: yes + +Package: mozilla-telify-sflphone +Depends: iceweasel-gnome-support, sflphone-gnome +Architecture: all +Description: This package provides telify firefox plugin and handler for SFLphone. + Telify recognizes phone numbers on web pages and converts them to clickable links. + Additionally, any text can be selected and handled as a phone number (including + vanity number conversion) by selecting the corresponding context menu item. + http://www.codepad.de/en/software/firefox-add-ons/telify.html + diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/control.ubuntu b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/control.ubuntu new file mode 100644 index 0000000000..e0bbb912d3 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/control.ubuntu @@ -0,0 +1,19 @@ +Source: mozilla-telify-sflphone +Section: web +Priority: optional +Maintainer: Julien Bonjean <julien.bonjean@savoirfairelinux.com> +Uploaders: Julien Bonjean <julien.bonjean@savoirfairelinux.com> +Build-Depends: debhelper (>= 7), unzip +Homepage: http://www.sflphone.org +Standards-Version: 3.8.3 +DM-Upload-Allowed: yes + +Package: mozilla-telify-sflphone +Depends: firefox-gnome-support, sflphone-gnome +Architecture: all +Description: This package provides telify firefox plugin and handler for SFLphone. + Telify recognizes phone numbers on web pages and converts them to clickable links. + Additionally, any text can be selected and handled as a phone number (including + vanity number conversion) by selecting the corresponding context menu item. + http://www.codepad.de/en/software/firefox-add-ons/telify.html + diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/copyright b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/copyright new file mode 100644 index 0000000000..a0990367ef --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/copyright @@ -0,0 +1 @@ +TBD diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/files b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/files new file mode 100644 index 0000000000..320b727519 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/files @@ -0,0 +1 @@ +mozilla-telify-sflphone_1.0_all.deb web optional diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.debhelper.log b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.debhelper.log new file mode 100644 index 0000000000..89ec40ebaf --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.debhelper.log @@ -0,0 +1,11 @@ +dh_prep +dh_installdirs +dh_install +dh_installchangelogs +dh_link +dh_compress +dh_fixperms +dh_installdeb +dh_gencontrol +dh_md5sums +dh_builddeb diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.install b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.install new file mode 100644 index 0000000000..937e53876e --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.install @@ -0,0 +1,2 @@ +tmp/telify usr/share/ +sflphone-handler usr/bin/ diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.links b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.links new file mode 100644 index 0000000000..f234168dd0 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.links @@ -0,0 +1 @@ +usr/share/telify usr/lib/firefox-addons/extensions/{6c5f349a-ddda-49ad-bdf0-326d3fe1f938} diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.links.debian b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.links.debian new file mode 100644 index 0000000000..f8f52cec90 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.links.debian @@ -0,0 +1 @@ +usr/share/telify usr/lib/iceweasel/extensions/{6c5f349a-ddda-49ad-bdf0-326d3fe1f938} diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.links.ubuntu b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.links.ubuntu new file mode 100644 index 0000000000..f234168dd0 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.links.ubuntu @@ -0,0 +1 @@ +usr/share/telify usr/lib/firefox-addons/extensions/{6c5f349a-ddda-49ad-bdf0-326d3fe1f938} diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.substvars b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.substvars new file mode 100644 index 0000000000..abd3ebebc3 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone.substvars @@ -0,0 +1 @@ +misc:Depends= diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/DEBIAN/control b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/DEBIAN/control new file mode 100644 index 0000000000..b6e813410f --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/DEBIAN/control @@ -0,0 +1,14 @@ +Package: mozilla-telify-sflphone +Version: 0.4.7.3 +Architecture: all +Maintainer: Julien Bonjean <julien.bonjean@savoirfairelinux.com> +Installed-Size: 1296 +Depends: firefox-gnome-support, sflphone-gnome +Section: web +Priority: optional +Homepage: http://www.sflphone.org +Description: This package provides telify firefox plugin and handler for SFLphone. + Telify recognizes phone numbers on web pages and converts them to clickable links. + Additionally, any text can be selected and handled as a phone number (including + vanity number conversion) by selecting the corresponding context menu item. + http://www.codepad.de/en/software/firefox-add-ons/telify.html diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/DEBIAN/md5sums b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/DEBIAN/md5sums new file mode 100644 index 0000000000..20effcb346 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/DEBIAN/md5sums @@ -0,0 +1,282 @@ +65ce74599376d092487f618de1e986fb usr/bin/sflphone-handler +1dfa9e4bdb5667ed2452cb1842598638 usr/share/telify/chrome/content/jshashtable.js +09146002421216f18e238dc9914df02a usr/share/telify/chrome/content/icon32.png +3efa199f5dc17af1806daf44c9c389be usr/share/telify/chrome/content/icon96.png +5897f6aab0803b91883a6235223c4c36 usr/share/telify/chrome/content/config.js +add45890afad2c0a4e7ae2ae7602662a usr/share/telify/chrome/content/warn32.png +d070239a3728cafad19bb8aef31739aa usr/share/telify/chrome/content/messagebox.js +5624cf500dd7c023075672d3fad866d6 usr/share/telify/chrome/content/config.xul +f6ea223fbe6e7f85e00e2164546b9bd4 usr/share/telify/chrome/content/messagebox.xul +e802bc3ff17f1ea2e68ccdb263dea3fd usr/share/telify/chrome/content/ask32.png +f4de0420b7dd240b29d42e2af3bbf50c usr/share/telify/chrome/content/util.js +1bb2b945ab6f911fd32f9b046bf735b0 usr/share/telify/chrome/content/flag/52.png +90576da5f20865375ea4719f1310000d usr/share/telify/chrome/content/flag/212.png +affd04a67faccde8d9539fb3b5ca0589 usr/share/telify/chrome/content/flag/685.png +f493cea29615a02b9a917bee58ac3af3 usr/share/telify/chrome/content/flag/228.png +d27ffb0f000c36985df32eb1b34de31d usr/share/telify/chrome/content/flag/250.png +6dd70c9752d8b50ea152f5c3f228312c usr/share/telify/chrome/content/flag/43.png +20edbfd5b61a01f2e619f22333635118 usr/share/telify/chrome/content/flag/61.png +573caf2b526203b5e27614e6cc9a655a usr/share/telify/chrome/content/flag/261.png +276bf37cf19ed4fdc5e9e92796b4878d usr/share/telify/chrome/content/flag/597.png +31b9f8c69f07fdfe837ea0bd470ce9bd usr/share/telify/chrome/content/flag/1809.png +1330db2111f10ec94e67aed37af92109 usr/share/telify/chrome/content/flag/31.png +22cfbf44cf2bb7559e07e3209c285c42 usr/share/telify/chrome/content/flag/7-kazakhstan.png +31675a29209b26b05bf2aed571b024c3 usr/share/telify/chrome/content/flag/39-vatican.png +4a7b9e35f3941cef8f31b7631337b513 usr/share/telify/chrome/content/flag/678.png +63bfbc37a0bba342ba7d409cba9df880 usr/share/telify/chrome/content/flag/594.png +f58e02ceba6a9d56b0fdace52f809575 usr/share/telify/chrome/content/flag/56.png +b684077e750479668a6b5471422aa009 usr/share/telify/chrome/content/flag/682.png +552223b24a21cfcae7a9dd24ca010ef4 usr/share/telify/chrome/content/flag/244.png +193393c898442920b2f2e6becf939b57 usr/share/telify/chrome/content/flag/673.png +fd3a4f77c790e3e0ca7c7a1936924d7c usr/share/telify/chrome/content/flag/996.png +bffee9389293cc9516811ca88039bbff usr/share/telify/chrome/content/flag/65.png +9323fd337065d66b3407bf19fdee3412 usr/share/telify/chrome/content/flag/1649.png +0966afaa93eee08c08672c762424654d usr/share/telify/chrome/content/flag/44.png +35c688e6e0fbaeede3d71a0d9970c8a9 usr/share/telify/chrome/content/flag/374.png +572dc608ba807fcfbe4f23083a529da3 usr/share/telify/chrome/content/flag/853.png +56a19c85de7d530c9b24b906d4ef1d78 usr/share/telify/chrome/content/flag/1284.png +12d703c057fe3a9d22b960df954259a1 usr/share/telify/chrome/content/flag/55.png +156d00760316249b723e65c4535f6a3a usr/share/telify/chrome/content/flag/502.png +63bfbc37a0bba342ba7d409cba9df880 usr/share/telify/chrome/content/flag/262.png +b469e92e74363294932063045ba5773b usr/share/telify/chrome/content/flag/852.png +b549b2c866e19bbaf05a71b142c8e277 usr/share/telify/chrome/content/flag/222.png +f0c6ad269ca79fd0a636c0f7e55ecd8f usr/share/telify/chrome/content/flag/63.png +9917632bbfeb986871fc2f0a9a0ef323 usr/share/telify/chrome/content/flag/54.png +cc0ef42fdf091b3cc7d79689f5d90d48 usr/share/telify/chrome/content/flag/873.png +0edfcb7932381257b781a33fdac17cba usr/share/telify/chrome/content/flag/37497.png +e57cb5d9b42417834781028e79c6ad80 usr/share/telify/chrome/content/flag/229.png +f38d97d28c2db81694c8d23bea8a544e usr/share/telify/chrome/content/flag/299.png +b9d35240a94c486c39ae66784cd4bb7c usr/share/telify/chrome/content/flag/1664.png +31b9f8c69f07fdfe837ea0bd470ce9bd usr/share/telify/chrome/content/flag/1829.png +cc0ef42fdf091b3cc7d79689f5d90d48 usr/share/telify/chrome/content/flag/874.png +63bfbc37a0bba342ba7d409cba9df880 usr/share/telify/chrome/content/flag/596.png +cf6d436e35c00502149621c9d2419633 usr/share/telify/chrome/content/flag/1473.png +cc0ef42fdf091b3cc7d79689f5d90d48 usr/share/telify/chrome/content/flag/871.png +59feda46a504c73c5de936e6cc463b91 usr/share/telify/chrome/content/flag/598.png +65637850eca375f5343b51772b2789e3 usr/share/telify/chrome/content/flag/34.png +63bfbc37a0bba342ba7d409cba9df880 usr/share/telify/chrome/content/flag/508.png +07be2870ef618a4d3b69d24b3b0523bf usr/share/telify/chrome/content/flag/92.png +bd75acef3588961ef3e1d8c1beea2ba1 usr/share/telify/chrome/content/flag/242.png +9416a0c7d9c580619faec6a6bff30cc9 usr/share/telify/chrome/content/flag/297.png +465c38736ae791e89917a99467567e44 usr/share/telify/chrome/content/flag/258.png +06a21021e5dce13dce475beb26697c69 usr/share/telify/chrome/content/flag/992.png +7efb0536329c0ec081b08f288a175d4c usr/share/telify/chrome/content/flag/94.png +ec7c4ad00ede6aebe170377b8b830b3e usr/share/telify/chrome/content/flag/680.png +4e6a1e93bcf98acb4a828bedb3b39bc5 usr/share/telify/chrome/content/flag/501.png +dae53d80a2dde3c0fca76ed93422eeb2 usr/share/telify/chrome/content/flag/223.png +8b5a3f217aa96af3a0e9c799db2d930b usr/share/telify/chrome/content/flag/599.png +88d8c448f7e85ceda0eff805b081fcff usr/share/telify/chrome/content/flag/420.png +0fc6b4583f851e874e64a68f2f365e96 usr/share/telify/chrome/content/flag/976.png +125aac84de4da114aef44a9a4282ac06 usr/share/telify/chrome/content/flag/994.png +36da0872dac993c2750a5a371903bb6e usr/share/telify/chrome/content/flag/373.png +4bef1704acdc80636c68dffee9c01b51 usr/share/telify/chrome/content/flag/265.png +ace80316bb7bfd2058e4bd8e281f93e3 usr/share/telify/chrome/content/flag/57.png +a879b03b7cca0d49600c24c7c2f335cc usr/share/telify/chrome/content/flag/254.png +f9733cf00793532e858c718c60488bd7 usr/share/telify/chrome/content/flag/49.png +6350bfec1685c569dcc6d52b5b5096f3 usr/share/telify/chrome/content/flag/30.png +abddcd041761748a1462bcf509f167ce usr/share/telify/chrome/content/flag/961.png +df3279055e7e07f46705c151ffa4b188 usr/share/telify/chrome/content/flag/995.png +9cf9ac77cf4c6d5504eb5cce3d9aeb4d usr/share/telify/chrome/content/flag/504.png +efc6524bcb70ef812b2725032e0b8cb9 usr/share/telify/chrome/content/flag/1787.png +42c9adf5c32e8574701c582dd970563c usr/share/telify/chrome/content/flag/220.png +06a1651c9d58610744de5edb29a60e58 usr/share/telify/chrome/content/flag/993.png +8f7b466990cbc4b0e6e31269cbc60ea8 usr/share/telify/chrome/content/flag/37744.png +d3f29ad4504d6360d19ef706f494d7da usr/share/telify/chrome/content/flag/298.png +aa57863f7e2c49d33d3a2a0a9fe6da64 usr/share/telify/chrome/content/flag/41.png +485ad10e06bd25ef65270af20dfc996b usr/share/telify/chrome/content/flag/84.png +929bd3c144dbcfc51d7d6836b6363cbe usr/share/telify/chrome/content/flag/232.png +3f9c4b8b9f8ee08e4e9f5135cb034657 usr/share/telify/chrome/content/flag/235.png +0f92d3e3a3e1e547af05567c2510ddf0 usr/share/telify/chrome/content/flag/593.png +77857f1400221cb796eebbb029a52617 usr/share/telify/chrome/content/flag/500.png +4304ebdaa34ab1885b1ab8e5a3e45303 usr/share/telify/chrome/content/flag/239.png +98bbf43c599a38d8eb6b5421fd7bacd0 usr/share/telify/chrome/content/flag/240.png +b6c5a45e5ce80d2b527d893b279caad8 usr/share/telify/chrome/content/flag/36.png +b5f4336bf7b5a30061b9eb3be8bdb71e usr/share/telify/chrome/content/flag/291.png +94c34b050a9d0860b91a96bddcbd2650 usr/share/telify/chrome/content/flag/850.png +63bfbc37a0bba342ba7d409cba9df880 usr/share/telify/chrome/content/flag/33.png +63bfbc37a0bba342ba7d409cba9df880 usr/share/telify/chrome/content/flag/590.png +0966afaa93eee08c08672c762424654d usr/share/telify/chrome/content/flag/247.png +74deccf3b9279029b092b0359088f955 usr/share/telify/chrome/content/flag/1.png +5c4b81590c1291ed24b6f2e0fb1db51b usr/share/telify/chrome/content/flag/1868.png +09aa245b094c7ebd785eb2d6651560b8 usr/share/telify/chrome/content/flag/1340.png +9fd5fd3d5ec1c59a5de2b2cd4a83d3ba usr/share/telify/chrome/content/flag/505.png +f6481f07b520e5494ccf4fd7f2130510 usr/share/telify/chrome/content/flag/40.png +2abd1722a95d7af6e316f906cde54e6d usr/share/telify/chrome/content/flag/507.png +66266f09754869056db176a7a99c999c usr/share/telify/chrome/content/flag/855.png +08d8941a6a94447cf6838bde5a2ec48c usr/share/telify/chrome/content/flag/45.png +a938868b993bbdc98caf18e1b022f5f8 usr/share/telify/chrome/content/flag/689.png +92cacebf8596b267f69b7b99bb2f5588 usr/share/telify/chrome/content/flag/974.png +5672868ef79592374bf990f652607a6e usr/share/telify/chrome/content/flag/387.png +8de229a968922437036b9731a376d12e usr/share/telify/chrome/content/flag/963.png +1bad50fdbd079efb01c9996dd3db9f39 usr/share/telify/chrome/content/flag/234.png +258acc3c84ef093985f20f0c4cc3e8bb usr/share/telify/chrome/content/flag/886.png +2a555ebb43f963161fa1046442d5bee8 usr/share/telify/chrome/content/flag/385.png +5b811ac6ef5af15e3a4fd75679814f01 usr/share/telify/chrome/content/flag/253.png +2748dfe6daeebefc78dc9e7e5261e2a7 usr/share/telify/chrome/content/flag/378.png +f4b5c9d9e2025a5cfa95652852aa0707 usr/share/telify/chrome/content/flag/688.png +f2ba52be3d5071a0fbf5ed58df529f14 usr/share/telify/chrome/content/flag/3883.png +d39cf563c53fd10e3f4e4d95f98b0035 usr/share/telify/chrome/content/flag/375.png +3bd363fac16b74cb23adbd7704ade65d usr/share/telify/chrome/content/flag/47.png +f7f4151604e5860cf696e7409ef173b2 usr/share/telify/chrome/content/flag/880.png +c964a9a106e3c9b76d3559c2cfcba90d usr/share/telify/chrome/content/flag/968.png +addd908670d7012818061979c7bace02 usr/share/telify/chrome/content/flag/221.png +90226addfca8c9861a770f15e94b78d2 usr/share/telify/chrome/content/flag/354.png +31675a29209b26b05bf2aed571b024c3 usr/share/telify/chrome/content/flag/379.png +63bfbc37a0bba342ba7d409cba9df880 usr/share/telify/chrome/content/flag/681.png +b5208534081b162fe790e854408b6dac usr/share/telify/chrome/content/flag/51.png +8f7b466990cbc4b0e6e31269cbc60ea8 usr/share/telify/chrome/content/flag/38649.png +168dc53ba860577f659345846d59cb45 usr/share/telify/chrome/content/flag/965.png +6e76764835f5db75d13b2498ba7e3efb usr/share/telify/chrome/content/flag/260.png +8678e140cb0965400a842595e8034e6e usr/share/telify/chrome/content/flag/670.png +07fcf184129db2d17310bed269bd032f usr/share/telify/chrome/content/flag/677.png +e8d950cb0f9b047987cfed72903a61b2 usr/share/telify/chrome/content/flag/82.png +2530d73e10540607d8c453f10030ccd3 usr/share/telify/chrome/content/flag/243.png +844f10c7a922da19a99fe6a9b7eb27dd usr/share/telify/chrome/content/flag/371.png +0fa3206cde59782c9998153683bff8d3 usr/share/telify/chrome/content/flag/238.png +26f26cccb7b432d4dde1a1eb394c0ceb usr/share/telify/chrome/content/flag/231.png +8bdeaa2a3bc46f876e7dff744fc47659 usr/share/telify/chrome/content/flag/90.png +0da3d2cf7c99fb9dfe238bfcb4ab11cf usr/share/telify/chrome/content/flag/251.png +4f9e0d2c82662f5b9b56acf472610c70 usr/share/telify/chrome/content/flag/998.png +46019464edfa1be7666087620fe7142b usr/share/telify/chrome/content/flag/382.png +eb0d4e938edac3bee751b17ca72849df usr/share/telify/chrome/content/flag/95.png +e9cdf4484d0646e09464a28350be4df2 usr/share/telify/chrome/content/flag/263.png +aa046c5e44c961089dbc6f45d8873122 usr/share/telify/chrome/content/flag/592.png +45e940da2982616d3cc99822128e6cae usr/share/telify/chrome/content/flag/267.png +e4d9d483a531c7a16436b09a8a4cf104 usr/share/telify/chrome/content/flag/20.png +7e6b762105a6b1679c1abd0280da2f73 usr/share/telify/chrome/content/flag/227.png +9fd0b3d477d8651a16241d130ee353ad usr/share/telify/chrome/content/flag/1758.png +d1e4b9739c6eff4f6beadc8eb4ab8650 usr/share/telify/chrome/content/flag/595.png +fb0490472ceed9b31b19dbe0a1d8f872 usr/share/telify/chrome/content/flag/236.png +390bf4371cc50c51c50d1d15d7e63e3f usr/share/telify/chrome/content/flag/7.png +bc4f5cc6c03cdd13217d6d32e6425989 usr/share/telify/chrome/content/flag/1246.png +757c67bf50c916597a5088829140286b usr/share/telify/chrome/content/flag/1767.png +f301e493e1c0271865c115a25f3f8680 usr/share/telify/chrome/content/flag/359.png +278b835433b00b47589b3d7cf28d52a7 usr/share/telify/chrome/content/flag/377.png +8f7b466990cbc4b0e6e31269cbc60ea8 usr/share/telify/chrome/content/flag/381-kosovo.png +50185d74128455cb2c16c0903a03ca0d usr/share/telify/chrome/content/flag/1684.png +ce84e48ba306877dd6112a8470bfdfb0 usr/share/telify/chrome/content/flag/248.png +0397fc499dce824e378c758682774440 usr/share/telify/chrome/content/flag/246.png +9bd332abc26865f07ebae01383b8494d usr/share/telify/chrome/content/flag/224.png +464067a945638f51c7d3ee7ed4dda81d usr/share/telify/chrome/content/flag/370.png +2c030899839f02c2954cda800e4c41db usr/share/telify/chrome/content/flag/266.png +f405c1ae5ff791f8f6c8ff31bfd98e91 usr/share/telify/chrome/content/flag/216.png +8b5f8aff9a34065f16db660509b6476b usr/share/telify/chrome/content/flag/1345.png +a3d9074daaf4b13a1c475469acb1ef40 usr/share/telify/chrome/content/flag/213.png +9b475b0988eff85c2e6758f3629d3e3a usr/share/telify/chrome/content/flag/1876.png +a4d03f19872b3a11d3c8c9f55477caac usr/share/telify/chrome/content/flag/355.png +ef1a2c9fc984d6e21c2543bac5d56b89 usr/share/telify/chrome/content/flag/53.png +7d71f58c22b314c64f304070cd4dcb4f usr/share/telify/chrome/content/flag/226.png +87d57818a5ad32dad3c512ebc93a80d6 usr/share/telify/chrome/content/flag/91.png +33f848b2031ff858cd7db75404640a6b usr/share/telify/chrome/content/flag/591.png +0dc574a3de142aecc3e996206371294a usr/share/telify/chrome/content/flag/225.png +c72a9c63d4d41272ea672a1c402ee189 usr/share/telify/chrome/content/flag/973.png +efe435a51af6d9c7e97c7c8a050d6efb usr/share/telify/chrome/content/flag/1264.png +0c3c91808881101dff1635101e02aa2b usr/share/telify/chrome/content/flag/269.png +4a6d3eee131acf89897c0b24689b82c3 usr/share/telify/chrome/content/flag/503.png +019ff6b8cd322dfa5663c511d07b0d5d usr/share/telify/chrome/content/flag/1671.png +4220598be43707f399cf5b62aec26a4e usr/share/telify/chrome/content/flag/27.png +cc0ef42fdf091b3cc7d79689f5d90d48 usr/share/telify/chrome/content/flag/872.png +94a4f42d77b298471b01e31068a4993c usr/share/telify/chrome/content/flag/421.png +6488bf155022194a4b9b419e5b411fdf usr/share/telify/chrome/content/flag/90392.png +0edfcb7932381257b781a33fdac17cba usr/share/telify/chrome/content/flag/37447.png +abac2bdc6ee86fed6f2a83b0bbc5af1b usr/share/telify/chrome/content/flag/218.png +9c120774fad7fae663c0c41e101410e9 usr/share/telify/chrome/content/flag/674.png +fb53ec0d0245f2e4ebb8361d8ff85f8f usr/share/telify/chrome/content/flag/690.png +5dcba80a818cf6b07c5b7ccd0297a65f usr/share/telify/chrome/content/flag/252.png +48967bc438e5a7bb69cca0f7732396eb usr/share/telify/chrome/content/flag/98.png +03453359634eecec412c8e467f67af76 usr/share/telify/chrome/content/flag/290.png +1cad1e69d782f42a29a1fcc532daf243 usr/share/telify/chrome/content/flag/48.png +4628248388e53df337b39730f8aefbfd usr/share/telify/chrome/content/flag/256.png +9a758bb3b0392ee2c6d814dbaeea01b4 usr/share/telify/chrome/content/flag/376.png +53ef3d82ba706db1e5dfdd5b0bf14c81 usr/share/telify/chrome/content/flag/972.png +acdae2b0851d5fec6c4b16ab884bbd9d usr/share/telify/chrome/content/flag/86.png +35a5b51d0e37656c93cca43686c7ce62 usr/share/telify/chrome/content/flag/233.png +c24a1dc23535660e8f013d7f4b15eba0 usr/share/telify/chrome/content/flag/60.png +9b0517891ee2538397a7140697bde925 usr/share/telify/chrome/content/flag/241.png +1f40c7e3f9513ea1a31d5b1efd2adae6 usr/share/telify/chrome/content/flag/249.png +6a190cf5b40502186d7a335a8e10dcfd usr/share/telify/chrome/content/flag/64.png +714928715307e35c9d96f82baa9e5658 usr/share/telify/chrome/content/flag/856.png +de204480ea7585fa2ab331b7c4b856af usr/share/telify/chrome/content/flag/257.png +fd92a03c9c1fbf78fea7e50b1e4085f9 usr/share/telify/chrome/content/flag/353.png +d9a1931187b34b91d8a0becbfcc8d92d usr/share/telify/chrome/content/flag/672.png +8f1c20cc56e3b0388c37fa9f300cdc80 usr/share/telify/chrome/content/flag/356.png +610a156d74efa1227e732319c0bddcdc usr/share/telify/chrome/content/flag/692.png +b44c818df281cad4bf13f54dcf67b151 usr/share/telify/chrome/content/flag/66.png +0399c70c9efba4caefd028ad5028bc25 usr/share/telify/chrome/content/flag/960.png +72f33ab0fff112755d90d65fc71042fd usr/share/telify/chrome/content/flag/423.png +a99812c389c8a521a9b7495e7d7318d7 usr/share/telify/chrome/content/flag/230.png +40796e5560f2fa63f523de45bfd20f07 usr/share/telify/chrome/content/flag/964.png +c5ba8166e7165bef7247b674faafa83a usr/share/telify/chrome/content/flag/32.png +44d65230c16730cc9b74ab0cf0f3a31d usr/share/telify/chrome/content/flag/962.png +328daac04aee3d4a868673667b29f561 usr/share/telify/chrome/content/flag/1268.png +a9123178222f6940496eeed41e4f7932 usr/share/telify/chrome/content/flag/245.png +7dd4f0a5d9fc3201cb03f90c5b9dd98d usr/share/telify/chrome/content/flag/509.png +5ac9bbaab7ef6edaf285a92655ad3100 usr/share/telify/chrome/content/flag/372.png +0215b1c78f1983ff93d4a71d994429b3 usr/share/telify/chrome/content/flag/967.png +bc2b7b1fea6191cadfe5563c4ec4f482 usr/share/telify/chrome/content/flag/1670.png +ad5a8bf6203aaa390432c164b7f98097 usr/share/telify/chrome/content/flag/1784.png +6cda8970ef0d1ac14ebda52fe7be9d50 usr/share/telify/chrome/content/flag/352.png +cf9a0a8e33c14011ca720d5c4308d145 usr/share/telify/chrome/content/flag/386.png +722cb977a10c1a2a8412fc5ee87d4fc4 usr/share/telify/chrome/content/flag/675.png +0641850973fa5605d26add1e6c859da4 usr/share/telify/chrome/content/flag/676.png +2e017a5dd612390255848dca5afb330d usr/share/telify/chrome/content/flag/255.png +01d51ff805bef4ce44d75fbbd93b28d9 usr/share/telify/chrome/content/flag/966.png +e81fd902853e1d3abfc59a8aa1ae6832 usr/share/telify/chrome/content/flag/1242.png +a71af7b8b48922f614b499239c8a51b8 usr/share/telify/chrome/content/flag/977.png +bea37d045f7026995838fcfc8b9b45ca usr/share/telify/chrome/content/flag/268.png +b24fbefbb7e51f8c68c3df2c74891878 usr/share/telify/chrome/content/flag/62.png +0c50a0a1bf667ae3271ffff33e2b9372 usr/share/telify/chrome/content/flag/380.png +8c0085810a9528c2bf6e3f5b6e43e281 usr/share/telify/chrome/content/flag/672-norfolk_island.png +b4e46a8d8576a1a717f8e1f2c5de85a9 usr/share/telify/chrome/content/flag/350.png +d45dfb7a968e89b66db04ddf446ffe09 usr/share/telify/chrome/content/flag/1869.png +8f797a746ef2192e4cfa5fb7fb1fb272 usr/share/telify/chrome/content/flag/58.png +949942100b8504bc91da99d9c237633b usr/share/telify/chrome/content/flag/683.png +cd560dd865ccea58212db45956248115 usr/share/telify/chrome/content/flag/975.png +3275d2a698a6fc4497c3628d681b5d12 usr/share/telify/chrome/content/flag/358.png +61c1ecffc039239ee6411394921aa241 usr/share/telify/chrome/content/flag/506.png +4a89ee4cf5fe72c332e90527e12c07cf usr/share/telify/chrome/content/flag/93.png +458b2d7875fc143704f636e7454e598d usr/share/telify/chrome/content/flag/971.png +111a19121adf21f6010aa43992cd5675 usr/share/telify/chrome/content/flag/389.png +63bfbc37a0bba342ba7d409cba9df880 usr/share/telify/chrome/content/flag/687.png +2d2714d2e0de8465ac12d65a5e52d82d usr/share/telify/chrome/content/flag/381.png +af283fcaf3f288709548bae1c4750031 usr/share/telify/chrome/content/flag/264.png +91c372b5e4167ab59eb858e442a8f91f usr/share/telify/chrome/content/flag/679.png +449f5fa5b87314615a7ea3aa501a78fd usr/share/telify/chrome/content/flag/691.png +a63f50a8081b441b48989267a6fb329c usr/share/telify/chrome/content/flag/39.png +bf17a973466b976ddefdcdf52503e6fa usr/share/telify/chrome/content/flag/46.png +efc6524bcb70ef812b2725032e0b8cb9 usr/share/telify/chrome/content/flag/1939.png +4b30c9135a576a61668ce14129c60ac7 usr/share/telify/chrome/content/flag/81.png +ec10eee9610f5ac6bc74311ae481fad7 usr/share/telify/chrome/content/flag/1441.png +300e025eca7079783221fcd86ea1942a usr/share/telify/chrome/content/flag/237.png +c9088d972106719cce7c9a226f2b1667 usr/share/telify/chrome/content/flag/1-canada.png +a676e93449fc74703d5f12636593b699 usr/share/telify/chrome/content/flag/686.png +63faf9dbefe527ed55e0d9170fb5fba2 usr/share/telify/chrome/content/flag/357.png +219a52448b0df4e66905086301b03b10 usr/share/telify/chrome/content/flag/351.png +cc0ef42fdf091b3cc7d79689f5d90d48 usr/share/telify/chrome/content/flag/870.png +dd8c6aa4b172928bc49969b80a0926a3 usr/share/telify/chrome/content/editNumber.js +8b49826c5b4ebfbb95c513be63a79b41 usr/share/telify/chrome/content/pref.js +cacb7d13f9781f1d71b6338f62df55e0 usr/share/telify/chrome/content/edit22x15.png +4d0fec589b212e8fd004abe97cae9884 usr/share/telify/chrome/content/telify.js +380345f5e1888667b412a9cc6d622ffb usr/share/telify/chrome/content/dialog.css +0c48e0a5c919244e39da008905bf3704 usr/share/telify/chrome/content/info32.png +b1b6854306f4547a0f17fa88e5bbfc2a usr/share/telify/chrome/content/browser.xul +b59d8264612fa19986333ce3f20c978a usr/share/telify/chrome/content/editNumber.xul +259e7299cf9c56c466e62b6d2e7a09be usr/share/telify/chrome/content/icon18_active.png +df8e1ad83cb3ee6cfb8073be99c9334d usr/share/telify/chrome/content/country_data.js +d11c921428d126525024f0269c289366 usr/share/telify/chrome/content/icon18_inactive.png +961bc17c9aab761902b5e755424c50f0 usr/share/telify/chrome/content/icon_menu.png +c192d0d65c0ee5093a9f520cbb203cde usr/share/telify/chrome/content/error32.png +081a39ee35f6fd034d47a52d18161e38 usr/share/telify/chrome/locale/en-US/custom_preset.js +f93c88f9730ab952c1607f01284c953b usr/share/telify/chrome/locale/en-US/lang.dtd +b05b084a52248112936f234f70afa445 usr/share/telify/chrome/locale/en-US/country_locale.js +d0568dafab9a1206113235173cc58c85 usr/share/telify/chrome/locale/en-US/lang.properties +878ffe167a481f93b26302871868618a usr/share/telify/chrome/locale/en-US/locale.js +24604d65d67874073ad9b3df7060e221 usr/share/telify/chrome/locale/de-DE/custom_preset.js +8ea28f7144c3da1b13824c751ed045d7 usr/share/telify/chrome/locale/de-DE/lang.dtd +ded79ce41ea89b117534c1af4e95d5b5 usr/share/telify/chrome/locale/de-DE/country_locale.js +f8c99d4e0ea6514f2d4f6ad71db85ae2 usr/share/telify/chrome/locale/de-DE/lang.properties +816dac9e3dc748bc9f7e6a46dd57e029 usr/share/telify/chrome/locale/de-DE/locale.js +4200a5c7a588e6aadbd6d56dc0f72ee7 usr/share/telify/defaults/preferences/preferences.js +8bed4b9918d4f36b8c3db1f0322e31bf usr/share/telify/chrome.manifest +7a164502d511ecbe38c3ad860e833579 usr/share/telify/install.rdf +a5a82bcdce14f9439cad46169b5bc354 usr/share/doc/mozilla-telify-sflphone/changelog.gz diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/DEBIAN/postinst b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/DEBIAN/postinst new file mode 100755 index 0000000000..1039df3268 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/DEBIAN/postinst @@ -0,0 +1,16 @@ +#!/bin/bash + +gconftool-2 --direct --config-source xml::/etc/gconf/gconf.xml.defaults -t string -s /desktop/gnome/url-handlers/tel/command "/usr/bin/sflphone-handler %s" +gconftool-2 --direct --config-source xml::/etc/gconf/gconf.xml.defaults -s /desktop/gnome/url-handlers/tel/needs_terminal false -t bool +gconftool-2 --direct --config-source xml::/etc/gconf/gconf.xml.defaults -t bool -s /desktop/gnome/url-handlers/tel/enabled true + +gconftool-2 --direct --config-source xml::/etc/gconf/gconf.xml.defaults -t string -s /desktop/gnome/url-handlers/callto/command "/usr/bin/sflphone-handler %s" +gconftool-2 --direct --config-source xml::/etc/gconf/gconf.xml.defaults -s /desktop/gnome/url-handlers/callto/needs_terminal false -t bool +gconftool-2 --direct --config-source xml::/etc/gconf/gconf.xml.defaults -t bool -s /desktop/gnome/url-handlers/callto/enabled true + +gconftool-2 --direct --config-source xml::/etc/gconf/gconf.xml.defaults -t string -s /desktop/gnome/url-handlers/sip/command "/usr/bin/sflphone-handler %s" +gconftool-2 --direct --config-source xml::/etc/gconf/gconf.xml.defaults -s /desktop/gnome/url-handlers/sip/needs_terminal false -t bool +gconftool-2 --direct --config-source xml::/etc/gconf/gconf.xml.defaults -t bool -s /desktop/gnome/url-handlers/sip/enabled true + +exit 0 + diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/bin/sflphone-handler b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/bin/sflphone-handler new file mode 100755 index 0000000000..c3686ff804 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/bin/sflphone-handler @@ -0,0 +1,52 @@ +#!/bin/sh +# +# This script can be used as a callto: (or other) protocol handler in +# Mozilla Firefox-based browser. +# In Firefox use Preferences > Applications and set the callto handler +# to this script. + +# The sflphone daemon config file +RESFILE=~/.config/sflphone/sflphonedrc + +# Parse sflphonedrc and get default account id string +if [ -f "$RESFILE" ]; then + + # Use first ID + ACCOUNTID=`grep Accounts.order $RESFILE | sed -e 's/Accounts.order=//' -e 's/\/.*//'` + + # Accounts.order is not set + if [ -z $ACCOUNTID ]; then + + # Use first account declared in sflphone config + ACCOUNTID="`grep -m 1 Account: $RESFILE | sed -e 's/\[//' -e 's/\]//'`" + fi + +else + echo Fatal: Cant find sflphonedrc config file. + exit 1 +fi + +# Check 1st argument (phone number) +if [ -z $1 ]; then + echo "Error: argument 1 (phone number) not provided." + exit 1 +fi + +# Cleanup destination, keeping numbers only +TO="`echo $1 | sed -e 's/[^0123456789]//g'`" + +# Generate call id. +CALLID=${RANDOM}$$ + +dbus-send \ + --type="method_call" \ + --dest="org.sflphone.SFLphone" \ + "/org/sflphone/SFLphone/CallManager" \ + "org.sflphone.SFLphone.CallManager.placeCall" \ + string:"$ACCOUNTID" \ + string:"$CALLID" \ + string:"$TO" + +exit 0 + +# EOF diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/doc/mozilla-telify-sflphone/changelog.gz b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/doc/mozilla-telify-sflphone/changelog.gz new file mode 100644 index 0000000000000000000000000000000000000000..186ab5d35fe47e1e0590fbfa544a38e8604cbdfd GIT binary patch literal 175 zcmb2|=3oE;Cg#{aYyKt!5x4TXqq4PIHMtBQC?7j`@Zb*V_DeAvBJz9}YW%B`zB^~% z9Q|~Q-rDSBmDd+u6eZ+l%9(p}C?6Ew_oUmfBFj2bBBrribdr?WF}uYa518EZO@!X) zUe(}S_i)aE$W^=72uyLlnd$AA`X)a1di=Aa{pD-ltSwu5MXzK+ePQZs-2(?-N}gez elOykzB9LAw^*oa)Sdd$=mZ577|J{8I3=9BrrAbo& literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome.manifest b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome.manifest new file mode 100644 index 0000000000..bc9507c881 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome.manifest @@ -0,0 +1,5 @@ +content telify chrome/content/ +locale telify en-US chrome/locale/en-US/ +locale telify de-DE chrome/locale/de-DE/ + +overlay chrome://browser/content/browser.xul chrome://telify/content/browser.xul diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/ask32.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/ask32.png new file mode 100644 index 0000000000000000000000000000000000000000..d56ba2c2449cb156979af2896e82be831f258d93 GIT binary patch literal 2671 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/browser.xul b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/browser.xul new file mode 100644 index 0000000000..68f72fbff7 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/browser.xul @@ -0,0 +1,87 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?> +<?xml-stylesheet href="chrome://telify/content/dialog.css" type="text/css"?> + +<!DOCTYPE overlay SYSTEM "chrome://telify/locale/lang.dtd"> +<overlay xmlns='http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul'> + + <stringbundleset id="stringbundleset"> + <stringbundle id="idTelifyStringBundle" src="chrome://telify/locale/lang.properties"/> + </stringbundleset> + + <script type='application/x-javascript' src='chrome://telify/content/jshashtable.js'></script> + <script type='application/x-javascript' src='chrome://telify/content/util.js'></script> + <script type='application/x-javascript' src='chrome://telify/content/pref.js'></script> + <script type='application/x-javascript' src='chrome://telify/locale/country_locale.js'></script> + <script type='application/x-javascript' src='chrome://telify/content/country_data.js'></script> + <script type='application/x-javascript' src='chrome://telify/locale/locale.js'></script> + <script type='application/x-javascript' src='chrome://telify/content/telify.js'></script> + + <statusbar id="status-bar"> + <statusbarpanel id="idTelify_status" collapsed="true"> + <popup id="idTelify_status_popup" onpopupshowing="objTelify.modifyPopup(event)"> + <menuitem id="idTelify_status_activity" oncommand="objTelify.toggleActive()" /> + <menuitem id="idTelify_status_blacklist" oncommand="objTelify.toggleBlacklist()" /> + </popup> + <hbox id="idTelify_statusicon" context="idTelify_status_popup" class="statusbarpanel-menu-iconic" src="chrome://telify/content/icon18_active.png" /> + </statusbarpanel> + </statusbar> + + <menupopup id="contentAreaContextMenu"> + <menu id="idTelify_menu_context" label="&menu.selection;" collapsed="true" insertbefore="context-sep-stop" + class="menu-iconic" image="chrome://telify/content/icon_menu.png"> + <menupopup id="idTelify_popup_context"> + <menuitem id="idTelify_context" class="menuitem-iconic"/> + <menuseparator id="idTelify_sep_context"/> + <menuitem id="idTelify_tld_context" class="menuitem-iconic"/> + <menuitem id="idTelify_context0" class="menuitem-iconic"/> + <menuitem id="idTelify_context1" class="menuitem-iconic"/> + <menuitem id="idTelify_context2" class="menuitem-iconic"/> + <menuitem id="idTelify_context3" class="menuitem-iconic"/> + <menuitem id="idTelify_context4" class="menuitem-iconic"/> + <menuitem id="idTelify_context5" class="menuitem-iconic"/> + <menuitem id="idTelify_context6" class="menuitem-iconic"/> + <menuitem id="idTelify_context7" class="menuitem-iconic"/> + <menuitem id="idTelify_context8" class="menuitem-iconic"/> + <menuitem id="idTelify_context9" class="menuitem-iconic"/> + <menuseparator /> + <menuitem id="idTelify_edit_context" class="menuitem-iconic" label="&menu.edit_number;" image="chrome://telify/content/edit22x15.png"/> + </menupopup> + </menu> + </menupopup> + + <popupset> + <menupopup id="idTelify_popup_dial"> + <menuitem id="idTelify_dial" class="menuitem-iconic"/> + <menuseparator id="idTelify_sep_dial"/> + <menuitem id="idTelify_tld_dial" class="menuitem-iconic"/> + <menuitem id="idTelify_dial0" class="menuitem-iconic"/> + <menuitem id="idTelify_dial1" class="menuitem-iconic"/> + <menuitem id="idTelify_dial2" class="menuitem-iconic"/> + <menuitem id="idTelify_dial3" class="menuitem-iconic"/> + <menuitem id="idTelify_dial4" class="menuitem-iconic"/> + <menuitem id="idTelify_dial5" class="menuitem-iconic"/> + <menuitem id="idTelify_dial6" class="menuitem-iconic"/> + <menuitem id="idTelify_dial7" class="menuitem-iconic"/> + <menuitem id="idTelify_dial8" class="menuitem-iconic"/> + <menuitem id="idTelify_dial9" class="menuitem-iconic"/> + <menuseparator /> + <menuitem id="idTelify_edit_dial" class="menuitem-iconic" label="&menu.edit_number;" image="chrome://telify/content/edit22x15.png"/> + </menupopup> + </popupset> + + <menupopup id="menu_ToolsPopup"> + <menu id="idTelify_menu" label="Telify" insertafter="devToolsSeparator"> + <menupopup onpopupshowing="objTelify.modifyPopup(event)"> + <menuitem id="idTelify_menu_activity" oncommand="objTelify.toggleActive()"/> + <menuitem id="idTelify_menu_blacklist" oncommand="objTelify.toggleBlacklist()"/> + <menuseparator /> + <menuitem id="idTelify_menu_config" label="&menu.onlinehelp;" oncommand="objTelifyLocale.openOnlineHelp()"/> + <menuseparator /> + <menuitem id="idTelify_menu_config" label="&menu.config;" oncommand="objTelifyPrefs.showConfigDialog()"/> + </menupopup> + </menu> + </menupopup> + +</overlay> + diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/config.js b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/config.js new file mode 100644 index 0000000000..25e529121a --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/config.js @@ -0,0 +1,196 @@ +/* +Creative Commons License: Attribution-No Derivative Works 3.0 Unported +http://creativecommons.org/licenses/by-nd/3.0/ +(c)2009 Michael Koch +*/ + +var objTelifyConfig = { + +tmplIndex: 0, +customLabelDefault: "", + +setConfigValues: function() +{ + objTelifyPrefs.telPrefs.setCharPref(objTelifyPrefs.PREF_IDD_PREFIX, document.getElementById("idTelifyPref_idd_prefix").value); + objTelifyPrefs.telPrefs.setIntPref(objTelifyPrefs.PREF_HREFTYPE, document.getElementById("idTelifyPref_hreftype").value); + objTelifyPrefs.telPrefs.setIntPref(objTelifyPrefs.PREF_HIGHLIGHT, document.getElementById("idTelifyPref_highlight").value); + objTelifyPrefs.telPrefs.setIntPref(objTelifyPrefs.PREF_NUMHISTORY, document.getElementById("idTelifyPref_num_history").value); + objTelifyPrefs.telPrefs.setBoolPref(objTelifyPrefs.PREF_STATUSICON, document.getElementById("idTelifyPref_statusicon").value == 1); + objTelifyPrefs.telPrefs.setBoolPref(objTelifyPrefs.PREF_DIAL_CC_DIRECT, document.getElementById("idTelifyPref_dialcc").value == 1); + + objTelifyPrefs.telPrefs.setCharPref(objTelifyPrefs.PREF_CUSTOM_URL, document.getElementById("idTelifyPref_url_input").value); + objTelifyPrefs.telPrefs.setIntPref(objTelifyPrefs.PREF_CUSTOM_TMPL, this.tmplIndex); + for (var i=1; i<objTelifyPrefs.NUM_CUSTOM_PARAMS+1; i++) { + objTelifyPrefs.telPrefs.setCharPref(objTelifyPrefs.PREF_CUSTOM_PARAM+i, document.getElementById("idTelifyPref_param"+i+"_value").value); + } + objTelifyPrefs.telPrefs.setIntPref(objTelifyPrefs.PREF_CUSTOM_OPENTYPE, document.getElementById("idTelifyPref_opentype").value); +}, + +onAccept: function() +{ + this.setConfigValues(); + return true; +}, + +onHelp: function() +{ + objTelifyLocale.openOnlineHelp(); + return true; +}, + +initConfig: function() +{ + objTelifyPrefs.initTelifyPrefs(); + document.getElementById("idTelifyPref_idd_prefix").value = objTelifyPrefs.idd_prefix; + document.getElementById("idTelifyPref_hreftype").value = objTelifyPrefs.hrefType; + this.hrefTypeChanged(objTelifyPrefs.hrefType); + document.getElementById("idTelifyPref_highlight").value = objTelifyPrefs.highlight; + document.getElementById("idTelifyPref_num_history").value = objTelifyPrefs.numHistory; + document.getElementById("idTelifyPref_statusicon").value = (objTelifyPrefs.fStatusIcon ? 1 : 0); + document.getElementById("idTelifyPref_dialcc").value = (objTelifyPrefs.fDialCCDirect ? 1 : 0); + + document.getElementById("idTelifyPref_url_input").value = objTelifyPrefs.custom_url; + this.tmplIndex = objTelifyPrefs.custom_tmpl; + for (var i=1; i<objTelifyPrefs.NUM_CUSTOM_PARAMS+1; i++) { + document.getElementById("idTelifyPref_param"+i+"_value").value = objTelifyPrefs.custom_param[i]; + } + document.getElementById("idTelifyPref_opentype").value = objTelifyPrefs.custom_opentype; + + this.customLabelDefault = document.getElementById("idTelifyPref_custom_caption").label + + var popup = document.getElementById("idTelifyPref_url_popup"); + for (var i=0; i<telify_custom_preset.length; i++) { + var item = document.createElement("menuitem"); + item.setAttribute("label", telify_custom_preset[i][0]); + popup.appendChild(item); + } + + this.setTemplate(this.tmplIndex, true); + + document.getElementById("idTelifyPref_version_label").value = "Telify v"+objTelifyUtil.getAddonVersion(); +}, + +getTemplateParam: function(nr) +{ + if (nr == 0) return objTelifyPrefs.telStrings.getString("phonenr_tmpl"); + var param = document.getElementById("idTelifyPref_param"+nr+"_value").value; + var label = document.getElementById("idTelifyPref_param"+nr+"_caption").value; + if (label.value == "") param = ""; + return param; +}, + +createResultDOM: function(node) +{ + if (node == null) return 0; // safety + if (node.nodeType == Node.TEXT_NODE) { + var text = node.data; + var len = text.length; + var escape = 0; + for (var i=0; i<len-1; i++) { + if (escape == 1) {escape = 0; continue;} + var c = text.charAt(i); + if (c == '\\') {escape = 1; continue} + if (c != '$') continue; + c = text.charAt(i+1); + var nr = "0123456789".indexOf(c); + if (nr < 0 || nr > objTelifyPrefs.NUM_CUSTOM_PARAMS) continue; + var prev_node = document.createTextNode(text.substr(0, i)); + var next_node = document.createTextNode(text.substr(i+2)); + var hilite_node = document.createElement("span"); + hilite_node.setAttribute("class", (nr == 0 ? "tmpl_number" : "tmpl_param")); + var param_node = document.createTextNode(this.getTemplateParam(nr)); + hilite_node.appendChild(param_node); + var parentNode = node.parentNode; + parentNode.replaceChild(next_node, node); + parentNode.insertBefore(hilite_node, next_node); + parentNode.insertBefore(prev_node, hilite_node); + break; + } + } else { + for (var i=0; i<node.childNodes.length; i++) { + this.createResultDOM(node.childNodes[i]); + } + } +}, + +urlChanged: function() +{ + var url = document.getElementById("idTelifyPref_url_input").value; + var result = document.getElementById("idTelifyPref_url_result"); + while (result.childNodes[0]) result.removeChild(result.childNodes[0]); + if (url == "") { + var item = document.createElement("span"); + var empty_url = objTelifyPrefs.telStrings.getString("empty_url") + item.appendChild(document.createTextNode(empty_url)); + item.setAttribute("class", "tmpl_empty"); + result.appendChild(item); + } else { + var item = document.createTextNode(url); + result.appendChild(item); + this.createResultDOM(result); + } +}, + +setTemplate: function(nr, init) +{ + var caption = document.getElementById("idTelifyPref_custom_caption"); + caption.label = this.customLabelDefault; + if (telify_custom_preset[nr][0].length) caption.label += " ("+telify_custom_preset[nr][0]+")"; + if (!init) document.getElementById("idTelifyPref_url_input").value = telify_custom_preset[nr][1]; + for (var j=0; j<objTelifyPrefs.NUM_CUSTOM_PARAMS; j++) { + var label = document.getElementById("idTelifyPref_param"+(j+1)+"_caption"); + var param = document.getElementById("idTelifyPref_param"+(j+1)+"_value"); + var row = document.getElementById("idTelifyPref_param"+(j+1)+"_row"); + label.value = telify_custom_preset[nr][2+j]; + if (label.value != "") label.value += ":"; + if (label.value == "") param.setAttribute("disabled", true); else param.removeAttribute("disabled"); + } + this.urlChanged(); +}, + +tmplChanged: function() +{ + var obj = document.getElementById("idTelifyPref_url_input"); + for (var i=0; i<telify_custom_preset.length; i++) { + if (obj.value == telify_custom_preset[i][0]) { + this.tmplIndex = i; + this.setTemplate(i, false); + break; + } + } +}, + +paramChanged: function(nr, value) +{ + this.urlChanged(); +}, + +enableDOMTree: function(node, enable) +{ + if (node == null) return; + if (enable) { + if (node.removeAttribute) node.removeAttribute("disabled"); + } else { + if (node.setAttribute) node.setAttribute("disabled", true); + } + for (var i=0; i<node.childNodes.length; i++) { + this.enableDOMTree(node.childNodes[i], enable); + } +}, + +hrefTypeChanged: function(nr) +{ + var group = document.getElementById("idTelifyPref_custom_group"); + if (nr == objTelifyPrefs.HREFTYPE_CUSTOM) { + group.removeAttribute("collapsed"); + window.sizeToContent(); + } else { + //alert(group.clientHeight); + group.setAttribute("collapsed", true); + //window.resizeTo(500, 500); + window.resizeBy(0, -200); + window.sizeToContent(); + } +} + +}; diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/config.xul b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/config.xul new file mode 100644 index 0000000000..967d4db722 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/config.xul @@ -0,0 +1,180 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?> +<?xml-stylesheet href="chrome://telify/content/dialog.css" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://telify/locale/lang.dtd"> + +<dialog id="dlgTelifyConfig" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + buttons="accept,cancel,help" + onload="objTelifyConfig.initConfig()" + ondialogaccept="objTelifyConfig.onAccept()" + ondialogcancel="" + ondialoghelp="objTelifyConfig.onHelp()" + title="&dialog.config.title;"> + + <stringbundleset id="stringbundleset"> + <stringbundle id="idTelifyStringBundle" src="chrome://telify/locale/lang.properties"/> + </stringbundleset> + + <script type='application/x-javascript' src='chrome://telify/content/util.js'></script> + <script type='application/x-javascript' src='chrome://telify/content/pref.js'></script> + <script type='application/x-javascript' src='chrome://telify/content/config.js'></script> + <script type='application/x-javascript' src='chrome://telify/locale/locale.js'></script> + <script type='application/x-javascript' src='chrome://telify/locale/custom_preset.js'></script> + + <hbox> + + <groupbox style="padding-bottom:8px;"> + <caption label="&dialog.config.general;"/> + <grid> + <columns> + <column flex="1"/> + <column flex="2"/> + </columns> + <rows> + <row align="center"> + <label value="&dialog.config.hreftype;:"/> + <menulist id="idTelifyPref_hreftype" onselect="objTelifyConfig.hrefTypeChanged(this.value)"> + <menupopup> + <menuitem label="&dialog.config.hreftype0;" value="0"/> + <menuitem label="&dialog.config.hreftype1;" value="1"/> + <menuitem label="&dialog.config.hreftype2;" value="2"/> + <menuitem label="&dialog.config.hreftype3;" value="3"/> + <menuitem label="&dialog.config.hreftype_custom;" value="9"/> + </menupopup> + </menulist> + </row> + + <row align="center"> + <label value="&dialog.config.dialcc;:"/> + <menulist id="idTelifyPref_dialcc"> + <menupopup> + <menuitem label="&dialog.config.dialcc_menu;" value="0"/> + <menuitem label="&dialog.config.dialcc_direct;" value="1"/> + </menupopup> + </menulist> + </row> + + <row align="center"> + <label value="&dialog.config.highlight;:"/> + <menulist id="idTelifyPref_highlight"> + <menupopup> + <menuitem label="&dialog.config.highlight0;" value="0"/> + <menuitem label="&dialog.config.highlight1;" value="25"/> + <menuitem label="&dialog.config.highlight2;" value="50"/> + <menuitem label="&dialog.config.highlight3;" value="100"/> + </menupopup> + </menulist> + </row> + <row align="center"> + <label value="&dialog.config.num_history;:"/> + <menulist id="idTelifyPref_num_history"> + <menupopup> + <menuitem label="3" value="3"/> + <menuitem label="4" value="4"/> + <menuitem label="5" value="5"/> + <menuitem label="6" value="6"/> + <menuitem label="7" value="7"/> + <menuitem label="8" value="8"/> + <menuitem label="9" value="9"/> + <menuitem label="10" value="10"/> + </menupopup> + </menulist> + </row> + <row align="center"> + <label value="&dialog.config.statusicon;:"/> + <menulist id="idTelifyPref_statusicon"> + <menupopup> + <menuitem label="&dialog.config.statusicon0;" value="0" /> + <menuitem label="&dialog.config.statusicon1;" value="1" /> + </menupopup> + </menulist> + </row> + <row align="center"> + <label value="&dialog.config.idd_prefix;"/> + <menulist id="idTelifyPref_idd_prefix" editable="true"> + <menupopup> + <menuitem label="" value=""/> + <menuitem label="00" value="00"/> + <menuitem label="001" value="001"/> + <menuitem label="011" value="011"/> + <menuitem label="0011" value="0011"/> + </menupopup> + </menulist> + </row> + </rows> + </grid> + </groupbox> + + <groupbox style="padding-bottom:8px;"> + <caption label="&dialog.config.about;"/> + <vbox style="width:96px;"> + <spacer style="height:0px;"/> + <hbox> + <image src="chrome://telify/content/icon96.png" style="width:96px;height:96px;margin-left:0px;"/> + <spacer/> + </hbox> + <spacer style="height:4px;"/> + <label id="idTelifyPref_version_label" value="" style="font-weight:bold;"/> + <label value="www.codepad.de" href="http://www.codepad.de" class="text-link"/> + <spacer flex="1"/> + </vbox> + </groupbox> + + </hbox> + + <groupbox id="idTelifyPref_custom_group" style="padding-bottom:8px;"> + <caption id="idTelifyPref_custom_caption" label="&dialog.config.custom;"/> + <vbox> + <description id="idTelifyPref_url_result" class="urlresult"> + </description> + + <menulist id="idTelifyPref_url_input" editable="true" + oninput="objTelifyConfig.urlChanged(this.value)" onselect="objTelifyConfig.tmplChanged(this.value)"> + <menupopup id="idTelifyPref_url_popup"> + </menupopup> + </menulist> + + <grid> + <columns> + <column flex="0"/> + <column flex="2"/> + <column flex="3"/> + </columns> + <rows> + <row align="center" id="idTelifyPref_param1_row" style="margin-top:4px;"> + <label id="idTelifyPref_param1_caption"/> + <textbox id="idTelifyPref_param1_value" emptytext="&dialog.config.replaces; $1 &dialog.config.in_template;" + oninput="objTelifyConfig.paramChanged(1, this.value)"/> + <spacer flex="2"/> + </row> + <row align="center" id="idTelifyPref_param2_row"> + <label id="idTelifyPref_param2_caption"/> + <textbox id="idTelifyPref_param2_value" emptytext="&dialog.config.replaces; $2 &dialog.config.in_template;" + oninput="objTelifyConfig.paramChanged(2, this.value)"/> + </row> + <row align="center" id="idTelifyPref_param3_row"> + <label id="idTelifyPref_param3_caption"/> + <textbox id="idTelifyPref_param3_value" emptytext="&dialog.config.replaces; $3 &dialog.config.in_template;" + oninput="objTelifyConfig.paramChanged(3, this.value)"/> + </row> + <row align="center" style="margin-top:4px;"> + <label value="&dialog.config.opentype;:"/> + <menulist id="idTelifyPref_opentype"> + <menupopup> + <menuitem label="&dialog.config.opentype0;" value="0"/> + <menuitem label="&dialog.config.opentype1;" value="1"/> + <menuitem label="&dialog.config.opentype2;" value="2"/> + <menuitem label="&dialog.config.opentype3;" value="3"/> + </menupopup> + </menulist> + </row> + </rows> + </grid> + + + </vbox> + </groupbox> + +</dialog> diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/country_data.js b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/country_data.js new file mode 100644 index 0000000000..63acf6089a --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/country_data.js @@ -0,0 +1,258 @@ +/* +Creative Commons License: Attribution-No Derivative Works 3.0 Unported +http://creativecommons.org/licenses/by-nd/3.0/ +(c)2009 Michael Koch +*/ +// must be saved as UTF-8 +var telify_country_data = [ +["", "", "", ""], +["+1", "USA", "us,mil,gov,edu", "1"], +["+1340", "U.S. Virgin Islands", "vi", "1"], +["+1670", "Northern Mariana Islands", "mp", "1"], +["+1671", "Guam", "gu", "1"], +["+1684", "American Samoa", "as", "1"], +["+1787", "Puerto Rico", "pr", "1"], +["+1939", "Puerto Rico", "", "1"], +["+1", "Canada", "ca", "1"], +["+1264", "Anguilla", "ai", "1"], +["+1268", "Antigua and Barbuda", "ag", "1"], +["+1242", "Bahamas", "bs", "1"], +["+1246", "Barbados", "bb", "1"], +["+1441", "Bermuda", "bm", "1"], +["+1284", "British Virgin Islands", "vg", "1"], +["+1345", "Cayman Islands", "ky", "1"], +["+1767", "Dominica", "dm", "1"], +["+1808", "Midway Island", "", "1"], +["+1809", "Dominican Republic", "do", "1"], +["+1829", "Dominican Republic", "", "1"], +["+1849", "Dominican Republic", "", "1"], +["+1473", "Grenada", "gd", "1"], +["+1876", "Jamaica", "jm", "1"], +["+1664", "Montserrat", "ms", "1"], +["+1869", "Saint Kitts and Nevis", "kn", "1"], +["+1758", "Saint Lucia", "lc", "1"], +["+1784", "Saint Vincent and the Grenadines", "vc", "1"], +["+1868", "Trinidad and Tobago", "tt", "1"], +["+1649", "Turks and Caicos Islands", "tc", "1"], +["+20", "Egypt", "eg", "0"], +["+212", "Morocco", "ma", ""], +["+213", "Algeria", "dz", "7"], +["+216", "Tunisia", "tn", "0"], +["+218", "Libya", "ly", "0"], +["+220", "Gambia", "gm", ""], +["+221", "Senegal", "sn", "0"], +["+222", "Mauritania", "mr", "0"], +["+223", "Mali", "ml", "0"], +["+224", "Guinea", "gn", "0"], +["+225", "Ivory Coast", "ci", "0"], +["+226", "Burkina Faso", "bf", ""], +["+227", "Niger", "ne", "0"], +["+228", "Togo", "tg", ""], +["+229", "Benin", "bj", ""], +["+230", "Mauritius", "mu", "0"], +["+231", "Liberia", "lr", "22"], +["+232", "Sierra Leone", "sl", "0"], +["+233", "Ghana", "gh", ""], +["+234", "Nigeria", "ng", "0"], +["+235", "Chad", "td", ""], +["+236", "Central African Republic", "cf", ""], +["+237", "Cameroon", "cm", ""], +["+238", "Cape Verde", "cv", ""], +["+239", "São Tomé and Príncipe", "st", "0"], +["+240", "Equatorial Guinea", "gq", ""], +["+241", "Gabon", "ga", ""], +["+242", "Congo (Republic)", "cg", ""], +["+243", "Congo (Democratic Republic)", "cd", ""], +["+244", "Angola", "ao", "0"], +["+245", "Guinea-Bissau", "gw", ""], +["+246", "Diego Garcia", "", ""], +["+247", "Ascension Island", "ac", ""], +["+248", "Seychelles", "sc", "0"], +["+249", "Sudan", "sd", "0"], +["+250", "Rwanda", "rw", "0"], +["+251", "Ethiopia", "et", "0"], +["+252", "Somalia", "so", ""], +["+253", "Djibouti", "dj", ""], +["+254", "Kenya", "ke", "0"], +["+255", "Tanzania", "tz", "0"], +["+256", "Uganda", "ug", "0"], +["+257", "Burundi", "bi", ""], +["+258", "Mozambique", "mz", "0"], +["+260", "Zambia", "zm", "0"], +["+261", "Madagascar", "mg", "0"], +["+262", "Réunion", "re", "0"], +["+262", "Mayotte", "yt", "0"], +["+263", "Zimbabwe", "zw", "0"], +["+264", "Namibia", "na", "0"], +["+265", "Malawi", "mw", ""], +["+266", "Lesotho", "ls", "0"], +["+267", "Botswana", "bw", ""], +["+268", "Swaziland", "sz", ""], +["+269", "Comoros", "km", ""], +["+27", "South Africa", "za", "0"], +["+290", "Saint Helena", "sh", ""], +["+290", "Tristan da Cunha", "", "0"], +["+291", "Eritrea", "er", "0"], +["+297", "Aruba", "aw", ""], +["+298", "Faroe Islands", "fo", ""], +["+299", "Greenland", "gl", ""], +["+30", "Greece", "gr", ""], +["+31", "Netherlands", "nl", "0"], +["+32", "Belgium", "be", "0"], +["+33", "France", "fr", "0"], +["+34", "Spain", "es", "0"], +["+350", "Gibraltar", "gi", ""], +["+351", "Portugal", "pt", ""], +["+352", "Luxembourg", "lu", ""], +["+353", "Ireland", "ie", "0"], +["+354", "Iceland", "is", "0"], +["+355", "Albania", "al", "0"], +["+356", "Malta", "mt", "0"], +["+357", "Cyprus (South)", "cy", ""], +["+358", "Finland", "fi", "0"], +["+359", "Bulgaria", "bg", "0"], +["+36", "Hungary", "hu", "06"], +["+370", "Lithuania", "lt", "8"], +["+371", "Latvia", "lv", "8"], +["+372", "Estonia", "ee", ""], +["+373", "Moldova", "md", "0"], +["+374", "Armenia", "am", "8"], +["+37447", "Nagorno-Karabakh", "", "0"], +["+37497", "Nagorno-Karabakh (Mobile)", "", "0"], +["+375", "Belarus", "by", "8"], +["+376", "Andorra", "ad", ""], +["+377", "Monaco", "mc", "0"], +["+37744", "Kosovo (Mobile)", "", "0"], +["+378", "San Marino", "sm", "0"], +["+379", "Vatican City", "va", ""], +["+380", "Ukraine", "ua", "8"], +["+381", "Serbia", "rs", "0"], +["+381", "Kosovo", "", "0"], +["+382", "Montenegro", "me", "0"], +["+385", "Croatia", "hr", "0"], +["+386", "Slovenia", "si", "0"], +["+38649", "Kosovo (Mobile)", "", "0"], +["+387", "Bosnia and Herzegovina", "ba", "0"], +["+389", "Macedonia", "mk", "0"], +["+39", "Italy and Vatican City", "it", ""], +["+40", "Romania", "ro", "0"], +["+41", "Switzerland", "ch", "0"], +["+420", "Czech Republic", "cz", ""], +["+421", "Slovakia", "sk", "0"], +["+423", "Liechtenstein", "li", ""], +["+43", "Austria", "at", "0"], +["+44", "United Kingdom", "uk,gb", "0"], +["+45", "Denmark", "dk", ""], +["+46", "Sweden", "se", "0"], +["+47", "Norway", "no", ""], +["+48", "Poland", "pl", "0"], +["+49", "Germany", "de", "0"], +["+500", "Falkland Islands", "fk", ""], +["+501", "Belize", "bz", "0"], +["+502", "Guatemala", "gt", ""], +["+503", "El Salvador", "sv", ""], +["+504", "Honduras", "hn", "0"], +["+505", "Nicaragua", "ni", "0"], +["+506", "Costa Rica", "cr", ""], +["+507", "Panama", "pa", "0"], +["+508", "Saint-Pierre and Miquelon", "pm", "0"], +["+509", "Haiti", "ht", "0"], +["+51", "Peru", "pe", "0"], +["+52", "Mexico", "mx", "01"], +["+53", "Cuba", "cu", "0"], +["+5399", "Guantanamo Bay", "", "0"], +["+54", "Argentina", "ar", "0"], +["+55", "Brazil", "br", "0"], +["+56", "Chile", "cl", "0"], +["+57", "Colombia", "co", "0"], +["+58", "Venezuela", "ve", "0"], +["+590", "Guadeloupe", "gp", ""], +["+591", "Bolivia", "bo", "0"], +["+592", "Guyana", "gy", "0"], +["+593", "Ecuador", "ec", "0"], +["+594", "French Guiana", "gf", ""], +["+595", "Paraguay", "py", "0"], +["+596", "Martinique", "mq", ""], +["+597", "Suriname", "sr", ""], +["+598", "Uruguay", "uy", "0"], +["+599", "Netherlands Antilles", "an", ""], +["+60", "Malaysia", "my", "0"], +["+61", "Australia", "au", "0"], +["+62", "Indonesia", "id", "0"], +["+63", "Philippines", "ph", "0"], +["+64", "New Zealand", "nz", ""], +["+65", "Singapore", "sg", ""], +["+66", "Thailand", "th", "0"], +["+670", "East Timor", "tp,tl", ""], +["+672", "Australian external territories", "", ""], +["+673", "Brunei", "bn", "0"], +["+674", "Nauru", "nr", "0"], +["+675", "Papua New Guinea", "pg", ""], +["+676", "Tonga", "to", ""], +["+677", "Solomon Islands", "sb", ""], +["+678", "Vanuatu", "vu", ""], +["+679", "Fiji", "fj", ""], +["+680", "Palau", "pw", ""], +["+681", "Wallis and Futuna", "wf", ""], +["+682", "Cook Islands", "ck", "00"], +["+683", "Niue Island", "nu", "0"], +["+685", "Samoa", "ws", ""], +["+686", "Kiribati", "ki", "0"], +["+687", "New Caledonia", "nc", "0"], +["+688", "Tuvalu", "tv", ""], +["+689", "French Polynesia", "pf", ""], +["+690", "Tokelau", "tk", ""], +["+691", "Micronesia", "fm", "1"], +["+692", "Marshall Islands", "mh", "1"], +["+7", "Russia", "ru,su", "8"], +["+7", "Kazakhstan", "kz", "8"], +["+81", "Japan", "jp", "0"], +["+82", "South Korea", "kr", "0"], +["+84", "Vietnam", "vn", "0"], +["+850", "North Korea", "", "0"], +["+852", "Hong Kong", "hk", ""], +["+853", "Macao", "mo", "0"], +["+855", "Cambodia", "kh", "0"], +["+856", "Laos", "la", "0"], +["+86", "China", "cn", "0"], +["+870", "Inmarsat SNAC", "", ""], +["+871", "Inmarsat (Atlantic East)", "", ""], +["+872", "Inmarsat (Pacific)", "", ""], +["+873", "Inmarsat (Indian)", "", ""], +["+874", "Inmarsat (Atlantic West)", "", ""], +["+880", "Bangladesh", "bd", "0"], +["+881", "Global Mobile Satellite System", "", ""], +["+882", "International Networks", "", ""], +["+883", "International Networks", "", ""], +["+886", "Taiwan", "tw", ""], +["+90", "Turkey", "tr", "0"], +["+90392", "Cyprus (North)", "", "0"], +["+91", "India", "in", "0"], +["+92", "Pakistan", "pk", "0"], +["+93", "Afghanistan", "af", "0"], +["+94", "Sri Lanka", "lk", "0"], +["+95", "Burma", "mm", ""], +["+960", "Maldives", "mv", "0"], +["+961", "Lebanon", "lb", "0"], +["+962", "Jordan", "jo", "0"], +["+963", "Syria", "sy", "0"], +["+964", "Iraq", "iq", "0"], +["+965", "Kuwait", "kw", "0"], +["+966", "Saudi Arabia", "sa", "0"], +["+967", "Yemen", "ye", "0"], +["+968", "Oman", "om", "0"], +["+971", "United Arab Emirates", "ae", ""], +["+972", "Israel", "il", "0"], +["+973", "Bahrain", "bh", ""], +["+974", "Qatar", "qa", "0"], +["+975", "Bhutan", "bt", ""], +["+976", "Mongolia", "mn", "0"], +["+977", "Nepal", "np", "0"], +["+98", "Iran", "ir", "0"], +["+992", "Tajikistan", "tj", "8"], +["+993", "Turkmenistan", "tm", "8"], +["+994", "Azerbaijan", "az", "8"], +["+995", "Georgia", "ge", "8"], +["+996", "Kyrgyzstan", "kg", "0"], +["+998", "Uzbekistan", "uz", "8"], +]; diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/dialog.css b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/dialog.css new file mode 100644 index 0000000000..89a400e72f --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/dialog.css @@ -0,0 +1,33 @@ +.telInputCC { + width: 6em; +} + +#idTelify_popup_dial .menu-iconic-icon { + width: 22px; + height: 15px; +} + +#idTelify_popup_context .menu-iconic-icon { + width: 22px; + height: 15px; +} + +.tmpl_empty { + color: #a0a0a0; +} + +.tmpl_number { + color: #008000; +} + +.tmpl_param { + color: #000080; +} + +.urlresult { + height:4.2em; + padding:2px 3px; + border:1px solid #e0e0e0; + background-color:#f0f0f0; + margin-left:4px; +} diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/edit22x15.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/edit22x15.png new file mode 100644 index 0000000000000000000000000000000000000000..cb4c614f0b8250a5424018630cc4663962591633 GIT binary patch literal 2946 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/editNumber.js b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/editNumber.js new file mode 100644 index 0000000000..64896ad5c8 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/editNumber.js @@ -0,0 +1,180 @@ +/* +Creative Commons License: Attribution-No Derivative Works 3.0 Unported +http://creativecommons.org/licenses/by-nd/3.0/ +(c)2009 Michael Koch +*/ + +var objTelifyEditNumber = { + +checkKey: function(event, allowed) +{ + if (event.which < 32) return + var key = String.fromCharCode(event.which) + if (allowed.indexOf(key) >= 0) return; + event.preventDefault(); +}, + + +createListItem: function() +{ + var item = document.createElement('listitem'); + for (var i=0; i<arguments.length; i++) { + var cell = document.createElement('listcell'); + cell.setAttribute("label", arguments[i]); + item.appendChild(cell); + } + return item; +}, + + +updateCountrySelection: function() +{ + var list = document.getElementById("idTelifyCountryCodeList"); + var editcc = document.getElementById("idTelifyInputCC"); + if (editcc.value == "" || editcc.value == "+" || editcc.value.charAt(0) != '+') { + list.scrollToIndex(0); + list.selectedIndex = 0; + editcc.style.color = "#ff0000"; + return; + } + var index = 0; + var maxlen = 1; + for (var i=0; i<telify_country_data.length; i++) { + if (editcc.value == telify_country_data[i][0]) { + index = i; + break; + } + for (var j=1; j<editcc.value.length; j++) { + if (editcc.value.charAt(j) == telify_country_data[i][0].charAt(j)) { + if (j+1 > maxlen) { + maxlen = j+1; + index = i; + } + } else { + break; + } + } + } + if (index >= 0) { + list.scrollToIndex(index); + if (editcc.value == telify_country_data[index][0]) { + list.selectedIndex = index; + editcc.style.color = "#000000"; + } else { + list.clearSelection(); + editcc.style.color = "#ff0000"; + } + } else { + list.scrollToIndex(0); + list.clearSelection(); + editcc.style.color = "#ff0000"; + } +}, + + +ccChanged: function() +{ + var editcc = document.getElementById("idTelifyInputCC"); + if (editcc.value.length == 1 && editcc.value.charAt(0) != '+') { + editcc.value = "+" + editcc.value; + } + this.updateCountrySelection(); +}, + + +updateNumberEdit: function() +{ + var list = document.getElementById("idTelifyCountryCodeList"); + var fClear = false; + if (list.getRowCount() != telify_country_data.length) { + while (list.getRowCount() > 0) list.removeItemAt(0); + fClear = true; + } + for (var i=0; i<telify_country_data.length; i++) { + var item = this.createListItem(telify_country_data[i][0], telify_country_data[i][1]); + if (fClear) { + list.appendChild(item); + } else { + list.replaceChild(item, list.getItemAtIndex(i)); + } + } + this.updateCountrySelection(); +}, + + +updateListSelection: function() +{ + var list = document.getElementById("idTelifyCountryCodeList"); + var editcc = document.getElementById("idTelifyInputCC"); + if (list.selectedCount > 0) { + editcc.value = telify_country_data[list.selectedIndex][0]; + editcc.style.color = "#000000"; + } +}, + + +compareCol1: function(a, b) +{ + var v = a[0].localeCompare(b[0]); + if (v == 0) return a[1].localeCompare(b[1]); + return v; +}, + + +compareCol2: function(a, b) +{ + var v = a[1].localeCompare(b[1]); + if (v == 0) return a[0].localeCompare(b[0]); + return v; +}, + + +last_sorted_column: -1, + +sortCountryCodeList: function(column) +{ + var telPrefs = objTelifyPrefs.getPrefObj(); + if (column < 0) { + column = telPrefs.getIntPref(objTelifyPrefs.PREF_COLSORTCC); + } else { + telPrefs.setIntPref(objTelifyPrefs.PREF_COLSORTCC, column); + } + if (column == this.last_sorted_column) return; + if (column == 0) { + telify_country_data.sort(this.compareCol1); + document.getElementById("idTelifyColCode").setAttribute("sortDirection", "descending"); + document.getElementById("idTelifyColCountry").setAttribute("sortDirection", "natural"); + } + if (column == 1) { + telify_country_data.sort(this.compareCol2); + document.getElementById("idTelifyColCode").setAttribute("sortDirection", "natural"); + document.getElementById("idTelifyColCountry").setAttribute("sortDirection", "descending"); + } + this.last_sorted_column = column; + this.updateNumberEdit(); +}, + + +setNumberEditReturnValue: function(fOK) +{ + window.arguments[0].cc = document.getElementById("idTelifyInputCC").value; + window.arguments[0].nr = document.getElementById("idTelifyInputNr").value; + window.arguments[0].fOK = fOK; +}, + + +initNumberEdit: function() +{ + var cc = window.arguments[0].cc; + var nr = window.arguments[0].nr; + var index = -1; + var maxlen = 0; + + objTelifyUtil.localizeCountryData(); + document.getElementById("idTelifyInputCC").value = (cc ? cc : ""); + document.getElementById("idTelifyInputNr").value = nr; + this.sortCountryCodeList(-1); +} + +}; + diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/editNumber.xul b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/editNumber.xul new file mode 100644 index 0000000000..251fbc0198 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/editNumber.xul @@ -0,0 +1,45 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?> +<?xml-stylesheet href="chrome://telify/content/dialog.css" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://telify/locale/lang.dtd"> + +<dialog id="dlgTelifyEditNumber" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + buttons="accept,cancel" + buttonlabelaccept="&dialog.edit.dial;" + onload="objTelifyEditNumber.initNumberEdit()" + ondialogaccept="objTelifyEditNumber.setNumberEditReturnValue(true)" + ondialogcancel="objTelifyEditNumber.setNumberEditReturnValue(false)" + title="&dialog.edit.title;"> + + <stringbundleset id="stringbundleset"> + <stringbundle id="idTelifyStringBundle" src="chrome://telify/locale/lang.properties"/> + </stringbundleset> + + <script type='application/x-javascript' src='chrome://telify/content/jshashtable.js'></script> + <script type='application/x-javascript' src='chrome://telify/content/util.js'></script> + <script type='application/x-javascript' src='chrome://telify/content/pref.js'></script> + <script type='application/x-javascript' src='chrome://telify/content/country_data.js'></script> + <script type='application/x-javascript' src='chrome://telify/locale/country_locale.js'></script> + <script type='application/x-javascript' src='chrome://telify/content/editNumber.js'></script> + + <vbox> + <hbox align="center"> + <textbox id="idTelifyInputCC" class="telInputCC" oninput="objTelifyEditNumber.ccChanged()" onkeypress="objTelifyEditNumber.checkKey(event,'+0123456789')"/> + <label value="–"/> + <textbox id="idTelifyInputNr" flex="1" onkeypress="objTelifyEditNumber.checkKey(event,'0123456789')"/> + </hbox> + <listbox id="idTelifyCountryCodeList" flex="1" width="280" height="250" onselect="objTelifyEditNumber.updateListSelection()"> + <listhead> + <listheader id="idTelifyColCode" class="telInputCC" label="&dialog.edit.code;" onclick="objTelifyEditNumber.sortCountryCodeList(0)"/> + <listheader id="idTelifyColCountry" label="&dialog.edit.country;" onclick="objTelifyEditNumber.sortCountryCodeList(1)"/> + </listhead> + <listcols> + <listcol flex="0"/> + <listcol flex="1"/> + </listcols> + </listbox> + </vbox> + +</dialog> diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/error32.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/error32.png new file mode 100644 index 0000000000000000000000000000000000000000..2d5e260a5f21585e0a76c81a0a66b6dbf5be60e6 GIT binary patch literal 2478 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 HB{Ts5^Zkk9 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1-canada.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1-canada.png new file mode 100644 index 0000000000000000000000000000000000000000..fd226fef001e55ec8e0ede8fdf897babf89c2ed3 GIT binary patch literal 2979 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 O1B0ilpUXO@geCwrg@PCW literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1.png new file mode 100644 index 0000000000000000000000000000000000000000..34bd21fd5cdfa2525a224ac596e07713d87de6af GIT binary patch literal 2878 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 LS3j3^P6<r_mo-_V literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1242.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1242.png new file mode 100644 index 0000000000000000000000000000000000000000..cb72ea2c0f48e6935ee8bad01b77b11d8108855a GIT binary patch literal 2928 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 LS3j3^P6<r_JVIRx literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1246.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1246.png new file mode 100644 index 0000000000000000000000000000000000000000..b6db2cd4a7aa6a3c71375bcaa8181440de130884 GIT binary patch literal 347 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U)QBu$0x!Z9l#+Ffjb}ba4!^=sh}lZx)lIgzI@9laK0APdL{wZ4BSy zk(#j4bGk@p#PThh&Su~Eefg%=Ee|Pi#Yxwc1iDpUP8B_TsPAY~M9T5I8Wo9M56{`& zZ+$2nvtO&`N}XY)*17**tkT+^yIs`p`clew<>f)I%I6vhORtET&01ISJK`<RMgNOU zv8--qlm0!vAf=P>ZF)_uwS*~mx&QBub2gKkvMhb`bBd>XGhHyxUG>x?sO!HuTZP8b ziur4|e}DOA`thKwc%S<c50tbbZs^^;cqv<n^?6|JZHI)0s;6BKkNmb+Jb^=qlkHr3 za!nK;!_L_^J|1BBzWw9^|HF;?j~`C^@<m%VOnPFd+c&#~EG7&L3=E#GelF{r5}E)< C?3bMY literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1264.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1264.png new file mode 100644 index 0000000000000000000000000000000000000000..45b6ec574ecf6afe62656e876878e1ed0b2fa253 GIT binary patch literal 3228 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 UCbWbwFfcH9y85}Sb4q9e0DPhi_y7O^ literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1268.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1268.png new file mode 100644 index 0000000000000000000000000000000000000000..d618aeb5f3d0490a063bde2f04aa11aa7e316038 GIT binary patch literal 587 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxH`{{wfm|V7#QDrx;Tbd^sb$>KRY;3<hcF!ecwNwINkqjW`}3{ zmMaM>nw|bP^eE|CtPBuuS9_%7p?SlVlZC6Pc;h-(!MjGR?S6B;Un#J3HF8aw<kf3B z(d^`IzF8C0^fTM)9v*r)=lsv2A4QQLSVRjJ<{uOe$!QnLiu&Bc@;GqiGBs0E(aRw= zTJD=J&vMUg7xLQPl5$r3@x%4Cvoxwi`8;`KFO}<l`xech^2qA-dufyC5094@x0ws9 z`I!{qq*V4Mo9n^dlY;A|LVtbzRmm`4b;nE-0f&uOmfQ=lIVa1%LHk;&+zc~@O_yU# za)a8|bF==PbXB$6Bf@CD(agCQJ>ss;@So|Tm$^nSlI8E+(xA5fqyLvNR4o^9&@(t0 zT;J`?xN$@C;_VyNZ&jQ%bNZ9KXW4;mdo7|GZZ3K9d!foAh40bsUsP`PE?PV{W0L#! z`42fxT1-0i(D$Kg@zhH<>+CJUH79C5J8r~!LgX{^vUSH8X2zD^_}UTsU72ms3%*6+ z98c_(-f&)bEe(16<D6~C(*vdJc9$`RS~~1GVf497w&jJi);_zFe5S36fh8f@Ie~X? z$h-(QH(M#X&224T|03=Ro=NlV9_%fYZLpueKVVm>eCaKj%$j%egO>ZAdYL+>ih23H v1#{x;u8X*(+*v(&uhIRmO?Rtg{<8M)98%DJZhVh{fq}u()z4*}Q$iB}xkd$! literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1284.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1284.png new file mode 100644 index 0000000000000000000000000000000000000000..93c6f1125582c3c4ed2ad91b13f94a4d225b297a GIT binary patch literal 3287 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1340.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1340.png new file mode 100644 index 0000000000000000000000000000000000000000..0949b5fa6d5194dc591ac483794fd0d3ab2f740c GIT binary patch literal 785 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTH10|&F=rN+4o3{0CnT^vI!dbdvT_6RAII9h-1j&<?#rN(x%Cz+^p z>u_yeptC3^IUvl-F=Y0l1q&8AWC-ps$`0ff&lC*gY}8$3zH5fu1d%`yTMe1qS(j$2 zO|85?^WB-h+c`K-X9>=G`}5qx54G+3?i`9Um?V}RDrqXzDe1ovz&>$e_+-8|W;vyM zwJF9QJu6M^d{S0)dpwY0xMZ>LwbjQ)<rzy<{+Z{^xokecaQ%U}koJ?0)TG`lo8Wil zqSYJK-jpp8uV%1MtZ}lr(0hhGD6?_tpF2m6R9=3yA*^KLSt&)~h643t5BNgfyX~v> zei+1N=qbSemBHX!nfb5dy(b<%Ua^sJQ>5(DExm^#cQc$2bn2KbZ?{q7{>jY+Q%+s+ zUbC<{gHwO==?6vc&HBoAXat(DO*Tpu>kYVdX5m@MS=;9ylxJv;>3Nc;`Xb|imUqZA z>v|PAgHtwM%QE@<0^_#LI@kX-;~aCvU$#$2b+f#SW=02`nWbbb%4dC=E5+-J+#kjd zFQa$9_%b1Sm#n|i(e9L2&-=`G%Dkzx_LzFw^Zjb+nEFc5;B$Q6!sahHbi?)Ygif30 zrW0BVxG%XmCN1>Sd2g_M((Qs3k@MP>_itZxCeb-m$7ENI$(5XgX{8T7-ghl~zog`& z=%Z^7ITx)kaeBI{Y)Rzt3zsz8ggKvC7OS6;h>nViVo1|cTlGfl%_Nr!l{0@2$o{d@ zziOGjajN={U+V<gk9yvivn=Co_@($;7SgfJI+`WxW4c18ZS-(U(@5hwyG;K0@2!&$ zZe*A)H*c{`Y2&R~hi5!Z`X&Ef{o;K8kBb8Dgs$Dw=OX#{&e306YiEew&fh!lN`*{R z*yAs6!)86xcYK|%bbisIWxOq2=PnD(a&EkPZuV1dHSz9xhwG(^Edm=a)INN@T6E&V xJi*Y=!2b_cTKv5C-TiL7@PGbgZ#Zk&6)dCO-o7|^mVtqR!PC{xWt~$(699JGYuEq) literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1345.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1345.png new file mode 100644 index 0000000000000000000000000000000000000000..4ade1ca9be95a1952190fafbfc153ffb3c35827f GIT binary patch literal 3245 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1441.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1441.png new file mode 100644 index 0000000000000000000000000000000000000000..775a6b67b56424fc2e5c950c88f902a5aa78c627 GIT binary patch literal 3238 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`(#+;1OBOz`!jG!i)^F z=14FwaQSCCI|l@0<d@_ZXXF<scse^P6cpvBW#*(ZFjU-{8=M{z{8G&J`@Jtt-W?57 zyz_%ZITV7Nx*J3$s%a^53V3xWF1#rkcu>&Ql|}l*f)3`szJ?yrKpmbgt{d!66c_b1 zFYf3%_4{G<;_PdCzCSzrZ|ilt=W9OC-F%+?00+-B592`72A-8pa)J+<j~_Yq@QI#7 zBbQ16g8;|o6vg7d%8U#@>TGPZl@74)V_>Yf%+0}Yz<2fm1DW>!{I8s5#xORtFditG zXyMJ#Aj05K)#tQ~!NHBOVeSmIV1@-O3<;-`lO8h!=rSaz*FU?=&@lJM%xXr4=qZY# zj0+|*C<v!^xG-c`Gpsou*5%3&VZb1g=6%GH;f5}QfoFJ{2g8Q#3<u5$aBpJZ;9)Qb z>}b_wV5w$EIH9au!O$|3LB#D-xXnlHbpp?|7#J#MZnD{_V(Ho#&7oV!9v-eIr*Y0x zTwBB}ktuPOgWjRanUY40Cj1XRpJ8BFFi}+SLG$Oo73cWYojYgNw~a4e_tk#3|4K<o zkN-VAzq-JIf#G3E-Nk=8`kSR1tl1iJ|1DB|$C44py`c1aciJX?hph|?Ufv4-TmT9i z*M$>y-n{wr$&(viN1UP)Pleb1OFtBUApCF6&pMkw-@iWC_eiDDD7Mj^<;R0VXTJC> zl{r6&vnA=yvwrtK+qwV$k-e;R#4~AUgqFzTK%v4umsvfZjg~5PI!)3%s(R^v%A@-_ zznSgsn=)+3vSX~CsJkLInfaTyx<Ew9fk)pN7(VUJKX^rhgSjE2`QiEU|L^Vpx-U>= zXh`#%RLH;(=c1w)a%!{vaSjFsmj#Vl8yuy-9F)*Gz$SB$_0B==KM7_nPAW$ZISDv% zr8LMCC1|c`a?)r!yFpefLH~+_tjU4EjJC-Ie0qmMcktO3@c&BWn0HWPPa`Lb;|UJo zM8#Vjj7eP)%6%N-Gh2Ts9`s-l>Q+%W*&!0B<UP^U!!W2TL?P5e_LFT9lVy91+pdY$ zCm4*H{<Q6JH=NSADbV%`&sOK1E6k-@+!k?PNGUO#)i1VS_JyJ?Qno#23!h(Doxy*r z-_H5C!GRYN?8ZlO4D^m2cG_s6!#O$m^hTvMJgbjMZ3tV#yu0&j^7)PBEJBU02RO}G z%AFJv<s3aCd?NgAc&t#6Q85v`#c9sT+!A<5U1{<P?=5P(1b-fpNHQ{PkMPh@z9!T! ztS?aA;e5ngMX1x+NPXu7CeMvaLM{nia=Vllr2JB6<!loVDdov0Z>3~!Vz->4qo94F z^~u~Pho2Zfk$$51DVl3;l0)H=AHk0_f=*?5Nj?>LYVwpVb%v2+=;B#HcULS75j5?O za$37^ThRKz_7%b=rL!#0Hj8<BFI~H6>Sd`6@r>`8>tB@Y61+QA&TsmK@Jpv(y1$tI zg85514{w`j^YM;!4awOO>=Nmc<r3>>_$-sW+%0)L#K7F}^^E5;`Df;bMrdu*su7)d zifgLZRJEznTKXX?S1nt0Zxwgw=g`w3{VU^wYuCAj8V5gK$$4ek>d;kl16~J92dA&J zUeUfPeMNqV{gS%KznyH6hkXwFJ1+@burdDeo?{B!$&#BZbQH6BSGScmnjTTxc=l#Z znbz(Ze`R-f+pb!<(9+r1%Q$><(B~;>_Uw;m6#ke~;>Y=1WBK&YbzS#F-fKS6lwM}y zv(qp&CVSPi>C+-sd#nyz?f!bN*}AvO=Pujpx;yfBBcJ1OFZCF8_w@dAtaG0S-weLH zoNKw5udUVBb$Y9^mr5_(?NYu!Z~40!^Y+Cx%=5`#ey?)x^ItNx`hP?I=Kfm#)tx7T zZ8MJw+ijk65;jLo3-fJc^^f2F80Z{(*ui;e;?%_H4_hYgylAuds#~uce^2JIn8zxQ zbC2a7Tdc;Wrl{ua`^xv-tRUZ=%QTl+`&j!NKYQlvqO;d#n$EU0yl(V-_Vd}-XW!qR z5LFSSvu(w;9jTt1o<uE+e79{+UTc(P<kE=HsMyVJYv-<2y|#Pp?&X5Zclu5CTRrbu z`r!>9x1HR!a=YY)r#IMcuiKWptu`?|Nj!b~(RGL4P2M}Xc2{lhUt4zG_M3dGWus*4 zinkPrScO^Ln{{r^JNw$BU#7j(Pmee+aQ=qz<88vHHIHP@4W513JzYF~XL0lAj_ywH zrqg=U?eu0#AG>>E?-Q?*uZ6J>t1f+=JA3o&LvL5yUjNSQUFN&?JM+(&%-5V3X<U5a z@CoM!fln{mbI+e@kbQqmoxihxWBS(T%KFLr6T{bD->`jY`Ks@U@5SxD*%i%yZ1>p4 z{GQRh$oMnn2ezNse%k+V{=E3s`_A>s|5N@~{lCbtyg`*Qow>8ou`!zI_m8R?#r=Ku zR<~2XZMyl^v1h?fM^VTArnOB^53W_rIPqq}TE*|rXPldOE|ae{pzg)B3+#!{61xxA z@u;`9w}!Xb_Z4(RNL@6XZd!ivj&t#SmtQW^T<*<IH}tn$uj^&1Ww%N%%jTB(E|XpM z$Mn)7&s>d|8{wJbEn}&twazRrXW#ulZ$Gy`n)`_Rakcmi{S&bTl?%@u`qH{wY^9!% zj@2{=pAJ8ZDIXjKv{s1qsD2VISMB4fJS2N4T}l6<*~N|<&M{hYexE$Fe5)qCnYhQa zQst=eQk|zeThv!h*yF?Nb9c7YJh5423b{tMR(lPbCrzAC=(I}Fck;IMSLx5v%1*CI zZQb<mXkPbreYvUZC$l$CSi>5r%eBpE*^7`Ujq6(Mo6=Lm)Apb3JY_j;^*rsfZ_jK# zb$f#OocHtpzZE(p^l;L)rET8fy0Q_^_O8jY++<T&d7JC8D64i`7~2|~9bMbBa<BTY zej7f0UEN-_D&OqJxPzyf4sK=4e$5@q^|ZD2^_}Z)WBnuaBkylb{M9yB+D^2yYn{k# z@#&)VH#F{4Je6+GKed16ohe&Zu0FCe<$T(uw3pBB>CTJho3?8Dv*~<!yf<%c;k_TW z<?YvRuDA84pVhd%yLp@Q&c*v}cI-)Ae)8N|Yx|HTAzwm%g_ym&ba!jw^&fc`-$_^R z`DXIw>h0(~-~Za3+;=;aG1T{J+SSzGp1(JLw_n$}>gA7VzjI^P`sEhBJ<fcI`7gUN z-?r;JuUwVAWE&FHe@*+fJKu5negEdZFW2QY;|=YdbL^3FflK9+m)|+>f0yI6x>eRw zz2+hNW%)AMbXol@r&*t7xy=rq^*cH#dYAthyNlOe-K+ZJep{Y@j)_fVrPt?>lbN?( zHeH^a{^@+r+^KVG>-2u!Nw~deTlMqgbN93O^WX0hD0z6v^?&zs>pj-zVm|FC`LyZw zQ{nI(;j`9T#pk{%d1do&@@4<yyXWklwQgrj_Re=zd$(4neSaxE&-~ooFMBILiv7QK zb)Kc|-Mycy^1mPb{`J1t-MS+6zvoWxzFDsRZtWibJ@H>0-a6iNzT1A)KKkFC8}IMb zXB{YC@cH5$_wQ`2&FBA3{Myg&%-TP*|8yH~+v|B2c9xcp?2G=s{IlHoe9C#XdE);T z{X4x*Tui_2$JN)@&t05xapCk&(<krGty%i_)q&TN>u(=BbWCpF$qHk`xG!P9-yST# zQ!e{{@qK>(Cp8N78viqXZu}a2VfpXLl9MO@XJMZ4`A@R3?rjDJ28CpgAYTTCDpdxC zhGqtapZ^&c8eTFmlo~KFyh>nTu$sZZAf7)d{-_%R1LH+c7srr_TUVy|djvDe9JkLu zZ*qT{p!7DWX-&ziS<X(EJ7llWBcLC<#9KLF*Amy|R##LHG6}9}W@&Bg$VvB%tPGgb zVi)C`t2J%yHO0*<C)`%PdoOPLm%FEgK34xa*nfYz{{8#kzYCYdrkf`uO`q`fU0l1K z*zSrCNuu-p7e4Lk+5TLs_N@QQcf1#)-Y<we=cZG(FmB(E=SO^-rFuV!znkZG(X4Xv ziQl3x8o95!##m2w(y?%Szs2oAV9W9Pg%LBRrY=4?t4)eedFs?g4XLOsjW4^;RoJ9> z3VlowT7A<eDpat&eY=Qzwv-?X<Lt9e|IS;m=Y(C6UMrKi(@$&Rr?;Pt?)!?B#6Evv z_UO<$y`nc|Y%+YWe*G@oefNl$*S2ZT`=19#yi+PSK6rS$npHX<(}jgE?o=iptMOZ= zz#muVA$euFxmm{k+U|%@)_(^Tr-fbXk*h9#7Wn6N59^_fOy2aTnv=HjMl5eyqg7n; z%SB7SGQ(n?8IN1~vWr(Vla9wk6y+XKi%-1&?ql|izzK`L-wxPUr}9zr)WXSrb7o7P zv#b2}-jYw}M+6Vsgj3#;GPUPch?y))THa-Rpd&iz&!5Cq>U9Cuvt9nkAE_?8bZB3- d(|$IFkox79cB`{CGcYhPc)I$ztaD0e0sva;3OE1& literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1473.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1473.png new file mode 100644 index 0000000000000000000000000000000000000000..19612cf7a73a7be8c0ffb1f405c025c30a2ad305 GIT binary patch literal 3288 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1649.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1649.png new file mode 100644 index 0000000000000000000000000000000000000000..d8f0e6ec7e98e80146665ad258cd7603285ff461 GIT binary patch literal 3206 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1664.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1664.png new file mode 100644 index 0000000000000000000000000000000000000000..35d8f052b93c5208e3d430bc5213c6c984f47423 GIT binary patch literal 3220 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 L{an^LB{Ts510ek! literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1670.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1670.png new file mode 100644 index 0000000000000000000000000000000000000000..a5c8609a24821b02da585b50bef280f9cc23d65e GIT binary patch literal 3233 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 bd<@Fl)?W;*o$!`{fq}u()z4*}Q$iB})tM2l literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1671.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1671.png new file mode 100644 index 0000000000000000000000000000000000000000..5189a550be437d825ec88b09ec090bfd8138ac5c GIT binary patch literal 3003 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1684.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1684.png new file mode 100644 index 0000000000000000000000000000000000000000..553b350cd38f66a8304aef45ca4c879f7559d5ff GIT binary patch literal 3124 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 N@O1TaS?83{1OTO~&bt5r literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1758.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1758.png new file mode 100644 index 0000000000000000000000000000000000000000..4e92314b7bd0ae2c79a532e0359a57edc262e506 GIT binary patch literal 3002 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`(#+;1OBOz`!jG!i)^F z=14FwaQSCCI|l@0<d@_ZXXF<scse^P6cpvBW#*(ZFjU-{8=M{z{8G&J`@Jtt-W?57 zyz_%ZITV7Nx*J3$s%a^53V3xWF1#rkcu>&Ql|}l*f)3`szJ?yrKpmbgt{d!66c_b1 zFYf3%_4{G<;_PdCzCSzrZ|ilt=W9OC-F%+?00+-B592`72A-8pa)J+<j~_Yq@QI#7 zBbQ16g8;|o6vg7d%8U#@>TGPZl@74)V_>Yf%+0}Yz<2fm1DW>!{I8s5#xORtFditG zXyMJ#Aj05K)#tQ~!NHBOVeSmIV1@-O3<;-`lO8h!=rSaz*FU?=&@lJM%xXr4=qZY# zj0+|*C<v!^xG-c`Gpsou*5%3&VZb1g=6%GH;f5}QfoFJ{2g8Q#3<u5$aBpJZ;9)Qb z>}b_wV5w$EIH9au!O$|3LB#D-xXnlHbpp?|7#J#MZnD{_V(Ho#&7oV!9v-eIr*Y0x zTwBB}ktuPOgWjRanUY40Cj1XRpJ8BFFi}+SLG$Oo73cWYojYgNw~a4e_tk#3|4K<o zkN-VAzq-JIf#G3E-Nk=8`kSR1tl1iJ|1DB|$C44py`c1aciJX?hph|?Ufv4-TmT9i z*M$>y-n{wr$&(viN1UP)Pleb1OFtBUApCF6&pMkw-@iWC_eiDDD7Mj^<;R0VXTJC> zl{r6&vnA=yvwrtK+qwV$k-e;R#4~AUgqFzTK%v4umsvfZjg~5PI!)3%s(R^v%A@-_ zznSgsn=)+3vSX~CsJkLInfaTyx<Ew9fk)pN7(VUJKX^rhgSjE2`QiEU|L^Vpx-U>= zXh`#%RLH;(=c1w)a%!{vaSjFsmj#Vl8yuy-9F)*Gz$SB$_0B==KM7_nPAW$ZISDv% zr8LMCC1|c`a?)r!yFpefLH~+_tjU4EjJC-Ie0qmMcktO3@c&BWn0HWPPa`Lb;|UJo zM8#Vjj7eP)%6%N-Gh2Ts9`s-l>Q+%W*&!0B<UP^U!!W2TL?P5e_LFT9lVy91+pdY$ zCm4*H{<Q6JH=NSADbV%`&sOK1E6k-@+!k?PNGUO#)i1VS_JyJ?Qno#23!h(Doxy*r z-_H5C!GRYN?8ZlO4D^m2cG_s6!#O$m^hTvMJgbjMZ3tV#yu0&j^7)PBEJBU02RO}G z%AFJv<s3aCd?NgAc&t#6Q85v`#c9sT+!A<5U1{<P?=5P(1b-fpNHQ{PkMPh@z9!T! ztS?aA;e5ngMX1x+NPXu7CeMvaLM{nia=Vllr2JB6<!loVDdov0Z>3~!Vz->4qo94F z^~u~Pho2Zfk$$51DVl3;l0)H=AHk0_f=*?5Nj?>LYVwpVb%v2+=;B#HcULS75j5?O za$37^ThRKz_7%b=rL!#0Hj8<BFI~H6>Sd`6@r>`8>tB@Y61+QA&TsmK@Jpv(y1$tI zg85514{w`j^YM;!4awOO>=Nmc<r3>>_$-sW+%0)L#K7F}^^E5;`Df;bMrdu*su7)d zifgLZRJEznTKXX?S1nt0Zxwgw=g`w3{VU^wYuCAj8V5gK$$4ek>d;kl16~J92dA&J zUeUfPeMNqV{gS%KznyH6hkXwFJ1+@burdDeo?{B!$&#BZbQH6BSGScmnjTTxc=l#Z znbz(Ze`R-f+pb!<(9+r1%Q$><(B~;>_Uw;m6#ke~;>Y=1WBK&YbzS#F-fKS6lwM}y zv(qp&CVSPi>C+-sd#nyz?f!bN*}AvO=Pujpx;yfBBcJ1OFZCF8_w@dAtaG0S-weLH zoNKw5udUVBb$Y9^mr5_(?NYu!Z~40!^Y+Cx%=5`#ey?)x^ItNx`hP?I=Kfm#)tx7T zZ8MJw+ijk65;jLo3-fJc^^f2F80Z{(*ui;e;?%_H4_hYgylAuds#~uce^2JIn8zxQ zbC2a7Tdc;Wrl{ua`^xv-tRUZ=%QTl+`&j!NKYQlvqO;d#n$EU0yl(V-_Vd}-XW!qR z5LFSSvu(w;9jTt1o<uE+e79{+UTc(P<kE=HsMyVJYv-<2y|#Pp?&X5Zclu5CTRrbu z`r!>9x1HR!a=YY)r#IMcuiKWptu`?|Nj!b~(RGL4P2M}Xc2{lhUt4zG_M3dGWus*4 zinkPrScO^Ln{{r^JNw$BU#7j(Pmee+aQ=qz<88vHHIHP@4W513JzYF~XL0lAj_ywH zrqg=U?eu0#AG>>E?-Q?*uZ6J>t1f+=JA3o&LvL5yUjNSQUFN&?JM+(&%-5V3X<U5a z@CoM!fln{mbI+e@kbQqmoxihxWBS(T%KFLr6T{bD->`jY`Ks@U@5SxD*%i%yZ1>p4 z{GQRh$oMnn2ezNse%k+V{=E3s`_A>s|5N@~{lCbtyg`*Qow>8ou`!zI_m8R?#r=Ku zR<~2XZMyl^v1h?fM^VTArnOB^53W_rIPqq}TE*|rXPldOE|ae{pzg)B3+#!{61xxA z@u;`9w}!Xb_Z4(RNL@6XZd!ivj&t#SmtQW^T<*<IH}tn$uj^&1Ww%N%%jTB(E|XpM z$Mn)7&s>d|8{wJbEn}&twazRrXW#ulZ$Gy`n)`_Rakcmi{S&bTl?%@u`qH{wY^9!% zj@2{=pAJ8ZDIXjKv{s1qsD2VISMB4fJS2N4T}l6<*~N|<&M{hYexE$Fe5)qCnYhQa zQst=eQk|zeThv!h*yF?Nb9c7YJh5423b{tMR(lPbCrzAC=(I}Fck;IMSLx5v%1*CI zZQb<mXkPbreYvUZC$l$CSi>5r%eBpE*^7`Ujq6(Mo6=Lm)Apb3JY_j;^*rsfZ_jK# zb$f#OocHtpzZE(p^l;L)rET8fy0Q_^_O8jY++<T&d7JC8D64i`7~2|~9bMbBa<BTY zej7f0UEN-_D&OqJxPzyf4sK=4e$5@q^|ZD2^_}Z)WBnuaBkylb{M9yB+D^2yYn{k# z@#&)VH#F{4Je6+GKed16ohe&Zu0FCe<$T(uw3pBB>CTJho3?8Dv*~<!yf<%c;k_TW z<?YvRuDA84pVhd%yLp@Q&c*v}cI-)Ae)8N|Yx|HTAzwm%g_ym&ba!jw^&fc`-$_^R z`DXIw>h0(~-~Za3+;=;aG1T{J+SSzGp1(JLw_n$}>gA7VzjI^P`sEhBJ<fcI`7gUN z-?r;JuUwVAWE&FHe@*+fJKu5negEdZFW2QY;|=YdbL^3FflK9+m)|+>f0yI6x>eRw zz2+hNW%)AMbXol@r&*t7xy=rq^*cH#dYAthyNlOe-K+ZJep{Y@j)_fVrPt?>lbN?( zHeH^a{^@+r+^KVG>-2u!Nw~deTlMqgbN93O^WX0hD0z6v^?&zs>pj-zVm|FC`LyZw zQ{nI(;j`9T#pk{%d1do&@@4<yyXWklwQgrj_Re=zd$(4neSaxE&-~ooFMBILiv7QK zb)Kc|-Mycy^1mPb{`J1t-MS+6zvoWxzFDsRZtWibJ@H>0-a6iNzT1A)KKkFC8}IMb zXB{YC@cH5$_wQ`2&FBA3{Myg&%-TP*|8yH~+v|B2c9xcp?2G=s{IlHoe9C#XdE);T z{X4x*Tui_2$JN)@&t05xapCk&(<krGty%i_)q&TN>u(=BbWCpF$qHk`xG!P9-yST# zQ!e{{@qK>(Cp8N78viqXZu}a2VfpXLl9MO@XJMZ4`A@R3?rjDJ28CpgAYTTCDpdxC zhGqtapZ^&c8eTFmlo~KFyh>nTu$sZZAf7)d{-_%R1H)5K7srr_TUVwm<T{if(z-vs z-Qwnj@Cbp`9NN_^hMAjRDgWRz+uhd5)}6?)@4%rZr5&r9_B9-Qz;oz4PgAPGqWvB0 zdNF*{4?pA%KT>$ce99y5OSiHg%{SXt^u2QD$JOmiUoaeSjCj>loEhQA_I>Zor%Iv= zH=Na8yzq3!i=^4%r9F!eIG<2<lIvvh_bs@{kbfur)@kFnPd^^3?P_B>b}iHSaOA@& zr3_NPMO!bo%?+#Czj0wBhxL|MzYb?#Ep@qZ@PeH~;MreSw7Og3SL>`u`};lc%sI2q qSHw~__1UD)W9!~u?X;bd;bXBKThNuKa~T*I7(8A5T-G@yGywpX&YZCT literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1767.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1767.png new file mode 100644 index 0000000000000000000000000000000000000000..472d05c29aa90043c2992380c7f924a6c7ebbbd8 GIT binary patch literal 3064 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`(#+;1OBOz`!jG!i)^F z=14FwaQSCCI|l@0<d@_ZXXF<scse^P6cpvBW#*(ZFjU-{8=M{z{8G&J`@Jtt-W?57 zyz_%ZITV7Nx*J3$s%a^53V3xWF1#rkcu>&Ql|}l*f)3`szJ?yrKpmbgt{d!66c_b1 zFYf3%_4{G<;_PdCzCSzrZ|ilt=W9OC-F%+?00+-B592`72A-8pa)J+<j~_Yq@QI#7 zBbQ16g8;|o6vg7d%8U#@>TGPZl@74)V_>Yf%+0}Yz<2fm1DW>!{I8s5#xORtFditG zXyMJ#Aj05K)#tQ~!NHBOVeSmIV1@-O3<;-`lO8h!=rSaz*FU?=&@lJM%xXr4=qZY# zj0+|*C<v!^xG-c`Gpsou*5%3&VZb1g=6%GH;f5}QfoFJ{2g8Q#3<u5$aBpJZ;9)Qb z>}b_wV5w$EIH9au!O$|3LB#D-xXnlHbpp?|7#J#MZnD{_V(Ho#&7oV!9v-eIr*Y0x zTwBB}ktuPOgWjRanUY40Cj1XRpJ8BFFi}+SLG$Oo73cWYojYgNw~a4e_tk#3|4K<o zkN-VAzq-JIf#G3E-Nk=8`kSR1tl1iJ|1DB|$C44py`c1aciJX?hph|?Ufv4-TmT9i z*M$>y-n{wr$&(viN1UP)Pleb1OFtBUApCF6&pMkw-@iWC_eiDDD7Mj^<;R0VXTJC> zl{r6&vnA=yvwrtK+qwV$k-e;R#4~AUgqFzTK%v4umsvfZjg~5PI!)3%s(R^v%A@-_ zznSgsn=)+3vSX~CsJkLInfaTyx<Ew9fk)pN7(VUJKX^rhgSjE2`QiEU|L^Vpx-U>= zXh`#%RLH;(=c1w)a%!{vaSjFsmj#Vl8yuy-9F)*Gz$SB$_0B==KM7_nPAW$ZISDv% zr8LMCC1|c`a?)r!yFpefLH~+_tjU4EjJC-Ie0qmMcktO3@c&BWn0HWPPa`Lb;|UJo zM8#Vjj7eP)%6%N-Gh2Ts9`s-l>Q+%W*&!0B<UP^U!!W2TL?P5e_LFT9lVy91+pdY$ zCm4*H{<Q6JH=NSADbV%`&sOK1E6k-@+!k?PNGUO#)i1VS_JyJ?Qno#23!h(Doxy*r z-_H5C!GRYN?8ZlO4D^m2cG_s6!#O$m^hTvMJgbjMZ3tV#yu0&j^7)PBEJBU02RO}G z%AFJv<s3aCd?NgAc&t#6Q85v`#c9sT+!A<5U1{<P?=5P(1b-fpNHQ{PkMPh@z9!T! ztS?aA;e5ngMX1x+NPXu7CeMvaLM{nia=Vllr2JB6<!loVDdov0Z>3~!Vz->4qo94F z^~u~Pho2Zfk$$51DVl3;l0)H=AHk0_f=*?5Nj?>LYVwpVb%v2+=;B#HcULS75j5?O za$37^ThRKz_7%b=rL!#0Hj8<BFI~H6>Sd`6@r>`8>tB@Y61+QA&TsmK@Jpv(y1$tI zg85514{w`j^YM;!4awOO>=Nmc<r3>>_$-sW+%0)L#K7F}^^E5;`Df;bMrdu*su7)d zifgLZRJEznTKXX?S1nt0Zxwgw=g`w3{VU^wYuCAj8V5gK$$4ek>d;kl16~J92dA&J zUeUfPeMNqV{gS%KznyH6hkXwFJ1+@burdDeo?{B!$&#BZbQH6BSGScmnjTTxc=l#Z znbz(Ze`R-f+pb!<(9+r1%Q$><(B~;>_Uw;m6#ke~;>Y=1WBK&YbzS#F-fKS6lwM}y zv(qp&CVSPi>C+-sd#nyz?f!bN*}AvO=Pujpx;yfBBcJ1OFZCF8_w@dAtaG0S-weLH zoNKw5udUVBb$Y9^mr5_(?NYu!Z~40!^Y+Cx%=5`#ey?)x^ItNx`hP?I=Kfm#)tx7T zZ8MJw+ijk65;jLo3-fJc^^f2F80Z{(*ui;e;?%_H4_hYgylAuds#~uce^2JIn8zxQ zbC2a7Tdc;Wrl{ua`^xv-tRUZ=%QTl+`&j!NKYQlvqO;d#n$EU0yl(V-_Vd}-XW!qR z5LFSSvu(w;9jTt1o<uE+e79{+UTc(P<kE=HsMyVJYv-<2y|#Pp?&X5Zclu5CTRrbu z`r!>9x1HR!a=YY)r#IMcuiKWptu`?|Nj!b~(RGL4P2M}Xc2{lhUt4zG_M3dGWus*4 zinkPrScO^Ln{{r^JNw$BU#7j(Pmee+aQ=qz<88vHHIHP@4W513JzYF~XL0lAj_ywH zrqg=U?eu0#AG>>E?-Q?*uZ6J>t1f+=JA3o&LvL5yUjNSQUFN&?JM+(&%-5V3X<U5a z@CoM!fln{mbI+e@kbQqmoxihxWBS(T%KFLr6T{bD->`jY`Ks@U@5SxD*%i%yZ1>p4 z{GQRh$oMnn2ezNse%k+V{=E3s`_A>s|5N@~{lCbtyg`*Qow>8ou`!zI_m8R?#r=Ku zR<~2XZMyl^v1h?fM^VTArnOB^53W_rIPqq}TE*|rXPldOE|ae{pzg)B3+#!{61xxA z@u;`9w}!Xb_Z4(RNL@6XZd!ivj&t#SmtQW^T<*<IH}tn$uj^&1Ww%N%%jTB(E|XpM z$Mn)7&s>d|8{wJbEn}&twazRrXW#ulZ$Gy`n)`_Rakcmi{S&bTl?%@u`qH{wY^9!% zj@2{=pAJ8ZDIXjKv{s1qsD2VISMB4fJS2N4T}l6<*~N|<&M{hYexE$Fe5)qCnYhQa zQst=eQk|zeThv!h*yF?Nb9c7YJh5423b{tMR(lPbCrzAC=(I}Fck;IMSLx5v%1*CI zZQb<mXkPbreYvUZC$l$CSi>5r%eBpE*^7`Ujq6(Mo6=Lm)Apb3JY_j;^*rsfZ_jK# zb$f#OocHtpzZE(p^l;L)rET8fy0Q_^_O8jY++<T&d7JC8D64i`7~2|~9bMbBa<BTY zej7f0UEN-_D&OqJxPzyf4sK=4e$5@q^|ZD2^_}Z)WBnuaBkylb{M9yB+D^2yYn{k# z@#&)VH#F{4Je6+GKed16ohe&Zu0FCe<$T(uw3pBB>CTJho3?8Dv*~<!yf<%c;k_TW z<?YvRuDA84pVhd%yLp@Q&c*v}cI-)Ae)8N|Yx|HTAzwm%g_ym&ba!jw^&fc`-$_^R z`DXIw>h0(~-~Za3+;=;aG1T{J+SSzGp1(JLw_n$}>gA7VzjI^P`sEhBJ<fcI`7gUN z-?r;JuUwVAWE&FHe@*+fJKu5negEdZFW2QY;|=YdbL^3FflK9+m)|+>f0yI6x>eRw zz2+hNW%)AMbXol@r&*t7xy=rq^*cH#dYAthyNlOe-K+ZJep{Y@j)_fVrPt?>lbN?( zHeH^a{^@+r+^KVG>-2u!Nw~deTlMqgbN93O^WX0hD0z6v^?&zs>pj-zVm|FC`LyZw zQ{nI(;j`9T#pk{%d1do&@@4<yyXWklwQgrj_Re=zd$(4neSaxE&-~ooFMBILiv7QK zb)Kc|-Mycy^1mPb{`J1t-MS+6zvoWxzFDsRZtWibJ@H>0-a6iNzT1A)KKkFC8}IMb zXB{YC@cH5$_wQ`2&FBA3{Myg&%-TP*|8yH~+v|B2c9xcp?2G=s{IlHoe9C#XdE);T z{X4x*Tui_2$JN)@&t05xapCk&(<krGty%i_)q&TN>u(=BbWCpF$qHk`xG!P9-yST# zQ!e{{@qK>(Cp8N78viqXZu}a2VfpXLl9MO@XJMZ4`A@R3?rjDJ28CpgAYTTCDpdxC zhGqtapZ^&c8eTFmlo~KFyh>nTu$sZZAf7)d{-_%R1EaF1i(^Q|t)xHy|JyU`G6*yG zGAKK5vvpK=UsaP-QT;}<d2zn-;ivoaC8o{2X<VSEb}Hf6e;%Hs`;ihA_i7RnRE+#0 zI5Z#p`aJ(&k+O30G2uYAWv{g*Bc8NMUR(Ju{l~dO9rq2l^LalmJ#;+u4$u7UMTUDs z!qa}7IB|BtpHD{33$BN=?c1>a2d~()r-!l&Y&VNE->i>oo4$#U$2G}aa@w4~2MgL> zT00+}zx+q@qboaaSBUPJZ#4hm-$_?__S?TtogkK;_~YQABMHLuj}&Mb_icJ|?CW!T z=lf^%d0z4VH~f?I<9tDAV8_C!ulffK)FdkJM@UHW{rsD5vFW{N^UPqm!;y{gJonGP zTk2Qz`}~OmAG$vC9}X9+Yy02R!#rncdyFEB1Vg=G^0UY}JUt8y3=E#GelF{r5}E+x CQ@FVR literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1784.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1784.png new file mode 100644 index 0000000000000000000000000000000000000000..ceb8103b06c6bc457c5263c5adf2e454281dac20 GIT binary patch literal 302 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWo=hoxj^g!)GY28IisE{-7<y~!yWR^R3`@=kfZCN;w}u1Bv&v}9jE z+KReWeTwH?rsNqNSaEQP<egtJtlG1cn!neIrLCx6cC6r;wEf|k`G0u6p1&gTtyWAe z!+=|nwfp`l0}#-uRPI%JcI5XoBk|*0_K67z2?++zf1XG<_gT=DM_%DYlKQ>vJiS5N zC9c&Aq=RHLrtEUPu+>HK%z*<3CTM(WaeTN*nVFfH`7q1P>?0Y9=Mprp$@8?u$Un?k x1y&QFT-YA5bcy5}<qutEY{rvPbeNeL{P>In-=9la#K6G7;OXk;vd$@?2>=5ebAtc? literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1787.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1787.png new file mode 100644 index 0000000000000000000000000000000000000000..7e25408dc26822567bdf78e54e4ded5eaadb3775 GIT binary patch literal 440 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U)P4yNZQC)Ujp;2F55)7sn8b-mOy%J)9B+8utIUt>yA^-Q&o?)xK>} z58Le~k0Rlm!VHcq4aEsf8A3{jRxY^KbZvpD21gc8lg1iXCGJUGYgj}d|EyW>saW!% z^<GK&o!WBta&@ME&c((%+qVDk&~_C#HM47>VC@anX}1n76^~|LCuO^o=i_gShtn5d zYg%n&myn!(?qtIJKlvN3oTw~ZZoDZbA#=UUx`^=WRuR(@>Vg;@)*pDc(d~cIyuZf0 zhn3<5PR(>W@MVVVdUqwMkO<2k55KK+(n`L<RD1ur%B0mjb+M-RH)cAS<!=%!ys+A< z<!{-g^;TUAA70~Laf^LE-_#oy%+%*41Sdp?TKd1TN%uXq>tyOGr<)TTf-b1EGUe5{ zX3JjRw&=F*ixSRD@7WH=&757r%COaW!i+*A>xBxP%VyLcEM0tJgTc}ba)p<kZgN=j w%)Dq@QvdC;O0$NtKU~od-cBj2*&e_5QO~hC8>@vF7#J8lUHx3vIVCg!0IjvX=>Px# literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1809.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1809.png new file mode 100644 index 0000000000000000000000000000000000000000..d1caf4f21030542284ed53c3c88e2aca2a061a69 GIT binary patch literal 2961 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`(#+;1OBOz`!jG!i)^F z=14FwaQSCCI|l@0<d@_ZXXF<scse^P6cpvBW#*(ZFjU-{8=M{z{8G&J`@Jtt-W?57 zyz_%ZITV7Nx*J3$s%a^53V3xWF1#rkcu>&Ql|}l*f)3`szJ?yrKpmbgt{d!66c_b1 zFYf3%_4{G<;_PdCzCSzrZ|ilt=W9OC-F%+?00+-B592`72A-8pa)J+<j~_Yq@QI#7 zBbQ16g8;|o6vg7d%8U#@>TGPZl@74)V_>Yf%+0}Yz<2fm1DW>!{I8s5#xORtFditG zXyMJ#Aj05K)#tQ~!NHBOVeSmIV1@-O3<;-`lO8h!=rSaz*FU?=&@lJM%xXr4=qZY# zj0+|*C<v!^xG-c`Gpsou*5%3&VZb1g=6%GH;f5}QfoFJ{2g8Q#3<u5$aBpJZ;9)Qb z>}b_wV5w$EIH9au!O$|3LB#D-xXnlHbpp?|7#J#MZnD{_V(Ho#&7oV!9v-eIr*Y0x zTwBB}ktuPOgWjRanUY40Cj1XRpJ8BFFi}+SLG$Oo73cWYojYgNw~a4e_tk#3|4K<o zkN-VAzq-JIf#G3E-Nk=8`kSR1tl1iJ|1DB|$C44py`c1aciJX?hph|?Ufv4-TmT9i z*M$>y-n{wr$&(viN1UP)Pleb1OFtBUApCF6&pMkw-@iWC_eiDDD7Mj^<;R0VXTJC> zl{r6&vnA=yvwrtK+qwV$k-e;R#4~AUgqFzTK%v4umsvfZjg~5PI!)3%s(R^v%A@-_ zznSgsn=)+3vSX~CsJkLInfaTyx<Ew9fk)pN7(VUJKX^rhgSjE2`QiEU|L^Vpx-U>= zXh`#%RLH;(=c1w)a%!{vaSjFsmj#Vl8yuy-9F)*Gz$SB$_0B==KM7_nPAW$ZISDv% zr8LMCC1|c`a?)r!yFpefLH~+_tjU4EjJC-Ie0qmMcktO3@c&BWn0HWPPa`Lb;|UJo zM8#Vjj7eP)%6%N-Gh2Ts9`s-l>Q+%W*&!0B<UP^U!!W2TL?P5e_LFT9lVy91+pdY$ zCm4*H{<Q6JH=NSADbV%`&sOK1E6k-@+!k?PNGUO#)i1VS_JyJ?Qno#23!h(Doxy*r z-_H5C!GRYN?8ZlO4D^m2cG_s6!#O$m^hTvMJgbjMZ3tV#yu0&j^7)PBEJBU02RO}G z%AFJv<s3aCd?NgAc&t#6Q85v`#c9sT+!A<5U1{<P?=5P(1b-fpNHQ{PkMPh@z9!T! ztS?aA;e5ngMX1x+NPXu7CeMvaLM{nia=Vllr2JB6<!loVDdov0Z>3~!Vz->4qo94F z^~u~Pho2Zfk$$51DVl3;l0)H=AHk0_f=*?5Nj?>LYVwpVb%v2+=;B#HcULS75j5?O za$37^ThRKz_7%b=rL!#0Hj8<BFI~H6>Sd`6@r>`8>tB@Y61+QA&TsmK@Jpv(y1$tI zg85514{w`j^YM;!4awOO>=Nmc<r3>>_$-sW+%0)L#K7F}^^E5;`Df;bMrdu*su7)d zifgLZRJEznTKXX?S1nt0Zxwgw=g`w3{VU^wYuCAj8V5gK$$4ek>d;kl16~J92dA&J zUeUfPeMNqV{gS%KznyH6hkXwFJ1+@burdDeo?{B!$&#BZbQH6BSGScmnjTTxc=l#Z znbz(Ze`R-f+pb!<(9+r1%Q$><(B~;>_Uw;m6#ke~;>Y=1WBK&YbzS#F-fKS6lwM}y zv(qp&CVSPi>C+-sd#nyz?f!bN*}AvO=Pujpx;yfBBcJ1OFZCF8_w@dAtaG0S-weLH zoNKw5udUVBb$Y9^mr5_(?NYu!Z~40!^Y+Cx%=5`#ey?)x^ItNx`hP?I=Kfm#)tx7T zZ8MJw+ijk65;jLo3-fJc^^f2F80Z{(*ui;e;?%_H4_hYgylAuds#~uce^2JIn8zxQ zbC2a7Tdc;Wrl{ua`^xv-tRUZ=%QTl+`&j!NKYQlvqO;d#n$EU0yl(V-_Vd}-XW!qR z5LFSSvu(w;9jTt1o<uE+e79{+UTc(P<kE=HsMyVJYv-<2y|#Pp?&X5Zclu5CTRrbu z`r!>9x1HR!a=YY)r#IMcuiKWptu`?|Nj!b~(RGL4P2M}Xc2{lhUt4zG_M3dGWus*4 zinkPrScO^Ln{{r^JNw$BU#7j(Pmee+aQ=qz<88vHHIHP@4W513JzYF~XL0lAj_ywH zrqg=U?eu0#AG>>E?-Q?*uZ6J>t1f+=JA3o&LvL5yUjNSQUFN&?JM+(&%-5V3X<U5a z@CoM!fln{mbI+e@kbQqmoxihxWBS(T%KFLr6T{bD->`jY`Ks@U@5SxD*%i%yZ1>p4 z{GQRh$oMnn2ezNse%k+V{=E3s`_A>s|5N@~{lCbtyg`*Qow>8ou`!zI_m8R?#r=Ku zR<~2XZMyl^v1h?fM^VTArnOB^53W_rIPqq}TE*|rXPldOE|ae{pzg)B3+#!{61xxA z@u;`9w}!Xb_Z4(RNL@6XZd!ivj&t#SmtQW^T<*<IH}tn$uj^&1Ww%N%%jTB(E|XpM z$Mn)7&s>d|8{wJbEn}&twazRrXW#ulZ$Gy`n)`_Rakcmi{S&bTl?%@u`qH{wY^9!% zj@2{=pAJ8ZDIXjKv{s1qsD2VISMB4fJS2N4T}l6<*~N|<&M{hYexE$Fe5)qCnYhQa zQst=eQk|zeThv!h*yF?Nb9c7YJh5423b{tMR(lPbCrzAC=(I}Fck;IMSLx5v%1*CI zZQb<mXkPbreYvUZC$l$CSi>5r%eBpE*^7`Ujq6(Mo6=Lm)Apb3JY_j;^*rsfZ_jK# zb$f#OocHtpzZE(p^l;L)rET8fy0Q_^_O8jY++<T&d7JC8D64i`7~2|~9bMbBa<BTY zej7f0UEN-_D&OqJxPzyf4sK=4e$5@q^|ZD2^_}Z)WBnuaBkylb{M9yB+D^2yYn{k# z@#&)VH#F{4Je6+GKed16ohe&Zu0FCe<$T(uw3pBB>CTJho3?8Dv*~<!yf<%c;k_TW z<?YvRuDA84pVhd%yLp@Q&c*v}cI-)Ae)8N|Yx|HTAzwm%g_ym&ba!jw^&fc`-$_^R z`DXIw>h0(~-~Za3+;=;aG1T{J+SSzGp1(JLw_n$}>gA7VzjI^P`sEhBJ<fcI`7gUN z-?r;JuUwVAWE&FHe@*+fJKu5negEdZFW2QY;|=YdbL^3FflK9+m)|+>f0yI6x>eRw zz2+hNW%)AMbXol@r&*t7xy=rq^*cH#dYAthyNlOe-K+ZJep{Y@j)_fVrPt?>lbN?( zHeH^a{^@+r+^KVG>-2u!Nw~deTlMqgbN93O^WX0hD0z6v^?&zs>pj-zVm|FC`LyZw zQ{nI(;j`9T#pk{%d1do&@@4<yyXWklwQgrj_Re=zd$(4neSaxE&-~ooFMBILiv7QK zb)Kc|-Mycy^1mPb{`J1t-MS+6zvoWxzFDsRZtWibJ@H>0-a6iNzT1A)KKkFC8}IMb zXB{YC@cH5$_wQ`2&FBA3{Myg&%-TP*|8yH~+v|B2c9xcp?2G=s{IlHoe9C#XdE);T z{X4x*Tui_2$JN)@&t05xapCk&(<krGty%i_)q&TN>u(=BbWCpF$qHk`xG!P9-yST# zQ!e{{@qK>(Cp8N78viqXZu}a2VfpXLl9MO@XJMZ4`A@R3?rjDJ28CpgAYTTCDpdxC zhGqtapZ^&c8eTFmlo~KFyh>nTu$sZZAf7)d{-_%R1H&Fq7srr_TS+M!^8cTAVDQl{ zNk8$$JS8C^A)&%J-}3O|No-D05;G(+SloEqn2&v$`J2;i9?SLkw)^#UMiT$4j((Wf z*naTOPv%2x|NsA=zu=(ox4fhum4ED-84mlmWq(@3Bb>~=QdnfonKL~5@7+zb`1{I^ z*<4+}(D7r^A)O|@w4l(Git|U09XRn}{=={GdToc-{}VF%F=v64V{>Y?l!VJ9B}oYh w35jL-Hs>c?-Ct}X%iqT1<YJ`8&A`TB^xMku{=4FI1_lNOPgg&ebxsLQ0EmZu7ytkO literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1829.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1829.png new file mode 100644 index 0000000000000000000000000000000000000000..d1caf4f21030542284ed53c3c88e2aca2a061a69 GIT binary patch literal 2961 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1868.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1868.png new file mode 100644 index 0000000000000000000000000000000000000000..a1e2ea500745872e6060bf58f0e125ee9f762e95 GIT binary patch literal 3278 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 Sbsqx*1B0ilpUXO@geCw?HXG&u literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1869.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1869.png new file mode 100644 index 0000000000000000000000000000000000000000..45e13d35c7d3ef278ca8beaf967eab7b00348854 GIT binary patch literal 689 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTH1gOcc~+#gRE7?^@RT^vI!dgoq>_PER_a{S}_|C@?_@9zG;CT&5; z1rBu~$C*18wX5iEnx~{K;B-XqTfihAd6N@d&1<G`=qkTee4+bF@ui{@FUxB7@SCjH z=PYBsyHqo3_WyaCu7xan{<Zu^fyHx<EuvXEo4c-Z>aD!LBA4UChnFQjYWnOxQ<Y89 zD>l@~B$xl>Z*yB*X!|JWBJ(tb_2xS&N{@R#Jb7SGMwh3DjG1@ft!uki^3*PP@Iu0} zb$g-J>XYZs_rJV7dwbLN{xH>TUAD^v&So}m*~X&rcE+KsSogp{!L-dY@7#%D5ZQZR z>#3B!+r1TYt5o>%Rz-7|bZn0CmtUk}<MDAO=e#|094{+qYje*(-@a;9g^irZ!K0hi zxNkD6?Fs+t!lZP_DSo4J<)qy&^W@5Vig%_?il2BRX8GlZCr&R+cx~<X*2Jjf=z%Zk znR@-vN4)|Uo8`9D1|Oa>h3#dr>%V+Ph1<&tH$SpnxmPRx)Z`0w$DSE}5IZdL-T2Oz zl2cDiStgcpO%hR)`deK&m*;9K*K;nPntTI;SCeL}Php6>P!Q#ADfBU@%&Oz|_O^ZI ze}DY_xAQSWO5T*pfPAILlMf}#oOg3(r7y#gv!0hrZnAm2{(0)#;m2RTIfsUdn$70j zeYfxMLkE$EHJy#?)I^JCw?35aUd(XF;d$2EH%ArD?@pe6<=#C%Utiu;hG*7tPq18O zaA}FUm8Oo~zs6vnrf2*|?c1*In&!OQ>B@YANBLnKrLTpUo+l{y`EK{xa!vB4+2k_? zi|2V5Y}6EQ?SJdPXyYNDAM+XybT9S&ng5qfJp7cF`~tB|1_lNOPgg&ebxsLQ0Q}BA AtN;K2 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1876.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1876.png new file mode 100644 index 0000000000000000000000000000000000000000..6398ed535192024ccf662d56747b8268d9e9d4a0 GIT binary patch literal 2954 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`(#+;1OBOz`!jG!i)^F z=14FwaQSCCI|l@0<d@_ZXXF<scse^P6cpvBW#*(ZFjU-{8=M{z{8G&J`@Jtt-W?57 zyz_%ZITV7Nx*J3$s%a^53V3xWF1#rkcu>&Ql|}l*f)3`szJ?yrKpmbgt{d!66c_b1 zFYf3%_4{G<;_PdCzCSzrZ|ilt=W9OC-F%+?00+-B592`72A-8pa)J+<j~_Yq@QI#7 zBbQ16g8;|o6vg7d%8U#@>TGPZl@74)V_>Yf%+0}Yz<2fm1DW>!{I8s5#xORtFditG zXyMJ#Aj05K)#tQ~!NHBOVeSmIV1@-O3<;-`lO8h!=rSaz*FU?=&@lJM%xXr4=qZY# zj0+|*C<v!^xG-c`Gpsou*5%3&VZb1g=6%GH;f5}QfoFJ{2g8Q#3<u5$aBpJZ;9)Qb z>}b_wV5w$EIH9au!O$|3LB#D-xXnlHbpp?|7#J#MZnD{_V(Ho#&7oV!9v-eIr*Y0x zTwBB}ktuPOgWjRanUY40Cj1XRpJ8BFFi}+SLG$Oo73cWYojYgNw~a4e_tk#3|4K<o zkN-VAzq-JIf#G3E-Nk=8`kSR1tl1iJ|1DB|$C44py`c1aciJX?hph|?Ufv4-TmT9i z*M$>y-n{wr$&(viN1UP)Pleb1OFtBUApCF6&pMkw-@iWC_eiDDD7Mj^<;R0VXTJC> zl{r6&vnA=yvwrtK+qwV$k-e;R#4~AUgqFzTK%v4umsvfZjg~5PI!)3%s(R^v%A@-_ zznSgsn=)+3vSX~CsJkLInfaTyx<Ew9fk)pN7(VUJKX^rhgSjE2`QiEU|L^Vpx-U>= zXh`#%RLH;(=c1w)a%!{vaSjFsmj#Vl8yuy-9F)*Gz$SB$_0B==KM7_nPAW$ZISDv% zr8LMCC1|c`a?)r!yFpefLH~+_tjU4EjJC-Ie0qmMcktO3@c&BWn0HWPPa`Lb;|UJo zM8#Vjj7eP)%6%N-Gh2Ts9`s-l>Q+%W*&!0B<UP^U!!W2TL?P5e_LFT9lVy91+pdY$ zCm4*H{<Q6JH=NSADbV%`&sOK1E6k-@+!k?PNGUO#)i1VS_JyJ?Qno#23!h(Doxy*r z-_H5C!GRYN?8ZlO4D^m2cG_s6!#O$m^hTvMJgbjMZ3tV#yu0&j^7)PBEJBU02RO}G z%AFJv<s3aCd?NgAc&t#6Q85v`#c9sT+!A<5U1{<P?=5P(1b-fpNHQ{PkMPh@z9!T! ztS?aA;e5ngMX1x+NPXu7CeMvaLM{nia=Vllr2JB6<!loVDdov0Z>3~!Vz->4qo94F z^~u~Pho2Zfk$$51DVl3;l0)H=AHk0_f=*?5Nj?>LYVwpVb%v2+=;B#HcULS75j5?O za$37^ThRKz_7%b=rL!#0Hj8<BFI~H6>Sd`6@r>`8>tB@Y61+QA&TsmK@Jpv(y1$tI zg85514{w`j^YM;!4awOO>=Nmc<r3>>_$-sW+%0)L#K7F}^^E5;`Df;bMrdu*su7)d zifgLZRJEznTKXX?S1nt0Zxwgw=g`w3{VU^wYuCAj8V5gK$$4ek>d;kl16~J92dA&J zUeUfPeMNqV{gS%KznyH6hkXwFJ1+@burdDeo?{B!$&#BZbQH6BSGScmnjTTxc=l#Z znbz(Ze`R-f+pb!<(9+r1%Q$><(B~;>_Uw;m6#ke~;>Y=1WBK&YbzS#F-fKS6lwM}y zv(qp&CVSPi>C+-sd#nyz?f!bN*}AvO=Pujpx;yfBBcJ1OFZCF8_w@dAtaG0S-weLH zoNKw5udUVBb$Y9^mr5_(?NYu!Z~40!^Y+Cx%=5`#ey?)x^ItNx`hP?I=Kfm#)tx7T zZ8MJw+ijk65;jLo3-fJc^^f2F80Z{(*ui;e;?%_H4_hYgylAuds#~uce^2JIn8zxQ zbC2a7Tdc;Wrl{ua`^xv-tRUZ=%QTl+`&j!NKYQlvqO;d#n$EU0yl(V-_Vd}-XW!qR z5LFSSvu(w;9jTt1o<uE+e79{+UTc(P<kE=HsMyVJYv-<2y|#Pp?&X5Zclu5CTRrbu z`r!>9x1HR!a=YY)r#IMcuiKWptu`?|Nj!b~(RGL4P2M}Xc2{lhUt4zG_M3dGWus*4 zinkPrScO^Ln{{r^JNw$BU#7j(Pmee+aQ=qz<88vHHIHP@4W513JzYF~XL0lAj_ywH zrqg=U?eu0#AG>>E?-Q?*uZ6J>t1f+=JA3o&LvL5yUjNSQUFN&?JM+(&%-5V3X<U5a z@CoM!fln{mbI+e@kbQqmoxihxWBS(T%KFLr6T{bD->`jY`Ks@U@5SxD*%i%yZ1>p4 z{GQRh$oMnn2ezNse%k+V{=E3s`_A>s|5N@~{lCbtyg`*Qow>8ou`!zI_m8R?#r=Ku zR<~2XZMyl^v1h?fM^VTArnOB^53W_rIPqq}TE*|rXPldOE|ae{pzg)B3+#!{61xxA z@u;`9w}!Xb_Z4(RNL@6XZd!ivj&t#SmtQW^T<*<IH}tn$uj^&1Ww%N%%jTB(E|XpM z$Mn)7&s>d|8{wJbEn}&twazRrXW#ulZ$Gy`n)`_Rakcmi{S&bTl?%@u`qH{wY^9!% zj@2{=pAJ8ZDIXjKv{s1qsD2VISMB4fJS2N4T}l6<*~N|<&M{hYexE$Fe5)qCnYhQa zQst=eQk|zeThv!h*yF?Nb9c7YJh5423b{tMR(lPbCrzAC=(I}Fck;IMSLx5v%1*CI zZQb<mXkPbreYvUZC$l$CSi>5r%eBpE*^7`Ujq6(Mo6=Lm)Apb3JY_j;^*rsfZ_jK# zb$f#OocHtpzZE(p^l;L)rET8fy0Q_^_O8jY++<T&d7JC8D64i`7~2|~9bMbBa<BTY zej7f0UEN-_D&OqJxPzyf4sK=4e$5@q^|ZD2^_}Z)WBnuaBkylb{M9yB+D^2yYn{k# z@#&)VH#F{4Je6+GKed16ohe&Zu0FCe<$T(uw3pBB>CTJho3?8Dv*~<!yf<%c;k_TW z<?YvRuDA84pVhd%yLp@Q&c*v}cI-)Ae)8N|Yx|HTAzwm%g_ym&ba!jw^&fc`-$_^R z`DXIw>h0(~-~Za3+;=;aG1T{J+SSzGp1(JLw_n$}>gA7VzjI^P`sEhBJ<fcI`7gUN z-?r;JuUwVAWE&FHe@*+fJKu5negEdZFW2QY;|=YdbL^3FflK9+m)|+>f0yI6x>eRw zz2+hNW%)AMbXol@r&*t7xy=rq^*cH#dYAthyNlOe-K+ZJep{Y@j)_fVrPt?>lbN?( zHeH^a{^@+r+^KVG>-2u!Nw~deTlMqgbN93O^WX0hD0z6v^?&zs>pj-zVm|FC`LyZw zQ{nI(;j`9T#pk{%d1do&@@4<yyXWklwQgrj_Re=zd$(4neSaxE&-~ooFMBILiv7QK zb)Kc|-Mycy^1mPb{`J1t-MS+6zvoWxzFDsRZtWibJ@H>0-a6iNzT1A)KKkFC8}IMb zXB{YC@cH5$_wQ`2&FBA3{Myg&%-TP*|8yH~+v|B2c9xcp?2G=s{IlHoe9C#XdE);T z{X4x*Tui_2$JN)@&t05xapCk&(<krGty%i_)q&TN>u(=BbWCpF$qHk`xG!P9-yST# zQ!e{{@qK>(Cp8N78viqXZu}a2VfpXLl9MO@XJMZ4`A@R3?rjDJ28CpgAYTTCDpdxC zhGqtapZ^&c8eTFmlo~KFyh>nTu$sZZAf7)d{-_%R1H)EN7srr_TS<TZ|F>t>We{fW zWjMY5cF%uF2?+_k{f|>-)H+Xj_(WT76OWPj??V$-eVpYO{=%9W1QvJm9{ztThK-He zxq~T4*TMY5kxI6<XD4}hdU$wvz$V1xNJw~WWSHJq-RJP+A=_aFMQ#qs4xWF>hxN>O z{`0R(`SJg=SXoVhstd2)U!Dq;f5rv|f8MV-V6tU1(|)-<Y{9RRX0;l=4Prfe>Erom jo-HRUo!1F4@i3&-Ja(NFpnr~mfq}u()z4*}Q$iB}Q1NnA literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1939.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/1939.png new file mode 100644 index 0000000000000000000000000000000000000000..7e25408dc26822567bdf78e54e4ded5eaadb3775 GIT binary patch literal 440 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U)P4yNZQC)Ujp;2F55)7sn8b-mOy%J)9B+8utIUt>yA^-Q&o?)xK>} z58Le~k0Rlm!VHcq4aEsf8A3{jRxY^KbZvpD21gc8lg1iXCGJUGYgj}d|EyW>saW!% z^<GK&o!WBta&@ME&c((%+qVDk&~_C#HM47>VC@anX}1n76^~|LCuO^o=i_gShtn5d zYg%n&myn!(?qtIJKlvN3oTw~ZZoDZbA#=UUx`^=WRuR(@>Vg;@)*pDc(d~cIyuZf0 zhn3<5PR(>W@MVVVdUqwMkO<2k55KK+(n`L<RD1ur%B0mjb+M-RH)cAS<!=%!ys+A< z<!{-g^;TUAA70~Laf^LE-_#oy%+%*41Sdp?TKd1TN%uXq>tyOGr<)TTf-b1EGUe5{ zX3JjRw&=F*ixSRD@7WH=&757r%COaW!i+*A>xBxP%VyLcEM0tJgTc}ba)p<kZgN=j w%)Dq@QvdC;O0$NtKU~od-cBj2*&e_5QO~hC8>@vF7#J8lUHx3vIVCg!0IjvX=>Px# literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/20.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/20.png new file mode 100644 index 0000000000000000000000000000000000000000..0bb0d4e877dfc90324faa34953da79e04d145f8b GIT binary patch literal 223 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U!?^ld;*A_d>-C3=EB)E{-7<y>Blo@*N5gXn9z_jo05HdV)$uUgLp+ zq;rj{U6KWtcrw>U#~$U<^02DgFF5&XZh1*s$Js``4=P39Z&du14Py`ZB|B$+_?OB? z)jfBTzW&sXal5rQd*O<N69y#{CrhkJ>b)cv>0D>;RFGk_+D{|D;QhvDLQSkX^S>|u az;V)1aiyD|^ezSl1_n=8KbLh*2~7a$(^G^1 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/212.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/212.png new file mode 100644 index 0000000000000000000000000000000000000000..3319fa7f6f16eedf976ebabc0c88b1e2058cf39d GIT binary patch literal 312 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U$R9kD|%_18cn)7#MDOx;Tbd^q!q;>vz~epml%b%_B}`5t$him{}J! zChvT}cBfnT!qSc@r}Q)ef?SH&g%Xs1rfc}jtPoG}uDoQn_~Q1@Y4=Zke!JJG@r=lY z#|-6{=3hKmnQ_9oym?;s<Dz^0)!z-7etZo%u}E$Qcgup%d3tBwvz{ugv<PUvw@zy* zM@wjqhu70j(UHg7lmx`C7I?8AbW^;$?M{tWzWRrE+@33!+!jmr+<&=mb8+IegOO|B zIBm~}nE&&1^NP0P)7#p8y>EOmoqgon(ro6s^!0x$AJsBQU*Y@{@5}a&@w&17&A`lC Sw;3217(8A5T-G@yGywqqfQG&R literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/213.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/213.png new file mode 100644 index 0000000000000000000000000000000000000000..92b1190e3dca8bb2caac257ccc69b840192b34fe GIT binary patch literal 411 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U)=FfUtSuKciR%21Z*?7sn8b-lLN@>N^LDwC}IJUwtni%6F@;NMNst zneybN-L5J+Y0bVZOS@fMnjDjaoBubs97);`p(EC;6v(Ecv~X2uZv5Ts7i((QcHFPp zSM&4f6Pbg58jhuYaXY2kRpM(l<G-jk??!Xak4C&+JB4O{Z8(_TJXKhFrRmHIc4-z3 z<s~fEa=CFbe?)R*Z_a5K+1#nL^eSt~O~<F)rP9}9jvm?CdR%Y~|5Q!s54$#>{(DAg z#<BNDCdz0m-1#~5dsOAg3APr`67zV!&dZPcbAv62``0EW2IU9G&8IEMHtzhg%}oDZ zw{=a#tS2jdPm8lGQ-66cj%|5RFjs<sOw6y$hz$?c1RK^R8%^Ex!RDz(?A;{SLo;Om z%}RRsZ0XXrjIg4KHb?rtHms@tSNdMUfzxBV_M3lk<`r9evNs+4GxLY^gj3O+&1d2a R7#J8BJYD@<);T3K0RRv{u@(RT literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/216.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/216.png new file mode 100644 index 0000000000000000000000000000000000000000..f00450b7afc3e22cddcdeca9c540747dba82969e GIT binary patch literal 401 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxE`-uhUroU21a8~7sn8b-m6pgdovYE9J9YySuXK+hrlT*?&)1T zA!nAZ{J-F+*zTnrFaBp_c5y9KT)SZ5Gmc-(hYl(p<mkJl#Oam0<FNJLTLN3Yx{9>V z`&nzZXV3Y=(iO^g5*dQn7-cmqZd{$RYSGbWj34B+F64dZzcx*HMys;RUdD!`zQFVE z9*BDf#XE@26Pl?|Q}1oNxzozjGefz#Am+<d6Zw;&E?@upZ}@UdF<bWe6B!%+&%dUm zZEQ(;QDT?PlN)`hcj<$KlD4p&P1F1y#l<{)Bmd|Lm&Rqmri+o95mk?;{cRG9UQk(l z{KY}Jf0KJBMmX&W%e;AhZ~mKS`kc9`j@MVNjg(kvap*Hg%kjdZO3er<xrS+7$@65D zrQ+?|CMHT<%d21eW9q6!y5|@lOqbvNs<8Zl+?W4#N6izzCM|FNcXl5G0|SGntDnm{ Hr-UW|Nqw(u literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/218.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/218.png new file mode 100644 index 0000000000000000000000000000000000000000..52530626a429d202624f72c4309f1f106a8c87a4 GIT binary patch literal 2820 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 CrZ}De literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/220.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/220.png new file mode 100644 index 0000000000000000000000000000000000000000..b5964b2715b6fb4fd993384843aac0973395360d GIT binary patch literal 146 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U##^lc}{}zvL4J1_l#P7sn8b-eeo~72oDJvNd#ywe?B$FiD(`t#X`v yBKgV0my*7sJUl#Gc#>Ba^<FfPTg1l9a6Xe~yVuDZe;61T7(8A5T-G@yGywp#<SWVm literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/221.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/221.png new file mode 100644 index 0000000000000000000000000000000000000000..ff6071bad1d33253fb04d625b496cdb36bbddb3b GIT binary patch literal 245 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTH11H1J+57jda3=GpfT^vI!dXrN!w*Eigz#P+=WVcN7KtikUyVHOC zkK29ScU<fEfddB)w0JiK8Dtr}WV`=^;ouP_F(<Ble}A4Y_+!a@_}A|VM}$|LIFX>~ z$=bX#LPGN1-#_OKo=Y7`sMd62(-U)R)1M-B=D>ji35o{~Bq$u>NEc@AF#2K7%zXHu zgGb4pKYhmb6Z1C6@GFOz*%^E|nfpVUhhgpurJX<GPR(IpU|{fc^>bP0l+XkKibh}q literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/222.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/222.png new file mode 100644 index 0000000000000000000000000000000000000000..08d9e225999a18374839a2cc754aee51df360ecd GIT binary patch literal 410 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RX!<w}s+;7R`wa42(9OE{-7<y;mpOdLIsuXq`V>Eq9iKX5H2x0Zt`X z!6R$!4_&<(`M+iHxl4O@=lR`Tnk*}`v?C~IrAL6r<yrl&gR)wW-Pu*SjQjf@{^$Gd zD6e?$9W+I7(j+;i3;d-?nwQ)z1j{<8u<AE1Oq%NzY$|3X*y^){$IoX^9?KUs&F?<* zAFGNlS@KY?*xrayLz?k#%XAgBb<d7n-Yorc&YC?%yMDDvXBOL@dhmAU!nlkALybba zh{fV*`wu?T5S+84E4H|0=~15KUXMeO`~N(?cPXjt-=rO;TXw7|x%ljlj<3VsOHZ$D z7H5y&>AGsCq3?3%v)=@S6V86iu#%ngM*aHxqb>!Hw3Jxd`}ul%)uW>MStbWbI2C;k zeDI=j@{j3RoKv64KB>)`#_o`vQT+V*Ww$G{u7CTyMKob^P~N>ozv>_8XH+EnPrEyZ RgMop8!PC{xWt~$(69B2PvKjyY literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/223.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/223.png new file mode 100644 index 0000000000000000000000000000000000000000..27da235bc70c04011749e7d5a0e3ee29be40662e GIT binary patch literal 151 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxS_<p_xCR_Ffdqpx;Tbd^d^hk%=$W?k=;V&<A)hir}MRQiq=ot zEL&|jnR~SiGcz-@upXz^!4(G=v-L~aWPNCm;$aZa=2F_Z!<e6efq}u()z4*}Q$iB} D?kOx$ literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/224.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/224.png new file mode 100644 index 0000000000000000000000000000000000000000..fba8996037e0cf45309c313628d775604cfff23b GIT binary patch literal 151 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxQQN*(moyr1_nz{7sn8b-eeo~72oDFvIoR}_$jgbP`q|dk!}Cf zzEGPB2eghKIB?)Vi+59yLDq*A2N$#TOW7=7V`ez`o=foeowlD03=9mOu6{1-oD!M< D5pXds literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/225.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/225.png new file mode 100644 index 0000000000000000000000000000000000000000..acc23943de8d5712d09ac176b9edbf2534bb001a GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxT?UsSq<A67#K`FT^vI!dXxXeo&PYOkv)Mo?$m)b(ygI+H}?wj zTx3swbm`QAmf$uggRBoL4lZWvm$F&F#>{a2GKaK%*|kOn1_lOCS3j3^P6<r_sE98> literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/226.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/226.png new file mode 100644 index 0000000000000000000000000000000000000000..8f43316c680dd169ae90a297722331a65befdd80 GIT binary patch literal 256 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWpTrwU`Ri|0ND28KnRE{-7<y>BnsavgFIXnR;*+PQjycZN$q^NoyT z&8F?2S>jwg(i4-HFL)rS(wq0s<J6SR9F79ZXC>`RPmuUJr$v=FWHsj({(8383X5e^ z&TW{+7Q+AK&Z2O!hCdl6&bNJ1J+Qk~KQF$Gb#^3=gUIhWL6(be<R7({n=$3^hc>2b z-v#@R2q%6NVUP_q*yqFeqPo-j2=BgqyG!$Xh5!65|6RL-Ni9feXUE%mdj<vu22WQ% Jmvv4FO#rEqV*daD literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/227.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/227.png new file mode 100644 index 0000000000000000000000000000000000000000..9d2a6556f0796c412ae9498ede3c5dd5982beaa5 GIT binary patch literal 2931 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 P1_lOCS3j3^P6<r_Ix=_G literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/228.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/228.png new file mode 100644 index 0000000000000000000000000000000000000000..efc38d4c716f41f2b207a4f6cc940983173be58e GIT binary patch literal 3019 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 E00lvl*8l(j literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/229.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/229.png new file mode 100644 index 0000000000000000000000000000000000000000..c433ed9e6da4ed1090acb54335e1d9b6fcad4ed0 GIT binary patch literal 164 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWo^v!wcLJHLet3=D3bE{-7<y~!yV{D00juuFWd-&uR<*CH)D(;E!J zE2Z3c<rvvE3j}@mE_?dFzibWr$~=R^+HS|<tu5YqnpV7M{kY=b7IO)PJLmZni>K|~ R&A`CG;OXk;vd$@?2>_zXHNpS@ literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/230.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/230.png new file mode 100644 index 0000000000000000000000000000000000000000..3fe55dc10a1a738e059c6064485f43949a94d183 GIT binary patch literal 172 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxVlm0nZ*|w7#RFKT^vI!dXx7kD}JBf$kxy))^_Pi!nC(Jr=Djd z8YnLg`<)?SYybl1qEm_+)Y$%4p0$@c#sdO6Rj=P>OzMlg_b0)4QR_ix{T{}hX?(}$ UEsFoez`(%Z>FVdQ&MBb@0QqG)b^rhX literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/231.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/231.png new file mode 100644 index 0000000000000000000000000000000000000000..82ba43a3af028eea86b9fd02443953c638a90bc8 GIT binary patch literal 3043 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/232.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/232.png new file mode 100644 index 0000000000000000000000000000000000000000..8577c25bd3c8b085a12c25030b81e5b304ddadae GIT binary patch literal 137 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWpTD~rj@%z$VH1_o_U7sn8b-sCB_v%b!6WNYXYYwMHhVUqa&|G&Ld orSp{px3)#eizk}9@Uk(?IK>e?&+qtI1_lNOPgg&ebxsLQ0C=(~i2wiq literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/233.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/233.png new file mode 100644 index 0000000000000000000000000000000000000000..33066499d0a6623939dd1e9a83c8c5525558c77d GIT binary patch literal 253 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWpTi?zvtQh@*l28Q{bE{-7<y~#G}E56NdWNYXYYwMHhVUqYCBciPJ zDeunz(+3V5IIv*h37;tkSz`Y@eqP`9ef~e=HMc%CCtmH7=Xr1cH~q)Q$s3|0I5dC! z`fKkT^ftY1ZF`-Ofq{X+oW0-L6XT5anc1zC<7!=4k1l>*FL|Z*9$50u9i=l0>Yf%B z9J^*THa0%IwAP-dU5x9{%_9<LPE5$PpY^$I#X*K6draP3%C&4@U|?YIboFyt=akR{ E0Q1CV761SM literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/234.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/234.png new file mode 100644 index 0000000000000000000000000000000000000000..b2fb61dbd0ea7f746536b65020019c1fa17e069d GIT binary patch literal 2837 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`(#+;1OBOz`!jG!i)^F z=14FwaQSCCI|l@0<d@_ZXXF<scse^P6cpvBW#*(ZFjU-{8=M{z{8G&J`@Jtt-W?57 zyz_%ZITV7Nx*J3$s%a^53V3xWF1#rkcu>&Ql|}l*f)3`szJ?yrKpmbgt{d!66c_b1 zFYf3%_4{G<;_PdCzCSzrZ|ilt=W9OC-F%+?00+-B592`72A-8pa)J+<j~_Yq@QI#7 zBbQ16g8;|o6vg7d%8U#@>TGPZl@74)V_>Yf%+0}Yz<2fm1DW>!{I8s5#xORtFditG zXyMJ#Aj05K)#tQ~!NHBOVeSmIV1@-O3<;-`lO8h!=rSaz*FU?=&@lJM%xXr4=qZY# zj0+|*C<v!^xG-c`Gpsou*5%3&VZb1g=6%GH;f5}QfoFJ{2g8Q#3<u5$aBpJZ;9)Qb z>}b_wV5w$EIH9au!O$|3LB#D-xXnlHbpp?|7#J#MZnD{_V(Ho#&7oV!9v-eIr*Y0x zTwBB}ktuPOgWjRanUY40Cj1XRpJ8BFFi}+SLG$Oo73cWYojYgNw~a4e_tk#3|4K<o zkN-VAzq-JIf#G3E-Nk=8`kSR1tl1iJ|1DB|$C44py`c1aciJX?hph|?Ufv4-TmT9i z*M$>y-n{wr$&(viN1UP)Pleb1OFtBUApCF6&pMkw-@iWC_eiDDD7Mj^<;R0VXTJC> zl{r6&vnA=yvwrtK+qwV$k-e;R#4~AUgqFzTK%v4umsvfZjg~5PI!)3%s(R^v%A@-_ zznSgsn=)+3vSX~CsJkLInfaTyx<Ew9fk)pN7(VUJKX^rhgSjE2`QiEU|L^Vpx-U>= zXh`#%RLH;(=c1w)a%!{vaSjFsmj#Vl8yuy-9F)*Gz$SB$_0B==KM7_nPAW$ZISDv% zr8LMCC1|c`a?)r!yFpefLH~+_tjU4EjJC-Ie0qmMcktO3@c&BWn0HWPPa`Lb;|UJo zM8#Vjj7eP)%6%N-Gh2Ts9`s-l>Q+%W*&!0B<UP^U!!W2TL?P5e_LFT9lVy91+pdY$ zCm4*H{<Q6JH=NSADbV%`&sOK1E6k-@+!k?PNGUO#)i1VS_JyJ?Qno#23!h(Doxy*r z-_H5C!GRYN?8ZlO4D^m2cG_s6!#O$m^hTvMJgbjMZ3tV#yu0&j^7)PBEJBU02RO}G z%AFJv<s3aCd?NgAc&t#6Q85v`#c9sT+!A<5U1{<P?=5P(1b-fpNHQ{PkMPh@z9!T! ztS?aA;e5ngMX1x+NPXu7CeMvaLM{nia=Vllr2JB6<!loVDdov0Z>3~!Vz->4qo94F z^~u~Pho2Zfk$$51DVl3;l0)H=AHk0_f=*?5Nj?>LYVwpVb%v2+=;B#HcULS75j5?O za$37^ThRKz_7%b=rL!#0Hj8<BFI~H6>Sd`6@r>`8>tB@Y61+QA&TsmK@Jpv(y1$tI zg85514{w`j^YM;!4awOO>=Nmc<r3>>_$-sW+%0)L#K7F}^^E5;`Df;bMrdu*su7)d zifgLZRJEznTKXX?S1nt0Zxwgw=g`w3{VU^wYuCAj8V5gK$$4ek>d;kl16~J92dA&J zUeUfPeMNqV{gS%KznyH6hkXwFJ1+@burdDeo?{B!$&#BZbQH6BSGScmnjTTxc=l#Z znbz(Ze`R-f+pb!<(9+r1%Q$><(B~;>_Uw;m6#ke~;>Y=1WBK&YbzS#F-fKS6lwM}y zv(qp&CVSPi>C+-sd#nyz?f!bN*}AvO=Pujpx;yfBBcJ1OFZCF8_w@dAtaG0S-weLH zoNKw5udUVBb$Y9^mr5_(?NYu!Z~40!^Y+Cx%=5`#ey?)x^ItNx`hP?I=Kfm#)tx7T zZ8MJw+ijk65;jLo3-fJc^^f2F80Z{(*ui;e;?%_H4_hYgylAuds#~uce^2JIn8zxQ zbC2a7Tdc;Wrl{ua`^xv-tRUZ=%QTl+`&j!NKYQlvqO;d#n$EU0yl(V-_Vd}-XW!qR z5LFSSvu(w;9jTt1o<uE+e79{+UTc(P<kE=HsMyVJYv-<2y|#Pp?&X5Zclu5CTRrbu z`r!>9x1HR!a=YY)r#IMcuiKWptu`?|Nj!b~(RGL4P2M}Xc2{lhUt4zG_M3dGWus*4 zinkPrScO^Ln{{r^JNw$BU#7j(Pmee+aQ=qz<88vHHIHP@4W513JzYF~XL0lAj_ywH zrqg=U?eu0#AG>>E?-Q?*uZ6J>t1f+=JA3o&LvL5yUjNSQUFN&?JM+(&%-5V3X<U5a z@CoM!fln{mbI+e@kbQqmoxihxWBS(T%KFLr6T{bD->`jY`Ks@U@5SxD*%i%yZ1>p4 z{GQRh$oMnn2ezNse%k+V{=E3s`_A>s|5N@~{lCbtyg`*Qow>8ou`!zI_m8R?#r=Ku zR<~2XZMyl^v1h?fM^VTArnOB^53W_rIPqq}TE*|rXPldOE|ae{pzg)B3+#!{61xxA z@u;`9w}!Xb_Z4(RNL@6XZd!ivj&t#SmtQW^T<*<IH}tn$uj^&1Ww%N%%jTB(E|XpM z$Mn)7&s>d|8{wJbEn}&twazRrXW#ulZ$Gy`n)`_Rakcmi{S&bTl?%@u`qH{wY^9!% zj@2{=pAJ8ZDIXjKv{s1qsD2VISMB4fJS2N4T}l6<*~N|<&M{hYexE$Fe5)qCnYhQa zQst=eQk|zeThv!h*yF?Nb9c7YJh5423b{tMR(lPbCrzAC=(I}Fck;IMSLx5v%1*CI zZQb<mXkPbreYvUZC$l$CSi>5r%eBpE*^7`Ujq6(Mo6=Lm)Apb3JY_j;^*rsfZ_jK# zb$f#OocHtpzZE(p^l;L)rET8fy0Q_^_O8jY++<T&d7JC8D64i`7~2|~9bMbBa<BTY zej7f0UEN-_D&OqJxPzyf4sK=4e$5@q^|ZD2^_}Z)WBnuaBkylb{M9yB+D^2yYn{k# z@#&)VH#F{4Je6+GKed16ohe&Zu0FCe<$T(uw3pBB>CTJho3?8Dv*~<!yf<%c;k_TW z<?YvRuDA84pVhd%yLp@Q&c*v}cI-)Ae)8N|Yx|HTAzwm%g_ym&ba!jw^&fc`-$_^R z`DXIw>h0(~-~Za3+;=;aG1T{J+SSzGp1(JLw_n$}>gA7VzjI^P`sEhBJ<fcI`7gUN z-?r;JuUwVAWE&FHe@*+fJKu5negEdZFW2QY;|=YdbL^3FflK9+m)|+>f0yI6x>eRw zz2+hNW%)AMbXol@r&*t7xy=rq^*cH#dYAthyNlOe-K+ZJep{Y@j)_fVrPt?>lbN?( zHeH^a{^@+r+^KVG>-2u!Nw~deTlMqgbN93O^WX0hD0z6v^?&zs>pj-zVm|FC`LyZw zQ{nI(;j`9T#pk{%d1do&@@4<yyXWklwQgrj_Re=zd$(4neSaxE&-~ooFMBILiv7QK zb)Kc|-Mycy^1mPb{`J1t-MS+6zvoWxzFDsRZtWibJ@H>0-a6iNzT1A)KKkFC8}IMb zXB{YC@cH5$_wQ`2&FBA3{Myg&%-TP*|8yH~+v|B2c9xcp?2G=s{IlHoe9C#XdE);T z{X4x*Tui_2$JN)@&t05xapCk&(<krGty%i_)q&TN>u(=BbWCpF$qHk`xG!P9-yST# zQ!e{{@qK>(Cp8N78viqXZu}a2VfpXLl9MO@XJMZ4`A@R3?rjDJ28CpgAYTTCDpdxC zhGqtapZ^&c8eTFmlo~KFyh>nTu$sZZAf7)d{-_%R1A~L7i(^Q|t)xHy|JyU`G6*yG zGAKJ&EBLAFf7MRuxU}i0*7i5I65C$*{{2$^amB&KZ2eL_`riCIoj4d682<lfX3%}Y W`*v>9u`>(|3=E#GelF{r5}E+i2}0Ze literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/235.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/235.png new file mode 100644 index 0000000000000000000000000000000000000000..de17bf8393dd9fc3c7b601d0e7f49553f76d1438 GIT binary patch literal 151 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RXoHr@8{)xfuxz3=Ec@E{-7<y~!y7XaAgUV2*hmUFZ}1Yp<x;&g<Go z`}fY!><;Z~Y;0`goX)Z|;mU)+W_MmW(+US(HinooF5^>`)y@nI3=E#GelF{r5}E)t Cqb~IT literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/236.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/236.png new file mode 100644 index 0000000000000000000000000000000000000000..4babeeb7ca2bd4af882bdaf462a6074499cfa9c0 GIT binary patch literal 380 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{W>=fx3SulTkM+{7#QU|T^vI!dXrNOGJc(BU_G_yy~MkHVo46{+4JQR z0{;H(Y;0_7d}yXE@4R6PbDLDj0hZ()8Fy17rtIodJp9mvnftPRlT@eKk2lVmY=-;v zdFuZe8vgltyv3hCu)%Ok$=OIYHa51nKL0cK>kI!qe){9gpMSG2$fvm8T%DH1Ra<lZ z<beYRKJ>+Z_<d!=m)9)3|Nj5~U+>hF7wvBJXGd8($nr*K?^egJFN&J<mfbo3#camX zs`{^vPF*$kY>gi#COk+>NJyyo{O;y;*59ko3z$^gJzM&%MI_R&!0>~E?f1Ti+~$T= zZ3z<chWm>9CM>$H6E$nU<P8f+2?>cQk^gG!lWN)%tkMM2ekQwkT(rKnpJ)HVoV08E h=Nx$<S(V7c@G>g&m!U9EF9QPugQu&X%Q~loCIInnrF{SZ literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/237.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/237.png new file mode 100644 index 0000000000000000000000000000000000000000..85a0664c5ce94f045586951d41c38a96faf8a1ee GIT binary patch literal 240 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{W>=fr=j|!{KgXu3=ESzT^vI!dXrODbp1Zxz`Q21@a2q4zw^UxJaJL3 zlbd|zM_*`PV`F0@=X92(30D#>@uofdcmBYcUgLR&4BUoYH&p){a#a0E`tg5$<HNsx zW^M83Id`{fG)FG|Z`9%TKOrF{A>l{W%$Zee^?Q#T`tZ|2yQOxfhVBu`KmYla4>w2U sakQU^Q1rWLFO$>7tH#_T#lz0<OGAw>x3VOFfq{X+)78&qol`;+0E2a1$N&HU literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/238.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/238.png new file mode 100644 index 0000000000000000000000000000000000000000..a587e353b2ed434f04c7ea3e24441ba2831f3e7b GIT binary patch literal 3037 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 a40D%8@|YfH@MB<LVDNPHb6Mw<&;$Tj$*Yb4 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/239.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/239.png new file mode 100644 index 0000000000000000000000000000000000000000..9b2c63a54e4604533710f926fc89ddac2e9df95a GIT binary patch literal 2990 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 Y{Ptd_>z_3;FfcH9y85}Sb4q9e009Vw6951J literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/240.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/240.png new file mode 100644 index 0000000000000000000000000000000000000000..097a4dab5681fab5316683991a997572d216e1eb GIT binary patch literal 434 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWpTrwY?4UkMfl2F4Ii7sn8b-nAD_Yqtc-9Q*j*ve<aN=BH`iJd4&$ z?OnQb)8Zv+o|1`+Pq=x8{bN{Jc=A-+gf(f;?Pn-0O_?>rQEKM&nQpy>eXZW>&id?4 zFMee?*MI+g<ym6xrIUAd-F1(<(<``Nev>>~hpPHcFV;8hjEhcct_x!Rb~wY)#PV?O z)ogB^8GSGE8yf=V+e^PODVD!^IXPt2owWfTZ#7pRdz&5Dn(+L*L-vdt-V$-sP1vkg z-Fxud%c(tD<x}Ca-OpBh?Q9Z0z5kH-Qpub9R6c(Av#)i@(YMQEw>~V`#l7hNAwJEB z_xVc}9%6M+o4oK?a?o=F&*hh05AI(jz%*_5*_;KT&!(g@2CSR%<+{05?Af&v6T+v3 zMjdB5o0QTy@9u^|-ecFz-~XP=ysmr3kIh_-X%9169du%jY!`a4^Ra2$V)ktd=6`r| rlI`H<+S>sPmbJT1{<zt<R^x!S@VE5a&$Ir5f}FwA)z4*}Q$iB}D?-PU literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/241.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/241.png new file mode 100644 index 0000000000000000000000000000000000000000..0080a167553681c5698549cf53da57cc3874e076 GIT binary patch literal 2888 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 YXNooEd*6P<z`(%Z>FVdQ&MBb@0O{6PS^xk5 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/242.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/242.png new file mode 100644 index 0000000000000000000000000000000000000000..9b775eb9a6204a8b6373c3486a2b6287d74363bd GIT binary patch literal 449 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxVpfaHIGag7#Nd1T^vI!dZ%9T*EpOgap2?o-R1A6$f!7JXl9!J zyd1>GH>tV1yC|5i@6<`pi9(*bn*S%A4ifV0n$#V{=cV~H&*avhZ8z;F+ZSKl^zGla z`}1z!{=8@YysCNMMb~^=s%dmeWlovquG6`<{@nIss8#cy;$=}bd-27+3WE6?EOvw( znizdCII&4is%1Mf!?lM((lRAlRlXN)OmwXHtXjN{#hT?!xz;c3uTMWt7FhUI{D=A4 zrVY*q^4gOYc*W1M=uTK;lfIDQTg?iQ2f5)qVY5rB<PJuBd%mF3dK%N7+^&UEll}A; zNyypPyxtyK?ag%~E0pK@tOe5p<HYCt1nTNE@J>%!;@7XXK1qW6=S7(ahHO3S#b2ww z{Ixpw`i}C;^@sXRl{pSP-eP=B;p+?ErUz@z3(aEHw|v`U{_5hDxAMO-|3+_QJhR<H z?aYPUDvg=)FR%2ciX1SztTQv`()!QGYbQOp;(Sr#fPPL!X<wJ?+Eok;3=E#GelF{r G5}E*Km&d38 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/243.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/243.png new file mode 100644 index 0000000000000000000000000000000000000000..9cd944fecdc3a410a6b685511e5661600c7345ce GIT binary patch literal 3451 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/244.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/244.png new file mode 100644 index 0000000000000000000000000000000000000000..64cd624bcb16e744dbad7459526a24b1d3a96da7 GIT binary patch literal 374 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxH`{{wfm|V7#JlzT^vI!de2U=^*$UR(RzNbr<8Nn<(3X7-J?p| z9xU=}+F?CIo-0pp5y!ix2d~!rV|;FPD{#(kD{fh>k_?9>o*sRZXFt2v-@tzFW2MEM zhoZLSof-<8I9}Qdc{{B3*>>ki66cTIf>P7hugFoWR~Az}aJO$_8`EEI>3?-B$$EeH z&M-{47Chst$j8kt75N8vAN?^pK5e~BZmIWz|13RhE<%i3#RTSX#mGNd9CG*Atb=jK zld~Pw8RXtY+E{N2ofBpA*mK=_i9Nel7OBs8ba?G?H{a8#C0BX2sb1JwWFV0y(JDDj z+_Xggc!gw|R><8O#VWUb=e}L$w_L~4e)Y#cOMEt~>`OUvT<*SaX_SIr!}gEQfAk++ fCGt)C9%D;fbLyJ!%jFpu7#KWV{an^LB{Ts59U`2c literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/245.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/245.png new file mode 100644 index 0000000000000000000000000000000000000000..bcd4652e3b0874a14963db07e7b68276a62be233 GIT binary patch literal 2932 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 SFHr^t1_n=8KbLh*2~7a>uWwWU literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/246.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/246.png new file mode 100644 index 0000000000000000000000000000000000000000..f8ad19709be8b5d8177f14a5ecd177bb39b10b60 GIT binary patch literal 3646 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/247.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/247.png new file mode 100644 index 0000000000000000000000000000000000000000..9714e588249b3295e67272e2b14d533882fc0a69 GIT binary patch literal 3739 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`(#+;1OBOz`!jG!i)^F z=14FwaQSCCI|l@0<d@_ZXXF<scse^P6cpvBW#*(ZFjU-{8=M{z{8G&J`@Jtt-W?57 zyz_%ZITV7Nx*J3$s%a^53V3xWF1#rkcu>&Ql|}l*f)3`szJ?yrKpmbgt{d!66c_b1 zFYf3%_4{G<;_PdCzCSzrZ|ilt=W9OC-F%+?00+-B592`72A-8pa)J+<j~_Yq@QI#7 zBbQ16g8;|o6vg7d%8U#@>TGPZl@74)V_>Yf%+0}Yz<2fm1DW>!{I8s5#xORtFditG zXyMJ#Aj05K)#tQ~!NHBOVeSmIV1@-O3<;-`lO8h!=rSaz*FU?=&@lJM%xXr4=qZY# zj0+|*C<v!^xG-c`Gpsou*5%3&VZb1g=6%GH;f5}QfoFJ{2g8Q#3<u5$aBpJZ;9)Qb z>}b_wV5w$EIH9au!O$|3LB#D-xXnlHbpp?|7#J#MZnD{_V(Ho#&7oV!9v-eIr*Y0x zTwBB}ktuPOgWjRanUY40Cj1XRpJ8BFFi}+SLG$Oo73cWYojYgNw~a4e_tk#3|4K<o zkN-VAzq-JIf#G3E-Nk=8`kSR1tl1iJ|1DB|$C44py`c1aciJX?hph|?Ufv4-TmT9i z*M$>y-n{wr$&(viN1UP)Pleb1OFtBUApCF6&pMkw-@iWC_eiDDD7Mj^<;R0VXTJC> zl{r6&vnA=yvwrtK+qwV$k-e;R#4~AUgqFzTK%v4umsvfZjg~5PI!)3%s(R^v%A@-_ zznSgsn=)+3vSX~CsJkLInfaTyx<Ew9fk)pN7(VUJKX^rhgSjE2`QiEU|L^Vpx-U>= zXh`#%RLH;(=c1w)a%!{vaSjFsmj#Vl8yuy-9F)*Gz$SB$_0B==KM7_nPAW$ZISDv% zr8LMCC1|c`a?)r!yFpefLH~+_tjU4EjJC-Ie0qmMcktO3@c&BWn0HWPPa`Lb;|UJo zM8#Vjj7eP)%6%N-Gh2Ts9`s-l>Q+%W*&!0B<UP^U!!W2TL?P5e_LFT9lVy91+pdY$ zCm4*H{<Q6JH=NSADbV%`&sOK1E6k-@+!k?PNGUO#)i1VS_JyJ?Qno#23!h(Doxy*r z-_H5C!GRYN?8ZlO4D^m2cG_s6!#O$m^hTvMJgbjMZ3tV#yu0&j^7)PBEJBU02RO}G z%AFJv<s3aCd?NgAc&t#6Q85v`#c9sT+!A<5U1{<P?=5P(1b-fpNHQ{PkMPh@z9!T! ztS?aA;e5ngMX1x+NPXu7CeMvaLM{nia=Vllr2JB6<!loVDdov0Z>3~!Vz->4qo94F z^~u~Pho2Zfk$$51DVl3;l0)H=AHk0_f=*?5Nj?>LYVwpVb%v2+=;B#HcULS75j5?O za$37^ThRKz_7%b=rL!#0Hj8<BFI~H6>Sd`6@r>`8>tB@Y61+QA&TsmK@Jpv(y1$tI zg85514{w`j^YM;!4awOO>=Nmc<r3>>_$-sW+%0)L#K7F}^^E5;`Df;bMrdu*su7)d zifgLZRJEznTKXX?S1nt0Zxwgw=g`w3{VU^wYuCAj8V5gK$$4ek>d;kl16~J92dA&J zUeUfPeMNqV{gS%KznyH6hkXwFJ1+@burdDeo?{B!$&#BZbQH6BSGScmnjTTxc=l#Z znbz(Ze`R-f+pb!<(9+r1%Q$><(B~;>_Uw;m6#ke~;>Y=1WBK&YbzS#F-fKS6lwM}y zv(qp&CVSPi>C+-sd#nyz?f!bN*}AvO=Pujpx;yfBBcJ1OFZCF8_w@dAtaG0S-weLH zoNKw5udUVBb$Y9^mr5_(?NYu!Z~40!^Y+Cx%=5`#ey?)x^ItNx`hP?I=Kfm#)tx7T zZ8MJw+ijk65;jLo3-fJc^^f2F80Z{(*ui;e;?%_H4_hYgylAuds#~uce^2JIn8zxQ zbC2a7Tdc;Wrl{ua`^xv-tRUZ=%QTl+`&j!NKYQlvqO;d#n$EU0yl(V-_Vd}-XW!qR z5LFSSvu(w;9jTt1o<uE+e79{+UTc(P<kE=HsMyVJYv-<2y|#Pp?&X5Zclu5CTRrbu z`r!>9x1HR!a=YY)r#IMcuiKWptu`?|Nj!b~(RGL4P2M}Xc2{lhUt4zG_M3dGWus*4 zinkPrScO^Ln{{r^JNw$BU#7j(Pmee+aQ=qz<88vHHIHP@4W513JzYF~XL0lAj_ywH zrqg=U?eu0#AG>>E?-Q?*uZ6J>t1f+=JA3o&LvL5yUjNSQUFN&?JM+(&%-5V3X<U5a z@CoM!fln{mbI+e@kbQqmoxihxWBS(T%KFLr6T{bD->`jY`Ks@U@5SxD*%i%yZ1>p4 z{GQRh$oMnn2ezNse%k+V{=E3s`_A>s|5N@~{lCbtyg`*Qow>8ou`!zI_m8R?#r=Ku zR<~2XZMyl^v1h?fM^VTArnOB^53W_rIPqq}TE*|rXPldOE|ae{pzg)B3+#!{61xxA z@u;`9w}!Xb_Z4(RNL@6XZd!ivj&t#SmtQW^T<*<IH}tn$uj^&1Ww%N%%jTB(E|XpM z$Mn)7&s>d|8{wJbEn}&twazRrXW#ulZ$Gy`n)`_Rakcmi{S&bTl?%@u`qH{wY^9!% zj@2{=pAJ8ZDIXjKv{s1qsD2VISMB4fJS2N4T}l6<*~N|<&M{hYexE$Fe5)qCnYhQa zQst=eQk|zeThv!h*yF?Nb9c7YJh5423b{tMR(lPbCrzAC=(I}Fck;IMSLx5v%1*CI zZQb<mXkPbreYvUZC$l$CSi>5r%eBpE*^7`Ujq6(Mo6=Lm)Apb3JY_j;^*rsfZ_jK# zb$f#OocHtpzZE(p^l;L)rET8fy0Q_^_O8jY++<T&d7JC8D64i`7~2|~9bMbBa<BTY zej7f0UEN-_D&OqJxPzyf4sK=4e$5@q^|ZD2^_}Z)WBnuaBkylb{M9yB+D^2yYn{k# z@#&)VH#F{4Je6+GKed16ohe&Zu0FCe<$T(uw3pBB>CTJho3?8Dv*~<!yf<%c;k_TW z<?YvRuDA84pVhd%yLp@Q&c*v}cI-)Ae)8N|Yx|HTAzwm%g_ym&ba!jw^&fc`-$_^R z`DXIw>h0(~-~Za3+;=;aG1T{J+SSzGp1(JLw_n$}>gA7VzjI^P`sEhBJ<fcI`7gUN z-?r;JuUwVAWE&FHe@*+fJKu5negEdZFW2QY;|=YdbL^3FflK9+m)|+>f0yI6x>eRw zz2+hNW%)AMbXol@r&*t7xy=rq^*cH#dYAthyNlOe-K+ZJep{Y@j)_fVrPt?>lbN?( zHeH^a{^@+r+^KVG>-2u!Nw~deTlMqgbN93O^WX0hD0z6v^?&zs>pj-zVm|FC`LyZw zQ{nI(;j`9T#pk{%d1do&@@4<yyXWklwQgrj_Re=zd$(4neSaxE&-~ooFMBILiv7QK zb)Kc|-Mycy^1mPb{`J1t-MS+6zvoWxzFDsRZtWibJ@H>0-a6iNzT1A)KKkFC8}IMb zXB{YC@cH5$_wQ`2&FBA3{Myg&%-TP*|8yH~+v|B2c9xcp?2G=s{IlHoe9C#XdE);T z{X4x*Tui_2$JN)@&t05xapCk&(<krGty%i_)q&TN>u(=BbWCpF$qHk`xG!P9-yST# zQ!e{{@qK>(Cp8N78viqXZu}a2VfpXLl9MO@XJMZ4`A@R3?rjDJ28CpgAYTTCDpdxC zhGqtapZ^&c8eTFmlo~KFyh>nTu$sZZAf7)d{-_%R1M_iD7srr_TP;&+eWtL=9JfFJ z`Oe+q2Xh~BvWx9=W-~0FCAnt0q<War)Wjt^$4d-0Zg|3<v*X^hkZUL6O1u|_Epg&m z?9!-`vpSVmd9rTR#^jWSf;}=-*6(*#-}}|<lzVpH|9{_p|J(PwX8ZgP=?AynzbpFm za_z;-nl~>VSnhtlDTi@2OB-jI^!+#Il-{ir^6^ctxo>B5%6}2NGuMJw4M%3ZF^TJW zneJ6=%$XYfciKgX?X&mzo}2OZsL8wg2N?ai&cCthyD#35A$>j6peehK>HOMxhi@65 z<!EN>^p$_mzx$$=U6G#OU#n_m@zwlB5ixETnNQZ-{$&!W9jvgRAx+`WY5(w24(W<8 z{bib$Pwjeq)#Ba0<|A$2GhT6SJ`=pM>z<#!^s2gNH?PFBxz4<|=J<@eR}VfnWewT) z%b-s~+RJ6WNxzH9vU4-MTptRpa*j9reWrZJ>hB3+xr;XZF)sS_?Xm51=K|gO+$#n= z;TlVdZ+}vWi&8WGej-uk`&xx{CEWJkdLGJrUbyBgd#w7dmGl0+i<|T+Tj0IV@z`%H z_pATr+`sAJ9xi{8&w7zY52yGZ=EV)%3TsxJF!Xz*WA(66F~|H!a-qw@Z{I#nR_8Zq zzOpSU=dz53plszk9Uj+%<}wO0*EZZ<`~QNJaGmGQ>`M{{417u$0$F^IA7Q!cuwR71 zVb&{`4Uvul2By3W-5N?=*G&x5IOoYfs8{9aD3fHkYR<H*@@&N4eo^l)Yi5Q^{+WAY zMudn8Cxh`KKecVUk{yKv1r8`RCMqQPsqA|-&*0PX2cpLkc~2?{y)II4yV=RZqr|P2 zWngmBV(D9hqE9wk7Bsz*J~-_`nvZaRKI_EP)1o5VpU59F@zrA&5!3Z~&>PDyXn*$P ziF@BK-txQj`-R)1%VG&zy=EL}P!f;{^PFwWT%W6aa9Y{9=g&U>$!tBxrym$7)TE)r zs@z@9;?5kn({g*z-)~iurV8qbOj|VX{qj0bGuKkvHM=UM)_oK1VB$$zb71pY!JAqW zcRqDY3Fo;K{l4J64`+Q4*K(`h9DKd)M~+w;ulSu|-2Ux}(BDE?=l=ZEgojG=dZIlu zuSOoLt$SmZDKtN4@(ziffL)J;-|7FDuy5(hGuf*T=eATly{;^y<CXev5^Klu{Tf@J z>Gm$w-#^3QGH2h~n?C0>I~VubH?Tx3U2T1kMWvg?&wB0JyGzzrO<pCY&-s2|-{u6{ z?=q_wK3tZ#`smNp-S4-HYlR-2?CGk~`HMMrfonon?xWqY{(dtrempHQ^~$A7_s-tC l`??_g4xe@WxBsRLm5+N8cgFGTVPIfj@O1TaS?83{1OT16`XB%R literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/248.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/248.png new file mode 100644 index 0000000000000000000000000000000000000000..b2cb6d37d7b7957319f3398bdb7ddf50789007dd GIT binary patch literal 3105 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/249.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/249.png new file mode 100644 index 0000000000000000000000000000000000000000..2d785b7516a7e614ab9928437b520a57d6c700e3 GIT binary patch literal 2972 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 I=akR{0PVMo*#H0l literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/250.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/250.png new file mode 100644 index 0000000000000000000000000000000000000000..0a68f35de952e90fac0b33b40674f3fb9c870c29 GIT binary patch literal 323 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWpTD}#LC&K!9L28O4eE{-7<y;m<8dNDgn9R2v;eCAyiW-U%mGe*Oe zZ+sG$FG&B$_w5nm8eQ9lgt=bUoSuq-I%~}zWGSiaK78iJ5})saN6H)GKi(5wH(&YG zlAxf89sN#{!3=A}a&P3Hd+p4ZEx$+nH_x-<PwJxCrJ2&d3Qm7(y(%c^n25Lz|K{Kx zHn9bn=~sG^^?#}ex*Pv=3J4QyobKAc^7c{p6My+8yY{bWWp2CvsA2V+rfFwS#x$SY z<bB}9@!7w1=hgMC5cnW*XjN?W?fsQk9=%pN8Xf)jacSV*NB#-7ciKzlvz(ijSugzI b|LY&(`wzNv*PG7<1s8*-tDnm{r-UW|pL&I{ literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/251.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/251.png new file mode 100644 index 0000000000000000000000000000000000000000..e4fe22ffc7676114a7787ed83693d5b87dc35f50 GIT binary patch literal 3025 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 OVDNPHb6Mw<&;$TXSE$DT literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/252.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/252.png new file mode 100644 index 0000000000000000000000000000000000000000..01c5ab21dab57f18048130d4eb87de81642bf814 GIT binary patch literal 248 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U$Su2%GxN2;oKs28LOlE{-7<y~!nS_WYjT$kxy)*48KGGw<N1Lu+_G z9a?k6ct&sQ#K!5(od+G8+d&w_ZtQKHxZt2pi%7u+Bg2r2PNN8oVnYK11A`Ub8;nbG zs?UCSeLntF%%7LP<J&lQ-#7U4^ZWdV=lB2T5iZ-G_~VQ0{)H|(XEZi8I!5;ND(1g5 xw)lN@x&0NxA3x^iAJK4TWS)PgZT~?Awq?qy3y*(qU|?Wi@O1TaS?83{1OUieXOjQ` literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/253.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/253.png new file mode 100644 index 0000000000000000000000000000000000000000..dfd4a6c385127dc422fc54075be8d370df532663 GIT binary patch literal 390 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWpTr<lBpwZa+(21X4}7sn8b-nAFcdNC!69Q(NcMQ?@vy1i23L93%u zR^Pd@$~BkkB{PqaM}~4+H;<vZ>zXrX1YDNila!ske*0COBO=S6-IP6dxSv@)<hB3P zn~M_+$~cS^HJ5IQJ$d$X%WL(y`O$3b)y-KJDbi9;@0mp0@XpRCTEt=3BCEsmz)NF~ z-;SJD64Rt*-ng2_>Pbm0U3c))Gou@Y3e%e_YS!KOxy2%sCHLwlPH)ammK37^9VZj3 z@Uo>TfB&tRR=>XIPi%}<qkurSqlHcIniX6pv${U5c=h=atK=-L*<Q0A`-z3G`*-BT zqE=m5zBr>LuXfi&-a7Rl=to(B`=(v4v0G}I_aCxde7<ed;mYr?&OhME*(FxJ*7?%e v+GF3I+}ChWxv#(B+Xmn0dTy34pO`0o+_Je`W9I<|1_lOCS3j3^P6<r_RVb!P literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/254.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/254.png new file mode 100644 index 0000000000000000000000000000000000000000..caed18859343cb4313b1df209d525e8c8f5b461c GIT binary patch literal 380 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTU`n2vyAa)~no1EZX$i(`mI@75{%y$%P69IMaVbj6<avZiE{w?V6g z$dU`52kjglHuXOc=7^Mu{bk_felTDTvublA-%YD4D;FL~5EqV}v*nJorsd_QA8Pj( z?<lb2{jsPq%am~~>o=SAVejuJD6(uzH7t}->ub)9<WQWFpv%GZQa(Jib?UO1-0ci| zw%p#cQ*Z6G8uiC%cHw^gi#ghy8LT!-PIq1SMIutdi`CMA^_&<t!?MbAo)VV=8$%~w zcVLtGX~%H0=%my)_HssHjY%wZ_hP4V8l1W4p=HomC{lRiHKW1391B^VD-WJqTkiOy zV_9L-c9oss{i9jxg^hVHg61Dv`)A4TFP^8u<$|Oes>@r~&leVc&%W%@d&#%2{%Vzm hy$QPFG2@ui3+dCAo(9K%=77SN!PC{xWt~$(698N1mL~uJ literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/255.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/255.png new file mode 100644 index 0000000000000000000000000000000000000000..0f43c74c1468360f70dc74e03494ba15ea334421 GIT binary patch literal 635 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWpTr!Het+o$gg3{0|~E{-7<y>qX`djuy+9RK*<^7f^(XLl|QT2tiI zB(TJB*3u(-T%2MaDkduz)Qi8?H@tKzRZN)Ex$}*h&>OX$4u{TN9Uqhy&DPNGx_csO z*Ob4Rw`b1CE`F^P@+HT#c3s@P&(-$ND=YZjZ+t59pJf`>`pLY!x6W>FU%^kmo)>3? zmd0>va20q-Kg>`%a8z4Jd2wN6+SP)YMvna#&u_oTVH#`LG;xYfaQM<wvSJO>nl#D` z9kSbxTv%-xsg^(YfNwRU@3yr&4@!%8?&7U`X6JU}uI9tfr+2Se7h-f{>GQb@XU_Wg z`G5Ot6GNF*4D04;^w0aRwD-}fsa!kCb~QX!KXYUG+&v$!{=0jgZJqe@LP@3-`&%}a zcvRGU-sF?Iv*~zj)g3i$>*KO6!MB!*r#VSqHMTIVtNN|_&VHuq)}*4q7H(ZBy}5T= zly4mCelIq2BhNKX%{)n_oNq0eIsK2mo!G5ot|_r2)?-DIed)chKc3FVI*+!xI{(vA zJ{ot7ZTU+3Z^3O`Omi&PJbN1;$-Go=*5133fecICJPNmJxO@%iaDJ|0w|nxY2`fXM zB&I9o-1k2Gq_n1U(wYZJ#}BIt|EaJ#suyjcT=m4k?*06k5@yyLtE!5f7+ZE}h5kAm z;q-064v!`KV~-#E73Jr5FTp176lY8e<4yk;3sn27{gnA9mHlcm3(7P+e6jxeru9zi tMPFoJ-k5uzZ;h~QP4Db*UcD>w(gIs&vCi3N%)r3F;OXk;vd$@?2>`8;9(4c! literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/256.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/256.png new file mode 100644 index 0000000000000000000000000000000000000000..89967cbd6b9def6deb79e9c11451af80277453cb GIT binary patch literal 306 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{W>=fr=e!)^)=@i7#Oa2x;Tbd^d_eyB>Xt<z<j`k+gVaVLL%Z+aYnMi z<Nwc{bu=X2M-~Z`7|!mqJS-br%~n~c$#zZmv}9eZrRRl&CcS^>l@BP~+d41t{^9#P zm)?2uJc@lS`R_A_fXR&1W0&nKL{CVpG<x!h)5xa!@2tePSM=EaA24WJduJ7of1aFr z#*g}%h=70(|96|e|0i!-^rgN&_Ur$4>F__|A|gjtw>LIEv_H6@;{QL}=KlHohv)7t zm#|Ptkx;T$H{4VIf8K)Z_JpF_5~rPn#W%mWm$<NX=N4`*2FG=QZ6%*3Co(WFFnGH9 KxvX<aXaWF_1bm|a literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/257.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/257.png new file mode 100644 index 0000000000000000000000000000000000000000..ff347ae1bfe101580f9914e57207b7ff7cbc2558 GIT binary patch literal 3326 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 TCHr>_3=9mOu6{1-oD!M<i<3Y1 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/258.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/258.png new file mode 100644 index 0000000000000000000000000000000000000000..1f6c7665207e2a4162f4766f34938ea87539d478 GIT binary patch literal 471 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxQWG&Z``pA42+eYE{-7<y=yP+^->OGXnXj5j-+sJq49<&iN&iQ zm}%zlXe|*a6bux(a?Q>0#$HxO0UkC-Z+!=8-O|?FZJRa-x#*+>_6S{w=#i+ORG9YI zQmm;pcG6G12by+q8*dn|xm<g{fVXk}<jLt~()IrYYrp+zWqefD`1byCmG$y}TN!n> zANh5^xK8f*gs5=dZei_@3r{R?+BxZSg|zi$<~=P<R}XUe>OHw&`qd?Ev!K8B6wSy? zjR}Tcq6e2HPW7-&d6`lB^hV)|evURNrHbpOOFkzC`Av&GuO~gt>A;UQ3g7q2oB!&5 zxR#OWtv&-wYl`L-zxv)#yO<s37I!ieb5u_DA7uGmt@g87pJ7MT#(+IZ|LgbVM94(m z-E|@R^2`qvb{zrsRqkq{hYeS}N-j)N-LfWF<N02j$mJYg+d9{pCAsu;1uzKzaG3a^ z^x3p!JEbQq=oa1m-k>3IYDVNfl{qylVt(^nKh}NZnjVKsY<vL2<yjjt=bcw#I%O{M bfcMPlh66XAUfRdNz`)??>gTe~DWM4f^cKk} literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/260.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/260.png new file mode 100644 index 0000000000000000000000000000000000000000..fd801209b2883c1cd8c4f0dfe0c6c437d0f668a5 GIT binary patch literal 280 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWo^qnhx;3odR93=F$GT^vI!df#5y$ak<n;P}V<%x*{FwG-TwCMG=Q z)H$Z-)_wN0N6x*2$q_j@9y&s7!o6)r7HIvwexW1qQrpS=olo?>pM0uP>~TO`$vt*W z+(IUu&=Z^+L@PK}+&?5GX}A2~E$4V9TjtjcCE_xS$;?Z3tUpnmU>Wc^QSI;&j(H-F zABFpA`S8xTpE$YpyYk_^u3eW@J9W9;+{GKpn5~}9D9B!Z!RADc>HLFQEnAsN&KAt; j;Hp0*|L?GU%Y2p~-O2u?o8uW67#KWV{an^LB{Ts5&HQWC literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/261.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/261.png new file mode 100644 index 0000000000000000000000000000000000000000..a9b1bdf47de6952e73435c96e09cce2f2f916ad7 GIT binary patch literal 161 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxS`>nSob{)3=GbmE{-7<y~%(6|NmdlCi5UZ*!1v!Tk8VP$E-dn zFQrT?Dx}Xe`4^r3znv%i=|?Ta^AALM)=Ny&o|yjO0{7#<=FEHphOI(;&Ff^Ri!v}U OFnGH9xvX<aXaWH3lQvuc literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/262.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/262.png new file mode 100644 index 0000000000000000000000000000000000000000..24b6a4bca6540e3c9f2bfb13dbf91cd37186842a GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U(!$h!%@}d3g~71B0oji(`mIZ*oe&jQ{f)*(0nD_9>o<=Ujbf{@QBE zx@lV<T{?B3CAiJWAnU`5gNxbvrEC_kF*7W?%TY5oS!4+V0|SGntDnm{r-UW|TbwR| literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/263.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/263.png new file mode 100644 index 0000000000000000000000000000000000000000..5f3008748d97f1c338c201757243c40b0673ecf5 GIT binary patch literal 3044 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/264.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/264.png new file mode 100644 index 0000000000000000000000000000000000000000..d349a7da20100f79192ffe6d564a9c378aba88f3 GIT binary patch literal 720 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{W_;8BeSyN2JT!22BsoU7sn8b-ldbGJuVlD9Jl{}_loKAv$`7(op#Px zar5Y<Q`3J2M5?%nXwB@fRjKSfa^i>(Px#xV+;%grDAjmqENq|Wwc@~=RYA*A#C4^t z)L-7oDc&V_=w-d|#$S8q-v2)5yycwFqBp9}9j@Q=<%z^BzBR{Vi#gN(i@)E0#yn8; z#i4go9)INUW}4bq#N%4USNrzhr0AyOw#t|GCM->wb!f}>7fo+#wyk;cZgqu>-p@rB z-gaj1oVi^xbM3^5^XfKgaAbViJmJjI-$M4AbYEGg8_(OIddX)=+8zEC6TA)Qcr4fT zJXlg@7;G0%+0wUR8k^tSuV?&a)@@rHd|Qx(->bFTV5)P-lr4HPmp6tq>Bpu2sGJ^u z`NCnzJ=)iTf9w+6oSY-~=Y_LLw$rhf#@QSHawVLQ`jj3X#2H#?tuZlL(l)5;uA%C^ zrGCxF0vt<URL`1!?R<X9LhUJCnPKg>4Ek4ZuogYynr$fS?_9pN^W-<b{hC*Qf8_~F zE84PpqT}riJ02c7@Nkl`!6HW`L5=gC54W7_+`3@S$xhF}i6Lv1-Z)wEUbNrw>5a{1 zx%%V3Zt7|&wcK9%(q-m5gHH?Yu6J1Ke=unF;e+?2zm(R0zAVlvo|d=BPH)1-MP6z; z$%|Y%jW}{+4Z|PjN?5%rINw#fTf@R>^`f0Rb6cvvOB&jJ@s66fOE9{YaW}h9$^M^P zR4#CzyEnDL(Dk&7tEv52v56Wi8WE+>EVh@OT2UF;o_%evjz+_qO?&0D_c7M+32afE zCYfv)>8x8)@=Z_rh3qPIlSmhll%NuI?u@&O7r#n1=G*>x{lW*udm3&?q#QFUa=%@1 h=i)E_13SM<cW|Da^K{!mb_NCp22WQ%mvv4FO#lO<PZj_G literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/265.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/265.png new file mode 100644 index 0000000000000000000000000000000000000000..daf195c20648708591b3f508db4b32c8fbf221ba GIT binary patch literal 286 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxS`>nSob{)3=9W6T^vI!df!g8<zoyKY2E+(+4Gq$Y)d=5d{ztW z<;{rfeA{6gFlhs$*aA0w<ulSYTa(h9OoW!(THfEe)1AS)u}1LKDdq=1JPfxPG(Bmo z75o)9bKByZ_CX9%r;^M4BbFBCHJ&nV+ttP3EgUS^9v~y8=GHKAU0OS%`RNxs7(2Jd zZ1g#-X2;aPsq^@A<La#?cN*Tl{Uo<TrK?oR_=EGalP`~UPf1Jj+afc!a$`<b;71{O ojrYFwAIeqkdcL>6*WWZ>CC$7t^{q?~0|Nttr>mdKI;Vst0G8-*4FCWD literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/266.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/266.png new file mode 100644 index 0000000000000000000000000000000000000000..2cb51a16dd4ca57a012b5ac250a5ad66a6179a4a GIT binary patch literal 284 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{W>=fr=j|!{KgXu3=I1`T^vI!df!e~<U8ab;Cg>jn-i;0<15wzQPI*E zw&0r+?lA;*yA@4R3thANNMZqp#=_mQc?Z_5R7m_XfBJlLN!J<?w;3lTF5G8f-~INM z_6^&23q(F=Je(S?^r&>ts#Pz3&)cN4_dsJf*Na_z=lL4ale2X*XC?J4%#=E0P%`<b z;k78uE++;?mGi}h$97yhveGHrH}-l;<S_%DExFy-W~q4YTYY1`zn|#)=iRrTatR+b oiZkp@^Aqkm{Otdw$4&N|E_PlmNK9;EU|?YIboFyt=akR{0N9vv`2YX_ literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/267.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/267.png new file mode 100644 index 0000000000000000000000000000000000000000..046aa3a6d090196eaed6a48e5960efde008e34cb GIT binary patch literal 165 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxQW8+S(CXL7#Q3=T^vI!dXsfxOMlxl^D+qQ^2nM>ux^;4{qf?F z1UGSU@mWi_+}POIW=WiR^yrZjcd?|z^*NQE(&F>?eD4%{#&@v9<_ixS1EU_VK>37o RX$%Yu44$rjF6*2UngFz9FcSa( literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/268.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/268.png new file mode 100644 index 0000000000000000000000000000000000000000..068b38ef54a682cc4659f052a7666f74776f982e GIT binary patch literal 539 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWpTD}(Cvty|YJFfi`*ba4!^=-oPHZ+38@NZb71H_euqn8ld5Daq>e zn#nHlX7iR+@eYWNVJQst_|M?T6uaB9qk3tco~nh$%24)|6EqbzcB`5D8YmuA+q=;| z|6P0YmH-y+L$<dYzuP>2I_L02v3L8waVk7fSitZq>65cU!pgrfoGBug(k%Y?e<&!K z(m#Fuft{OeVxk@`h*`FAU*G-r|Lu2J*`77F_IY&4=-flTqv84!QYBa4Tr%bKMSZdV z9L|bUHX7c^`@SP==jzw1-8<z!1qSQA>hVrBJL>KIIO!47zU3D)!!xezS87Sv9;I5C zE@}BoR&c99-o3-pJd)R5*ISEyPXA$Kd4ADvF?ZRA-)1{2P2^}xOt@{fH?F_L%GYlG z@ol+}K3Y6h=9+p(XPv}`$FZuf$|UcvV_YCts<Aa)!#^%P>$k1&J%737K{-qtBX|^9 z)`oTGn005);!wOG+In}Z={Ig>8P7!(Z`qF3^+sRuv-+me8D(+oisR=aJ@ZSfYK(nN zd>033OgNdc;oCQjwQDZizh!mpABXG2x$!<XuDv|XTu?ICzqq(Ytm$*x!RC}n&yF2? v87_E5cD28Bf763S`&R3)F!8<N{L3~aeWUN|GVMPM3=9mOu6{1-oD!M<qbBO7 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/269.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/269.png new file mode 100644 index 0000000000000000000000000000000000000000..67e1e9aac9bbf52bb14080ed3a16899955473786 GIT binary patch literal 3169 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 KelF{r5}E*DZu6S} literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/27.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/27.png new file mode 100644 index 0000000000000000000000000000000000000000..8ddf0c22c9600cb1243f11131e9a660ceab65fb4 GIT binary patch literal 558 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U)OTuc>Njq0A`;2F44XE{-7<y<4yNdt5G*IR5c_{yEF-ma~JWr>&S+ zKVj2-1x|0(iLxPcoTl?l{K0PP6Y}DLtB2y33#!6`Zv<SX^EJB6<I-Qo``^U;sQp?G z0dFB+k&8R)<&x};RO<g6dj9S2hYyb)o<6L6AyAU-LiQG$bt{$|{*}6IJ6FcX`se(v zF3p{HM0vP>G&8zQTUL;_Ht^+(BPpIopU-M?a1dNGOUypu>U)oa*6X`h`$tvOYPNpX zZ@U;HIMw&h>*tBry>Dw-@%Fq|6L2~p{=s<9gCl#ldxqY2=5Z5l-)t<vb*hb3Va_Cm z^HB#TN&CM|pV(U4d}YE4bEAmI3!<hUNh@csdq1hh;#0;3$Na@z_rAvb+LtciG(qZp zq<3Xj{xOkki<3Xic_w5wK7V__)wuLt%|6)|DUQnzF}%|FVZ?Ir`IeX&I>9Wf|M3~= ze)uQ2=Ew}qPa;}Q4|)Da@P2q6b|=oJQoG2ca>_l)qkDI#a);g3WenecAf?Exbzb!G zfa-g^ch)l3$eITw)N4z#3OtKD+WKC<d&jq)Ac^%?FNa?};#<tPmH*g}ADPZ(8oo8P z+g?ayo!;~)Q$8WxPITj=UL_HSx7>f@dQOyT`)e;dXL#_%5q{T%C4CI@3uisbJ$A;H Qfq{X+)78&qol`;+0I_ZNKL7v# literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/290.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/290.png new file mode 100644 index 0000000000000000000000000000000000000000..38484c45c1ad4cf584589633c91b1604ea9fe3b2 GIT binary patch literal 3202 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/291.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/291.png new file mode 100644 index 0000000000000000000000000000000000000000..46564ddbb54c11f0424a8fa359f9437d383c60e7 GIT binary patch literal 3091 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/297.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/297.png new file mode 100644 index 0000000000000000000000000000000000000000..7b9bf9de6991ac980dd189bea4cb6d03bb2a07d8 GIT binary patch literal 260 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxQWb>ukU*p7#Nm$x;Tbd^d^_Q+4FloBbS7vq{P3&l~WkiB$}tr zoT%vbfw$DBRr1V<69)uN{cJjzbXtfl&`(S9%#jlZCMf;rI#_nqq^*AWIpdJ~w+}2( zI^Yta&Uk=>J6U&LJddaOcB3gC>_Isi%*;U&@xKop+u(D3zE;~Ji8BWd94L4v`_!@F zYC=}i!gPbjQ<KX=PGzrXNWD5&B5KCz1B<i@CabX-D~UT@VKM2pVNh7DGWGQt&#ep$ O3=E#GelF{r5}E-2Eo12b literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/298.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/298.png new file mode 100644 index 0000000000000000000000000000000000000000..8d9ee26cb801accac4a0c0ccb028e04f857f8511 GIT binary patch literal 2877 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`(#+;1OBOz`!jG!i)^F z=14FwaQSCCI|l@0<d@_ZXXF<scse^P6cpvBW#*(ZFjU-{8=M{z{8G&J`@Jtt-W?57 zyz_%ZITV7Nx*J3$s%a^53V3xWF1#rkcu>&Ql|}l*f)3`szJ?yrKpmbgt{d!66c_b1 zFYf3%_4{G<;_PdCzCSzrZ|ilt=W9OC-F%+?00+-B592`72A-8pa)J+<j~_Yq@QI#7 zBbQ16g8;|o6vg7d%8U#@>TGPZl@74)V_>Yf%+0}Yz<2fm1DW>!{I8s5#xORtFditG zXyMJ#Aj05K)#tQ~!NHBOVeSmIV1@-O3<;-`lO8h!=rSaz*FU?=&@lJM%xXr4=qZY# zj0+|*C<v!^xG-c`Gpsou*5%3&VZb1g=6%GH;f5}QfoFJ{2g8Q#3<u5$aBpJZ;9)Qb z>}b_wV5w$EIH9au!O$|3LB#D-xXnlHbpp?|7#J#MZnD{_V(Ho#&7oV!9v-eIr*Y0x zTwBB}ktuPOgWjRanUY40Cj1XRpJ8BFFi}+SLG$Oo73cWYojYgNw~a4e_tk#3|4K<o zkN-VAzq-JIf#G3E-Nk=8`kSR1tl1iJ|1DB|$C44py`c1aciJX?hph|?Ufv4-TmT9i z*M$>y-n{wr$&(viN1UP)Pleb1OFtBUApCF6&pMkw-@iWC_eiDDD7Mj^<;R0VXTJC> zl{r6&vnA=yvwrtK+qwV$k-e;R#4~AUgqFzTK%v4umsvfZjg~5PI!)3%s(R^v%A@-_ zznSgsn=)+3vSX~CsJkLInfaTyx<Ew9fk)pN7(VUJKX^rhgSjE2`QiEU|L^Vpx-U>= zXh`#%RLH;(=c1w)a%!{vaSjFsmj#Vl8yuy-9F)*Gz$SB$_0B==KM7_nPAW$ZISDv% zr8LMCC1|c`a?)r!yFpefLH~+_tjU4EjJC-Ie0qmMcktO3@c&BWn0HWPPa`Lb;|UJo zM8#Vjj7eP)%6%N-Gh2Ts9`s-l>Q+%W*&!0B<UP^U!!W2TL?P5e_LFT9lVy91+pdY$ zCm4*H{<Q6JH=NSADbV%`&sOK1E6k-@+!k?PNGUO#)i1VS_JyJ?Qno#23!h(Doxy*r z-_H5C!GRYN?8ZlO4D^m2cG_s6!#O$m^hTvMJgbjMZ3tV#yu0&j^7)PBEJBU02RO}G z%AFJv<s3aCd?NgAc&t#6Q85v`#c9sT+!A<5U1{<P?=5P(1b-fpNHQ{PkMPh@z9!T! ztS?aA;e5ngMX1x+NPXu7CeMvaLM{nia=Vllr2JB6<!loVDdov0Z>3~!Vz->4qo94F z^~u~Pho2Zfk$$51DVl3;l0)H=AHk0_f=*?5Nj?>LYVwpVb%v2+=;B#HcULS75j5?O za$37^ThRKz_7%b=rL!#0Hj8<BFI~H6>Sd`6@r>`8>tB@Y61+QA&TsmK@Jpv(y1$tI zg85514{w`j^YM;!4awOO>=Nmc<r3>>_$-sW+%0)L#K7F}^^E5;`Df;bMrdu*su7)d zifgLZRJEznTKXX?S1nt0Zxwgw=g`w3{VU^wYuCAj8V5gK$$4ek>d;kl16~J92dA&J zUeUfPeMNqV{gS%KznyH6hkXwFJ1+@burdDeo?{B!$&#BZbQH6BSGScmnjTTxc=l#Z znbz(Ze`R-f+pb!<(9+r1%Q$><(B~;>_Uw;m6#ke~;>Y=1WBK&YbzS#F-fKS6lwM}y zv(qp&CVSPi>C+-sd#nyz?f!bN*}AvO=Pujpx;yfBBcJ1OFZCF8_w@dAtaG0S-weLH zoNKw5udUVBb$Y9^mr5_(?NYu!Z~40!^Y+Cx%=5`#ey?)x^ItNx`hP?I=Kfm#)tx7T zZ8MJw+ijk65;jLo3-fJc^^f2F80Z{(*ui;e;?%_H4_hYgylAuds#~uce^2JIn8zxQ zbC2a7Tdc;Wrl{ua`^xv-tRUZ=%QTl+`&j!NKYQlvqO;d#n$EU0yl(V-_Vd}-XW!qR z5LFSSvu(w;9jTt1o<uE+e79{+UTc(P<kE=HsMyVJYv-<2y|#Pp?&X5Zclu5CTRrbu z`r!>9x1HR!a=YY)r#IMcuiKWptu`?|Nj!b~(RGL4P2M}Xc2{lhUt4zG_M3dGWus*4 zinkPrScO^Ln{{r^JNw$BU#7j(Pmee+aQ=qz<88vHHIHP@4W513JzYF~XL0lAj_ywH zrqg=U?eu0#AG>>E?-Q?*uZ6J>t1f+=JA3o&LvL5yUjNSQUFN&?JM+(&%-5V3X<U5a z@CoM!fln{mbI+e@kbQqmoxihxWBS(T%KFLr6T{bD->`jY`Ks@U@5SxD*%i%yZ1>p4 z{GQRh$oMnn2ezNse%k+V{=E3s`_A>s|5N@~{lCbtyg`*Qow>8ou`!zI_m8R?#r=Ku zR<~2XZMyl^v1h?fM^VTArnOB^53W_rIPqq}TE*|rXPldOE|ae{pzg)B3+#!{61xxA z@u;`9w}!Xb_Z4(RNL@6XZd!ivj&t#SmtQW^T<*<IH}tn$uj^&1Ww%N%%jTB(E|XpM z$Mn)7&s>d|8{wJbEn}&twazRrXW#ulZ$Gy`n)`_Rakcmi{S&bTl?%@u`qH{wY^9!% zj@2{=pAJ8ZDIXjKv{s1qsD2VISMB4fJS2N4T}l6<*~N|<&M{hYexE$Fe5)qCnYhQa zQst=eQk|zeThv!h*yF?Nb9c7YJh5423b{tMR(lPbCrzAC=(I}Fck;IMSLx5v%1*CI zZQb<mXkPbreYvUZC$l$CSi>5r%eBpE*^7`Ujq6(Mo6=Lm)Apb3JY_j;^*rsfZ_jK# zb$f#OocHtpzZE(p^l;L)rET8fy0Q_^_O8jY++<T&d7JC8D64i`7~2|~9bMbBa<BTY zej7f0UEN-_D&OqJxPzyf4sK=4e$5@q^|ZD2^_}Z)WBnuaBkylb{M9yB+D^2yYn{k# z@#&)VH#F{4Je6+GKed16ohe&Zu0FCe<$T(uw3pBB>CTJho3?8Dv*~<!yf<%c;k_TW z<?YvRuDA84pVhd%yLp@Q&c*v}cI-)Ae)8N|Yx|HTAzwm%g_ym&ba!jw^&fc`-$_^R z`DXIw>h0(~-~Za3+;=;aG1T{J+SSzGp1(JLw_n$}>gA7VzjI^P`sEhBJ<fcI`7gUN z-?r;JuUwVAWE&FHe@*+fJKu5negEdZFW2QY;|=YdbL^3FflK9+m)|+>f0yI6x>eRw zz2+hNW%)AMbXol@r&*t7xy=rq^*cH#dYAthyNlOe-K+ZJep{Y@j)_fVrPt?>lbN?( zHeH^a{^@+r+^KVG>-2u!Nw~deTlMqgbN93O^WX0hD0z6v^?&zs>pj-zVm|FC`LyZw zQ{nI(;j`9T#pk{%d1do&@@4<yyXWklwQgrj_Re=zd$(4neSaxE&-~ooFMBILiv7QK zb)Kc|-Mycy^1mPb{`J1t-MS+6zvoWxzFDsRZtWibJ@H>0-a6iNzT1A)KKkFC8}IMb zXB{YC@cH5$_wQ`2&FBA3{Myg&%-TP*|8yH~+v|B2c9xcp?2G=s{IlHoe9C#XdE);T z{X4x*Tui_2$JN)@&t05xapCk&(<krGty%i_)q&TN>u(=BbWCpF$qHk`xG!P9-yST# zQ!e{{@qK>(Cp8N78viqXZu}a2VfpXLl9MO@XJMZ4`A@R3?rjDJ28CpgAYTTCDpdxC zhGqtapZ^&c8eTFmlo~KFyh>nTu$sZZAf7)d{-_%R14D+Vi(^Q|t)w6Q%myN|zyHTY zfB#>dd-p$&>+ye+xn@XYu(<KIF^e60^w>pHB}_KvFG$i^OG84ktM6gS$KP{b@_<0o zJ7Z7&$N%?Eb7|GoD_#7uf+6_m|NkbQ$9NbR7#RNlXJ(LZPHO#Je9;BO_jL7hS?83{ F1OScNRZ9Q> literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/299.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/299.png new file mode 100644 index 0000000000000000000000000000000000000000..f77fe6f8ac31cd597e7892b55e6bc3f30b48ec36 GIT binary patch literal 408 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWo^lbAv?!`fdA42)KuE{-7<y~%(6|NmdlCdHr@C2{J&fddDcydS-9 z_ve{kCX@K%?;lg<?hXa!`Lj)#yLT9*vh9!F)%q~gQ~dCL{reA+eCE92d35z9Ptwmq z7s)dna>wMay-%!=h(CFtLHWhf-ChzdM#>(>KmOmZ=eb^5BUxu(*%{1itZ&}@>})R2 znLT$Zc-=bpycb{h*mO!`W8=g3&)4(Bf19uJ<`KU&zkEaCzrDY8e$0Qk-^MZa|3ABQ zF~K9RnT~PW9Q&}~fzh?J0NV`{7VP9-<RxjBcdtL3x6-ljfr#VjK%Rg9zsn!K+!Z%R zvk7FX>*J8N{qy%vN;6<)W<Gqp_qT*xs)@m$zds~h|J?sRpXdClUy}FE#JFAvU}M{2 z;Kp`_sfMQ}FN|l+rAIvVaeI@0Z27Hmcz*c%4kMBE2d6O25_MwWim$(9w2AXA0|Ntt Mr>mdKI;Vst0Pmr)DF6Tf literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/30.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/30.png new file mode 100644 index 0000000000000000000000000000000000000000..c288eda81631852ee988e604ddb3b9ac117dd29f GIT binary patch literal 325 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RS5klYs8MnRQJJ3=GdbT^vI!dXrNUGJc$AU=`O-+c$mMvs6*RmyZ$` z2y9R&wyXW6sG)G6Z_$CXzB3!=%ZK&|B&4PNI_BgN=-)5hyl$O@C!4W>L4o1B8->5m zF8ANxA}JvuAtNg)v13Ps<hH!Myx|+OugA9a%hyjx(K0r^Y<}$j|NsAwFZb}zw>tdv z^m>m{LuTcoC$e^PPAI4!Kd+!~&$#ZvrKLVaO-+pn67O%7PJei;*IY&N)bZorrY$(A zs=C|5NQA3}(@oB@p}+sF?!gnv;tO2lOc}jhTF!0XU;n=_@N&SSONKie43(13hR@nD dBP@}J;SO`KCU;G~DFXuogQu&X%Q~loCIE8;e|P`@ literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/31.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/31.png new file mode 100644 index 0000000000000000000000000000000000000000..33bb87b3ac9b40b8a07c0ad694042bd878a9c654 GIT binary patch literal 137 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RX!ro0zEV1_O{HZBG}+5R2aA81=;;=Qpx7bc(h0N%b&E{Qv*oUaHdh mN`jl~_IDRgNH1bzW|+dj{%c9<sw@Tu1_n=8KbLh*2~7Y<i74a% literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/32.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/32.png new file mode 100644 index 0000000000000000000000000000000000000000..51ece017cec4f19ee0f2a8dcc7ee0410ef9a11c4 GIT binary patch literal 145 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U#&7xT0ik^|f*a1_onK7sn8b-sF^ogdgV{n04kJI3Vz}J@(yoHMX|( xtUF4K3=AGlTg1~NWs~(`#lgjF{3{MJcuKQzzumyIk%57M!PC{xWt~$(695ukEkytT literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/33.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/33.png new file mode 100644 index 0000000000000000000000000000000000000000..24b6a4bca6540e3c9f2bfb13dbf91cd37186842a GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U(!$h!%@}d3g~71B0oji(`mIZ*oe&jQ{f)*(0nD_9>o<=Ujbf{@QBE zx@lV<T{?B3CAiJWAnU`5gNxbvrEC_kF*7W?%TY5oS!4+V0|SGntDnm{r-UW|TbwR| literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/34.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/34.png new file mode 100644 index 0000000000000000000000000000000000000000..823535c5fc0a584c9090ed7ca540dd28174dcc04 GIT binary patch literal 325 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U$SyAS*x1ex^MP3=GdbT^vI!daqvDn{_BaqT%89Bgu7HIV_F~osS-j zjG6Q3((V_rUl#59Viz(;gmqzwsR#Sf`4f)0*}i05aX};fL(lyWJnxT}yX^lkXVF7R ztAMo|zC>B;Zt&P0lQTzy`Eo$`!B2NSsGl=_Bg$c@8O{0joRx9SC5t}R`N57KN)>u+ zC$&p8d%i2H*jzTjb8_XqC!W7-rrp<gRBU;}{^8s0Dfv5Bw4Obl;AT+y+<Z%5d(H>X z<n_xeR>^)1a=F~;F<rY$KlI4W+?0masfR_4t=98RtX$%Cpd#@?!RCWWufn&?*>a~R gNA{iUKhgRr98TZf=ufI)U|?YIboFyt=akR{0C5P7qyPW_ literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/350.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/350.png new file mode 100644 index 0000000000000000000000000000000000000000..ef227ecfcbd29b8ff2bd7d542db526dc3b4761ae GIT binary patch literal 3054 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`(#+;1OBOz`!jG!i)^F z=14FwaQSCCI|l@0<d@_ZXXF<scse^P6cpvBW#*(ZFjU-{8=M{z{8G&J`@Jtt-W?57 zyz_%ZITV7Nx*J3$s%a^53V3xWF1#rkcu>&Ql|}l*f)3`szJ?yrKpmbgt{d!66c_b1 zFYf3%_4{G<;_PdCzCSzrZ|ilt=W9OC-F%+?00+-B592`72A-8pa)J+<j~_Yq@QI#7 zBbQ16g8;|o6vg7d%8U#@>TGPZl@74)V_>Yf%+0}Yz<2fm1DW>!{I8s5#xORtFditG zXyMJ#Aj05K)#tQ~!NHBOVeSmIV1@-O3<;-`lO8h!=rSaz*FU?=&@lJM%xXr4=qZY# zj0+|*C<v!^xG-c`Gpsou*5%3&VZb1g=6%GH;f5}QfoFJ{2g8Q#3<u5$aBpJZ;9)Qb z>}b_wV5w$EIH9au!O$|3LB#D-xXnlHbpp?|7#J#MZnD{_V(Ho#&7oV!9v-eIr*Y0x zTwBB}ktuPOgWjRanUY40Cj1XRpJ8BFFi}+SLG$Oo73cWYojYgNw~a4e_tk#3|4K<o zkN-VAzq-JIf#G3E-Nk=8`kSR1tl1iJ|1DB|$C44py`c1aciJX?hph|?Ufv4-TmT9i z*M$>y-n{wr$&(viN1UP)Pleb1OFtBUApCF6&pMkw-@iWC_eiDDD7Mj^<;R0VXTJC> zl{r6&vnA=yvwrtK+qwV$k-e;R#4~AUgqFzTK%v4umsvfZjg~5PI!)3%s(R^v%A@-_ zznSgsn=)+3vSX~CsJkLInfaTyx<Ew9fk)pN7(VUJKX^rhgSjE2`QiEU|L^Vpx-U>= zXh`#%RLH;(=c1w)a%!{vaSjFsmj#Vl8yuy-9F)*Gz$SB$_0B==KM7_nPAW$ZISDv% zr8LMCC1|c`a?)r!yFpefLH~+_tjU4EjJC-Ie0qmMcktO3@c&BWn0HWPPa`Lb;|UJo zM8#Vjj7eP)%6%N-Gh2Ts9`s-l>Q+%W*&!0B<UP^U!!W2TL?P5e_LFT9lVy91+pdY$ zCm4*H{<Q6JH=NSADbV%`&sOK1E6k-@+!k?PNGUO#)i1VS_JyJ?Qno#23!h(Doxy*r z-_H5C!GRYN?8ZlO4D^m2cG_s6!#O$m^hTvMJgbjMZ3tV#yu0&j^7)PBEJBU02RO}G z%AFJv<s3aCd?NgAc&t#6Q85v`#c9sT+!A<5U1{<P?=5P(1b-fpNHQ{PkMPh@z9!T! ztS?aA;e5ngMX1x+NPXu7CeMvaLM{nia=Vllr2JB6<!loVDdov0Z>3~!Vz->4qo94F z^~u~Pho2Zfk$$51DVl3;l0)H=AHk0_f=*?5Nj?>LYVwpVb%v2+=;B#HcULS75j5?O za$37^ThRKz_7%b=rL!#0Hj8<BFI~H6>Sd`6@r>`8>tB@Y61+QA&TsmK@Jpv(y1$tI zg85514{w`j^YM;!4awOO>=Nmc<r3>>_$-sW+%0)L#K7F}^^E5;`Df;bMrdu*su7)d zifgLZRJEznTKXX?S1nt0Zxwgw=g`w3{VU^wYuCAj8V5gK$$4ek>d;kl16~J92dA&J zUeUfPeMNqV{gS%KznyH6hkXwFJ1+@burdDeo?{B!$&#BZbQH6BSGScmnjTTxc=l#Z znbz(Ze`R-f+pb!<(9+r1%Q$><(B~;>_Uw;m6#ke~;>Y=1WBK&YbzS#F-fKS6lwM}y zv(qp&CVSPi>C+-sd#nyz?f!bN*}AvO=Pujpx;yfBBcJ1OFZCF8_w@dAtaG0S-weLH zoNKw5udUVBb$Y9^mr5_(?NYu!Z~40!^Y+Cx%=5`#ey?)x^ItNx`hP?I=Kfm#)tx7T zZ8MJw+ijk65;jLo3-fJc^^f2F80Z{(*ui;e;?%_H4_hYgylAuds#~uce^2JIn8zxQ zbC2a7Tdc;Wrl{ua`^xv-tRUZ=%QTl+`&j!NKYQlvqO;d#n$EU0yl(V-_Vd}-XW!qR z5LFSSvu(w;9jTt1o<uE+e79{+UTc(P<kE=HsMyVJYv-<2y|#Pp?&X5Zclu5CTRrbu z`r!>9x1HR!a=YY)r#IMcuiKWptu`?|Nj!b~(RGL4P2M}Xc2{lhUt4zG_M3dGWus*4 zinkPrScO^Ln{{r^JNw$BU#7j(Pmee+aQ=qz<88vHHIHP@4W513JzYF~XL0lAj_ywH zrqg=U?eu0#AG>>E?-Q?*uZ6J>t1f+=JA3o&LvL5yUjNSQUFN&?JM+(&%-5V3X<U5a z@CoM!fln{mbI+e@kbQqmoxihxWBS(T%KFLr6T{bD->`jY`Ks@U@5SxD*%i%yZ1>p4 z{GQRh$oMnn2ezNse%k+V{=E3s`_A>s|5N@~{lCbtyg`*Qow>8ou`!zI_m8R?#r=Ku zR<~2XZMyl^v1h?fM^VTArnOB^53W_rIPqq}TE*|rXPldOE|ae{pzg)B3+#!{61xxA z@u;`9w}!Xb_Z4(RNL@6XZd!ivj&t#SmtQW^T<*<IH}tn$uj^&1Ww%N%%jTB(E|XpM z$Mn)7&s>d|8{wJbEn}&twazRrXW#ulZ$Gy`n)`_Rakcmi{S&bTl?%@u`qH{wY^9!% zj@2{=pAJ8ZDIXjKv{s1qsD2VISMB4fJS2N4T}l6<*~N|<&M{hYexE$Fe5)qCnYhQa zQst=eQk|zeThv!h*yF?Nb9c7YJh5423b{tMR(lPbCrzAC=(I}Fck;IMSLx5v%1*CI zZQb<mXkPbreYvUZC$l$CSi>5r%eBpE*^7`Ujq6(Mo6=Lm)Apb3JY_j;^*rsfZ_jK# zb$f#OocHtpzZE(p^l;L)rET8fy0Q_^_O8jY++<T&d7JC8D64i`7~2|~9bMbBa<BTY zej7f0UEN-_D&OqJxPzyf4sK=4e$5@q^|ZD2^_}Z)WBnuaBkylb{M9yB+D^2yYn{k# z@#&)VH#F{4Je6+GKed16ohe&Zu0FCe<$T(uw3pBB>CTJho3?8Dv*~<!yf<%c;k_TW z<?YvRuDA84pVhd%yLp@Q&c*v}cI-)Ae)8N|Yx|HTAzwm%g_ym&ba!jw^&fc`-$_^R z`DXIw>h0(~-~Za3+;=;aG1T{J+SSzGp1(JLw_n$}>gA7VzjI^P`sEhBJ<fcI`7gUN z-?r;JuUwVAWE&FHe@*+fJKu5negEdZFW2QY;|=YdbL^3FflK9+m)|+>f0yI6x>eRw zz2+hNW%)AMbXol@r&*t7xy=rq^*cH#dYAthyNlOe-K+ZJep{Y@j)_fVrPt?>lbN?( zHeH^a{^@+r+^KVG>-2u!Nw~deTlMqgbN93O^WX0hD0z6v^?&zs>pj-zVm|FC`LyZw zQ{nI(;j`9T#pk{%d1do&@@4<yyXWklwQgrj_Re=zd$(4neSaxE&-~ooFMBILiv7QK zb)Kc|-Mycy^1mPb{`J1t-MS+6zvoWxzFDsRZtWibJ@H>0-a6iNzT1A)KKkFC8}IMb zXB{YC@cH5$_wQ`2&FBA3{Myg&%-TP*|8yH~+v|B2c9xcp?2G=s{IlHoe9C#XdE);T z{X4x*Tui_2$JN)@&t05xapCk&(<krGty%i_)q&TN>u(=BbWCpF$qHk`xG!P9-yST# zQ!e{{@qK>(Cp8N78viqXZu}a2VfpXLl9MO@XJMZ4`A@R3?rjDJ28CpgAYTTCDpdxC zhGqtapZ^&c8eTFmlo~KFyh>nTu$sZZAf7)d{-_%R1EZv;i(^Q|tt(T`docw{9IY?U z^O|{fL2ppwmI>3oayfnx+nRSh`l>g}#DHw`x3i8p$&_TA`IvU?&YYdLjf(#wOTOLj zKYZNMT_QQ6RVQqT>47y|f0oOwUl%y9zy3CZ;)?k)$?{h$ZS13Y{#@mhxO>-_hw0N! znZ?JLclLfgTDPWJv39+y@#Pnf)T&l{82i4c)-BPWbARKS9XZu@%M<7Cy=BKRw@f<m z=ZUV}3=v*c`s-WfHq1_!Z_sL*`$Ssv>7RtPB7r+y%0Hajp)`SGSH9$?7$<Gld)t=Z z>-zAhDC^Fpk|rr0$<>PTc`i0T7mB?P+93O8QuCC{wVmo+#!oG*j8Y6kZYS+L`@7_T r*O^b+bH$9-xSZh;`=8fR$IigOpS>=i!QPjFfq}u()z4*}Q$iB}Pus1| literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/351.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/351.png new file mode 100644 index 0000000000000000000000000000000000000000..856c4266e2e9c7b9843a00a6b4b615d4f23bf6a3 GIT binary patch literal 413 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RXqRkP(Y-#<3&@21a{N7sn8b-m{nYdov}<9RK+J<;wCZwTh;t7A&39 zrCJ1a9fJc@t^^3_=Kkg6{i}L0K;Q>E2d`tWv$LbGs-svruj!Q5wCU3dd~^T3;rO-Y z{O3JCK79Dgm6LE$v1qb%@$`!2)@vWV_<rzzo2}o8tRK>g{~k`--F$S)>@N%YH)v=) z+v2Lr&TPGYhg|M~YD<rpr3T;MXKE};5=otRJp0JD#0t?n@^>Fji~sn6DY3slq=+@~ zugKdqM?wr+X0@O1={<H*;6+>K;h!z*u4{;t2t`dzV0$H`p}s$-CZg%{vI8>;-Ag=@ z^wI=Yt~epa^2tnU_Z)$ldmd~vI{4<9&HKRaPn&wm*_xvF9(3A$_H>p&xZ>OJv#pC) zU4QsnX#)H9xhokgcCdN2ES*?X`c`6n$Q#~2p2t~NnlCIk8NELv{D$8@o_}ns=LDD9 U$z}^PFfcH9y85}Sb4q9e0K+x3W&i*H literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/352.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/352.png new file mode 100644 index 0000000000000000000000000000000000000000..8ab3e5925ebbfa5a0ba2636b0b62c8eb43f84818 GIT binary patch literal 2844 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 b!~g%x3>&YDmZheICxZB%u6{1-oD!M<03AgY literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/353.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/353.png new file mode 100644 index 0000000000000000000000000000000000000000..54c9b405a3ea025e4a9bee8df9df97dc59c05c33 GIT binary patch literal 2839 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 Wx|Z)U=jFHDAik%opUXO@geCws96_%D literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/354.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/354.png new file mode 100644 index 0000000000000000000000000000000000000000..48a016944efd9057ffaa04b1a070217e905928a6 GIT binary patch literal 2895 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/355.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/355.png new file mode 100644 index 0000000000000000000000000000000000000000..ce8467aea1f081d9676e2f6e05aaec14a9ad1171 GIT binary patch literal 3109 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/356.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/356.png new file mode 100644 index 0000000000000000000000000000000000000000..69e7525933dcfe35998584580c79dec3a855402c GIT binary patch literal 235 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxQt$4y}&XC28Lcw7sn8b-sC_3|NpONV`FRM-Wx1YBD7dCqJ>4W zppko#mBrHwjKa*!%!ik|_e<=X`O;vIO=(+3FAML!3-(hVzF8~HW1bh2w&UoZ_J<YW z@})dspFbx~sQNa|(b2QH@&7Spwp@PyRT5`7xF0)DUT!#pmBpaogxA9mA1{f+ylQL{ mWGc5XEN1JMvXS9oV@P4rk<#vd@PmPYfx*+&&t;ucLK6U?PEVBp literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/357.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/357.png new file mode 100644 index 0000000000000000000000000000000000000000..209b5f073cb42653929fa4426cb3e92a49f8c3d0 GIT binary patch literal 3049 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/358.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/358.png new file mode 100644 index 0000000000000000000000000000000000000000..0f8476dc7de76b11b85911bda3e0f52c54291899 GIT binary patch literal 2910 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/359.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/359.png new file mode 100644 index 0000000000000000000000000000000000000000..34bb5e4877b780057f169bceb38178c681a7e320 GIT binary patch literal 2842 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 bpP6C%Onwt4ZCOqR1_lOCS3j3^P6<r_WcWWx literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/36.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/36.png new file mode 100644 index 0000000000000000000000000000000000000000..b7bef807073e241bbd0de46148259990229fd363 GIT binary patch literal 2839 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 Z95b$;x7OA^WME)m@O1TaS?83{1OP+@M{588 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/370.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/370.png new file mode 100644 index 0000000000000000000000000000000000000000..01ca64fc001b753cd4541defbdafb3fff8997e24 GIT binary patch literal 2842 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 b&&(k7Pk=?MtZX#{0|SGntDnm{r-UW|+L%F! literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/371.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/371.png new file mode 100644 index 0000000000000000000000000000000000000000..324c5ce6b23ec72dfd7c12b797826f6f90d47d9b GIT binary patch literal 2841 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 bnZZL!=;)8l)6O$6Ffe$!`njxgN@xNAAKOF( literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/372.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/372.png new file mode 100644 index 0000000000000000000000000000000000000000..2417be67f663f4bccc4c65df2385ca1d19a879bb GIT binary patch literal 2838 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 ToVMhPGKlZ#>gTe~DWM4fv&cj` literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/373.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/373.png new file mode 100644 index 0000000000000000000000000000000000000000..af1fcbf4b86432e28db96347fde091e9f5d2c544 GIT binary patch literal 2976 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 N@O1TaS?83{1OR%ahYJ7z literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/374.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/374.png new file mode 100644 index 0000000000000000000000000000000000000000..fbf4cd84eee773fd3293d125ce07ac69d9bbcba2 GIT binary patch literal 2842 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 bXJ&X1FJRQ6Wt+pmz`)??>gTe~DWM4f4Ru0M literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/37447.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/37447.png new file mode 100644 index 0000000000000000000000000000000000000000..2bd0a8fa986ccf03ab61b7126c5a67854a8720c5 GIT binary patch literal 3069 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 GGywofHLuwK literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/37497.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/37497.png new file mode 100644 index 0000000000000000000000000000000000000000..2bd0a8fa986ccf03ab61b7126c5a67854a8720c5 GIT binary patch literal 3069 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 GGywofHLuwK literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/375.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/375.png new file mode 100644 index 0000000000000000000000000000000000000000..ff3603ea87997cae8f55bbad844a3a22eb349278 GIT binary patch literal 2946 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/376.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/376.png new file mode 100644 index 0000000000000000000000000000000000000000..a28055171420ec44cf9344c4e71bedc51f1f34b6 GIT binary patch literal 404 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U#F!zof_+@nsVj7#PhwT^vI!daq92>vt$Yq-}oxHWLTYfGc9c!ksLo zS7ua*yXhZWT~PeRDyFx)ySbY?klj-uh-st7!i8U6KYx-CbZg16gXOj7E6<($DfskQ ztCCxl-mCJw^KUKQM<0CoZuToFzd6&Rrz~L!6cTZ8)!Mew<wO40^aNYo1I7F^dQ=bJ zQ*`QH@!m#Z%A##QXUMLZa^U}+<GTzaw=Fx-o8F^7+3)1SEcIq#cf(g^k~^c<n0bY) zkxI2((B%E(?MJ&C=lJ3Z<4#B?I~BTXyq_r|o3hev0q@Fnd)1nR-4#<A>!VzLe@IxP ze2vl6osIQb9pgmNo(_W&&)as++f`>s)tanXs!;m2bKAu4nR2m4pI3jmrQTBKu(#BA z<D0};ikr{KGPhYCH(=k=xBik-{fqy#_nbBaymWQ-)3NE%m)V~bx#im@eg*~x22WQ% Jmvv4FO#m$Cs@(tp literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/377.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/377.png new file mode 100644 index 0000000000000000000000000000000000000000..c680a315bf8a78ee30ce2826418e571d03d1159c GIT binary patch literal 2836 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 V!}8YBZww3!44$rjF6*2UngIVdL*xJe literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/37744.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/37744.png new file mode 100644 index 0000000000000000000000000000000000000000..e5ec034a6f5b9fbcb4fb4fba7f8c9859fe3c23fb GIT binary patch literal 473 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWpTD}xq0*9sd32F7Yn7sn8b-mOz&wVM+qj@O?zOHS);otN|3tkorm zuj3P^$k9gr2S>^aPEEhjsmy+)Q`z&Bcc)0$jMg_tPk(!H^VT<s+ixAF+>*PU_5bke zUH|sKd%ZN;$Zs8omV&eD0tU6cDSNN_1pQ8_+A>wRzDI8R+yI@{Nfqi6zKQ}JOn;xT zR;Nfi$MW6j4V!oG(w-Gk3>SB}nW@i^%zmsUa$NcLjnB5lVgX&u-a>m;y<a11!=S*z zW1?!oe`V4?lcK`^Y!2&B_su%>q;_M-n@`)8`RxdM_2Ji?AB(otzb;Mxv-sva^_1+k zX}cueeiQk8`>)I8(5KDMs}A|>5M`S!EV1PMF{?M3V(xj7cJEXsnJBkRxxTtOFY4Nf z4Qql@PyFrdw-z;1^Dhjk7y0t4$vl7d&S2fj;@5REXQb)z&k+{lwyu?FeO;d{Z^`_7 zAzRq`s4r{&>+4TibFBL1q1?+6R_|2i8Ge-5z98+(TGv=tgTTXi7Y-)wSU96-L5sCs fsOayOeGHY2ZAYpz>k1hd7#KWV{an^LB{Ts5b?DMg literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/378.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/378.png new file mode 100644 index 0000000000000000000000000000000000000000..a0bb8102e6c7612401aa52b3ca1c6e8896e7fac2 GIT binary patch literal 3126 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 R9}El(44$rjF6*2UngBBr-pv32 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/379.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/379.png new file mode 100644 index 0000000000000000000000000000000000000000..68e637378ed913fc4077c9d4391aca4eefa5f9b1 GIT binary patch literal 3050 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/380.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/380.png new file mode 100644 index 0000000000000000000000000000000000000000..15b68969955bb0629f7bc467f92c787c76ed09d7 GIT binary patch literal 137 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U#%`C@ZtZ;-$3=3=G<yE{-7<y~$g&Y(LF!WNYXYYwMG;Il`OPX%gWt o+$>ptPgL6X_+qxpCleT2Ua@Tnvf6!(fq{X+)78&qol`;+03r1#6951J literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/381-kosovo.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/381-kosovo.png new file mode 100644 index 0000000000000000000000000000000000000000..e5ec034a6f5b9fbcb4fb4fba7f8c9859fe3c23fb GIT binary patch literal 473 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWpTD}xq0*9sd32F7Yn7sn8b-mOz&wVM+qj@O?zOHS);otN|3tkorm zuj3P^$k9gr2S>^aPEEhjsmy+)Q`z&Bcc)0$jMg_tPk(!H^VT<s+ixAF+>*PU_5bke zUH|sKd%ZN;$Zs8omV&eD0tU6cDSNN_1pQ8_+A>wRzDI8R+yI@{Nfqi6zKQ}JOn;xT zR;Nfi$MW6j4V!oG(w-Gk3>SB}nW@i^%zmsUa$NcLjnB5lVgX&u-a>m;y<a11!=S*z zW1?!oe`V4?lcK`^Y!2&B_su%>q;_M-n@`)8`RxdM_2Ji?AB(otzb;Mxv-sva^_1+k zX}cueeiQk8`>)I8(5KDMs}A|>5M`S!EV1PMF{?M3V(xj7cJEXsnJBkRxxTtOFY4Nf z4Qql@PyFrdw-z;1^Dhjk7y0t4$vl7d&S2fj;@5REXQb)z&k+{lwyu?FeO;d{Z^`_7 zAzRq`s4r{&>+4TibFBL1q1?+6R_|2i8Ge-5z98+(TGv=tgTTXi7Y-)wSU96-L5sCs fsOayOeGHY2ZAYpz>k1hd7#KWV{an^LB{Ts5b?DMg literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/381.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/381.png new file mode 100644 index 0000000000000000000000000000000000000000..c900d994fbd85684a22dbbc50238703fa17b5aa6 GIT binary patch literal 408 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTH11FQDNli^wn42)KuE{-7<y<4yB^*Wp&a_nQh-7J%dCM%{2wNLX} z-I2h~9(d&9!Pf%%2aZY$q^rB%J#z5wMOW(`ohvfBJ6x3|l4q}4yTx};!?Ftj)z_Hq z<?cQI!1eS@$pHa388$A3?@=`pTHiMdEi*slbJF!)iPFJ^o%Zg&dea|ycuj44S+vbU z%rL+INZQ)AYb6;Gxhf~qA8q1$F)=}L`>xxe_Y!xVss1eHdj6uv$qaqDeUs8(Uwk9Y zSKD<egze^x!{?s;EDhZ!7PdliqerLn!)a27=WWsWp>S*c6=AmASXr6A<03BS4}0`Y z-lX&7z5J4}Evq?n{nt*pFjsor9j(5<ZPzp(AD_;+G(Uo)X~F6}yPIX@{Js@l+a@@F zy{FCGP5a9Grk&$s6J`nN@6(JE@#JD^J<EILP|f{}{2%gZ8TCgmJg}Eto7^AiVJgJH Pz`)??>gTe~DWM4f9>TBe literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/382.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/382.png new file mode 100644 index 0000000000000000000000000000000000000000..00f247911a40dd9ca96c84aa47b8bcc22685ad0b GIT binary patch literal 3001 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/385.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/385.png new file mode 100644 index 0000000000000000000000000000000000000000..dadf7b2f397f9c3fb5d987934929f88562e9f92a GIT binary patch literal 3045 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/386.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/386.png new file mode 100644 index 0000000000000000000000000000000000000000..3600748d60e07aed4cd4e436e4838013e6225355 GIT binary patch literal 2933 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 UZ+y3hfq{X+)78&qol`;+03!)^IsgCw literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/38649.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/38649.png new file mode 100644 index 0000000000000000000000000000000000000000..e5ec034a6f5b9fbcb4fb4fba7f8c9859fe3c23fb GIT binary patch literal 473 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWpTD}xq0*9sd32F7Yn7sn8b-mOz&wVM+qj@O?zOHS);otN|3tkorm zuj3P^$k9gr2S>^aPEEhjsmy+)Q`z&Bcc)0$jMg_tPk(!H^VT<s+ixAF+>*PU_5bke zUH|sKd%ZN;$Zs8omV&eD0tU6cDSNN_1pQ8_+A>wRzDI8R+yI@{Nfqi6zKQ}JOn;xT zR;Nfi$MW6j4V!oG(w-Gk3>SB}nW@i^%zmsUa$NcLjnB5lVgX&u-a>m;y<a11!=S*z zW1?!oe`V4?lcK`^Y!2&B_su%>q;_M-n@`)8`RxdM_2Ji?AB(otzb;Mxv-sva^_1+k zX}cueeiQk8`>)I8(5KDMs}A|>5M`S!EV1PMF{?M3V(xj7cJEXsnJBkRxxTtOFY4Nf z4Qql@PyFrdw-z;1^Dhjk7y0t4$vl7d&S2fj;@5REXQb)z&k+{lwyu?FeO;d{Z^`_7 zAzRq`s4r{&>+4TibFBL1q1?+6R_|2i8Ge-5z98+(TGv=tgTTXi7Y-)wSU96-L5sCs fsOayOeGHY2ZAYpz>k1hd7#KWV{an^LB{Ts5b?DMg literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/387.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/387.png new file mode 100644 index 0000000000000000000000000000000000000000..c7ea660b758bd41cb82c54d010aca9c0bb671413 GIT binary patch literal 3078 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 QFfcH9y85}Sb4q9e037kSxc~qF literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/3883.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/3883.png new file mode 100644 index 0000000000000000000000000000000000000000..d9cdd7b04dac4bcc565266746535e1cd9f83fb85 GIT binary patch literal 421 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxUQ+J`@J6w42<rcE{-7<y;~>m&0=yCY2E+EzieX1UAv})xn8RU za&lA-CIm%BE)~$$u4ULL*zMr3K~$AZ=ZMylJLUqP7@Eqm4&1ePy-dKF!}?##lWpJc z<)52lTzu%~;`8=Xie4;O$juY`hc_bJcG5<{Woj$pzORV0P0VUa$!S?vwV%CvtCOWE z!_nneP6t%&V?4Tij+DZ+-TKu^pLfj5{rsWo<n1e5F+EYi@ATs|%HH(_+r+mOv}%Pu z;hOr>sASJ0<I4~48ZPbroyH{k{E^w)nqqa41#|N!b>D8>>u~h;k??tUx@wCXyTa#r z>0VaN@?COah3Ud+FTMDuw{0(Q+Tk?4DR$xJf+r6>4^FL{^-<^0h6@Tg7V-A_ITk$J zyvH_kOs$LJy0%2tY}%GLEZo<Z%zNALKKRYOOPhqN|4z5tKEHHTZqu(DAGh&`{n^XM cn)h0GetntGgdaxI3=9kmp00i_>zopr0I*WH_y7O^ literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/389.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/389.png new file mode 100644 index 0000000000000000000000000000000000000000..0f3ba712423b81de3c838f375d374b0dacf68c42 GIT binary patch literal 3170 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 II;Vst02A}y+5i9m literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/39-vatican.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/39-vatican.png new file mode 100644 index 0000000000000000000000000000000000000000..68e637378ed913fc4077c9d4391aca4eefa5f9b1 GIT binary patch literal 3050 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/39.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/39.png new file mode 100644 index 0000000000000000000000000000000000000000..4fa1169543b3645a2cd29eb4d4472a042dc1678e GIT binary patch literal 151 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWGyAS?Uqy;~15Ffdqpx;Tbd^d_gwc=ByNBl`|9+ut*mO_(EUwln<m zbeZZ~)~u^#n3<WGh4nbS4z4)3n5|#RChJ3k6c58iM=oEkq8w`m1_lOCS3j3^P6<r_ D@NO)- literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/40.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/40.png new file mode 100644 index 0000000000000000000000000000000000000000..4787d77bcba76196842d46952c278280dee94326 GIT binary patch literal 151 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U$RPCy$}B6Q>sg1B0cfi(`mIZ*t0ob^p&dFvq-$D)b3{wpY|_XZY!x zeS2q|Yz^&eY;0`goX)Z|;mU)+W_MmW(+US(HU{2eE~N`j$^RJ`7#KWV{an^LB{Ts5 D3xzK6 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/41.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/41.png new file mode 100644 index 0000000000000000000000000000000000000000..737149fd040f764ca255204e8fb3209d03de0ac5 GIT binary patch literal 2849 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/420.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/420.png new file mode 100644 index 0000000000000000000000000000000000000000..5b01670a952df71a8a685c8bff47bdb9a9fc37cb GIT binary patch literal 400 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RX!rtCjjYfi3b342(veE{-7<y~!SVv48*HU7r7cCGQM*gKV=kcZw%E z`i8YQM!L;tjB1z}d~@H!*WcrLvcJWrRs8xT+}yiQiP>74`zV{SV)3*$7RM7N+x0%Y z(E3|M`plV?IrkfMKD>O*-!`>c&uGv8KfMQ|{2Lpmw$9XPp1FbZ-~a#f6R)*R=HZJp zmN<8>!fDz;2|YKq_ja|(5&K><9X@}*UQ(yzspO2A8+I{kCy11+{q5g#$U)MtO^-Ql z5wDuW-!uRJ8y?xS>&S$Xk1~f}pZ&igdBdN6ex9xUd`20ywZ2;#D|pjV5)wEni>E5S zewoSkt-jvFOl0*zq5b;|LTs&$Bz*cQ8OfGpdgJ+9-?l%?)g^cQ|F`Hwz>b55_U|_i zv9mgo@b#tPQMN<8WtH{)3!j_}WZP$NzcNjOnVFfHVdcJUlj?MH9T*rG7(8A5T-G@y GGywp9{jJyl literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/421.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/421.png new file mode 100644 index 0000000000000000000000000000000000000000..2fef0e26e14e3eaecac16189cf9377efdb8f593f GIT binary patch literal 409 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxFNgo^Gm7>42;&EE{-7<y;rZ;dLIrDVSDiXRc>&UNS>(Dt`@K6 z7cUm=uz2xK!{UeVANGH&=8j!EI^=gRkZqEm;drpY*>R(X`qs5mpIy5#&CE7$l13}r z*$2C2ExzxZ&ivd+NvUPRK7$JZiSKl+-t*0weOumlV+q&9ixU%!DuayIpA@o+bXxvu zqvN#;ERjyjA3ft^<xSt*9zQ+h*{d0B3v%-vJTpDy-zx3j(zW}JafpAs{>QU33i<5j zxERMbXsc$Wt0n!i__cV??VtA^2zf8~9N2E6ccH4v@$3$l-7$?JZ1wEv-CQ~I4rHD? zb}CL({kyNoy;}ycrUqvWiXTKzFg<&$Zr(y6*@kNx$wr)WE=w%A%*7V%^!=a@M~<(q za&XW_iTb0v&M1BRA@Jwu{X~X@iyTs)xjW)#7UW7wyyg1u`tN@HN6wj7SK0BWlrl3g PFfe$!`njxgN@xNAj5Vt= literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/423.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/423.png new file mode 100644 index 0000000000000000000000000000000000000000..1a7db9fa5a614ddf75c9125b76c048f74c94db28 GIT binary patch literal 2921 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 I=akR{0Nx#N(EtDd literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/43.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/43.png new file mode 100644 index 0000000000000000000000000000000000000000..40e0b1a0ea35572fc9d87bbf7c5a061b16103a69 GIT binary patch literal 134 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyu&~y(=7cp23=A5cE{-7<y~%qNkN=wA$kxy)*48K0!zA(l|9^X_ lO6MyH$1ct>FX3flxcrNQXTEMBGXnzygQu&X%Q~loCIB5FC`14N literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/44.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/44.png new file mode 100644 index 0000000000000000000000000000000000000000..9714e588249b3295e67272e2b14d533882fc0a69 GIT binary patch literal 3739 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/45.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/45.png new file mode 100644 index 0000000000000000000000000000000000000000..969748551ec2684350e526cb03bdad0f30181c7e GIT binary patch literal 3015 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 BpKJgC literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/46.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/46.png new file mode 100644 index 0000000000000000000000000000000000000000..143799009caa4c49364369cc7cb1ea1d5cb641e0 GIT binary patch literal 3059 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/47.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/47.png new file mode 100644 index 0000000000000000000000000000000000000000..1afaeccf0977d2aa0f1e98d2117b0b9ac73a1773 GIT binary patch literal 3115 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 E09L)sCjbBd literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/48.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/48.png new file mode 100644 index 0000000000000000000000000000000000000000..8a8fe27ba9d1c24b3b5a5a888d4f8f7f406b6b90 GIT binary patch literal 2820 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 E0CoU6L;wH) literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/49.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/49.png new file mode 100644 index 0000000000000000000000000000000000000000..72f0868642f812b89768f6dd45b984ba90239b5a GIT binary patch literal 2870 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 F2>?^VP$B>T literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/500.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/500.png new file mode 100644 index 0000000000000000000000000000000000000000..e73802c19bb3b2d8354550c19d52c0e5f915cb32 GIT binary patch literal 3238 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`(#+;1OBOz`!jG!i)^F z=14FwaQSCCI|l@0<d@_ZXXF<scse^P6cpvBW#*(ZFjU-{8=M{z{8G&J`@Jtt-W?57 zyz_%ZITV7Nx*J3$s%a^53V3xWF1#rkcu>&Ql|}l*f)3`szJ?yrKpmbgt{d!66c_b1 zFYf3%_4{G<;_PdCzCSzrZ|ilt=W9OC-F%+?00+-B592`72A-8pa)J+<j~_Yq@QI#7 zBbQ16g8;|o6vg7d%8U#@>TGPZl@74)V_>Yf%+0}Yz<2fm1DW>!{I8s5#xORtFditG zXyMJ#Aj05K)#tQ~!NHBOVeSmIV1@-O3<;-`lO8h!=rSaz*FU?=&@lJM%xXr4=qZY# zj0+|*C<v!^xG-c`Gpsou*5%3&VZb1g=6%GH;f5}QfoFJ{2g8Q#3<u5$aBpJZ;9)Qb z>}b_wV5w$EIH9au!O$|3LB#D-xXnlHbpp?|7#J#MZnD{_V(Ho#&7oV!9v-eIr*Y0x zTwBB}ktuPOgWjRanUY40Cj1XRpJ8BFFi}+SLG$Oo73cWYojYgNw~a4e_tk#3|4K<o zkN-VAzq-JIf#G3E-Nk=8`kSR1tl1iJ|1DB|$C44py`c1aciJX?hph|?Ufv4-TmT9i z*M$>y-n{wr$&(viN1UP)Pleb1OFtBUApCF6&pMkw-@iWC_eiDDD7Mj^<;R0VXTJC> zl{r6&vnA=yvwrtK+qwV$k-e;R#4~AUgqFzTK%v4umsvfZjg~5PI!)3%s(R^v%A@-_ zznSgsn=)+3vSX~CsJkLInfaTyx<Ew9fk)pN7(VUJKX^rhgSjE2`QiEU|L^Vpx-U>= zXh`#%RLH;(=c1w)a%!{vaSjFsmj#Vl8yuy-9F)*Gz$SB$_0B==KM7_nPAW$ZISDv% zr8LMCC1|c`a?)r!yFpefLH~+_tjU4EjJC-Ie0qmMcktO3@c&BWn0HWPPa`Lb;|UJo zM8#Vjj7eP)%6%N-Gh2Ts9`s-l>Q+%W*&!0B<UP^U!!W2TL?P5e_LFT9lVy91+pdY$ zCm4*H{<Q6JH=NSADbV%`&sOK1E6k-@+!k?PNGUO#)i1VS_JyJ?Qno#23!h(Doxy*r z-_H5C!GRYN?8ZlO4D^m2cG_s6!#O$m^hTvMJgbjMZ3tV#yu0&j^7)PBEJBU02RO}G z%AFJv<s3aCd?NgAc&t#6Q85v`#c9sT+!A<5U1{<P?=5P(1b-fpNHQ{PkMPh@z9!T! ztS?aA;e5ngMX1x+NPXu7CeMvaLM{nia=Vllr2JB6<!loVDdov0Z>3~!Vz->4qo94F z^~u~Pho2Zfk$$51DVl3;l0)H=AHk0_f=*?5Nj?>LYVwpVb%v2+=;B#HcULS75j5?O za$37^ThRKz_7%b=rL!#0Hj8<BFI~H6>Sd`6@r>`8>tB@Y61+QA&TsmK@Jpv(y1$tI zg85514{w`j^YM;!4awOO>=Nmc<r3>>_$-sW+%0)L#K7F}^^E5;`Df;bMrdu*su7)d zifgLZRJEznTKXX?S1nt0Zxwgw=g`w3{VU^wYuCAj8V5gK$$4ek>d;kl16~J92dA&J zUeUfPeMNqV{gS%KznyH6hkXwFJ1+@burdDeo?{B!$&#BZbQH6BSGScmnjTTxc=l#Z znbz(Ze`R-f+pb!<(9+r1%Q$><(B~;>_Uw;m6#ke~;>Y=1WBK&YbzS#F-fKS6lwM}y zv(qp&CVSPi>C+-sd#nyz?f!bN*}AvO=Pujpx;yfBBcJ1OFZCF8_w@dAtaG0S-weLH zoNKw5udUVBb$Y9^mr5_(?NYu!Z~40!^Y+Cx%=5`#ey?)x^ItNx`hP?I=Kfm#)tx7T zZ8MJw+ijk65;jLo3-fJc^^f2F80Z{(*ui;e;?%_H4_hYgylAuds#~uce^2JIn8zxQ zbC2a7Tdc;Wrl{ua`^xv-tRUZ=%QTl+`&j!NKYQlvqO;d#n$EU0yl(V-_Vd}-XW!qR z5LFSSvu(w;9jTt1o<uE+e79{+UTc(P<kE=HsMyVJYv-<2y|#Pp?&X5Zclu5CTRrbu z`r!>9x1HR!a=YY)r#IMcuiKWptu`?|Nj!b~(RGL4P2M}Xc2{lhUt4zG_M3dGWus*4 zinkPrScO^Ln{{r^JNw$BU#7j(Pmee+aQ=qz<88vHHIHP@4W513JzYF~XL0lAj_ywH zrqg=U?eu0#AG>>E?-Q?*uZ6J>t1f+=JA3o&LvL5yUjNSQUFN&?JM+(&%-5V3X<U5a z@CoM!fln{mbI+e@kbQqmoxihxWBS(T%KFLr6T{bD->`jY`Ks@U@5SxD*%i%yZ1>p4 z{GQRh$oMnn2ezNse%k+V{=E3s`_A>s|5N@~{lCbtyg`*Qow>8ou`!zI_m8R?#r=Ku zR<~2XZMyl^v1h?fM^VTArnOB^53W_rIPqq}TE*|rXPldOE|ae{pzg)B3+#!{61xxA z@u;`9w}!Xb_Z4(RNL@6XZd!ivj&t#SmtQW^T<*<IH}tn$uj^&1Ww%N%%jTB(E|XpM z$Mn)7&s>d|8{wJbEn}&twazRrXW#ulZ$Gy`n)`_Rakcmi{S&bTl?%@u`qH{wY^9!% zj@2{=pAJ8ZDIXjKv{s1qsD2VISMB4fJS2N4T}l6<*~N|<&M{hYexE$Fe5)qCnYhQa zQst=eQk|zeThv!h*yF?Nb9c7YJh5423b{tMR(lPbCrzAC=(I}Fck;IMSLx5v%1*CI zZQb<mXkPbreYvUZC$l$CSi>5r%eBpE*^7`Ujq6(Mo6=Lm)Apb3JY_j;^*rsfZ_jK# zb$f#OocHtpzZE(p^l;L)rET8fy0Q_^_O8jY++<T&d7JC8D64i`7~2|~9bMbBa<BTY zej7f0UEN-_D&OqJxPzyf4sK=4e$5@q^|ZD2^_}Z)WBnuaBkylb{M9yB+D^2yYn{k# z@#&)VH#F{4Je6+GKed16ohe&Zu0FCe<$T(uw3pBB>CTJho3?8Dv*~<!yf<%c;k_TW z<?YvRuDA84pVhd%yLp@Q&c*v}cI-)Ae)8N|Yx|HTAzwm%g_ym&ba!jw^&fc`-$_^R z`DXIw>h0(~-~Za3+;=;aG1T{J+SSzGp1(JLw_n$}>gA7VzjI^P`sEhBJ<fcI`7gUN z-?r;JuUwVAWE&FHe@*+fJKu5negEdZFW2QY;|=YdbL^3FflK9+m)|+>f0yI6x>eRw zz2+hNW%)AMbXol@r&*t7xy=rq^*cH#dYAthyNlOe-K+ZJep{Y@j)_fVrPt?>lbN?( zHeH^a{^@+r+^KVG>-2u!Nw~deTlMqgbN93O^WX0hD0z6v^?&zs>pj-zVm|FC`LyZw zQ{nI(;j`9T#pk{%d1do&@@4<yyXWklwQgrj_Re=zd$(4neSaxE&-~ooFMBILiv7QK zb)Kc|-Mycy^1mPb{`J1t-MS+6zvoWxzFDsRZtWibJ@H>0-a6iNzT1A)KKkFC8}IMb zXB{YC@cH5$_wQ`2&FBA3{Myg&%-TP*|8yH~+v|B2c9xcp?2G=s{IlHoe9C#XdE);T z{X4x*Tui_2$JN)@&t05xapCk&(<krGty%i_)q&TN>u(=BbWCpF$qHk`xG!P9-yST# zQ!e{{@qK>(Cp8N78viqXZu}a2VfpXLl9MO@XJMZ4`A@R3?rjDJ28CpgAYTTCDpdxC zhGqtapZ^&c8eTFmlo~KFyh>nTu$sZZAf7)d{-_%R1LGx67srr_TUVys_YN+UIA(vp z_|Bym@tumdvNm2~b5fWi*15H#?!+z)4WEUVxL0<Y?0$LdG}oS2$2hbyM038fU+LPV zC14h|*=U2yY>OK?)-GpDAHDhU)z7m0(u5a#s~$eB)U&fWuf1f-n&TR4E?=qHWu|p? z8iRTK3zx;5Y`;y<-QRy;apUeD*C3wOy*wFySEe!KKA*StW&}&KdCK-}os)0-&N<#^ z{Bx^EaK1V>f8564|4qBjx<6@lwXyRw`kDLo*nHEod^a~~EWezSlg}Qyb?&X8Tc_&2 zc+EJhDDQu1zkyC;vEA__hp(^JNOQi+q4=R9#zcx$M$Y`{Lq#+GxMMm~Po|6gShr(B zkW*Hg>x_%qGFfXEuU|ZKP296DU+2s%Tyx=sM>zAfFCyFTX?HA(I>)q_)AQhp^d;Vl z%c{DnH7~1~oKe-Ytvx5dxMdZ?n+?+PHzqvy(>eXNVwzWK=ih49x$NgB%sKJV%+TFr z)rHH#DqCE)bNaveTy$i6$L1OC>%SY_dy}s7Ep4h{%%9Z??Z3YltXkZ((yCx;k&R#e z)6|qbzhvUy=BjRfaqREz13erUp3h&qPoYcc$IPE6-tAH{<=y?^e+4^V=c0~Rfqyo8 gb^m|IsmsqGJay5!Wjfxz3=9kmp00i_>zopr0Q&P3D*ylh literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/501.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/501.png new file mode 100644 index 0000000000000000000000000000000000000000..57eb05c951a0e39f4c9706cba6dc6c7783d30cb6 GIT binary patch literal 654 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxUNe1_kUar3`_=|E{-7<y<4Z)`Uo?M9IwAFw|CpTrBl9Thz1F; zUJ_#yKA^=W?AfzhjYV9&M_#&{Pl{K~N<yZ0cAlFs8@qDhMMhUw<|Uk5E-L~iRW1G2 zye)jkwsXhK)-vbUzW;yv_le)!@zPdaZyY<8{}Ep$R=RZ;hfE%m!WN;Yw`WDk&M}&$ zrPg1zW2fz{<`e#}e2q%ogm*r&E3#JK`R|2svG&ETDCea|9LkL!hl_q{b)De7CwTdc zUf<wN9VuT}=iPhr)MdfX{|CZ~Wgk!POEBXq;r2bVr_wd>YDd1#r(aK<V<g!x-cNiN z|2c8uky{53FTd@XcD>+u3D?5sbKZXmys$m6{Ibjyz70R#a9&!UyJ;qevh%-N2M@}z zdYUY@5M`On>uhr_;{%)c-dGJbX|{0TcW(MtR>zsXZF;b&xZ>4Yj-?Xk9PFQk*>}$H zN~vR89=!bf8-{h?6jLJ8iW9roqJKV~{?=lak8)=kgNd*6q+FG#W|vpzH^gT;UN&I} z`Y95<a_%kbe;WjjXZjoOw>*9Psd&!P#b<K2IbLkK{;hS}F+Vr9<0syw{S@(<!phBa z@Avc1I+IR3X|6l-Bp^k9wSAI^toN0vYgDeM-m1wnn{;}M^)HUNc`DPdXGgp0tuOvw zIAhVpmRUTHGdEt+kU6f!V)3MSvD)F1>R&sI3}eNj1bGZ(Zzbd;P5rWtyFVx0*4la5 zhq+FP(w<+6-(8Eo*RC}63CFfZ>rmenhhP6Wtv&6~#w?+hy+zyYd3#SY%c`FK%fP_E N;OXk;vd$@?2>=;DA-Dhl literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/502.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/502.png new file mode 100644 index 0000000000000000000000000000000000000000..5163b833e9a1094e9b04e61556033e2d40c11f7b GIT binary patch literal 2978 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 PU|{fc^>bP0l+XkK1-Oc0 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/503.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/503.png new file mode 100644 index 0000000000000000000000000000000000000000..efa15872dc3e335a1ac64dedfae871866b245e0e GIT binary patch literal 2902 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/504.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/504.png new file mode 100644 index 0000000000000000000000000000000000000000..f362afaf493b9a84b7b24ce5585ec4c2a35a6c0b GIT binary patch literal 2912 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/505.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/505.png new file mode 100644 index 0000000000000000000000000000000000000000..083033e12dba965ace9af837e41b61d1266d89b0 GIT binary patch literal 2934 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 UItV{tU|?YIboFyt=akR{02>KzlmGw# literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/506.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/506.png new file mode 100644 index 0000000000000000000000000000000000000000..59e1f37393f51e88566824926c42fd96f2e31cd0 GIT binary patch literal 2852 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/507.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/507.png new file mode 100644 index 0000000000000000000000000000000000000000..a6b852c9785475de9af4a24f47ed57c2b71602b4 GIT binary patch literal 320 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxG9tR!^i6w7#JRTx;Tbd^d|rL|NnnIo6^Bl$Ka%?Mh1z(e?IqT z{9Ph>XV+iDQr>@GzuP;1*>ju6clLaPBWHG>{qS1<;=-2u31(?-Y}TT7Y^9!hY|)~2 zY-VY0n|RY)`uF(T|1<bdxZyx%ibLR&gFMdWQ$h~h*!Y<9BBNw|lI=$}<0bOSw;udA z*6B2p$SzW5{j;O2eHYvR-ygdlzI_wQ!=L0WdGB1@(GTyB>%WpXbL8N$5ATorx846f z-{{ZppOYIqn|rO;j13G7HfWwSS|JyG^1w2UZbkRNS&zP#C4j(#3js$aEVlSmz{oIB X#O2mpm10K*1_lOCS3j3^P6<r_$`6RJ literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/508.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/508.png new file mode 100644 index 0000000000000000000000000000000000000000..24b6a4bca6540e3c9f2bfb13dbf91cd37186842a GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U(!$h!%@}d3g~71B0oji(`mIZ*oe&jQ{f)*(0nD_9>o<=Ujbf{@QBE zx@lV<T{?B3CAiJWAnU`5gNxbvrEC_kF*7W?%TY5oS!4+V0|SGntDnm{r-UW|TbwR| literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/509.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/509.png new file mode 100644 index 0000000000000000000000000000000000000000..f9269b395de1c66ee46e911586a72a9d70427edc GIT binary patch literal 2964 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 Bf%E_X literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/51.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/51.png new file mode 100644 index 0000000000000000000000000000000000000000..afb3e6ea871c39fb1992fbabf6e5a0abaf549f9f GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxQ<eLe^wm>1B0oji(`mIZ?etd10UuyvIoTMn&POT#kKm*?$px~ z@7&!VUpjT5CAiJWAnU`5gNxbvrEC_kF*BU0V}JQGu*QUefq}u()z4*}Q$iB}f;%rC literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/52.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/52.png new file mode 100644 index 0000000000000000000000000000000000000000..8fc90266fb3a34a700b3239baa97eda842033df7 GIT binary patch literal 2954 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/53.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/53.png new file mode 100644 index 0000000000000000000000000000000000000000..fd7ce15da4cc7c4786515401ef10172f9ed307ed GIT binary patch literal 3234 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 XeziZ(q)>4N1_lOCS3j3^P6<r_weta) literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/54.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/54.png new file mode 100644 index 0000000000000000000000000000000000000000..64a961aa762f80fe60b3c6c8fbe9be0bbd432ca3 GIT binary patch literal 3003 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/55.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/55.png new file mode 100644 index 0000000000000000000000000000000000000000..d2b4c2231efa14dcf4afd6b9eecc13fd2d859a8b GIT binary patch literal 662 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U#GL1BarhLgRb}1||zn7sn8b-mQ~;Jth~*9JfDzd0WZ3otAU;-yRiB zX76OvD!t_VLQ%;kbvj?^hHghel{h8;X9``f1e|wqT;wkCvg(*_!1HB6->I#a%p%Xt zEZ(}ce12&_x_*DQKyCm3>F58S{{H(n_nns~KFh02s|rwk7TCG8T7&b>x=y3FGZJqZ zI`3Bd^**6l>2ybGqv7oKvkThJE@(5H-7a{ViOqGJMug&a4Tkj9&OEXaPu|Yy6#jmG zv)D?80~sB~Zx6A2c3&pRA(vtj)F0zeZ&5ZyRbu=3N873*zrNo2&dyh7q4t~h>!vYf zb5n97+i$;0y<^wCSVu8AuUhMrYv<}x#&r_Rt&`?ez5AHw+#R%Z!7e9nzY~I=TqUY4 z{M}!(=Y6$jS5W4>!xLSwHp;{6QJ%;mhW_0A7nj`XD!-m=)~soi($c<qBq@4bpX~Na z&tE0IYZbb|wEBn*^VhnlD~7wo`IVn;Up(Q{h0Rya1f1}46XHskZRxn!)8A_D(Tpp> z(W{?+ZJB>jI(Umk`QwuwOwNkhw%%twe%?3o^HOTo_T5q$J+mjAv0&YA(Rk(i8sXZV zNB2+mdTucD%N5fp#akYlF--obb!*RBo^>XoNfUI>&knl$viQ>L@`Nop1~o4we}8ZF zRNZ_e*>lS-mD=TZuFP2c<(F*9uQ?ULulAmvJlSz#>W+`f=_T2F{)aRxG~E5w>V7fm zm%V(F?eaUXvkE`--7*b)H>;K*CT81bqjkSe_X-_!NMxzk+dd<ue%>4})^{)1!(+~d VKNYDiV_;xl@O1TaS?83{1OQ$3Eg}E_ literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/56.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/56.png new file mode 100644 index 0000000000000000000000000000000000000000..e1ebcdc4d00eeacf068fc738b36d8b69a9221b26 GIT binary patch literal 239 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U(#7fUfFIo5oiR3=9)JT^vI!dXrNOVt>zPWRJL&b4uW;UbnZDeuC3O zgNH$G3lDDfoOQ)~hh}W|LOs{!hc9HR*$f@aUP<ox`hvSnx__U+oc&)XKD_#SzKHZ0 zrsO+2{?Bx@&M{|}H`hMeW@MnS^T*@s_jy2I{sNPw4XQuSm(7`!^n1O*p2}1H(+>88 ne`zULmh8nK8vJg>!NbQA7%o_;a=wpy&j13Ru6{1-oD!M<1qocQ literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/57.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/57.png new file mode 100644 index 0000000000000000000000000000000000000000..56d1d716f022f124ec49e28da33229243e9a912f GIT binary patch literal 157 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RX!ryP##_>jl*e3=H<3E{-7<y~%%y1K-#)^D+qQ^2nN2bV#3>wB^8i z1zn!Blnpa%X7x!*fWY^T7xS049@KHF-!Faf(1V5I#~AcxaXmaOWwD-tfq}u()z4*} HQ$iB}?*lPl literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/58.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/58.png new file mode 100644 index 0000000000000000000000000000000000000000..0c259dd70208631cbefd34936fee59117071cd47 GIT binary patch literal 273 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWo^qm<I&zqZW`3=Eq+T^vI!dXxXu1^%&T=4BAp<&ia&U^Ot3Sbt2q zvZsfKXU>~5JZxD^JRl&RSDWzT-)H^9^-O_&7mnUssKsNY^@pd%W(^pF*f$qyvBaKf z^510Z-o7yZWp3N`_w|c9?$`gCx)3B%bV_tiRUunz^G1!slf&;zzO(t)6uP)ehrhW; zLj7>#=Jgv-J?7~Oe_wA@^Xr%N@@~8OzlT3?omJP+v(sN>JL_}Xih~UIS&UaMP<&Cv Qz`(%Z>FVdQ&MBb@0C|pQhyVZp literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/590.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/590.png new file mode 100644 index 0000000000000000000000000000000000000000..24b6a4bca6540e3c9f2bfb13dbf91cd37186842a GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U(!$h!%@}d3g~71B0oji(`mIZ*oe&jQ{f)*(0nD_9>o<=Ujbf{@QBE zx@lV<T{?B3CAiJWAnU`5gNxbvrEC_kF*7W?%TY5oS!4+V0|SGntDnm{r-UW|TbwR| literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/591.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/591.png new file mode 100644 index 0000000000000000000000000000000000000000..397b63958533972636df624646a2576e7084a0b7 GIT binary patch literal 140 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U)<4x48H;<3Fhk3=DdnE{-7<y~#G}E56NdWNYXYYwMHhVUnni6PE7X rqnKsD{adJXmW0ihrlc2=JPgjxtTzkA?tW)rU|{fc^>bP0l+XkKm5L}r literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/592.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/592.png new file mode 100644 index 0000000000000000000000000000000000000000..7ee71fd7e9258889a32fad74e618b029a2cba9ce GIT binary patch literal 3185 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 ahPl%o+}zIf!HI!^fx*+&&t;ucLK6UE`1ExE literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/593.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/593.png new file mode 100644 index 0000000000000000000000000000000000000000..371dfcba928413beb06ac80e337be95964646c1c GIT binary patch literal 3025 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 P7#KWV{an^LB{Ts5w*ITn literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/594.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/594.png new file mode 100644 index 0000000000000000000000000000000000000000..24b6a4bca6540e3c9f2bfb13dbf91cd37186842a GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U(!$h!%@}d3g~71B0oji(`mIZ*oe&jQ{f)*(0nD_9>o<=Ujbf{@QBE zx@lV<T{?B3CAiJWAnU`5gNxbvrEC_kF*7W?%TY5oS!4+V0|SGntDnm{r-UW|TbwR| literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/595.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/595.png new file mode 100644 index 0000000000000000000000000000000000000000..e5b17e7b71e05fd6f430d05a054a03c2101c953f GIT binary patch literal 2922 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`(#+;1OBOz`!jG!i)^F z=14FwaQSCCI|l@0<d@_ZXXF<scse^P6cpvBW#*(ZFjU-{8=M{z{8G&J`@Jtt-W?57 zyz_%ZITV7Nx*J3$s%a^53V3xWF1#rkcu>&Ql|}l*f)3`szJ?yrKpmbgt{d!66c_b1 zFYf3%_4{G<;_PdCzCSzrZ|ilt=W9OC-F%+?00+-B592`72A-8pa)J+<j~_Yq@QI#7 zBbQ16g8;|o6vg7d%8U#@>TGPZl@74)V_>Yf%+0}Yz<2fm1DW>!{I8s5#xORtFditG zXyMJ#Aj05K)#tQ~!NHBOVeSmIV1@-O3<;-`lO8h!=rSaz*FU?=&@lJM%xXr4=qZY# zj0+|*C<v!^xG-c`Gpsou*5%3&VZb1g=6%GH;f5}QfoFJ{2g8Q#3<u5$aBpJZ;9)Qb z>}b_wV5w$EIH9au!O$|3LB#D-xXnlHbpp?|7#J#MZnD{_V(Ho#&7oV!9v-eIr*Y0x zTwBB}ktuPOgWjRanUY40Cj1XRpJ8BFFi}+SLG$Oo73cWYojYgNw~a4e_tk#3|4K<o zkN-VAzq-JIf#G3E-Nk=8`kSR1tl1iJ|1DB|$C44py`c1aciJX?hph|?Ufv4-TmT9i z*M$>y-n{wr$&(viN1UP)Pleb1OFtBUApCF6&pMkw-@iWC_eiDDD7Mj^<;R0VXTJC> zl{r6&vnA=yvwrtK+qwV$k-e;R#4~AUgqFzTK%v4umsvfZjg~5PI!)3%s(R^v%A@-_ zznSgsn=)+3vSX~CsJkLInfaTyx<Ew9fk)pN7(VUJKX^rhgSjE2`QiEU|L^Vpx-U>= zXh`#%RLH;(=c1w)a%!{vaSjFsmj#Vl8yuy-9F)*Gz$SB$_0B==KM7_nPAW$ZISDv% zr8LMCC1|c`a?)r!yFpefLH~+_tjU4EjJC-Ie0qmMcktO3@c&BWn0HWPPa`Lb;|UJo zM8#Vjj7eP)%6%N-Gh2Ts9`s-l>Q+%W*&!0B<UP^U!!W2TL?P5e_LFT9lVy91+pdY$ zCm4*H{<Q6JH=NSADbV%`&sOK1E6k-@+!k?PNGUO#)i1VS_JyJ?Qno#23!h(Doxy*r z-_H5C!GRYN?8ZlO4D^m2cG_s6!#O$m^hTvMJgbjMZ3tV#yu0&j^7)PBEJBU02RO}G z%AFJv<s3aCd?NgAc&t#6Q85v`#c9sT+!A<5U1{<P?=5P(1b-fpNHQ{PkMPh@z9!T! ztS?aA;e5ngMX1x+NPXu7CeMvaLM{nia=Vllr2JB6<!loVDdov0Z>3~!Vz->4qo94F z^~u~Pho2Zfk$$51DVl3;l0)H=AHk0_f=*?5Nj?>LYVwpVb%v2+=;B#HcULS75j5?O za$37^ThRKz_7%b=rL!#0Hj8<BFI~H6>Sd`6@r>`8>tB@Y61+QA&TsmK@Jpv(y1$tI zg85514{w`j^YM;!4awOO>=Nmc<r3>>_$-sW+%0)L#K7F}^^E5;`Df;bMrdu*su7)d zifgLZRJEznTKXX?S1nt0Zxwgw=g`w3{VU^wYuCAj8V5gK$$4ek>d;kl16~J92dA&J zUeUfPeMNqV{gS%KznyH6hkXwFJ1+@burdDeo?{B!$&#BZbQH6BSGScmnjTTxc=l#Z znbz(Ze`R-f+pb!<(9+r1%Q$><(B~;>_Uw;m6#ke~;>Y=1WBK&YbzS#F-fKS6lwM}y zv(qp&CVSPi>C+-sd#nyz?f!bN*}AvO=Pujpx;yfBBcJ1OFZCF8_w@dAtaG0S-weLH zoNKw5udUVBb$Y9^mr5_(?NYu!Z~40!^Y+Cx%=5`#ey?)x^ItNx`hP?I=Kfm#)tx7T zZ8MJw+ijk65;jLo3-fJc^^f2F80Z{(*ui;e;?%_H4_hYgylAuds#~uce^2JIn8zxQ zbC2a7Tdc;Wrl{ua`^xv-tRUZ=%QTl+`&j!NKYQlvqO;d#n$EU0yl(V-_Vd}-XW!qR z5LFSSvu(w;9jTt1o<uE+e79{+UTc(P<kE=HsMyVJYv-<2y|#Pp?&X5Zclu5CTRrbu z`r!>9x1HR!a=YY)r#IMcuiKWptu`?|Nj!b~(RGL4P2M}Xc2{lhUt4zG_M3dGWus*4 zinkPrScO^Ln{{r^JNw$BU#7j(Pmee+aQ=qz<88vHHIHP@4W513JzYF~XL0lAj_ywH zrqg=U?eu0#AG>>E?-Q?*uZ6J>t1f+=JA3o&LvL5yUjNSQUFN&?JM+(&%-5V3X<U5a z@CoM!fln{mbI+e@kbQqmoxihxWBS(T%KFLr6T{bD->`jY`Ks@U@5SxD*%i%yZ1>p4 z{GQRh$oMnn2ezNse%k+V{=E3s`_A>s|5N@~{lCbtyg`*Qow>8ou`!zI_m8R?#r=Ku zR<~2XZMyl^v1h?fM^VTArnOB^53W_rIPqq}TE*|rXPldOE|ae{pzg)B3+#!{61xxA z@u;`9w}!Xb_Z4(RNL@6XZd!ivj&t#SmtQW^T<*<IH}tn$uj^&1Ww%N%%jTB(E|XpM z$Mn)7&s>d|8{wJbEn}&twazRrXW#ulZ$Gy`n)`_Rakcmi{S&bTl?%@u`qH{wY^9!% zj@2{=pAJ8ZDIXjKv{s1qsD2VISMB4fJS2N4T}l6<*~N|<&M{hYexE$Fe5)qCnYhQa zQst=eQk|zeThv!h*yF?Nb9c7YJh5423b{tMR(lPbCrzAC=(I}Fck;IMSLx5v%1*CI zZQb<mXkPbreYvUZC$l$CSi>5r%eBpE*^7`Ujq6(Mo6=Lm)Apb3JY_j;^*rsfZ_jK# zb$f#OocHtpzZE(p^l;L)rET8fy0Q_^_O8jY++<T&d7JC8D64i`7~2|~9bMbBa<BTY zej7f0UEN-_D&OqJxPzyf4sK=4e$5@q^|ZD2^_}Z)WBnuaBkylb{M9yB+D^2yYn{k# z@#&)VH#F{4Je6+GKed16ohe&Zu0FCe<$T(uw3pBB>CTJho3?8Dv*~<!yf<%c;k_TW z<?YvRuDA84pVhd%yLp@Q&c*v}cI-)Ae)8N|Yx|HTAzwm%g_ym&ba!jw^&fc`-$_^R z`DXIw>h0(~-~Za3+;=;aG1T{J+SSzGp1(JLw_n$}>gA7VzjI^P`sEhBJ<fcI`7gUN z-?r;JuUwVAWE&FHe@*+fJKu5negEdZFW2QY;|=YdbL^3FflK9+m)|+>f0yI6x>eRw zz2+hNW%)AMbXol@r&*t7xy=rq^*cH#dYAthyNlOe-K+ZJep{Y@j)_fVrPt?>lbN?( zHeH^a{^@+r+^KVG>-2u!Nw~deTlMqgbN93O^WX0hD0z6v^?&zs>pj-zVm|FC`LyZw zQ{nI(;j`9T#pk{%d1do&@@4<yyXWklwQgrj_Re=zd$(4neSaxE&-~ooFMBILiv7QK zb)Kc|-Mycy^1mPb{`J1t-MS+6zvoWxzFDsRZtWibJ@H>0-a6iNzT1A)KKkFC8}IMb zXB{YC@cH5$_wQ`2&FBA3{Myg&%-TP*|8yH~+v|B2c9xcp?2G=s{IlHoe9C#XdE);T z{X4x*Tui_2$JN)@&t05xapCk&(<krGty%i_)q&TN>u(=BbWCpF$qHk`xG!P9-yST# zQ!e{{@qK>(Cp8N78viqXZu}a2VfpXLl9MO@XJMZ4`A@R3?rjDJ28CpgAYTTCDpdxC zhGqtapZ^&c8eTFmlo~KFyh>nTu$sZZAf7)d{-_%R1H)8L7srr_TS<TZ|F>t>WjI}_ zSUPLQnIjI$248s6YD|kS8YYR<CI2{n?8JdD^CvexG!JC^udWoiXK(f7hsR#+<Wc&Q z#Ixw#KOQHqRaZE>^pE!$`24GFP3-RwIeh)`@0H@mYObB*`5131nIpG9<$+PTqF#^s z+Fh<862^c2o;!8m#EAn3jyT3h7)Pv;IICcCPGO7IT?S?brEj`3)mdcP85kHCJYD@< J);T3K0RT+NZubBH literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/596.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/596.png new file mode 100644 index 0000000000000000000000000000000000000000..24b6a4bca6540e3c9f2bfb13dbf91cd37186842a GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U(!$h!%@}d3g~71B0oji(`mIZ*oe&jQ{f)*(0nD_9>o<=Ujbf{@QBE zx@lV<T{?B3CAiJWAnU`5gNxbvrEC_kF*7W?%TY5oS!4+V0|SGntDnm{r-UW|TbwR| literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/597.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/597.png new file mode 100644 index 0000000000000000000000000000000000000000..546ed104b93f776f2f85af1a4081f827357607c0 GIT binary patch literal 295 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RXQfpOMgE`PEw(7#L1^x;Tbd^uC?4m(S5rpmqOU?HEU=ehw+#&I<PY z2dXAYS*-pKdMlVZow|3FNc|5_3Re{2+TEG+JjD3qOk?%QlLbq(rPwFvxy<F_Ub}U2 zuuIdogruWW${8Czt?B8A+?D9+bM5;A*5jF{PPrd(>3F*C#15s)cNh!oD_3-@#QfgR zxmfXb^rPwL&%3sAzfQbpk>=wn@gv9WXZe2Fr<~32^9~*}oUlSR;;T0YztC0X=arW% zl*QJbyWukF;_Q&^D}Jrn&$av1#vlK?#N{LzZA+8(@kKK*Ffe$!`njxgN@xNA9qx45 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/598.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/598.png new file mode 100644 index 0000000000000000000000000000000000000000..3f28450d7fdda086e88991fc30dcfa8d0a67ceb0 GIT binary patch literal 380 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{W>>~kUEEHsOxnG21Yqg7sn8b-m91QdmSziIr{OvZssi$p@pu|+EO|k zN=l2I=eSI_Yk2TXdxzyup1gf6FP^A1S>1dT#8S9gR9AFuy+K&8lyBX+U8WNb{$`Np zYcF=yT-x!_gkh=3`M1(vQYNMCJ>`+vnWNX=QBd-);rCBbUcSQ<j$LJ(6@T!1+{bF& zrjwy<+-9?_#be@jUwja@xa(aw$DQCyQYG@cS(5n@Ht#hQ*WDP@^}Wyg#qNkSCQp_x zd-S&VO6`2al)C(aIoE<tj%yDD-cFaiXXRh>+jh$vuB9(iOKgw6`IY!ZtLvN1N}qYZ zf3c)lHRtst@$@hUlyUjo47<PK&)nJ1YZD~YlrEWn<Bq#+)t!3$(4#z?f}Hu=>o4^j k?%mk6_wnxkS3mL>tS^|6qp|n~0|Nttr>mdKI;Vst0HLR&8UO$Q literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/599.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/599.png new file mode 100644 index 0000000000000000000000000000000000000000..677c952bde158de0418c70b96b809be7833e66ea GIT binary patch literal 255 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxQS)|N9)rJ3=9iBT^vI!df!eq<Z5;haJ_$QgR8;Z4Q7^(?-VN7 z^fyniZGB*9)0*xvU2Sex!o&>gmrRPA@+K=iXl`t@|16m%k<z+x&66}gdBbT}qh`PP zAvpV^G^;qLrcReJLuoCq(Y@bCR4fg6j2V23-8CbxO#86z&`z1;B!*xAJg@sQNC-cU zc{kT0Lo~B$Nv?eS&i)0DBWEuxZMi0RElp8(L+!o)Qu`Uq%J*zCvi$Xmfq{X+)78&q Iol`;+02+{Af&c&j literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/60.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/60.png new file mode 100644 index 0000000000000000000000000000000000000000..20cf467e4d985b3db724d9e649965c553e61aa2b GIT binary patch literal 3266 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 F698cd6p8=< literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/61.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/61.png new file mode 100644 index 0000000000000000000000000000000000000000..59f8fbabf96de683340ee0d8b331ff9ffc8b260c GIT binary patch literal 3547 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/62.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/62.png new file mode 100644 index 0000000000000000000000000000000000000000..6e2480649711fcffd5ec7c3a378f893d1d7d25e6 GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxP}I^nOh121A~sIi(`mIZ?cX0if{89*%~^<+WMqyj_{_{Y%Y1r pI7{Ns|Ns9lme^z&%;jNYIB<tk$~rZ_g@J*A!PC{xWt~$(698jODGdMs literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/63.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/63.png new file mode 100644 index 0000000000000000000000000000000000000000..e240964121dd1a00f3a60af77260e83c43c1069d GIT binary patch literal 3087 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 YN|TqbQz`w;z`(%Z>FVdQ&MBb@08{_D$N&HU literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/64.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/64.png new file mode 100644 index 0000000000000000000000000000000000000000..44955986fe3f7bd9705a5d55bd70f41bb4ef28f7 GIT binary patch literal 3206 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/65.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/65.png new file mode 100644 index 0000000000000000000000000000000000000000..8f4d09771500007dd65c4790d8627d52a50205a1 GIT binary patch literal 319 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U#$9yRy*ZbUS+n28M^8E{-7<y>BmV^g9$FarEQ=y)T!pb>#?5u3%!? z%&9oB_2}}1lvLGq`<j)TXA5&r)-h3RX>2@@Aa&Q!Q0UmSxCd4f4Loz)Z7)ln`##%l zqt<nv<AGdT)N<E23;vY3ci49KgWd`QmfMNnrBvFUE}T<(>&<j|7yt8b?DtO)XLY&e z&A<A#)-CzPQl%4wY-df_nXMqBE}IjUedMa@$I}9ld)#*D-M+~r?E3P-hmGC0vy?tt z9;p<Qk$&T&B;p`b-x{j9Fv~orx%^8B-$yk|nQy=99=)EQklbTw`@8nZ%aitho<GPp Y-(oB9SylNf0|Nttr>mdKI;Vst0ED7}zW@LL literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/66.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/66.png new file mode 100644 index 0000000000000000000000000000000000000000..eee0b35558823195dc7f86f2366625962d142c9d GIT binary patch literal 157 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxQ<SOxqBr81B1P%i(`mIZ}J{>g^%+a*%~^<+Kdbg3^sfae{$hS z!m<DV|MRb1kdP9vQZ`CY&QyZ0Ev_i}r$r){FHf3GQL6yMLJNTdNjlfJGcYhPc)I$z JtaD0e0ssK-Fk=7! literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/670.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/670.png new file mode 100644 index 0000000000000000000000000000000000000000..abfd2b8b5ba9f9cb0a3626f75bac7a33ca24c2e6 GIT binary patch literal 3040 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/672-norfolk_island.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/672-norfolk_island.png new file mode 100644 index 0000000000000000000000000000000000000000..dbe675de43b003a20ecea8ebeea485f3a11f48a2 GIT binary patch literal 3053 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/672.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/672.png new file mode 100644 index 0000000000000000000000000000000000000000..706fb7c9b8e36a30ff3a7e5841bbd4400c3e68bc GIT binary patch literal 3237 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/673.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/673.png new file mode 100644 index 0000000000000000000000000000000000000000..7c574d3e592b52c178fd54a4f1e50217832b5376 GIT binary patch literal 3236 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/674.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/674.png new file mode 100644 index 0000000000000000000000000000000000000000..15c514149847b9c08038a1ee1f8907449da9b1a7 GIT binary patch literal 2918 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`(#+;1OBOz`!jG!i)^F z=14FwaQSCCI|l@0<d@_ZXXF<scse^P6cpvBW#*(ZFjU-{8=M{z{8G&J`@Jtt-W?57 zyz_%ZITV7Nx*J3$s%a^53V3xWF1#rkcu>&Ql|}l*f)3`szJ?yrKpmbgt{d!66c_b1 zFYf3%_4{G<;_PdCzCSzrZ|ilt=W9OC-F%+?00+-B592`72A-8pa)J+<j~_Yq@QI#7 zBbQ16g8;|o6vg7d%8U#@>TGPZl@74)V_>Yf%+0}Yz<2fm1DW>!{I8s5#xORtFditG zXyMJ#Aj05K)#tQ~!NHBOVeSmIV1@-O3<;-`lO8h!=rSaz*FU?=&@lJM%xXr4=qZY# zj0+|*C<v!^xG-c`Gpsou*5%3&VZb1g=6%GH;f5}QfoFJ{2g8Q#3<u5$aBpJZ;9)Qb z>}b_wV5w$EIH9au!O$|3LB#D-xXnlHbpp?|7#J#MZnD{_V(Ho#&7oV!9v-eIr*Y0x zTwBB}ktuPOgWjRanUY40Cj1XRpJ8BFFi}+SLG$Oo73cWYojYgNw~a4e_tk#3|4K<o zkN-VAzq-JIf#G3E-Nk=8`kSR1tl1iJ|1DB|$C44py`c1aciJX?hph|?Ufv4-TmT9i z*M$>y-n{wr$&(viN1UP)Pleb1OFtBUApCF6&pMkw-@iWC_eiDDD7Mj^<;R0VXTJC> zl{r6&vnA=yvwrtK+qwV$k-e;R#4~AUgqFzTK%v4umsvfZjg~5PI!)3%s(R^v%A@-_ zznSgsn=)+3vSX~CsJkLInfaTyx<Ew9fk)pN7(VUJKX^rhgSjE2`QiEU|L^Vpx-U>= zXh`#%RLH;(=c1w)a%!{vaSjFsmj#Vl8yuy-9F)*Gz$SB$_0B==KM7_nPAW$ZISDv% zr8LMCC1|c`a?)r!yFpefLH~+_tjU4EjJC-Ie0qmMcktO3@c&BWn0HWPPa`Lb;|UJo zM8#Vjj7eP)%6%N-Gh2Ts9`s-l>Q+%W*&!0B<UP^U!!W2TL?P5e_LFT9lVy91+pdY$ zCm4*H{<Q6JH=NSADbV%`&sOK1E6k-@+!k?PNGUO#)i1VS_JyJ?Qno#23!h(Doxy*r z-_H5C!GRYN?8ZlO4D^m2cG_s6!#O$m^hTvMJgbjMZ3tV#yu0&j^7)PBEJBU02RO}G z%AFJv<s3aCd?NgAc&t#6Q85v`#c9sT+!A<5U1{<P?=5P(1b-fpNHQ{PkMPh@z9!T! ztS?aA;e5ngMX1x+NPXu7CeMvaLM{nia=Vllr2JB6<!loVDdov0Z>3~!Vz->4qo94F z^~u~Pho2Zfk$$51DVl3;l0)H=AHk0_f=*?5Nj?>LYVwpVb%v2+=;B#HcULS75j5?O za$37^ThRKz_7%b=rL!#0Hj8<BFI~H6>Sd`6@r>`8>tB@Y61+QA&TsmK@Jpv(y1$tI zg85514{w`j^YM;!4awOO>=Nmc<r3>>_$-sW+%0)L#K7F}^^E5;`Df;bMrdu*su7)d zifgLZRJEznTKXX?S1nt0Zxwgw=g`w3{VU^wYuCAj8V5gK$$4ek>d;kl16~J92dA&J zUeUfPeMNqV{gS%KznyH6hkXwFJ1+@burdDeo?{B!$&#BZbQH6BSGScmnjTTxc=l#Z znbz(Ze`R-f+pb!<(9+r1%Q$><(B~;>_Uw;m6#ke~;>Y=1WBK&YbzS#F-fKS6lwM}y zv(qp&CVSPi>C+-sd#nyz?f!bN*}AvO=Pujpx;yfBBcJ1OFZCF8_w@dAtaG0S-weLH zoNKw5udUVBb$Y9^mr5_(?NYu!Z~40!^Y+Cx%=5`#ey?)x^ItNx`hP?I=Kfm#)tx7T zZ8MJw+ijk65;jLo3-fJc^^f2F80Z{(*ui;e;?%_H4_hYgylAuds#~uce^2JIn8zxQ zbC2a7Tdc;Wrl{ua`^xv-tRUZ=%QTl+`&j!NKYQlvqO;d#n$EU0yl(V-_Vd}-XW!qR z5LFSSvu(w;9jTt1o<uE+e79{+UTc(P<kE=HsMyVJYv-<2y|#Pp?&X5Zclu5CTRrbu z`r!>9x1HR!a=YY)r#IMcuiKWptu`?|Nj!b~(RGL4P2M}Xc2{lhUt4zG_M3dGWus*4 zinkPrScO^Ln{{r^JNw$BU#7j(Pmee+aQ=qz<88vHHIHP@4W513JzYF~XL0lAj_ywH zrqg=U?eu0#AG>>E?-Q?*uZ6J>t1f+=JA3o&LvL5yUjNSQUFN&?JM+(&%-5V3X<U5a z@CoM!fln{mbI+e@kbQqmoxihxWBS(T%KFLr6T{bD->`jY`Ks@U@5SxD*%i%yZ1>p4 z{GQRh$oMnn2ezNse%k+V{=E3s`_A>s|5N@~{lCbtyg`*Qow>8ou`!zI_m8R?#r=Ku zR<~2XZMyl^v1h?fM^VTArnOB^53W_rIPqq}TE*|rXPldOE|ae{pzg)B3+#!{61xxA z@u;`9w}!Xb_Z4(RNL@6XZd!ivj&t#SmtQW^T<*<IH}tn$uj^&1Ww%N%%jTB(E|XpM z$Mn)7&s>d|8{wJbEn}&twazRrXW#ulZ$Gy`n)`_Rakcmi{S&bTl?%@u`qH{wY^9!% zj@2{=pAJ8ZDIXjKv{s1qsD2VISMB4fJS2N4T}l6<*~N|<&M{hYexE$Fe5)qCnYhQa zQst=eQk|zeThv!h*yF?Nb9c7YJh5423b{tMR(lPbCrzAC=(I}Fck;IMSLx5v%1*CI zZQb<mXkPbreYvUZC$l$CSi>5r%eBpE*^7`Ujq6(Mo6=Lm)Apb3JY_j;^*rsfZ_jK# zb$f#OocHtpzZE(p^l;L)rET8fy0Q_^_O8jY++<T&d7JC8D64i`7~2|~9bMbBa<BTY zej7f0UEN-_D&OqJxPzyf4sK=4e$5@q^|ZD2^_}Z)WBnuaBkylb{M9yB+D^2yYn{k# z@#&)VH#F{4Je6+GKed16ohe&Zu0FCe<$T(uw3pBB>CTJho3?8Dv*~<!yf<%c;k_TW z<?YvRuDA84pVhd%yLp@Q&c*v}cI-)Ae)8N|Yx|HTAzwm%g_ym&ba!jw^&fc`-$_^R z`DXIw>h0(~-~Za3+;=;aG1T{J+SSzGp1(JLw_n$}>gA7VzjI^P`sEhBJ<fcI`7gUN z-?r;JuUwVAWE&FHe@*+fJKu5negEdZFW2QY;|=YdbL^3FflK9+m)|+>f0yI6x>eRw zz2+hNW%)AMbXol@r&*t7xy=rq^*cH#dYAthyNlOe-K+ZJep{Y@j)_fVrPt?>lbN?( zHeH^a{^@+r+^KVG>-2u!Nw~deTlMqgbN93O^WX0hD0z6v^?&zs>pj-zVm|FC`LyZw zQ{nI(;j`9T#pk{%d1do&@@4<yyXWklwQgrj_Re=zd$(4neSaxE&-~ooFMBILiv7QK zb)Kc|-Mycy^1mPb{`J1t-MS+6zvoWxzFDsRZtWibJ@H>0-a6iNzT1A)KKkFC8}IMb zXB{YC@cH5$_wQ`2&FBA3{Myg&%-TP*|8yH~+v|B2c9xcp?2G=s{IlHoe9C#XdE);T z{X4x*Tui_2$JN)@&t05xapCk&(<krGty%i_)q&TN>u(=BbWCpF$qHk`xG!P9-yST# zQ!e{{@qK>(Cp8N78viqXZu}a2VfpXLl9MO@XJMZ4`A@R3?rjDJ28CpgAYTTCDpdxC zhGqtapZ^&c8eTFmlo~KFyh>nTu$sZZAf7)d{-_%R1H(j57srr_TS<TZ|F>t>We{fW zWl(M|`}<$<>7&4A!HMPNlfTu^TF&L>=EkO*8py_NE7>MIeV#$g?%%T-@09cjiR7#~ z!xQVej;DCmABkOEF_JeTX08-IW?%m|(W2;|;Nj-w@)CV>>W_T*d|m!ksz9Ef8JnG4 zS(|@+ol(u-f89CFkr!C^)%|y!*jOOWztf4sfZ@a+6Pd2Rvg;Wb7#KWV{an^LB{Ts5 De&uIP literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/675.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/675.png new file mode 100644 index 0000000000000000000000000000000000000000..257f7c27c828b843f207c94b5f3b497654414351 GIT binary patch literal 3408 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/676.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/676.png new file mode 100644 index 0000000000000000000000000000000000000000..d1ea1a14f13d13640e04b490b672fa4b761b57ce GIT binary patch literal 2945 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 YI=z1<m>ZiJ7#J8lUHx3vIVCg!08hGZP5=M^ literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/677.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/677.png new file mode 100644 index 0000000000000000000000000000000000000000..1e49ea6356a3affc9fea64f9f44b0739fc105b69 GIT binary patch literal 3090 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/678.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/678.png new file mode 100644 index 0000000000000000000000000000000000000000..a5e36f5d4b40164d483b825538a93b54e2ae3c2c GIT binary patch literal 3137 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/679.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/679.png new file mode 100644 index 0000000000000000000000000000000000000000..fec582b952687b2570b97151402c621c3991d2c7 GIT binary patch literal 3236 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/680.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/680.png new file mode 100644 index 0000000000000000000000000000000000000000..a3320c7fd398e7f0bf3d19556b4434fe9a4284f8 GIT binary patch literal 3019 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 Bp1%M9 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/681.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/681.png new file mode 100644 index 0000000000000000000000000000000000000000..24b6a4bca6540e3c9f2bfb13dbf91cd37186842a GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U(!$h!%@}d3g~71B0oji(`mIZ*oe&jQ{f)*(0nD_9>o<=Ujbf{@QBE zx@lV<T{?B3CAiJWAnU`5gNxbvrEC_kF*7W?%TY5oS!4+V0|SGntDnm{r-UW|TbwR| literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/682.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/682.png new file mode 100644 index 0000000000000000000000000000000000000000..fda0828867c02e6116e14e1dc556b0a1d818b37c GIT binary patch literal 3333 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 a;a|w{yl1PwpJrfSVDNPHb6Mw<&;$T14Ldjh literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/683.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/683.png new file mode 100644 index 0000000000000000000000000000000000000000..a9eb7f285bb11c2ac876c72346da032bafd76deb GIT binary patch literal 3104 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/685.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/685.png new file mode 100644 index 0000000000000000000000000000000000000000..d20e142173432ffd762fcf58fce6e3e8ee72d12b GIT binary patch literal 2936 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 Uvnh8N7#J8lUHx3vIVCg!03}R!VE_OC literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/686.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/686.png new file mode 100644 index 0000000000000000000000000000000000000000..e6814ccf3b2e6ff0034423046c5eccf405fc7e56 GIT binary patch literal 3301 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/687.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/687.png new file mode 100644 index 0000000000000000000000000000000000000000..24b6a4bca6540e3c9f2bfb13dbf91cd37186842a GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U(!$h!%@}d3g~71B0oji(`mIZ*oe&jQ{f)*(0nD_9>o<=Ujbf{@QBE zx@lV<T{?B3CAiJWAnU`5gNxbvrEC_kF*7W?%TY5oS!4+V0|SGntDnm{r-UW|TbwR| literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/688.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/688.png new file mode 100644 index 0000000000000000000000000000000000000000..96e1da3c2a5dde8b84d556b92e99e0acec56c77d GIT binary patch literal 3330 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 Z=^<yC!e5;7Vqjok@O1TaS?83{1ONoBKNA1| literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/689.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/689.png new file mode 100644 index 0000000000000000000000000000000000000000..5a325063fd1a942cc902987cb7b1cbe99d8b576c GIT binary patch literal 347 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxQTX+hgUNL1H)fW7sn8b-nUZ>y`2Ij+U8fEGkw+8vO>kpbHXhp zlV@T-`2Vmw71t|l*t=H#!nF$uyuEA9S~{A0mq}>Ol$s*jw5PN`c(r=h=C9}O|Nijt z)86@)J5o%<RB{~FHr>72T&tbno}GKH;?l#PyZWZbrf2H%b{j4}YU*bbR=L~q@z+!8 z6&)WX<puR*4xPFdGg~p4xBNLzR3L}Gv8>j-Tb>#7{uDkw(EgoKe=>{F{_Y7)7jl_H z<iChpe43uH@xV&0&4%j?)@?6fmiN!hl27$2UXW%m;kIAVMO}lbdQR*oET)tNsog%m zZjpINOxSxyjd!_|I;zqN{=c~4wRG<P_>XHP`JecI7gU+r!@$76;OXk;vd$@?2>_{F BkQe{} literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/690.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/690.png new file mode 100644 index 0000000000000000000000000000000000000000..a3513fc62b1eddf991e13412a016a98764f72498 GIT binary patch literal 3179 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 Uwx1U>FfcH9y85}Sb4q9e00e3L^#A|> literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/691.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/691.png new file mode 100644 index 0000000000000000000000000000000000000000..6bf6ea1352624b0e9ba957537d8cb705e96d09a4 GIT binary patch literal 2956 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`(#+;1OBOz`!jG!i)^F z=14FwaQSCCI|l@0<d@_ZXXF<scse^P6cpvBW#*(ZFjU-{8=M{z{8G&J`@Jtt-W?57 zyz_%ZITV7Nx*J3$s%a^53V3xWF1#rkcu>&Ql|}l*f)3`szJ?yrKpmbgt{d!66c_b1 zFYf3%_4{G<;_PdCzCSzrZ|ilt=W9OC-F%+?00+-B592`72A-8pa)J+<j~_Yq@QI#7 zBbQ16g8;|o6vg7d%8U#@>TGPZl@74)V_>Yf%+0}Yz<2fm1DW>!{I8s5#xORtFditG zXyMJ#Aj05K)#tQ~!NHBOVeSmIV1@-O3<;-`lO8h!=rSaz*FU?=&@lJM%xXr4=qZY# zj0+|*C<v!^xG-c`Gpsou*5%3&VZb1g=6%GH;f5}QfoFJ{2g8Q#3<u5$aBpJZ;9)Qb z>}b_wV5w$EIH9au!O$|3LB#D-xXnlHbpp?|7#J#MZnD{_V(Ho#&7oV!9v-eIr*Y0x zTwBB}ktuPOgWjRanUY40Cj1XRpJ8BFFi}+SLG$Oo73cWYojYgNw~a4e_tk#3|4K<o zkN-VAzq-JIf#G3E-Nk=8`kSR1tl1iJ|1DB|$C44py`c1aciJX?hph|?Ufv4-TmT9i z*M$>y-n{wr$&(viN1UP)Pleb1OFtBUApCF6&pMkw-@iWC_eiDDD7Mj^<;R0VXTJC> zl{r6&vnA=yvwrtK+qwV$k-e;R#4~AUgqFzTK%v4umsvfZjg~5PI!)3%s(R^v%A@-_ zznSgsn=)+3vSX~CsJkLInfaTyx<Ew9fk)pN7(VUJKX^rhgSjE2`QiEU|L^Vpx-U>= zXh`#%RLH;(=c1w)a%!{vaSjFsmj#Vl8yuy-9F)*Gz$SB$_0B==KM7_nPAW$ZISDv% zr8LMCC1|c`a?)r!yFpefLH~+_tjU4EjJC-Ie0qmMcktO3@c&BWn0HWPPa`Lb;|UJo zM8#Vjj7eP)%6%N-Gh2Ts9`s-l>Q+%W*&!0B<UP^U!!W2TL?P5e_LFT9lVy91+pdY$ zCm4*H{<Q6JH=NSADbV%`&sOK1E6k-@+!k?PNGUO#)i1VS_JyJ?Qno#23!h(Doxy*r z-_H5C!GRYN?8ZlO4D^m2cG_s6!#O$m^hTvMJgbjMZ3tV#yu0&j^7)PBEJBU02RO}G z%AFJv<s3aCd?NgAc&t#6Q85v`#c9sT+!A<5U1{<P?=5P(1b-fpNHQ{PkMPh@z9!T! ztS?aA;e5ngMX1x+NPXu7CeMvaLM{nia=Vllr2JB6<!loVDdov0Z>3~!Vz->4qo94F z^~u~Pho2Zfk$$51DVl3;l0)H=AHk0_f=*?5Nj?>LYVwpVb%v2+=;B#HcULS75j5?O za$37^ThRKz_7%b=rL!#0Hj8<BFI~H6>Sd`6@r>`8>tB@Y61+QA&TsmK@Jpv(y1$tI zg85514{w`j^YM;!4awOO>=Nmc<r3>>_$-sW+%0)L#K7F}^^E5;`Df;bMrdu*su7)d zifgLZRJEznTKXX?S1nt0Zxwgw=g`w3{VU^wYuCAj8V5gK$$4ek>d;kl16~J92dA&J zUeUfPeMNqV{gS%KznyH6hkXwFJ1+@burdDeo?{B!$&#BZbQH6BSGScmnjTTxc=l#Z znbz(Ze`R-f+pb!<(9+r1%Q$><(B~;>_Uw;m6#ke~;>Y=1WBK&YbzS#F-fKS6lwM}y zv(qp&CVSPi>C+-sd#nyz?f!bN*}AvO=Pujpx;yfBBcJ1OFZCF8_w@dAtaG0S-weLH zoNKw5udUVBb$Y9^mr5_(?NYu!Z~40!^Y+Cx%=5`#ey?)x^ItNx`hP?I=Kfm#)tx7T zZ8MJw+ijk65;jLo3-fJc^^f2F80Z{(*ui;e;?%_H4_hYgylAuds#~uce^2JIn8zxQ zbC2a7Tdc;Wrl{ua`^xv-tRUZ=%QTl+`&j!NKYQlvqO;d#n$EU0yl(V-_Vd}-XW!qR z5LFSSvu(w;9jTt1o<uE+e79{+UTc(P<kE=HsMyVJYv-<2y|#Pp?&X5Zclu5CTRrbu z`r!>9x1HR!a=YY)r#IMcuiKWptu`?|Nj!b~(RGL4P2M}Xc2{lhUt4zG_M3dGWus*4 zinkPrScO^Ln{{r^JNw$BU#7j(Pmee+aQ=qz<88vHHIHP@4W513JzYF~XL0lAj_ywH zrqg=U?eu0#AG>>E?-Q?*uZ6J>t1f+=JA3o&LvL5yUjNSQUFN&?JM+(&%-5V3X<U5a z@CoM!fln{mbI+e@kbQqmoxihxWBS(T%KFLr6T{bD->`jY`Ks@U@5SxD*%i%yZ1>p4 z{GQRh$oMnn2ezNse%k+V{=E3s`_A>s|5N@~{lCbtyg`*Qow>8ou`!zI_m8R?#r=Ku zR<~2XZMyl^v1h?fM^VTArnOB^53W_rIPqq}TE*|rXPldOE|ae{pzg)B3+#!{61xxA z@u;`9w}!Xb_Z4(RNL@6XZd!ivj&t#SmtQW^T<*<IH}tn$uj^&1Ww%N%%jTB(E|XpM z$Mn)7&s>d|8{wJbEn}&twazRrXW#ulZ$Gy`n)`_Rakcmi{S&bTl?%@u`qH{wY^9!% zj@2{=pAJ8ZDIXjKv{s1qsD2VISMB4fJS2N4T}l6<*~N|<&M{hYexE$Fe5)qCnYhQa zQst=eQk|zeThv!h*yF?Nb9c7YJh5423b{tMR(lPbCrzAC=(I}Fck;IMSLx5v%1*CI zZQb<mXkPbreYvUZC$l$CSi>5r%eBpE*^7`Ujq6(Mo6=Lm)Apb3JY_j;^*rsfZ_jK# zb$f#OocHtpzZE(p^l;L)rET8fy0Q_^_O8jY++<T&d7JC8D64i`7~2|~9bMbBa<BTY zej7f0UEN-_D&OqJxPzyf4sK=4e$5@q^|ZD2^_}Z)WBnuaBkylb{M9yB+D^2yYn{k# z@#&)VH#F{4Je6+GKed16ohe&Zu0FCe<$T(uw3pBB>CTJho3?8Dv*~<!yf<%c;k_TW z<?YvRuDA84pVhd%yLp@Q&c*v}cI-)Ae)8N|Yx|HTAzwm%g_ym&ba!jw^&fc`-$_^R z`DXIw>h0(~-~Za3+;=;aG1T{J+SSzGp1(JLw_n$}>gA7VzjI^P`sEhBJ<fcI`7gUN z-?r;JuUwVAWE&FHe@*+fJKu5negEdZFW2QY;|=YdbL^3FflK9+m)|+>f0yI6x>eRw zz2+hNW%)AMbXol@r&*t7xy=rq^*cH#dYAthyNlOe-K+ZJep{Y@j)_fVrPt?>lbN?( zHeH^a{^@+r+^KVG>-2u!Nw~deTlMqgbN93O^WX0hD0z6v^?&zs>pj-zVm|FC`LyZw zQ{nI(;j`9T#pk{%d1do&@@4<yyXWklwQgrj_Re=zd$(4neSaxE&-~ooFMBILiv7QK zb)Kc|-Mycy^1mPb{`J1t-MS+6zvoWxzFDsRZtWibJ@H>0-a6iNzT1A)KKkFC8}IMb zXB{YC@cH5$_wQ`2&FBA3{Myg&%-TP*|8yH~+v|B2c9xcp?2G=s{IlHoe9C#XdE);T z{X4x*Tui_2$JN)@&t05xapCk&(<krGty%i_)q&TN>u(=BbWCpF$qHk`xG!P9-yST# zQ!e{{@qK>(Cp8N78viqXZu}a2VfpXLl9MO@XJMZ4`A@R3?rjDJ28CpgAYTTCDpdxC zhGqtapZ^&c8eTFmlo~KFyh>nTu$sZZAf7)d{-_%R1H*Pt7srr_TS<TZ|F>t>W$?cF ztlGqIW@kjJ(j6V1LwD9(F`dylu~G4w%N)gfE^`_?C!ScK;C6BUe;%IC{(Dn?@TUgn z7zi^nAGX){efYb*#ZwP1uDG(awwUTnHbc2H1)`4=Q%`K3ocJI$T}{77^8aJL3;%n5 z9B$cIa{2E6BP^_%MJ%yBYbMU0-0IjcnRT^{>q*_4K23@jd)Q9qKAbt7r-$eN%E@;o s9K5}Lzvrn-JzKO|W=LePFfcRBe64p}xn$KG1_lNOPgg&ebxsLQ04~UWcmMzZ literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/692.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/692.png new file mode 100644 index 0000000000000000000000000000000000000000..c94bac9dcb889445ebefd423dec160af5a2ce8d2 GIT binary patch literal 3333 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 ZLEYtQ!2R5LkqitB44$rjF6*2UngER&H4*>- literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/7-kazakhstan.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/7-kazakhstan.png new file mode 100644 index 0000000000000000000000000000000000000000..ec69dd0e2e9301a2da7330df8de3be4a25a839e3 GIT binary patch literal 3324 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 P1_lOCS3j3^P6<r_^c6D+ literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/7.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/7.png new file mode 100644 index 0000000000000000000000000000000000000000..53261c06fcd02a8f850a5fd8d7e66a1c7f568e21 GIT binary patch literal 136 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U#HeD5p$iLT(xZ1A~^Qi(`mIZ}Ol2|NqysNinEJNmykWa3!Q9Nc8Sm nm}T&|o~`ZT3F$>_%nT{lI7DtG^BrSgU|{fc^>bP0l+XkK0CFbd literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/81.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/81.png new file mode 100644 index 0000000000000000000000000000000000000000..6c029f42c208ef2f5df6713ccdffbc3ef82821c6 GIT binary patch literal 327 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U)O{uY$ngvYmez7#Lo9x;Tbd^j^KPk?&xD2-|~r;k{x<mUa3q(-TS+ zNPOL9$5t!!kxT5OfLXM2(gl~Uh#U?v&bjLjZWPL!?dCG)>-qmrpWT@)t*CTJ-0c_J zujm`I^&VWCwc&B)jgK~Ws<`KE=4nx`v$o?sKEqt+&X2D@O?=$)F1Vi3Xm8K|)8%=M z;lAvBv6-tDY>{)Cv^c-`q1HKN_YNk8`)>;6EDyzSe17W~QFX-mu+C(@mYJ(s+9zLK zls|DL*QBXV6%msbU#QCUF#I;_sKk~!_liOW2Cf;WOB~l_PMmw;q~D7tv&1SlhX(Z4 i-2bTlxxW6Wtiz6p!Y^A-axyS5FnGH9xvX<aXaWG+q={Al literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/82.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/82.png new file mode 100644 index 0000000000000000000000000000000000000000..30d01ec01717a12d50b7da648ec57a881d42ffa1 GIT binary patch literal 666 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RXF;Ft2up>>FtY1|}O%7sn8b-mR1TJwy^Ej@27m?F#12w!E}zLHpb( zKB@tmuI_4SU*LS>pq}itC=DilBkp-tY_YZ#vkxDNv(fAHda$s0O^27U<J_{30{<48 z=E!c3GPuuo@Y1VyLN5F@?eG8Id(Qs;yIofY$A8DHrH>vxdKAyR>g%=ndOEtg8~5xv z^Xiq>#f&Kh7BLrRoK5po5fb5Im6DQ@kd<A__pMg`c%guxiLbH*&%Zi>hqsUOaJ&dV ztWm0UFkym^n)Aj8mAQV#CMGS35~^W0!<PhU-nw;bgHHFxjfOs#pA@7Dmp@><ceZfP z35)l?-WBfH)R{TU$KU_*<;%``)0L-sP3>1%{$$s>8{2f1l>RNZjm(yv_@(uAYPzn8 z($TwfIy!D_(_Qy^-m#7#ZPEOA)r=a;;9c@7rRLR5yeH58Wp9Ps?!P-`eSP!k-<8*E z*WX#0zvSnR6Z4jR-t(BPdY0ek!inb5ySH5baE&GY)%#}`kL9kbwSTbp-jRu`7(XoC zH<9<E<cf#3latSX?l}IK!KSjJdvdYW%g?&18V!OMcKrTg$MCU2=KSZZ=E)~j`W`P> zrBz&9yk!>KXJg@#mRVl5cjxM!Iq!4fru?zeSf)mXzQ+@M)J`8rJ^i_Meujycicn&0 z?cAF;BYiGEd47^pe$)Iwkr%x2Zh<1-zJH%NXHH5%!33v;2d-QR`6?sb=BOi<oSxpk zZk^tzpEeAotGxbC6{rp4a1CTQ@%(edw4>j?nH@fSI5prwPM@obn3$N>RP9w!`DbPR cch@maWb?n<!tkM#fq{X+)78&qol`;+0FIY6egFUf literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/84.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/84.png new file mode 100644 index 0000000000000000000000000000000000000000..1a101f13622a34fdff0e3389a4d53fb7bc7c23f8 GIT binary patch literal 354 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWpTt2Vz?U84{K10##4i(`mI@776%euo_dT<=${b-m}(&>g~|#p230 zQIGwEWD$#(mzRnKkBOy=l-3jvL51xLnXEgnJ0~^<d^pDTF#g=TJ#+u3Up284jtaQI z_9Z+amo<0x>};OC_H4fP+5g$!^P9==T<bl!@cU7ZfW*w$)(bDKSrmJ?RrKF`tkVbz zy}7<-zmk=%#lD*4_eU0PtMO!9oqqOoXnRRq?k#CoEB+b1yLH~5?ce-#Zlc+{s)g1E z1EPHMbl%O5o$geWJ#mj!lUoAA?Q#~k6JoP+7G7Mo=IY7ad;Gt;Nv;=mE|O&|D4BRs zdTmA32J7E@PXC_u<Icx_E3&h1uj*aI6~La$@^5t%b3H?YMX-{{nq3hL3=9mOu6{1- HoD!M<I0K9h literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/850.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/850.png new file mode 100644 index 0000000000000000000000000000000000000000..efecdb6b018af952ebd1340c6c4244608e306f68 GIT binary patch literal 2968 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 E03H*F1poj5 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/852.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/852.png new file mode 100644 index 0000000000000000000000000000000000000000..bc443392e955cac72568952bf0f8d7d9b080abd9 GIT binary patch literal 403 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIa0H@WH4xY~p42-6pE{-7<y=N!y^>#`WX_t?;_u7*b-n~dkqlxFv zY?)gv9J^Y01uuqOxNs@VnZ;N4(k-2<0<{gv$=>x26DBx$K5~!U;@x~NJX-ooRqK*@ zKmW{oIOl$0N*epDFANvCU;Y=+zNNMBZ}RP=>2np&`>>Q2i$`9qcVDBkE}**Zkga8} zM8MBo`;|9r>@BVkd93BB`}c;~>kU~)D_$|S9N4jUfxgT^9npX!si1oI6^i#BeV*|% z=6h0V;>?51ENOPfkDnB@VD&5IxPC(7>gHgnr$WA&+s;JEo%e{Hba_$Dy*ZCL-3xv; zvy^{Zk$(Av)4sJ^7E2tk7C$N<ufAa$Yn=Y3m?Pqst&XladT9BBZe2y2zb`tSdtR$- ziaAnqwoD}Y-44-roti1G%L6yAo3~c(|B`=`Hky9_u76kee#+v+O|#E?+A%ONFnGH9 KxvX<aXaWFUaj_@> literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/853.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/853.png new file mode 100644 index 0000000000000000000000000000000000000000..f1b63fc10a547025a8b9421fffc936b9e2ffead5 GIT binary patch literal 417 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{W>=fFNcAIp<*Tj1EaI2i(`mI@7*c3-cE@k$LssQ-%2o7>SnswZE<H= z+W~Qo+uY{u%Q*h<=+(ANyEbiK`%agX?h7xTonF$i==(H%&G6cFyk~6}KDktHJ4gQi zXZv>L9s8m-u>HKo7|WIOLdbb~J$oPfZ;4$h+1pOe%H@d_;{R69`g+wOfdFZ?0^45o z-y96ri&yY6b1-rrEt}G3yihNB<wPT0uAMfmG4rNm8cvg%8Bkt&eCa$-C81;QFL$th zXZ-NnL};S3*IO}zNZy*t8mE6>=G=QO;9J9AUR-wY-ZQc7`uumM%GbXMbu*kiX{WlU zThbObzO{|cMncK&Jkx?|6}^r>O>6#rJ-w%wH|lRW!=c;OJu%aZHgB}rkypN94s#hZ zo1G#<g>+3qzx$Q9eL`z`l8j~ttqGfOuc_pUlI+$A_nMNn9!fC_Z94k@ujuJl+;?Uc WS>Jh4bBck1fx*+&&t;ucLK6UyA*_l3 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/855.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/855.png new file mode 100644 index 0000000000000000000000000000000000000000..8ddee31654d7f75c59c720120ca6dc1efb15e394 GIT binary patch literal 421 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxQx2S;uTB`42<rcE{-7<y;r9k^kNDWY1?nTSaq?XR+olrt`^54 zvCb61knS#x48bjVg1Sv{3tYb#*n0k8aN=I+x=q8!ury7=_xU}sCIOR#?#Pm|cINv( ze?E|L{1+zZkm=~a%-Y^{E?ptvqsZ-V<vJ`Y9$IX&>7Uef@A2n|gnCh@1ev^^TQBMw zZ#X7ik*<Dy_^6%eCuyI&bprPGi=wrRHyU`SnDcWe@u%+73VkGgT;udX-Kq8GpDtIb zu5X{_*_682rgNU(0u$NDYzD5gCLg7A@14*4H!b3{3TJxo1n-3{OxyeI=C&P`OxfEj zRq-P8a`@5peZ6x{zZ?;{RP@2hthq{BRdv(T3+qEB%1S>iJ2+V)di7&fzVlBz;^#fM zIx8cM=jJv>i5q&mZaYW4GngoOINPi<e(t6EzYG3<`qr%6<$b+zPRR-pUzUS^+xI_` ZwzytsrIPJ0#K6G7;OXk;vd$@?2>_~Ut5^U4 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/856.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/856.png new file mode 100644 index 0000000000000000000000000000000000000000..1461d62e71ee0e1d11dbb283267c9f473c7e1201 GIT binary patch literal 264 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{W_;0k1{)d?|CT(28LChE{-7<y~#G}E56NdWNYXYYrAwMVcO&zClkXN z6CIVC6C}DhKO6n9d}U~0U|^7QtG@H$)!XM;c+(V@IE4GoO1$61#3tw{$HvCS*7ogt zyu=(KHG`OaUq#m2H6`AOl;JR+(%9JO=rW;w;bw~o3%WU_)+|<Yog#6PEq>?M9u31u zyn63r&wqIKSKQ+N+28s+Pxrp&l=9gea?m37PLo>XtfPOfTSv{XW;xBpV6ejA@XU@$ S%NQ6K7(8A5T-G@yGywoZw_#}j literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/86.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/86.png new file mode 100644 index 0000000000000000000000000000000000000000..8cc127bc3f115545663b659913f684524c4264cd GIT binary patch literal 319 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWQ|hlr5$%b9x_7#JRUx;Tbd^uE35o5f@(aqMILq{2C^ix)py#eIEV zN|>DE3)QMg_YS(g57^VBF3=^t@JiRI7d0v`o!p`o5BY>Qo!8s*aC-ZmB*QM9J`T=n zJC<!%{x5TDy7`_)#aS}1S6(l8|FhwPf5k`5n?B4<fsL=i*XuH-BuMU2X(>E*`88kt z3dRkV9cl`z%)c?53EVK>bb_w;#z`siuWA&dHNI@yrE}`(%6X3KRaa$SxIbM!t3Q~p zxW8uQoQ}<g4RP}~2!+aeODxa1*K{iPVOHh-WTAua?yhgW*OJTk>$K%M{{QCuKl&cZ Zi-<TT7xTTZU|?Wi@O1TaS?83{1OT~jhXDWp literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/870.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/870.png new file mode 100644 index 0000000000000000000000000000000000000000..c68fefef31d27d0b28ef370ba9b0e278d772a5b2 GIT binary patch literal 3427 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 Vy1hS`GB7YOc)I$ztaD0e0swP^WPShu literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/871.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/871.png new file mode 100644 index 0000000000000000000000000000000000000000..c68fefef31d27d0b28ef370ba9b0e278d772a5b2 GIT binary patch literal 3427 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 Vy1hS`GB7YOc)I$ztaD0e0swP^WPShu literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/872.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/872.png new file mode 100644 index 0000000000000000000000000000000000000000..c68fefef31d27d0b28ef370ba9b0e278d772a5b2 GIT binary patch literal 3427 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 Vy1hS`GB7YOc)I$ztaD0e0swP^WPShu literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/873.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/873.png new file mode 100644 index 0000000000000000000000000000000000000000..c68fefef31d27d0b28ef370ba9b0e278d772a5b2 GIT binary patch literal 3427 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 Vy1hS`GB7YOc)I$ztaD0e0swP^WPShu literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/874.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/874.png new file mode 100644 index 0000000000000000000000000000000000000000..c68fefef31d27d0b28ef370ba9b0e278d772a5b2 GIT binary patch literal 3427 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 Vy1hS`GB7YOc)I$ztaD0e0swP^WPShu literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/880.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/880.png new file mode 100644 index 0000000000000000000000000000000000000000..56500cb283c9ccea8f49643692b9b5713f794903 GIT binary patch literal 2992 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/886.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/886.png new file mode 100644 index 0000000000000000000000000000000000000000..9e08a99299eefd264f6597c3933130b23a9e46c8 GIT binary patch literal 322 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWSOh^YCu!|zlW7#N;-x;Tbd^d>(E@cn<jflWeEQu5#b;{s_v>Z;Y+ zB=in6=`C2QbyPuL_soY6FQePCx6c>M6^Lt|yT(LPX3Z7JD<VfE^K2_l7CgA&+g5vg z-Q-jQ;X7<;YHX>jfo$Q@xoq|3&TQt=xnRCnnp({-PU%0--`ltS=8reNv+H|PV`F3E z!(Vfs^L&@zm-gfHclk)R#VkiB8kb94nW8GW?oQRI4_|NVw<)K;n~<u}cF?}=U)qeF zUl^JvFqj|S%^xrMZ_>*sHa0f4X^*~(8@S$|kSix+@Gm{#fX{{yh9|B+R~$TiJAq+o Xtcyu?>LV!z1_lOCS3j3^P6<r_pLu}! literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/90.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/90.png new file mode 100644 index 0000000000000000000000000000000000000000..f0baa01617e063f2375cd96af633cf1b8fd0781f GIT binary patch literal 389 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U$T3n1*)3#izXt42<fYE{-7<y;rZC^<oN?IQDV><&A2JB^+*@hvtZh z>PBho(CXR1AH$-%tE23$yP_jo?yXjVT%|>?J2FGwBnL<C2${i{e#y-3$^?r}m-D|K zmh<U9x7~N#VTY@{(pToc(h)}%@u&%V9DJ}o;h~i$=fWr7CS1Q1As)WRc7K92>zT=y z?<G9Vkhk3GP^#HnyLNkY)54Udw!8~Qy6-n!P?J2jCg}V#yFiBH2lr_T-ME#auqa9V zbj0)Nat}0l8E$Lr?7H+rLfQH@hwicLnv1u4=l*7A_)~1WF6r}1C)?@{k-(4V{T@$w zZ*^$jdv2py_HJ>0fxh`Qa|>(cwZ*-tI;Iy9<fOsA`^0PQx8Z>YukoJw6B+)X|AV_r u36tiUM^o}LuVu}<&{wejXMO!qYolw4cV%@SCNMBCFnGH9xvX<aXaWG`siqqM literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/90392.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/90392.png new file mode 100644 index 0000000000000000000000000000000000000000..ba425f0f787d1e1bfa8d116481a1b6c95930564f GIT binary patch literal 390 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWpTt2851!>nfv42&9{E{-7<y~%(6|NmdlCdDxAd~I+>q8{_(WWB;! zJ7x%m2mIOjf5OB2tFQCuNUh@W`SV9b`b?AFhxdQk+rHmC%41*lChftOuPF)BCZ!}? zy_oc%i%<FNlm8hHd^7I+S6|fizdm+Hi@=}1zyF^XUjG08?_a#JmVOOMAKvf&{@?zY z(EtAncX2vD+^--1uYTVCC;yj!yZ8UTS)O8sfmgtvoz?w~AP^eOd^o*+zFqo{OZL4F z&+o0~5lZKqD0WQelot;O>|cNHK)?wd-IE7`XB_?T|EYG{ee-<74J%epk~k*VX>sTW zk7r`f#5@1?{y*@+Zh6bY<KJQ@gedABJ+Nx^*#n}IP8WWkU?{h$`!VC;{nOzSO3oa2 tpR|yN$K*_<&K_6GEe7J{0RdbLpUgbe_C(*dVqjok@O1TaS?83{1OVzEw;TWf literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/91.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/91.png new file mode 100644 index 0000000000000000000000000000000000000000..0e8ce1fa92f59c27cdfd7133008f057fb72f6458 GIT binary patch literal 249 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{U)Ovm!aTQOP{k03=Fe9T^vI!df#5&$lGinz<Qxx@xif#BMdiqxl>N9 znQ(K#21$m*(`Oqe1TQaY)KL2Pdj0are)BIqpIYE37rwy!kFb|!>iqP#T+eMf8uo=7 zEIWJXX7^0~p2tlB68DUkWggrn`#do_+@#l`$2#dw;B}^RKR<6U)OAYmh;%w7q-d}u zEb_-pZH~YM39B=k*##Z5TUTjqGAPY2)PL{)P=DPVlP85AzJff);OXk;vd$@?2>?dj BV9Nji literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/92.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/92.png new file mode 100644 index 0000000000000000000000000000000000000000..6e105a14409b87ebba7c0c7dfdbd8aac53614843 GIT binary patch literal 419 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWpTrwDh>^=ZKj42-UxE{-7<y~%(6|NmdlW^!TU;^u`#wW_-eJR3Td z?l#|h;Lz;A=IiPEN-E}k@c9F$51uag`?GXM#GF)_=E=K+!@lYqkyxd@_H@9@$dd{& zDm>zo#iymukT9G0@bC8TJYHA5o=D2%37z}!<gaksdi(u{j>_$;JrS^JTg$^AcOUb- z(RstO|KGnP2fqXtNgZ8<vkE8mPaZgS=-7wX=k<5|xT9SBFo#c!uT8$b?!Zr%){mF@ z{UvwY*dg&q_i^E>pTGa>bIsRi4xbl!^27W8_HFMT<grPMO7m38$I7?eudiR+At)*` zeSTcpiu)nwFTA;breMPpg~Pi(zn18`V|nPn(PKvw)bfu_*sR3v%pb^RY+zupB_}&^ z$JbY4&Yjtu&Hd~6o40N<J$$TNTJqBLWyX7c{pqS{mXwf?2%8&ubi=DNT8pmm_38L> bF)-u_Cu^$yd2pS9fq}u()z4*}Q$iB}+fuwB literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/93.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/93.png new file mode 100644 index 0000000000000000000000000000000000000000..45e31c8fdde0c1653225b4015f5c350290cf7428 GIT binary patch literal 377 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxQtr-k{MGN7#O8JT^vI!daq92>vfnxq;>xlfusP%jU7r70Wn8c zb}v<8TgYmyVb{3+;ernS2QMCJ@wsmId8IQ+S0H2SBGLa@3$LjpY`J(=S8<8Omo?Al z^&fs3$M{3hDf&QB%l7%rm(Q+HNm;b^-j(_U!;=%1yk61fcp>9L?$#F(Veb<P6XhbW zet5;l)8X;ca>+q|jyE@A-e{?~Cf2O&sjof#<(-_d_3a(v(|e|No!B&I-^UYc{3O~^ z%)@?Elzq^deKCk}i=vwPvWBc($KG$1W_dXKTd1gK=!G8DnB9IKu047A)v)%lzP-YV zyM{3lPo7>-X>N!*_t=X6$Gq8cJQr*%oFernnT9F8xnyU(ytsyG=Z~00#h<m+zKa^4 jd@=vm`_KP)|8U=a6Jj$n{SiL{0|SGntDnm{r-UW|k3FaG literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/94.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/94.png new file mode 100644 index 0000000000000000000000000000000000000000..b102a002100b583ce99176321556c6c0e16958a8 GIT binary patch literal 3164 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 F2>?i7?O*@^ literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/95.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/95.png new file mode 100644 index 0000000000000000000000000000000000000000..c840e3835685b9ca18ed5316cbdd9075da369f63 GIT binary patch literal 2998 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/960.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/960.png new file mode 100644 index 0000000000000000000000000000000000000000..97a58b98d2c76159ef4068d57120b1c215bb7773 GIT binary patch literal 295 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWo^v!V1$o;f`X3=AhdT^vI!dXx94Bz&6R$kxy))^_Pif>_EqX+5?! zYo(qP$t9nj**BjsmtmVfDKTlqhXlv#&LS^vd~84X`kMV4w#Uq(W`;cR_5V{MN(!Y9 z@2~$aY4`tETexi?+itDdMlZUwja}B~wsadB8X5ih-EZwEC|a3zDT*h3-dzX5!|56q z+2+?2^&WKbVh&YS{^y}~>VfTdKjWCH!gj^;N>dygn%f(v&+T5&-PyP2++y#}&{GFM upkvO`%xQhD`wR^_Y!>-lH2iXki-G%_E#K|a?UoD-3=E#GelF{r5}E*oW_3LP literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/961.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/961.png new file mode 100644 index 0000000000000000000000000000000000000000..c7001fb90c622dd11fbac14d7932b8d371ab91cb GIT binary patch literal 383 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RXFuxGJ;n*De_b21Z3s7sn8b-nUmAwOI-!j(>b#|8~=jGTmw2Q<yC^ zJk54$<R6UfJLUQ==8nu9nLSM6J1sO+JuPz<6(n@_EcMx4HrsUe%!~iz)vwR&{PxW9 z|F17!zS#5^EpKmO3t1wV;ruWorA8~{LZDMjNJd1b#|p{wa%(Ti`foI{a*wZ8Gmz(P z&a;;*3awo5Btj)Qca6W!f)DTQ3XZkqZYtf~{xfpnX4R7iUrX+M<eOl;Z-dy`bC(UL zg*$g2`;_x3tnqWuzx)ls-wocF&&yVmJ<ECO9sA!-IlX)DHFsaVf4D8z=$h#Xx3Y<g zk_{)xB@`ER=A_No{i<7EW`}6ghWHbONpVG4aht6-yX9x!5@!vFKmB>%hQ%!sU22WH nn4&X-tbVM!@_+r0mHEOddE7Y)|I!#37#KWV{an^LB{Ts5HyWgW literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/962.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/962.png new file mode 100644 index 0000000000000000000000000000000000000000..240d0f52ca1787e3cd77a141ad6b763e19fa1d5b GIT binary patch literal 2965 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 Bfo=c* literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/963.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/963.png new file mode 100644 index 0000000000000000000000000000000000000000..32f0122ab71a70c0b16940f5f0ac53b1fcef8269 GIT binary patch literal 241 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{W>=fkFM&RyY(PllRaG=Lo9leZPZtMo8QRR&?(l|C)L9w@&Et-e$!9? zjQ^x3q$C_!EjeFmj^4cD*8+~=4?{o8b4PrCaHhVklJo!Tb8G+qfB)Z}=V$%@|9{t8 zZ~te1cu&j!*K%_XrX8tY<M{Ez>FBoNe0hV8;!nmAohuVcFKi4imx!zTe<DF;%>jk; s$~}tx#yy&>Jv=--i!Jjzq<9!Ygo<y!TH9pLz`(%Z>FVdQ&MBb@0J)B0v;Y7A literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/964.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/964.png new file mode 100644 index 0000000000000000000000000000000000000000..1e3371f0029bcfacc903ac1b4b1f4d035b46450f GIT binary patch literal 261 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxE`-uhUroU28QLHE{-7<y>BP$a~}#|I8y&U_i$$yN0I?&)HR<v zr~S=0lSIz2ZRBrH5OCedaai4mE$@uI&E^G7i5(kOAOCwuR((&~VoygS-VC<7Nf)C2 z=j}OJ$JS6C!L)(9w(a)I<2^smdAtk%^P**Wn9KH-dr2;vW89B2Hh-0HmdY?;`Y3C$ zXU@xAwOwIy4PNV7aw9~1q(#|FviLqfZY$gR)o6ayaVM{tw(tE9^548^TKX__$7==# O1_n=8KbLh*2~7ZB9%ZZm literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/965.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/965.png new file mode 100644 index 0000000000000000000000000000000000000000..ec3f620292c2bd0423608470e1cffcef7021722a GIT binary patch literal 2923 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 Kb6Mw<&;$U_hH-EJ literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/966.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/966.png new file mode 100644 index 0000000000000000000000000000000000000000..811fbe23dbc1c42e844a9b914f5687235a386637 GIT binary patch literal 437 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWo^vz)HUD#Ho}2F7qt7sn8b-m6n~dmUyFY1?nIZ0#|r+fCv<$Hew1 zHZ}7#ZEntgz-{r9&#txqp<14+0Q*a&M+-WpuUczz>B_M!YPou)MFNXgL>$cjV_73p z$LF4qz0a4uqr~Y}U}1Q~j0q`KODvR>@@^mfYHL)hd}No3UbZX4rMrJ6{CJONC+_TB z-?_Q#)U!iY$EOw-Ew#~J?A_tS64>z}Av}{!FDhw9^lG1jyV93$_<v3?>geX|;t<PZ zqm_npWsVm~znS_eWRBD-n-2LOGSmELcxBy~CmiLvB4Ru5tbM10S09XxRaZQjb7t+r zBSLeot#cHZ@k0Lbk|IYFAzo4L#OG$WtdBf8pmTKBIS2o)RmqQ7?fA>4u340{RVrpi z`I#j~ccN0eO8;g*y8j^m{M)IaxssuBPd5JiqUL{Gl;?QLZ!Px=eEan8?X~1Md%3VR sXMWd*%f0r83Ve8u|0#L)|8JN0GvTHnhg(Vt3=9kmp00i_>zopr0FHRQ4*&oF literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/967.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/967.png new file mode 100644 index 0000000000000000000000000000000000000000..cf4fc8a339cc5d53d0aa0dc9a1a528fe84b211d5 GIT binary patch literal 137 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RR&|pOEZ6w;x*>7#OrYT^vI!dXsI`SA3h_$kxy)*48K0!zA(l|9^X_ oO6MyHZftCA7f(npVq<0qS;j7{vGV^71_lNOPgg&ebxsLQ0Ct@y*8l(j literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/968.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/968.png new file mode 100644 index 0000000000000000000000000000000000000000..3d7b6102e7d09105d30bbaa621f428f9a32f3771 GIT binary patch literal 2927 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 N44$rjF6*2UngC3qZ$|(C literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/971.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/971.png new file mode 100644 index 0000000000000000000000000000000000000000..db976ea171d47f8e2155be63cfde307e4ef16a4b GIT binary patch literal 2873 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 Hl+XkKwUJlb literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/972.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/972.png new file mode 100644 index 0000000000000000000000000000000000000000..560752617fed5108121fa882a025beb7442d4f26 GIT binary patch literal 3052 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/973.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/973.png new file mode 100644 index 0000000000000000000000000000000000000000..fb803fd859294e979f25ea7ef7ae76f4e954f739 GIT binary patch literal 2964 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 A?f?J) literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/974.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/974.png new file mode 100644 index 0000000000000000000000000000000000000000..88f902a36b9d7d3a954f4f337a4015f866941512 GIT binary patch literal 2834 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 R>T(0b_jL7hS?83{1OT7UKD__{ literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/975.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/975.png new file mode 100644 index 0000000000000000000000000000000000000000..acf16d9ff145df0ea71f052ac835eaebb878251f GIT binary patch literal 601 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RTIyxQx2S;uTB`42-`$T^vI!de>gI&Ik^aIsWne&YdsMo?UISwd}&I zpslhsjS&L7G+#V;@aRccx%<IDE+q?2rE_^!77|e!K?35_n*yR<I_F+il!#t)>~*f+ zJ6nGCjY6}hvHY4}|NQX(gM#<H({<P*w(%9YJ(lB0V|9wSvdYy@JICXB!Gn2+mb9NZ zF2CWc5g*&rc@F-%Lg)WW?^BLQb1SUYpQtg%wTmZkiAVix*V;9LdDlLyc#|@>Eb#G< z9Q7`X_Tv|hP4ao_G37bODJk2?L#xAPeabC=Gs9}${9{p#_o9}6{Gzxjzh%k$!#lZe zyb^Eey4AjRw(6UGU4gtoSA_~|CtNup@#&gD(qb>8Niz%_eD=OskbGR-qDp+S)PaMI z|7Nea<)3uZ!G2$VpVE;}OcBjL-oM<p;Y{3-AI%*n*>`kzW&AMNR3TKq+b=0k?DA8^ zp1ix4>o%0Nd+xB@^Vq!OH~WV5^Cr)$_usSCvFMN4zqy=`YW%AU4ySQmPG;Hr{l>pg zmADv(y0~N7BL7(K`CR;(!L)Z@TYXN)nV%P@ZOHg_DQ}uaHLrU8%z{kTdA5qXisnvv zp6IPFka*$GuKyfmn{O4#`dUwWYIo+B)UUfg&S~t7;oJUPpmFkz+6n8Y#=2)W<#H57 zEes29n7XW?^7kyaaz^tX8`(FqubIYE<d*z;E%UBFE0sUw|7DwZ;PTu9@-sk*h{4m< K&t;ucLK6U|$Qe%n literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/976.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/976.png new file mode 100644 index 0000000000000000000000000000000000000000..57d20aec04316e3644230e01a28fed71b1734771 GIT binary patch literal 2972 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 Iol`;+09?3!A^-pY literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/977.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/977.png new file mode 100644 index 0000000000000000000000000000000000000000..03ca19284acf9aad8812ac67720d2986f22e652a GIT binary patch literal 3373 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 O1B0ilpUXO@geCw@Tt$5V literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/98.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/98.png new file mode 100644 index 0000000000000000000000000000000000000000..f8c7d6749636bcffd74fbb3963cb97464643ace1 GIT binary patch literal 2957 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`(#+;1OBOz`!jG!i)^F z=14FwaQSCCI|l@0<d@_ZXXF<scse^P6cpvBW#*(ZFjU-{8=M{z{8G&J`@Jtt-W?57 zyz_%ZITV7Nx*J3$s%a^53V3xWF1#rkcu>&Ql|}l*f)3`szJ?yrKpmbgt{d!66c_b1 zFYf3%_4{G<;_PdCzCSzrZ|ilt=W9OC-F%+?00+-B592`72A-8pa)J+<j~_Yq@QI#7 zBbQ16g8;|o6vg7d%8U#@>TGPZl@74)V_>Yf%+0}Yz<2fm1DW>!{I8s5#xORtFditG zXyMJ#Aj05K)#tQ~!NHBOVeSmIV1@-O3<;-`lO8h!=rSaz*FU?=&@lJM%xXr4=qZY# zj0+|*C<v!^xG-c`Gpsou*5%3&VZb1g=6%GH;f5}QfoFJ{2g8Q#3<u5$aBpJZ;9)Qb z>}b_wV5w$EIH9au!O$|3LB#D-xXnlHbpp?|7#J#MZnD{_V(Ho#&7oV!9v-eIr*Y0x zTwBB}ktuPOgWjRanUY40Cj1XRpJ8BFFi}+SLG$Oo73cWYojYgNw~a4e_tk#3|4K<o zkN-VAzq-JIf#G3E-Nk=8`kSR1tl1iJ|1DB|$C44py`c1aciJX?hph|?Ufv4-TmT9i z*M$>y-n{wr$&(viN1UP)Pleb1OFtBUApCF6&pMkw-@iWC_eiDDD7Mj^<;R0VXTJC> zl{r6&vnA=yvwrtK+qwV$k-e;R#4~AUgqFzTK%v4umsvfZjg~5PI!)3%s(R^v%A@-_ zznSgsn=)+3vSX~CsJkLInfaTyx<Ew9fk)pN7(VUJKX^rhgSjE2`QiEU|L^Vpx-U>= zXh`#%RLH;(=c1w)a%!{vaSjFsmj#Vl8yuy-9F)*Gz$SB$_0B==KM7_nPAW$ZISDv% zr8LMCC1|c`a?)r!yFpefLH~+_tjU4EjJC-Ie0qmMcktO3@c&BWn0HWPPa`Lb;|UJo zM8#Vjj7eP)%6%N-Gh2Ts9`s-l>Q+%W*&!0B<UP^U!!W2TL?P5e_LFT9lVy91+pdY$ zCm4*H{<Q6JH=NSADbV%`&sOK1E6k-@+!k?PNGUO#)i1VS_JyJ?Qno#23!h(Doxy*r z-_H5C!GRYN?8ZlO4D^m2cG_s6!#O$m^hTvMJgbjMZ3tV#yu0&j^7)PBEJBU02RO}G z%AFJv<s3aCd?NgAc&t#6Q85v`#c9sT+!A<5U1{<P?=5P(1b-fpNHQ{PkMPh@z9!T! ztS?aA;e5ngMX1x+NPXu7CeMvaLM{nia=Vllr2JB6<!loVDdov0Z>3~!Vz->4qo94F z^~u~Pho2Zfk$$51DVl3;l0)H=AHk0_f=*?5Nj?>LYVwpVb%v2+=;B#HcULS75j5?O za$37^ThRKz_7%b=rL!#0Hj8<BFI~H6>Sd`6@r>`8>tB@Y61+QA&TsmK@Jpv(y1$tI zg85514{w`j^YM;!4awOO>=Nmc<r3>>_$-sW+%0)L#K7F}^^E5;`Df;bMrdu*su7)d zifgLZRJEznTKXX?S1nt0Zxwgw=g`w3{VU^wYuCAj8V5gK$$4ek>d;kl16~J92dA&J zUeUfPeMNqV{gS%KznyH6hkXwFJ1+@burdDeo?{B!$&#BZbQH6BSGScmnjTTxc=l#Z znbz(Ze`R-f+pb!<(9+r1%Q$><(B~;>_Uw;m6#ke~;>Y=1WBK&YbzS#F-fKS6lwM}y zv(qp&CVSPi>C+-sd#nyz?f!bN*}AvO=Pujpx;yfBBcJ1OFZCF8_w@dAtaG0S-weLH zoNKw5udUVBb$Y9^mr5_(?NYu!Z~40!^Y+Cx%=5`#ey?)x^ItNx`hP?I=Kfm#)tx7T zZ8MJw+ijk65;jLo3-fJc^^f2F80Z{(*ui;e;?%_H4_hYgylAuds#~uce^2JIn8zxQ zbC2a7Tdc;Wrl{ua`^xv-tRUZ=%QTl+`&j!NKYQlvqO;d#n$EU0yl(V-_Vd}-XW!qR z5LFSSvu(w;9jTt1o<uE+e79{+UTc(P<kE=HsMyVJYv-<2y|#Pp?&X5Zclu5CTRrbu z`r!>9x1HR!a=YY)r#IMcuiKWptu`?|Nj!b~(RGL4P2M}Xc2{lhUt4zG_M3dGWus*4 zinkPrScO^Ln{{r^JNw$BU#7j(Pmee+aQ=qz<88vHHIHP@4W513JzYF~XL0lAj_ywH zrqg=U?eu0#AG>>E?-Q?*uZ6J>t1f+=JA3o&LvL5yUjNSQUFN&?JM+(&%-5V3X<U5a z@CoM!fln{mbI+e@kbQqmoxihxWBS(T%KFLr6T{bD->`jY`Ks@U@5SxD*%i%yZ1>p4 z{GQRh$oMnn2ezNse%k+V{=E3s`_A>s|5N@~{lCbtyg`*Qow>8ou`!zI_m8R?#r=Ku zR<~2XZMyl^v1h?fM^VTArnOB^53W_rIPqq}TE*|rXPldOE|ae{pzg)B3+#!{61xxA z@u;`9w}!Xb_Z4(RNL@6XZd!ivj&t#SmtQW^T<*<IH}tn$uj^&1Ww%N%%jTB(E|XpM z$Mn)7&s>d|8{wJbEn}&twazRrXW#ulZ$Gy`n)`_Rakcmi{S&bTl?%@u`qH{wY^9!% zj@2{=pAJ8ZDIXjKv{s1qsD2VISMB4fJS2N4T}l6<*~N|<&M{hYexE$Fe5)qCnYhQa zQst=eQk|zeThv!h*yF?Nb9c7YJh5423b{tMR(lPbCrzAC=(I}Fck;IMSLx5v%1*CI zZQb<mXkPbreYvUZC$l$CSi>5r%eBpE*^7`Ujq6(Mo6=Lm)Apb3JY_j;^*rsfZ_jK# zb$f#OocHtpzZE(p^l;L)rET8fy0Q_^_O8jY++<T&d7JC8D64i`7~2|~9bMbBa<BTY zej7f0UEN-_D&OqJxPzyf4sK=4e$5@q^|ZD2^_}Z)WBnuaBkylb{M9yB+D^2yYn{k# z@#&)VH#F{4Je6+GKed16ohe&Zu0FCe<$T(uw3pBB>CTJho3?8Dv*~<!yf<%c;k_TW z<?YvRuDA84pVhd%yLp@Q&c*v}cI-)Ae)8N|Yx|HTAzwm%g_ym&ba!jw^&fc`-$_^R z`DXIw>h0(~-~Za3+;=;aG1T{J+SSzGp1(JLw_n$}>gA7VzjI^P`sEhBJ<fcI`7gUN z-?r;JuUwVAWE&FHe@*+fJKu5negEdZFW2QY;|=YdbL^3FflK9+m)|+>f0yI6x>eRw zz2+hNW%)AMbXol@r&*t7xy=rq^*cH#dYAthyNlOe-K+ZJep{Y@j)_fVrPt?>lbN?( zHeH^a{^@+r+^KVG>-2u!Nw~deTlMqgbN93O^WX0hD0z6v^?&zs>pj-zVm|FC`LyZw zQ{nI(;j`9T#pk{%d1do&@@4<yyXWklwQgrj_Re=zd$(4neSaxE&-~ooFMBILiv7QK zb)Kc|-Mycy^1mPb{`J1t-MS+6zvoWxzFDsRZtWibJ@H>0-a6iNzT1A)KKkFC8}IMb zXB{YC@cH5$_wQ`2&FBA3{Myg&%-TP*|8yH~+v|B2c9xcp?2G=s{IlHoe9C#XdE);T z{X4x*Tui_2$JN)@&t05xapCk&(<krGty%i_)q&TN>u(=BbWCpF$qHk`xG!P9-yST# zQ!e{{@qK>(Cp8N78viqXZu}a2VfpXLl9MO@XJMZ4`A@R3?rjDJ28CpgAYTTCDpdxC zhGqtapZ^&c8eTFmlo~KFyh>nTu$sZZAf7)d{-_%R1H%qa7srr_TW_W;<U8ab;BtSP zDofW2m4lB86*xE5pImNI+QJfYnWgif$;w8Dmv?oUe1k6*_f)1CC8d|UhJIX_vGabV zC*KE^{DT*(_8qct7IA3~;@iHtiNQ<!=|2XC^x&$WTaQQCZ03o+F2K^_c_?MKY}&E+ zn^>MN$gLDVTJZE}=Bd{eODyMhT;3AC!RB+!)hHF7pPtDZLR+N{XKZws`}$jScZ|98 s)Q=LmQf*6O_L=_E=l}Tit{pRjRL*Q)p%<0T3=9kmp00i_>zopr02*d}&Hw-a literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/992.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/992.png new file mode 100644 index 0000000000000000000000000000000000000000..9b571f6d8502a7671ba1592785a5140568f185c0 GIT binary patch literal 2933 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`(#+;1OBOz`!jG!i)^F z=14FwaQSCCI|l@0<d@_ZXXF<scse^P6cpvBW#*(ZFjU-{8=M{z{8G&J`@Jtt-W?57 zyz_%ZITV7Nx*J3$s%a^53V3xWF1#rkcu>&Ql|}l*f)3`szJ?yrKpmbgt{d!66c_b1 zFYf3%_4{G<;_PdCzCSzrZ|ilt=W9OC-F%+?00+-B592`72A-8pa)J+<j~_Yq@QI#7 zBbQ16g8;|o6vg7d%8U#@>TGPZl@74)V_>Yf%+0}Yz<2fm1DW>!{I8s5#xORtFditG zXyMJ#Aj05K)#tQ~!NHBOVeSmIV1@-O3<;-`lO8h!=rSaz*FU?=&@lJM%xXr4=qZY# zj0+|*C<v!^xG-c`Gpsou*5%3&VZb1g=6%GH;f5}QfoFJ{2g8Q#3<u5$aBpJZ;9)Qb z>}b_wV5w$EIH9au!O$|3LB#D-xXnlHbpp?|7#J#MZnD{_V(Ho#&7oV!9v-eIr*Y0x zTwBB}ktuPOgWjRanUY40Cj1XRpJ8BFFi}+SLG$Oo73cWYojYgNw~a4e_tk#3|4K<o zkN-VAzq-JIf#G3E-Nk=8`kSR1tl1iJ|1DB|$C44py`c1aciJX?hph|?Ufv4-TmT9i z*M$>y-n{wr$&(viN1UP)Pleb1OFtBUApCF6&pMkw-@iWC_eiDDD7Mj^<;R0VXTJC> zl{r6&vnA=yvwrtK+qwV$k-e;R#4~AUgqFzTK%v4umsvfZjg~5PI!)3%s(R^v%A@-_ zznSgsn=)+3vSX~CsJkLInfaTyx<Ew9fk)pN7(VUJKX^rhgSjE2`QiEU|L^Vpx-U>= zXh`#%RLH;(=c1w)a%!{vaSjFsmj#Vl8yuy-9F)*Gz$SB$_0B==KM7_nPAW$ZISDv% zr8LMCC1|c`a?)r!yFpefLH~+_tjU4EjJC-Ie0qmMcktO3@c&BWn0HWPPa`Lb;|UJo zM8#Vjj7eP)%6%N-Gh2Ts9`s-l>Q+%W*&!0B<UP^U!!W2TL?P5e_LFT9lVy91+pdY$ zCm4*H{<Q6JH=NSADbV%`&sOK1E6k-@+!k?PNGUO#)i1VS_JyJ?Qno#23!h(Doxy*r z-_H5C!GRYN?8ZlO4D^m2cG_s6!#O$m^hTvMJgbjMZ3tV#yu0&j^7)PBEJBU02RO}G z%AFJv<s3aCd?NgAc&t#6Q85v`#c9sT+!A<5U1{<P?=5P(1b-fpNHQ{PkMPh@z9!T! ztS?aA;e5ngMX1x+NPXu7CeMvaLM{nia=Vllr2JB6<!loVDdov0Z>3~!Vz->4qo94F z^~u~Pho2Zfk$$51DVl3;l0)H=AHk0_f=*?5Nj?>LYVwpVb%v2+=;B#HcULS75j5?O za$37^ThRKz_7%b=rL!#0Hj8<BFI~H6>Sd`6@r>`8>tB@Y61+QA&TsmK@Jpv(y1$tI zg85514{w`j^YM;!4awOO>=Nmc<r3>>_$-sW+%0)L#K7F}^^E5;`Df;bMrdu*su7)d zifgLZRJEznTKXX?S1nt0Zxwgw=g`w3{VU^wYuCAj8V5gK$$4ek>d;kl16~J92dA&J zUeUfPeMNqV{gS%KznyH6hkXwFJ1+@burdDeo?{B!$&#BZbQH6BSGScmnjTTxc=l#Z znbz(Ze`R-f+pb!<(9+r1%Q$><(B~;>_Uw;m6#ke~;>Y=1WBK&YbzS#F-fKS6lwM}y zv(qp&CVSPi>C+-sd#nyz?f!bN*}AvO=Pujpx;yfBBcJ1OFZCF8_w@dAtaG0S-weLH zoNKw5udUVBb$Y9^mr5_(?NYu!Z~40!^Y+Cx%=5`#ey?)x^ItNx`hP?I=Kfm#)tx7T zZ8MJw+ijk65;jLo3-fJc^^f2F80Z{(*ui;e;?%_H4_hYgylAuds#~uce^2JIn8zxQ zbC2a7Tdc;Wrl{ua`^xv-tRUZ=%QTl+`&j!NKYQlvqO;d#n$EU0yl(V-_Vd}-XW!qR z5LFSSvu(w;9jTt1o<uE+e79{+UTc(P<kE=HsMyVJYv-<2y|#Pp?&X5Zclu5CTRrbu z`r!>9x1HR!a=YY)r#IMcuiKWptu`?|Nj!b~(RGL4P2M}Xc2{lhUt4zG_M3dGWus*4 zinkPrScO^Ln{{r^JNw$BU#7j(Pmee+aQ=qz<88vHHIHP@4W513JzYF~XL0lAj_ywH zrqg=U?eu0#AG>>E?-Q?*uZ6J>t1f+=JA3o&LvL5yUjNSQUFN&?JM+(&%-5V3X<U5a z@CoM!fln{mbI+e@kbQqmoxihxWBS(T%KFLr6T{bD->`jY`Ks@U@5SxD*%i%yZ1>p4 z{GQRh$oMnn2ezNse%k+V{=E3s`_A>s|5N@~{lCbtyg`*Qow>8ou`!zI_m8R?#r=Ku zR<~2XZMyl^v1h?fM^VTArnOB^53W_rIPqq}TE*|rXPldOE|ae{pzg)B3+#!{61xxA z@u;`9w}!Xb_Z4(RNL@6XZd!ivj&t#SmtQW^T<*<IH}tn$uj^&1Ww%N%%jTB(E|XpM z$Mn)7&s>d|8{wJbEn}&twazRrXW#ulZ$Gy`n)`_Rakcmi{S&bTl?%@u`qH{wY^9!% zj@2{=pAJ8ZDIXjKv{s1qsD2VISMB4fJS2N4T}l6<*~N|<&M{hYexE$Fe5)qCnYhQa zQst=eQk|zeThv!h*yF?Nb9c7YJh5423b{tMR(lPbCrzAC=(I}Fck;IMSLx5v%1*CI zZQb<mXkPbreYvUZC$l$CSi>5r%eBpE*^7`Ujq6(Mo6=Lm)Apb3JY_j;^*rsfZ_jK# zb$f#OocHtpzZE(p^l;L)rET8fy0Q_^_O8jY++<T&d7JC8D64i`7~2|~9bMbBa<BTY zej7f0UEN-_D&OqJxPzyf4sK=4e$5@q^|ZD2^_}Z)WBnuaBkylb{M9yB+D^2yYn{k# z@#&)VH#F{4Je6+GKed16ohe&Zu0FCe<$T(uw3pBB>CTJho3?8Dv*~<!yf<%c;k_TW z<?YvRuDA84pVhd%yLp@Q&c*v}cI-)Ae)8N|Yx|HTAzwm%g_ym&ba!jw^&fc`-$_^R z`DXIw>h0(~-~Za3+;=;aG1T{J+SSzGp1(JLw_n$}>gA7VzjI^P`sEhBJ<fcI`7gUN z-?r;JuUwVAWE&FHe@*+fJKu5negEdZFW2QY;|=YdbL^3FflK9+m)|+>f0yI6x>eRw zz2+hNW%)AMbXol@r&*t7xy=rq^*cH#dYAthyNlOe-K+ZJep{Y@j)_fVrPt?>lbN?( zHeH^a{^@+r+^KVG>-2u!Nw~deTlMqgbN93O^WX0hD0z6v^?&zs>pj-zVm|FC`LyZw zQ{nI(;j`9T#pk{%d1do&@@4<yyXWklwQgrj_Re=zd$(4neSaxE&-~ooFMBILiv7QK zb)Kc|-Mycy^1mPb{`J1t-MS+6zvoWxzFDsRZtWibJ@H>0-a6iNzT1A)KKkFC8}IMb zXB{YC@cH5$_wQ`2&FBA3{Myg&%-TP*|8yH~+v|B2c9xcp?2G=s{IlHoe9C#XdE);T z{X4x*Tui_2$JN)@&t05xapCk&(<krGty%i_)q&TN>u(=BbWCpF$qHk`xG!P9-yST# zQ!e{{@qK>(Cp8N78viqXZu}a2VfpXLl9MO@XJMZ4`A@R3?rjDJ28CpgAYTTCDpdxC zhGqtapZ^&c8eTFmlo~KFyh>nTu$sZZAf7)d{-_%R1H%GO7srr_TW=;AavgFIaJ_#f z&&<i-0plB%^9pjub{~uG2xZZJYr?6vJ3I9?m!j)|c>eeGZR&<@%Q}n#-&}qBc#W7p z+q8!v*I#b#;GfGN5$iI+#NywAqpvHE@$<VFU3s!2$@rh>LGu;5foBCxSLkx?ljIjO z?1;Mcd*Zh+#a9A`nRjbm=%38nd}M8zB*U^7%nmR1H}hXD`}_UJ;@f+~_Omk_YS#bT U&mu0vz`(%Z>FVdQ&MBb@0E%#M!2kdN literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/993.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/993.png new file mode 100644 index 0000000000000000000000000000000000000000..be6df0d155f2c9f1618c18d89d4cf08bac55daa6 GIT binary patch literal 519 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{W_;0Bab|r>YpM82F8`1E{-7<y=yP;^$!k|IR5ed&gc6AZaI4^ES<p? zFSYVmi(_wdj+%se%wOg?J~DUb{B53d_2h~`;UxB61&(H4*Y#Us@=x#7f4+m`Rl+5w z`7b5??0!Bi|HScbZB)bU`@8Pk=a~^VYw`AN$0pBu6u9_Q;c?5DU7h|vuSu3^aplFO z=<_j5=sWziSbWE(f_X2UGD~(mQg`u>59Ix((YAbsmI}j)1rAI2IUdWEPY-WvyL;G` zL!!i{Zcjw|g?nyh4jcz-b~gD5Z#x{D=EddC(X;iidY{mAecr~ICtjE&{6BC!s{35g zC7blpms0DL+nxS<uG!P`Ynkd=?pL|q>sK<~_;oLF*@_!CazF3*TJ=S%Nc)El@B4bI zD@)wcC%g9_vQX+-yfE;`>MlpUpC&Vd!d=U_IIX5CJO1}wv!_RT<MHcxK9^axh<&Me zP^VW}x$DI4N@vq4K1bDy{)ar|c8+|0Cd<(`{l_{<ex25(H^uMlyz*pIv{J$6j|YCQ zJbtrXyQTB|Rrb>*zYkU<XtPA0kJ8ce`|$85+pF)M^5uPY=Ot#;eir;&Hg}WBmpwa$ c5AJ99uyD43(5fq@3=9kmp00i_>zopr0M*^?fdBvi literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/994.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/994.png new file mode 100644 index 0000000000000000000000000000000000000000..bd0fb1adc861300c81c2dd5ad461a265ba0bd011 GIT binary patch literal 2927 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 Lu6{1-oD!M<>7`~U literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/995.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/995.png new file mode 100644 index 0000000000000000000000000000000000000000..ca25b95487020021ee6f3d6aabafc8f86d15b107 GIT binary patch literal 286 zcmeAS@N?(olHy`uVBq!ia0y~yU=U+qVBqIqV_;x-kZYg8z`($k<n8Xl@E-&h>|H*Y zfq{X&#M9T6{RWo^v!r4EtKT0O7#I$Cx;Tbd^d|rCXZdl-{`r4y$GiXaSxW!cznt&f zGwr_*PsW4euh#O^=iPS~KgRj*&aUGhlFyp)6pA$0uep2fL+&r3Hu>&rMt^F5Oj)q7 zUfw40$Ho-d!`CyrCF|DQ?Yo`8=H?bMOCTX7B_Sc9C1^!e!~{zjM>!sz|Njg(yvQ~( zIPEQLGCeYGR->|6N29C1@FMZ)2YGmScw`jgj_edmYgnbk+`USv&Ehe)<K6!%GhI{; pc!)ONOK7{u({%O0#JP?Pv9sRk{(t4On}LCW!PC{xWt~$(699KZbHo4u literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/996.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/996.png new file mode 100644 index 0000000000000000000000000000000000000000..f64723f6ffc304e27cb3a1dc49ad612aafe692d2 GIT binary patch literal 3044 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/998.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/flag/998.png new file mode 100644 index 0000000000000000000000000000000000000000..f0d8338ed7cb3ed23f5510bb364101224b1935c1 GIT binary patch literal 2949 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/icon18_active.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/icon18_active.png new file mode 100644 index 0000000000000000000000000000000000000000..4daf42d88e83cd92acd59008c986abc7bab51cfb GIT binary patch literal 3820 zcmeAS@N?(olHy`uVBq!ia0y~yU=RXf4mJh`hOl#e;S3B6oCO|{#S9GG!XV7ZFl&wk z0|S?Trn7TEKt_H^esM;Afr6*AvqC{pep+TuDg#5st+~PJA;B-jY`@?8;^f`YFvUAR zNR&e%$f>(QWTKjuBBy{?hvLGUqJakmU0qqEPb}zQ?(1vl5e?Mg>EgP<{zP$6U-ROQ zu2a7sRxi%Jw&(k^v;VeUw|l<k^W4qn*$;5=O!F`fG;QEn=_Dukp!xWbV-KI`IW%&q z6fg*IY)(-u{;SN$@T1PgMqB9s`#uK7ip$&_3<rE?A25(<|Ih!*X=V&#Lkr`9l8F}H z91S824pn_l%NQKo7#rr!Pzz>Qz`~GlIyvbvLx3(rf_nY4%M1;3f6T0AWQd-kD9X5C zB7=f(YKIF$hBd>Q^I=`C3=swlB5B@7EE#U-G8lM<r+F}J*v@d^oB;PG1`ZwugTRhf zO$L@~hJ+K!$`uSPGZ{qOK84$S)Ltj>T#JFBV&*2Bohp{DjnN#sh3w(sdU6`)JjJy| z%o3RrXF2E{s+=il)M�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 N44$rjF6*2UngBg)A+rDg literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/icon18_inactive.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/icon18_inactive.png new file mode 100644 index 0000000000000000000000000000000000000000..ec52c3ef7b742bd46939a10b8ed10deb76dbc199 GIT binary patch literal 1424 zcmeAS@N?(olHy`uVBq!ia0y~yU=RXf4i*LmhCj#M?l3Sga29w(7Bet#3xhBt!>l<H z3=GWDna<7u0U7xv`NbLe1qz<d&I$!Z`DvLssSFGiw~`YQ<~?}v=*cOS0|x>ELV}*i zJ&}+Ulw5kCZeC+=<IF}7P&C}R<A9;Uf%eA6Mn!#l{i%$i21W)oyN?|>ap1s#g2?L^ zkDi>`XmVgyOhCw?0|yEk8XFI4sBX2G+1U7yo0-|Us?yVulbJc0hnczABd83_W(2Xz zJSG@3GbeL1GYk8BX1Q`QGcz|A*EyyJrd7FhHZ5GbbZJwvS6$F5r-IZ%&#b(SP3dt( zb%Bm`fk~cOT+GbOSNPc2*x2Hx{}E#Se&mb|t8;KDn^tqj2@O^bZb3T^LlzG`U9Dzz zRpw@fX0c{@XVt@6ZR|aldIEbsOUyTtG%_`aO}dtlm$L74(qW_isWVMyf7O~Td|Rzd z^xI4sCYiZ&*33B@b0#MD&Ye5Ll}jq7eUkbt_lWyh^|5$e1(gj^7kocV3|PEj(TBu| zC4q4l*CrJ{n)4`HHAtmZwcjh$<F%Ll#F@&{TIw3*Vf)+<x=#%M*mOzcsfMWP)a$FF zuS#A0Ym#DmFYDdqSu5tQN?xnI<hR%Rj(;`HQinB^y^}p3-&gzY!#{hi!TaqiHu!A$ zv~k;pz^$SilQ;g(`hVx)w#Tf?lh0MhZrio4Jo@FnE6Ed|JFREE$6YJ-Tkmu9)BSb{ z*$?h5NIv-7Ij_}@`?t_R5lxAuBDpe;#iT{2i{GDQU|m>z>E@H?D}6GHUYbb;&J2y* zyy^RsJExORh3kLN)X|$2TeIUy?o5}R-mZyH_pI7{wLbgnt4FWZO=YtG+|9W#>7Ld4 zw^i*4%ge*7zdwv?v-|yT7w2!afAim1EG>Q2`ErkOQ~J4CUo-ly-I-MRTdY8rCwfc8 z$Ky-+POItE@3>SE`uf%E?9%I|YxnQGm=JvY8GH2FZN7K^B`$xzF0|U>+p_o8bq;?M zo-e%a{--a`POom!w<nV?-%H;%XR_`6x`&sS?l`@CT~thTne&&l*K608UwVIU&->30 z4k%wfzIwiv-Rz%EA5yPpKQk}QuivNt_ruHJgU`$3KkwW7d*c7khu=T4zyB}v?|*&< zJs#Qa1vZ?93=9kk$sR$z3=CDO3=9p;3=BX2GcYu~WMC*YU|@Kaz`$TNgMmRje^UHW zHwFgA_nt0}Ar-f5ChgA_PGoSn|NZHt-7!M_E;-4*tn~@9EP@x8Iv-r55Zdf~)HP$P zf}ShOf5xNs2VxyX)^O+rm0Vhyadk!D1`*w3@>`e1DSKA!%`V!z`~1RX`E~i#kJw&w zhzLp~X&Y^Dc2A6rt9>|~VatC$#YY9-vd&MKI%Q3({QHyNrgndR_|`eG(z(k(j*n0N zg~-N39*i$~OMAoJ9ho%NY=7SV{qljM$Dc{6Nk;ix>sH=t?$~r_j)t6wj-a}0)Ay7Y z$I`lFZ{~^|{~8>m*#GRj-h@P9sV*(C(>fnGyWd_h@OZXV$#&M!ht_<0oBt@aJ9dV0 z{c(HDeQu81C5vlnr{iZGsc@DSp6hz)%R@l}_ZicS<nRA!zW&EW@Ivc3uFqRKA2OsW z1?^rs!OqH4D@5Q+LCahTm77ko?ESVJzHHkvo=s@zJuPLHsiG#Ow|U8h%)b-&3EP@y zCOrsTD9tVJ*~MlWQ)6e|TrPE^|ITHueil7>jtHf`?&ErFfp_L7xC!rkFsb>-frOcC zLUY9!e_Qk&Yu;AQYqB-`n|8>I38f1=81)Q)NixhcC~nj8h-*_{>~>{UH-l?~|Eyx3 z2gT|$<Q^5KPxYM2o8QUZB;eR)x6knEkE-kY{c>x&&iN<5T4{CA<muF!lRV;AwQuEo b$Y%(cbV$Es$BRY=1_lOCS3j3^P6<r_r`v;2 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/icon32.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/icon32.png new file mode 100644 index 0000000000000000000000000000000000000000..38949785768fa37e6490cd89025e35f38518997b GIT binary patch literal 5391 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}oCO|{#S9GG!XV7ZFl&wk z0|S?Trn7TEKt_H^esM;Afr6*AvqC{pep+TuDg#5st+~PJA;B-jY`@?8;^f`YFvUAR zNR&e%$f>(QWTKjuBBy{?hvLGUqJakmU0qqEPb}zQ?(1vl5e?Mg>EgP<{zP$6U-ROQ zu2a7sRxi%Jw&(k^v;VeUw|l<k^W4qn*$;5=O!F`fG;QEn=_Dukp!xWbV-KI`IW%&q z6fg*IY)(-u{;SN$@T1PgMqB9s`#uK7ip$&_3<rE?A25(<|Ih!*X=V&#Lkr`9l8F}H z91S824pn_l%NQKo7#rr!Pzz>Qz`~GlIyvbvLx3(rf_nY4%M1;3f6T0AWQd-kD9X5C zB7=f(YKIF$hBd>Q^I=`C3=swlB5B@7EE#U-G8lM<r+F}J*v@d^oB;PG1`ZwugTRhf zO$L@~hJ+K!$`uSPGZ{qOK84$S)Ltj>T#JFBV&*2Bohp{DjnN#sh3w(sdU6`)JjJy| z%o3RrXF2E{s+=il)M�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 L`njxgN@xNAPyR=V literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/icon96.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/icon96.png new file mode 100644 index 0000000000000000000000000000000000000000..0c0ea89ad2356ba6a6253bc2b545cf6584ed0298 GIT binary patch literal 12314 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 UtFwQ`z`(%Z>FVdQ&MBb@0Bj4I)&Kwi literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/icon_menu.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/icon_menu.png new file mode 100644 index 0000000000000000000000000000000000000000..62dd257f7e86daefb4f01088da641f90a2a9e6be GIT binary patch literal 583 zcmeAS@N?(olHy`uVBq!ia0y~yVBiN~4mJh`2J356y%-o6I14-?iy0WWg+Z8+Vb&Z8 z1_lO&WRD<U28JqC28M=a28N&i85kN~GBA`HFfhDIU|_JC!N4G%KPmpG8v_I5Sx*<o zkcwMVE^W*cb`)THP+$JO?7iNnyE7%`9P#XFXcF<*!zkM8>gYJtgCmeJNTA<Av~w2w z0w<-Q70O;(DlUw?LK8I#dpdoK`u2W%_m+>h(?e?Z%IBBQy!&&Y=cq*Xk-|+!C9)sb zubz~8_3qVYp{G7`zh_`!P)KX=oECb2N#s`7Cnqb9?=8Nw^Aq!It4)*kom;)NROz|< z;)~&$PnDi4z5kc9y(x1>hl#_5sCw?hACd%GKKB@Utjzn6`Qt~$hMO<{RCu^Vd4;Mg zRlHAFd-JjJp2+CrxT)9PF)}=tiF)mS?bi2$J^SZ+EzJ{RbdhU*^ntbE(V3K$i;jD_ z&VHUMqwc?0W8XOm=Rldf=<M<Wu~qK9PnSi<?etkVMOyu4K;W00%*?B6Ox9nhd3#Bm zfm>aTp}~OXdiK^|-sddMkA0a`@$O(tx9ieH(Nn#u+PND)Mr{ARxA`}N)$*0XPIuPn zo0PQ*BpFF=pIhu+XtbS8$>12nu?6mj-~YY0zWUzp^Z)Zs?_J)vHEq9+_uEsO<d2_} zs;s|dAR{7Q&3^Y<<qz|DSFSF8*k?G|Q}yh7E=86D32R^efA@VGTk~RdhHuN~HhXKD oU(%_URzF#M?8es22j>~2{SQhx@3&sfz`(%Z>FVdQ&MBb@0F)R39smFU literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/info32.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/info32.png new file mode 100644 index 0000000000000000000000000000000000000000..d8197d61a38f508651d3ce759bcd60d620226bbb GIT binary patch literal 2328 zcmeAS@N?(olHy`uVBq!ia0y~yU{C;I4mJh`hT^KKFANL}Ea{HEjtmUzPnffIy<}iu zkSuYHC<)F_D=AMbN@Z|N$xljE@XSq2PYp^<OsOn9nQFtpz_H5H#WAGfR??sU|LqSu zFf}kFB}pVO2skjmV`yMVa(rem>&&6GLL$Nc|DQJc^Yih653ko79^U_7%E8cZfrRjc zzhWAd%o&GR+0vf<b@*t_^Z%cr!JnV}mkuBJAl~v{v8O7ME1j7|fIINqpa1bQA3lFy z)Aqk7?oZO8Q(Rs_JV{ASJagvc@YMe|GN}3aY4XFbr@#B1m$OOz@!`u;;RVXy4K{4> zNl8fAalk>LlaWQBj@^Li<NN)ZJp2D|)yUB7wzkePe35a&;6tIY@dl>Sf6P+<K8rIS zzW$zvM}D7?!JglLra!zlce&AY|2V&jnx9o10glql#@_lD*!LGiG732SkU4PX!{c&w zZSnGr`8!mbw{Ny;RuE@C-2Yy|+|b}o{fEYf|Ne^e)HfW|*3&z5;OyCx2N-!$O+z`^ zI4v#PvRAFL5ift2{rlg$y=KhZ(PkI+^xx27<WLZsQ}_Sp?(XLK`!!6j_8z?H)ANx3 z#PWE@egDoEy#6Q6HqSm^f~&?=qVE6C;~Sn`;BGrE|5Nfu#ZHNRwZB+;^2*p$bxqmI z>>S%RzczQNulzTwwQ;Jhn8eim7j>C96yEH7aQ?oCncllYS3=6!#Dfjn<nK4w{pS?= zclyJ_&-rY}<mDy2ltU%HTy>T7*>Y53Rq|9xiTZjrx1;fG@%tSYerbJp`?WjI9wsK9 zTlX4y<~03z$g{uxdV}zj{q-7Qj4T4e(^!_D`0zZwyS%}4@#dWxhrj15bUPOQ74P_O zT=VbS?8e!XB_+15Il%HmyyL&Yhff7Y1sjc-Wk0aLXsCZs^6~X`{SC@DDqSU><itwM zkdQpp$QIco%qPUaq2R;*=TGdK6)RPqy<T(Z1N)QL^$Mr|H+=j)f4}6788ao`?M+L# zQT)r&;J6`^q442Td;2Bd?frHB0Efx%-~J1f5A?NKJvz(G=9ii9EAV;}`>u5oGqh(+ zT+7U%aK+@q`~4<|?cMUNt!3E~<=oo*=Qp<fY5XD6^q;Z#Mm<}iU}9V4?|8#EyT6Fe z@N3L`bTghUE-tPu-@cCd?T_;xm|rx+J8ZRgsH&?=`0@3<GIM_y<Kf+_r^o*P|93h2 zmX|hXyc!sscpLB6&-tO+B6`DRiA3DK|K~sam%s37e*MCO|HU=_&VP7!dOT0Q{hy?S zw5N$Xezdece3Y!#mcQTe;D7Ep3W`~d)BZDCGrd{Rf4(vB;F$xuiU--hOjO@Am4UHE zimCqJj1yWO#(VxBJhS1$3G+6IVzC4nXCBtn?e6+^#s$W;X%{9wVLEmto~<lQjm=C< zjV)~1e;#SSn#3Q!y5~N8|9=02k`LS-|5=u{+xPI~+wV&dNE1zbARwIlqv(GpBgmE` zO<{MkK5%jw)cpN8|KSVOfVT8B4x^-2t4Hz%jQ^4*Oif8EFc(V*nEsThO~An2fI*m3 zSo`q8>~hI@_J0l^IMUF;G=cdZ)8mh)r=RDEsoBGGW=@Pkw8(_DEF21IChQ%XHf}Vm zVOCRNduGq$zP>&^qUMSAhAF=ff8c+?QRtYLuwk>&lQR`Z3Z5QJZ;N**mACtw@`JVK z_x*YWOV%k2|Ns2sPuu_V$Ha%9ub*FVgjIxjnOE9^ddZnTz!sRW|4IMFen#!T!_?#T z3HOr!9WQuzue^<w-QF;!>Wl0g;Uhn9O4j{LP2BP2fi<(WwD6KmjAA$I4;ucEmw7JV zkotLZ`Fn|dHNTm*$w-7G@c&_+%J~1ku~IarfWwQO5AOEAx0l!<p*ZV6sKV>|w(Il% z8RXb~Z+2|z7wnjM&@L@Gk!99b@faf`Bd41GZ{DUSME&GF(`DcA?mzQI{!Jn$*jE1J zK6C6q&0p4=poCJ)aJbh$-`=8YrLob5%?+CbE(lCWp5f5;C$-|ESV#PSR`KrtlCB5m z{pZ)cX;=IA=!ciO8+JTTXlao<T;HMok5QL>ljO`u$-2f*U-$3t2m4=bi`0WZtJ}{@ zL^RC$VJUe~k3EYan$L{qiGW+iiH4rP?3`f*ItE4#&C;*3XV@ilY+!gik8?&q{oXq6 zpb|;2t4bM+Ki?}qA7@wp{|#&N77g_y>I%&XCOy1spZsrDD4H$w<i!7aL6aMT^%6IB zR7n1-`*rBU>z}9P4=lLRnWO5!V7Sorplw3Yz0&W8O4&3O%h(+45_tap<Td0Gk^28% z$v|0n$AVwnT&@2vOgnGCV%GkEKkvnNoJ)8vn)ajaz2b#6^Vu65OQpb0InT;-V4BbU z$=c@o_w1;h@8})a(KuC7<J)g@m5POSNep3D_5Yl%9Cl&|`NSA3e`uk@X-CC_x=Lq1 zNY5~<yR(<O>oBJSuSTzM1A`McW5Jy`CatX9+ulnY{;xk@V#^fQ231++BZ3O-42`R1 zF*Hxpkye@5Bz()iO(B|-O;IzkL(<{2WMkh0!D)#JzfPa|@b-=6f`9SGjeIvc@3}C8 z%Bcne<Kpk%x4)krduLO7<KOfAY`UeKY<YSKB{l!f3B1xdx?!zzyJKY2<cHSE=FHmL z6%J32*Wt;u3*zCvwN`RRg=OQ($rBsHV>%mWPHcX7`eZOqz5SlM>^!%)a(ww47?NE4 zTzWqJ<(Rqu|BpsTt7hi_1*ePG<}?P!%z5~;yZpeR=km=G8<zdyTT?UR0VjWm7{lV` zsdFFRo2-8z;z`nv&;NuEZ!dl>`EOtSZuy8AN0>wH+>Vqe-;ruyICNd@&xr%arhRyP z_jt6q*?Ud#u<$hTu;8>Gr_Y@I@Zl<d+x+{EM_=a`EM|F<?3NIgobv1N0j8dZ@6GcJ z{;(YT`Tzd@GzQ)EETY0YKvh|dVnfFH|3A}tb)IlbL`K|k2-RA?!hl)Wd_xkWnBD>< zv5-T5|M8qjTYryf^Q~_A|NsA3Fie@l()0Q4jp&~p0t_4qkAAWG^td-JEd28S{(iQ2 zIlGU4ir!CDyl|Q8K^LpUuLC+CC8G|-GO-=aV{_Z~*dh9#V6uq=gCSqZa}k9GwmtbA d(6$IO!}A7P`7~XfTMP^g44$rjF6*2Ung9Y<ItBm$ literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/jshashtable.js b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/jshashtable.js new file mode 100644 index 0000000000..3806f818f9 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/jshashtable.js @@ -0,0 +1,380 @@ +/** + * Copyright 2009 Tim Down. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * jshashtable + * + * jshashtable is a JavaScript implementation of a hash table. It creates a + * single constructor function called Hashtable in the global scope. + * + * Author: Tim Down <tim@timdown.co.uk> + * Version: 1.0 + * Build date: 5 February 2009 + * Website: http://www.timdown.co.uk/jshashtable + */ + +var Hashtable = (function() { + function isUndefined(obj) { + return (typeof obj === "undefined"); + } + + function isFunction(obj) { + return (typeof obj === "function"); + } + + function isString(obj) { + return (typeof obj === "string"); + } + + function hasMethod(obj, methodName) { + if (obj[methodName]) { + return isFunction(obj[methodName]); + } else { + return false; + } + } + + function hasEquals(obj) { + return hasMethod(obj, "equals"); + } + + function hasHashCode(obj) { + return hasMethod(obj, "hashCode"); + } + + function keyForObject(obj) { + if (isString(obj)) { + return obj; + } else if (hasHashCode(obj)) { + // Check the hashCode method really has returned a string + var hashCode = obj.hashCode(); + if (!isString(hashCode)) { + return keyForObject(hashCode); + } + return hashCode; + } else if (hasMethod(obj, "toString")) { + return obj.toString(); + } else { + return String(obj); + } + } + + function equals_fixedValueHasEquals(fixedValue, variableValue) { + return fixedValue.equals(variableValue); + } + + function equals_fixedValueNoEquals(fixedValue, variableValue) { + if (hasEquals(variableValue)) { + return variableValue.equals(fixedValue); + } else { + return fixedValue === variableValue; + } + } + + function equals_equivalence(o1, o2) { + return o1 === o2; + } + + function arraySearch(arr, value, arrayValueFunction, returnFoundItem, equalityFunction) { + var currentValue; + for (var i = 0, len = arr.length; i < len; i++) { + currentValue = arr[i]; + if (equalityFunction(value, arrayValueFunction(currentValue))) { + return returnFoundItem ? [i, currentValue] : true; + } + } + return false; + } + + function arrayRemoveAt(arr, idx) { + if (hasMethod(arr, "splice")) { + arr.splice(idx, 1); + } else { + if (idx === arr.length - 1) { + arr.length = idx; + } else { + var itemsAfterDeleted = arr.slice(idx + 1); + arr.length = idx; + for (var i = 0, len = itemsAfterDeleted.length; i < len; i++) { + arr[idx + i] = itemsAfterDeleted[i]; + } + } + } + } + + function checkKeyOrValue(kv, kvStr) { + if (kv === null) { + throw new Error("null is not a valid " + kvStr); + } else if (isUndefined(kv)) { + throw new Error(kvStr + " must not be undefined"); + } + } + + var keyStr = "key", valueStr = "value"; + + function checkKey(key) { + checkKeyOrValue(key, keyStr); + } + + function checkValue(value) { + checkKeyOrValue(value, valueStr); + } + + /*------------------------------------------------------------------------*/ + + function Bucket(firstKey, firstValue, equalityFunction) { + this.entries = []; + this.addEntry(firstKey, firstValue); + + if (equalityFunction !== null) { + this.getEqualityFunction = function() { + return equalityFunction; + }; + } + } + + function getBucketEntryKey(entry) { + return entry[0]; + } + + function getBucketEntryValue(entry) { + return entry[1]; + } + + Bucket.prototype = { + getEqualityFunction: function(searchValue) { + if (hasEquals(searchValue)) { + return equals_fixedValueHasEquals; + } else { + return equals_fixedValueNoEquals; + } + }, + + searchForEntry: function(key) { + return arraySearch(this.entries, key, getBucketEntryKey, true, this.getEqualityFunction(key)); + }, + + getEntryForKey: function(key) { + return this.searchForEntry(key)[1]; + }, + + getEntryIndexForKey: function(key) { + return this.searchForEntry(key)[0]; + }, + + removeEntryForKey: function(key) { + var result = this.searchForEntry(key); + if (result) { + arrayRemoveAt(this.entries, result[0]); + return true; + } + return false; + }, + + addEntry: function(key, value) { + this.entries[this.entries.length] = [key, value]; + }, + + size: function() { + return this.entries.length; + }, + + keys: function(keys) { + var startIndex = keys.length; + for (var i = 0, len = this.entries.length; i < len; i++) { + keys[startIndex + i] = this.entries[i][0]; + } + }, + + values: function(values) { + var startIndex = values.length; + for (var i = 0, len = this.entries.length; i < len; i++) { + values[startIndex + i] = this.entries[i][1]; + } + }, + + containsKey: function(key) { + return arraySearch(this.entries, key, getBucketEntryKey, false, this.getEqualityFunction(key)); + }, + + containsValue: function(value) { + return arraySearch(this.entries, value, getBucketEntryValue, false, equals_equivalence); + } + }; + + /*------------------------------------------------------------------------*/ + + function BucketItem() {} + BucketItem.prototype = []; + + // Supporting functions for searching hashtable bucket items + + function getBucketKeyFromBucketItem(bucketItem) { + return bucketItem[0]; + } + + function searchBucketItems(bucketItems, bucketKey, equalityFunction) { + return arraySearch(bucketItems, bucketKey, getBucketKeyFromBucketItem, true, equalityFunction); + } + + function getBucketForBucketKey(bucketItemsByBucketKey, bucketKey) { + var bucketItem = bucketItemsByBucketKey[bucketKey]; + + // Check that this is a genuine bucket item and not something + // inherited from prototype + if (bucketItem && (bucketItem instanceof BucketItem)) { + return bucketItem[1]; + } + return null; + } + + /*------------------------------------------------------------------------*/ + + function Hashtable(hashingFunction, equalityFunction) { + var bucketItems = []; + var bucketItemsByBucketKey = {}; + + hashingFunction = isFunction(hashingFunction) ? hashingFunction : keyForObject; + equalityFunction = isFunction(equalityFunction) ? equalityFunction : null; + + this.put = function(key, value) { + checkKey(key); + checkValue(value); + var bucketKey = hashingFunction(key); + + // Check if a bucket exists for the bucket key + var bucket = getBucketForBucketKey(bucketItemsByBucketKey, bucketKey); + if (bucket) { + // Check this bucket to see if it already contains this key + var bucketEntry = bucket.getEntryForKey(key); + if (bucketEntry) { + // This bucket entry is the current mapping of key to value, so replace + // old value and we're done. + bucketEntry[1] = value; + } else { + // The bucket does not contain an entry for this key, so add one + bucket.addEntry(key, value); + } + } else { + // No bucket, so create one and put our key/value mapping in + var bucketItem = new BucketItem(); + bucketItem[0] = bucketKey; + bucketItem[1] = new Bucket(key, value, equalityFunction); + bucketItems[bucketItems.length] = bucketItem; + bucketItemsByBucketKey[bucketKey] = bucketItem; + } + }; + + this.get = function(key) { + if (key == null) return null; + checkKey(key); + var bucketKey = hashingFunction(key); + // Check if a bucket exists for the bucket key + var bucket = getBucketForBucketKey(bucketItemsByBucketKey, bucketKey); + if (bucket) { + // Check this bucket to see if it contains this key + var bucketEntry = bucket.getEntryForKey(key); + if (bucketEntry) { + // This bucket entry is the current mapping of key to value, so return + // the value. + return bucketEntry[1]; + } + } + return null; + }; + + this.containsKey = function(key) { + checkKey(key); + + var bucketKey = hashingFunction(key); + + // Check if a bucket exists for the bucket key + var bucket = getBucketForBucketKey(bucketItemsByBucketKey, bucketKey); + if (bucket) { + return bucket.containsKey(key); + } + + return false; + }; + + this.containsValue = function(value) { + checkValue(value); + for (var i = 0, len = bucketItems.length; i < len; i++) { + if (bucketItems[i][1].containsValue(value)) { + return true; + } + } + return false; + }; + + this.clear = function() { + bucketItems.length = 0; + bucketItemsByBucketKey = {}; + }; + + this.isEmpty = function() { + return bucketItems.length === 0; + }; + + this.keys = function() { + var keys = []; + for (var i = 0, len = bucketItems.length; i < len; i++) { + bucketItems[i][1].keys(keys); + } + return keys; + }; + + this.values = function() { + var values = []; + for (var i = 0, len = bucketItems.length; i < len; i++) { + bucketItems[i][1].values(values); + } + return values; + }; + + this.remove = function(key) { + checkKey(key); + + var bucketKey = hashingFunction(key); + + // Check if a bucket exists for the bucket key + var bucket = getBucketForBucketKey(bucketItemsByBucketKey, bucketKey); + + if (bucket) { + // Remove entry from this bucket for this key + if (bucket.removeEntryForKey(key)) { + // Entry was removed, so check if bucket is empty + if (bucket.size() === 0) { + // Bucket is empty, so remove it + var result = searchBucketItems(bucketItems, bucketKey, bucket.getEqualityFunction(key)); + arrayRemoveAt(bucketItems, result[0]); + delete bucketItemsByBucketKey[bucketKey]; + } + } + } + }; + + this.size = function() { + var total = 0; + for (var i = 0, len = bucketItems.length; i < len; i++) { + total += bucketItems[i][1].size(); + } + return total; + }; + } + + return Hashtable; +})(); \ No newline at end of file diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/messagebox.js b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/messagebox.js new file mode 100644 index 0000000000..8be1b98285 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/messagebox.js @@ -0,0 +1,43 @@ +/* +Creative Commons License: Attribution-No Derivative Works 3.0 Unported +http://creativecommons.org/licenses/by-nd/3.0/ +(c)2009 Michael Koch +*/ + +var objTelifyMessageBox = { + +init: function() +{ + var title = window.arguments[0].title; + if (title == null || title == "") title =" Telify"; + document.getElementById("dlgTelifyMessageBox").setAttribute("title", title); + var msg_node = document.createTextNode(window.arguments[0].msg); + document.getElementById("idTelify_mb_msg").appendChild(msg_node); + var flags = window.arguments[0].flags; + if ((flags & objTelifyUtil.MB_MASK) == 0) flags |= objTelifyUtil.MB_OK; // default button + if ((flags & objTelifyUtil.MB_OK) == 0) document.documentElement.getButton("accept").collapsed = true; + if ((flags & objTelifyUtil.MB_CANCEL) == 0) document.documentElement.getButton("cancel").collapsed = true; + var icon = "info32.png"; + switch (flags & objTelifyUtil.MB_ICON_MASK) { + case objTelifyUtil.MB_ICON_ERROR: icon = "error32.png"; break; + case objTelifyUtil.MB_ICON_WARNING: icon = "warn32.png"; break; + case objTelifyUtil.MB_ICON_ASK: icon = "ask32.png"; break; + case objTelifyUtil.MB_ICON_INFO: icon = "info32.png"; break; + } + document.getElementById("idTelify_mb_icon").setAttribute("src", "chrome://telify/content/"+icon); +}, + +onAccept: function() +{ + window.arguments[0].fResult = true; + return true; +}, + +onCancel: function() +{ + window.arguments[0].fResult = false; + return true; +} + +}; + diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/messagebox.xul b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/messagebox.xul new file mode 100644 index 0000000000..1be8d587f4 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/messagebox.xul @@ -0,0 +1,34 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?> +<?xml-stylesheet href="chrome://telify/content/dialog.css" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://telify/locale/lang.dtd"> + +<dialog id="dlgTelifyMessageBox" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + buttons="accept, cancel" + onload="objTelifyMessageBox.init()" + ondialogaccept="objTelifyMessageBox.onAccept()" + ondialogcancel="objTelifyMessageBox.onCancel()" + title=""> + + <stringbundleset id="stringbundleset"> + <stringbundle id="idTelifyStringBundle" src="chrome://telify/locale/lang.properties"/> + </stringbundleset> + + <script type='application/x-javascript' src='chrome://telify/content/pref.js'></script> + <script type='application/x-javascript' src='chrome://telify/content/util.js'></script> + <script type='application/x-javascript' src='chrome://telify/content/messagebox.js'></script> + + + <groupbox style="background-color:white;padding:8px;"> + <hbox> + <vbox> + <image id="idTelify_mb_icon" src="chrome://telify/content/info32.png" style="width:32px;height:32px;margin-right:4px;"/> + <spacer flex="1"/> + </vbox> + <description id="idTelify_mb_msg" style="width:240px;text-align:justify;"/> + </hbox> + </groupbox> + +</dialog> diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/pref.js b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/pref.js new file mode 100644 index 0000000000..2d3b635917 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/pref.js @@ -0,0 +1,164 @@ +/* +Creative Commons License: Attribution-No Derivative Works 3.0 Unported +http://creativecommons.org/licenses/by-nd/3.0/ +(c)2009 Michael Koch +*/ + +var objTelifyPrefs = { + +PREF_BLACKLIST: "blacklist", +PREF_HIGHLIGHT: "highlight", +PREF_EXCLUDE: "exclude", +PREF_DEBUG: "debug", +PREF_ACTIVE: "active", +PREF_STATUSICON: "statusicon", +PREF_HREFTYPE: "linktype", +PREF_COLSORTCC: "colsortcc", +PREF_NUMHISTORY: "num_history", +PREF_IDD_PREFIX: "idd_prefix", +PREF_DONT_ESCAPE_PLUS: "dont_escape_plus", +PREF_DIAL_CC_DIRECT: "dial_cc_direct", + +NUM_CUSTOM_PARAMS: 3, + +PREF_CUSTOM_URL: "custom_url", +PREF_CUSTOM_TMPL: "custom_tmpl", +PREF_CUSTOM_PARAM: "custom_param", +PREF_CUSTOM_OPENTYPE: "custom_opentype", + +maxHistory: 10, + +telPrefs: null, +telStrings: null, + +blacklist: null, +excludedHosts: null, +highlight: null, +excludedTags: null, +hrefType: null, +numHistory: null, +idd_prefix: null, +fStatusIcon: null, +fActive: null, +fDebug: null, +fDontEscapePlus: null, +fDialCCDirect: null, + +custom_url: null, +custom_tmpl: null, +custom_param: [], +custom_opentype: null, + +HREFTYPE_CUSTOM: 9, + +protoList: new Array("tel", "callto", "skype", "sip"), + + +showConfigDialog: function() +{ + while (true) { + window.openDialog("chrome://telify/content/config.xul", "dlgTelifyConfig", "centerscreen,chrome,modal").focus; + if (this.hrefType == this.HREFTYPE_CUSTOM && this.custom_url.indexOf("$0") < 0) { + var result = objTelifyUtil.showMessageBox("", objTelifyLocale.msgNumberTemplateMissing(), + objTelifyUtil.MB_OK|objTelifyUtil.MB_CANCEL|objTelifyUtil.MB_ICON_WARNING); + if (result == false) continue; + } + break; + } +}, + + +getPrefObj: function() +{ + var obj = Components.classes["@mozilla.org/preferences-service;1"]; + obj = obj.getService(Components.interfaces.nsIPrefService); + obj = obj.getBranch("telify.settings."); + obj.QueryInterface(Components.interfaces.nsIPrefBranch2); + return obj; +}, + + +getCharPref: function(name) +{ + try { + return this.telPrefs.getCharPref(name); + } catch (e) { + alert(e); + return ""; + } +}, + + +getIntPref: function(name) +{ + try { + return this.telPrefs.getIntPref(name); + } catch (e) { + return 0; + } +}, + + +getBoolPref: function(name) +{ + try { + return this.telPrefs.getBoolPref(name); + } catch (e) { + return false; + } +}, + + +getPrefs: function() +{ + this.blacklist = this.telPrefs.getCharPref(this.PREF_BLACKLIST); + if (this.blacklist.length > 0) { + this.excludedHosts = this.blacklist.toLowerCase().split(","); + } else { + this.excludedHosts = new Array(); + } + this.highlight = this.telPrefs.getIntPref(this.PREF_HIGHLIGHT); + this.highlight = objTelifyUtil.trimInt(this.highlight, 0, 100); + this.numHistory = this.telPrefs.getIntPref(this.PREF_NUMHISTORY); + this.numHistory = objTelifyUtil.trimInt(this.numHistory, 1, 10); + this.idd_prefix = this.telPrefs.getCharPref(this.PREF_IDD_PREFIX); + var exclude = this.telPrefs.getCharPref(this.PREF_EXCLUDE); + this.excludedTags = exclude.toLowerCase().split(","); + this.hrefType = this.telPrefs.getIntPref(this.PREF_HREFTYPE); + if ((this.hrefType < 0 || this.hrefType >= this.protoList.length) && this.hrefType != this.HREFTYPE_CUSTOM) this.hrefType = 0; + this.fStatusIcon = this.telPrefs.getBoolPref(this.PREF_STATUSICON); + var status = document.getElementById("idTelify_status"); + if (status) status.setAttribute("collapsed", !this.fStatusIcon); + this.fDebug = this.telPrefs.getBoolPref(this.PREF_DEBUG); + this.fActive = this.telPrefs.getBoolPref(this.PREF_ACTIVE); + this.fDontEscapePlus = this.telPrefs.getBoolPref(this.PREF_DONT_ESCAPE_PLUS); + this.fDialCCDirect = this.telPrefs.getBoolPref(this.PREF_DIAL_CC_DIRECT); + // custom url + this.custom_url = this.getCharPref(this.PREF_CUSTOM_URL); + this.custom_tmpl = this.getIntPref(this.PREF_CUSTOM_TMPL); + for (var i=1; i<this.NUM_CUSTOM_PARAMS+1; i++) { + this.custom_param[i] = this.getCharPref(this.PREF_CUSTOM_PARAM+i); + } + this.custom_opentype = this.getIntPref(this.PREF_CUSTOM_OPENTYPE); +}, + + +prefObserver: { + observe: function(subject, topic, data) { + if (topic != "nsPref:changed") return; + objTelifyPrefs.getPrefs(); + } +}, + + +initTelifyPrefs: function() +{ + objTelifyPrefs.telPrefs = objTelifyPrefs.getPrefObj(); + objTelifyPrefs.telPrefs.addObserver("", objTelifyPrefs.prefObserver, false); + objTelifyPrefs.telStrings = document.getElementById("idTelifyStringBundle"); + objTelifyPrefs.getPrefs(); +} + +}; + + diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/telify.js b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/telify.js new file mode 100644 index 0000000000..58a615ec06 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/telify.js @@ -0,0 +1,715 @@ +/* +Creative Commons License: Attribution-No Derivative Works 3.0 Unported +http://creativecommons.org/licenses/by-nd/3.0/ +(c)2009 Michael Koch +*/ + +var objTelify = { + +digits_min: 7, +digits_max: 16, + +hilite_color: new Array(0,0,255), +hilite_bgcolor: new Array(255,255,0), + +// special chars +sc_nbsp: String.fromCharCode(0xa0), + +// chars which look like dashes +token_dash: + String.fromCharCode(0x2013) + + String.fromCharCode(0x2014) + + String.fromCharCode(0x2212), + +exclPatternList: [ + /^\d{2}\.\d{2} ?(-|–) ?\d{2}\.\d{2}$/, // time range e.g. 08.00 - 17.00 + /^\d{2}\/\d{2}\/\d{2}$/, // date e.g. 09/03/09 + /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, // ip address + /^[0-3]?[0-9]\.[0-3]?[0-9]\.(19|20)\d{2} - \d{2}\.\d{2}$/, // date and time e.g. 09.03.2009 - 17.59 + /^[0-3]?[0-9][\/\.-][0-3]?[0-9][\/\.-](19|20)\d{2}$/, // date e.g. 09/03/2009, 09.03.2009, 09-03-2009 + /^[0-3]?[0-9][\/\.-][0-3]?[0-9][\/\.-]\d{2} ?(-|–) ?[0-3]?[0-9][\/\.-][0-3]?[0-9][\/\.-]\d{2}$/, // date range short + /^[0-3]?[0-9][\/\.-][0-3]?[0-9][\/\.-] ?(-|–) ?[0-3]?[0-9][\/\.-][0-3]?[0-9][\/\.-](19|20)\d{2}$/, // date range medium + /^[0-3]?[0-9][\/\.-][0-3]?[0-9][\/\.-](19|20)\d{2} ?(-|–) ?[0-3]?[0-9][\/\.-][0-3]?[0-9][\/\.-](19|20)\d{2}$/, // date range long + /^0\.\d+$/, // e.g. 0.12345678 +], + +inclLocalList: [ + [/^[1-9]\d{2}[\.-]\d{3}[\.-]\d{4}$/, "+1"], // US +], + +token_trigger: "+(0123456789", +token_part: " -/()[].\r\n" + + String.fromCharCode(0xa0) // sc_nbsp + + String.fromCharCode(0x2013) + String.fromCharCode(0x2014) + String.fromCharCode(0x2212), // token_dash +token_start: "+(0", +token_sep: " -/(.", +token_disallowed_post: ":-", +token_disallowed_prev: "-,.", + +dialHistory: new Array(objTelifyPrefs.maxHistory), + + +getDialHistory: function() +{ + for (var i=0; i<objTelifyPrefs.maxHistory; i++) { + try { + this.dialHistory[i] = objTelifyPrefs.telPrefs.getCharPref("history"+i); + } catch (e) { + this.dialHistory[i] = ""; + } + } +}, + + +saveDialHistory: function() +{ + for (var i=0; i<objTelifyPrefs.maxHistory; i++) { + if (this.dialHistory[i] == null) this.dialHistory[i] = ""; + objTelifyPrefs.telPrefs.setCharPref("history"+i, this.dialHistory[i]); + } +}, + + +updateDialHistory: function(prefix) +{ + //logmsg("updateDialHistory("+prefix+")"); + var newList = new Array(objTelifyPrefs.maxHistory); + newList[0] = prefix; + for (var i=0, j=1; i<objTelifyPrefs.maxHistory && j<objTelifyPrefs.maxHistory; i++) { + if (this.dialHistory[i] == null || this.dialHistory[i] == "" || this.dialHistory[i] == prefix) continue; + newList[j++] = this.dialHistory[i]; + } + this.dialHistory = newList; + this.saveDialHistory(); +}, + + +setStatus: function() +{ + var statusicon = document.getElementById("idTelify_statusicon"); + if (objTelifyPrefs.fActive) { + statusicon.setAttribute("src", "chrome://telify/content/icon18_active.png"); + var text = objTelifyPrefs.telStrings.getString("telify_active"); + statusicon.setAttribute("tooltiptext", text); + } else { + statusicon.setAttribute("src", "chrome://telify/content/icon18_inactive.png"); + var text = objTelifyPrefs.telStrings.getString("telify_inactive"); + statusicon.setAttribute("tooltiptext", text); + } +}, + + +toggleBlacklist: function() +{ + var host = objTelifyUtil.getHost(); + if (host == null) return; + if (objTelifyPrefs.excludedHosts.indexOf(host) >= 0) { + objTelifyUtil.arrayRemove(objTelifyPrefs.excludedHosts, host); + } else { + objTelifyPrefs.excludedHosts.push(host); + } + objTelifyPrefs.blacklist = objTelifyPrefs.excludedHosts.join(","); + objTelifyPrefs.telPrefs.setCharPref(objTelifyPrefs.PREF_BLACKLIST, objTelifyPrefs.blacklist); +}, + + +toggleActive: function() +{ + objTelifyPrefs.telPrefs.setBoolPref(objTelifyPrefs.PREF_ACTIVE, !objTelifyPrefs.fActive); + this.setStatus(); +}, + + +getSelectionNumber: function() +{ + //var sel = content.window.getSelection().toString(); + var sel = document.commandDispatcher.focusedWindow.getSelection().toString(); + sel = this.convertVanityNr(sel); + sel = objTelifyUtil.stripNumber(sel); + return sel; +}, + + +dialNumber: function(nr) +{ + var requ = new XMLHttpRequest(); + var url = objTelifyUtil.createDialURL(nr); + + if (objTelifyPrefs.hrefType == objTelifyPrefs.HREFTYPE_CUSTOM) { + if (objTelifyPrefs.custom_opentype == 1) { + window.open(url, "_blank"); + return; + } + if (objTelifyPrefs.custom_opentype == 2) { + var browser = top.document.getElementById("content"); + var tab = browser.addTab(url); + return; + } + if (objTelifyPrefs.custom_opentype == 3) { + var browser = top.document.getElementById("content"); + var tab = browser.addTab(url); + browser.selectedTab = tab; + return; + } + } + + try { + requ.open("GET", url, true); + requ.send(null); + } catch(e) { + // throws exception because answer is empty (or protocol is unknown) + if (e.name == "NS_ERROR_UNKNOWN_PROTOCOL") { + objTelifyUtil.showMessageBox("", objTelifyLocale.msgUnknownProtocol(), objTelifyUtil.MB_ICON_ERROR); + } + } +}, + + +modifyPopup: function(event) +{ + var label, key; + + //var selText = content.window.getSelection().toString(); + var selText = document.commandDispatcher.focusedWindow.getSelection().toString(); + + if (document.popupNode && document.popupNode.getAttribute("class") == "telified") { + var nr = document.popupNode.getAttribute("nr"); + var nr_parts = objTelifyUtil.splitPhoneNr(nr); + objTelify.modifyDialPopup(nr_parts[0], nr_parts[1], "context"); + objTelifyUtil.setIdAttr("collapsed", false, "idTelify_menu_context"); + } else if (objTelifyPrefs.fActive && selText.length > 0 && objTelifyUtil.countDigits(selText) > 1) { + var nr = objTelify.getSelectionNumber(); + var nr_parts = objTelifyUtil.splitPhoneNr(nr); + objTelify.modifyDialPopup(nr_parts[0], nr_parts[1], "context"); + objTelifyUtil.setIdAttr("collapsed", false, "idTelify_menu_context"); + } else { + objTelifyUtil.setIdAttr("collapsed", true, "idTelify_menu_context"); + } + + if (objTelifyPrefs.fActive) { + label = objTelifyPrefs.telStrings.getString("telify_deactivate"); + } else { + label = objTelifyPrefs.telStrings.getString("telify_activate"); + } + objTelifyUtil.setIdAttr("label", label, "idTelify_menu_activity", "idTelify_status_activity"); + + var host = objTelifyUtil.getHost(); + if (host) { + objTelifyUtil.setIdAttr("disabled", !objTelifyPrefs.fActive, "idTelify_menu_blacklist", "idTelify_status_blacklist"); + if (objTelifyPrefs.excludedHosts.indexOf(host) >= 0) key = "host_active_arg"; else key = "host_inactive_arg"; + label = objTelifyUtil.substArgs(objTelifyPrefs.telStrings.getString(key), host); + objTelifyUtil.setIdAttr("label", label, "idTelify_menu_blacklist", "idTelify_status_blacklist"); + } else { + objTelifyUtil.setIdAttr("label", "Kein Host aktiv", "idTelify_menu_blacklist", "idTelify_status_blacklist"); + objTelifyUtil.setIdAttr("disabled", true, "idTelify_menu_blacklist", "idTelify_status_blacklist"); + } +}, + + +showEditNumberDialog: function(cc, nr) +{ + var argObj = {cc: cc, nr: nr, fOK: false}; + window.openDialog("chrome://telify/content/editNumber.xul", "dlgTelifyEditNumber", "centerscreen,chrome,modal", argObj); + if (argObj.fOK) { + this.updateDialHistory(argObj.cc); + var dial = objTelifyUtil.prefixNumber(argObj.cc, argObj.nr, ""); + objTelify.dialNumber(dial); + } +}, + + +dialMenuSelection: function(cc, nr) +{ + this.updateDialHistory(cc); + var dial = objTelifyUtil.prefixNumber(cc, nr, ""); + objTelify.dialNumber(dial); +}, + + +createTargetCountryInfo: function(prefix) +{ + var cstring = objTelifyUtil.getCountryListString(prefix); + if (cstring) return "\n" + objTelifyPrefs.telStrings.getString('country_code') + ": " + cstring; + return ""; +}, + + +setDialMenuItem: function(item, code, nr) +{ + var label = objTelifyUtil.prefixNumber(code, nr, "-"); + item.setAttribute("label", label); + var cmd = "objTelify.dialMenuSelection('"+code+"','"+nr+"');"; + item.setAttribute("oncommand", cmd); + label = objTelifyUtil.substArgs(objTelifyPrefs.telStrings.getString('call_arg'), label); + label += objTelify.createTargetCountryInfo(code); + item.setAttribute("tooltiptext", label); + item.setAttribute("image", "chrome://telify/content/flag/"+code.substr(1)+".png"); +}, + + +modifyDialPopup: function(cc, nr, id) +{ + var item = document.getElementById("idTelify_"+id); + var sep = document.getElementById("idTelify_sep_"+id); + var numShown = 0; + + if (cc) { + this.setDialMenuItem(item, cc, nr); + } else { + item.setAttribute("label", nr); + var label = objTelifyUtil.substArgs(objTelifyPrefs.telStrings.getString('call_arg'), nr); + item.setAttribute("tooltiptext", label); + item.removeAttribute("image"); + item.setAttribute("oncommand", "objTelify.dialNumber('"+nr+"')"); + } + + item = document.getElementById("idTelify_edit_"+id); + if (cc) { + item.setAttribute("oncommand", "objTelify.showEditNumberDialog('"+cc+"','"+nr+"')"); + } else { + item.setAttribute("oncommand", "objTelify.showEditNumberDialog(null,'"+nr+"')"); + } + + var tldcc = objTelifyUtil.tld2cc(objTelifyUtil.getHostTLD()); + item = document.getElementById("idTelify_tld_"+id); + if (!cc && tldcc) { + item.setAttribute("collapsed", false); + this.setDialMenuItem(item, tldcc, nr); + numShown = 1; + } else { + item.setAttribute("collapsed", true); + tldcc = null; + } + + this.getDialHistory(); + + if (!cc && nr.charAt(0) != '+') { + var numLeft = objTelifyPrefs.numHistory; + if (tldcc) numLeft--; + for (var i=0; i<objTelifyPrefs.maxHistory; i++) { + item = document.getElementById("idTelify_"+id+i); + if (numLeft == 0 || this.dialHistory[i] == null || this.dialHistory[i].length == 0 || this.dialHistory[i] == cc || this.dialHistory[i] == tldcc) { + item.setAttribute("collapsed", true); + } else { + item.setAttribute("collapsed", false); + this.setDialMenuItem(item, this.dialHistory[i], nr); + numLeft--; + numShown++; + } + } + } else { + for (var i=0; i<objTelifyPrefs.maxHistory; i++) { + item = document.getElementById("idTelify_"+id+i); + item.setAttribute("collapsed", true); + } + } + sep.setAttribute("collapsed", numShown == 0); +}, + + +showDialPopup: function(target, cc, nr) +{ + var menu = document.getElementById("idTelify_popup_dial"); + var nr_parts = objTelifyUtil.splitPhoneNr(nr); + this.modifyDialPopup(cc, nr, "dial"); + menu.openPopup(target, "after_start", 0, 0, true, false); +}, + + +onClick: function(event) +{ + if (event.button != 0) return; + var class = event.target.getAttribute("class"); + if (class != "telified") return; + event.preventDefault(); + var nr = event.target.getAttribute("nr"); + var nr_parts = objTelifyUtil.splitPhoneNr(nr); + if (event.button == 0) { + if (nr_parts[0] && objTelifyPrefs.fDialCCDirect) { + objTelify.dialNumber(nr); + } else { + objTelify.showDialPopup(event.target, nr_parts[0], nr_parts[1]); + } + } + if (event.button == 2) { + objTelify.showDialPopup(event.target, nr_parts[0], nr_parts[1]); + } +}, + + +getNodeBackgroundColor: function(node) +{ + node = node.parentNode; + if (node == null) return null; + if (node.nodeType == Node.ELEMENT_NODE) { + var style = content.document.defaultView.getComputedStyle(node, ""); + var image = style.getPropertyValue("background-image"); + if (image && image != "none") return null; + var color = style.getPropertyValue("background-color"); + if (color && color != "transparent") return color; + } + return this.getNodeBackgroundColor(node); +}, + + +getNodeColor: function(node) +{ + node = node.parentNode; + if (node == null) return null; + if (node.nodeType == Node.ELEMENT_NODE) { + var style = content.document.defaultView.getComputedStyle(node, ""); + var color = style.getPropertyValue("color"); + if (color && color != "transparent") return color; + } + return this.getNodeColor(node); +}, + + +formatPhoneNr: function(phonenr) +{ + var substList = [ + [" ", " "], // double spaces to single space + [this.sc_nbsp, " "], // non-breaking space to plain old space + ["+ ", "+"], // remove space after + + ["--", "-"], // double dashes to single dash + ["(0)", " "], // remove optional area code prefix + ["[0]", " "], // remove optional area code prefix + ["-/", "/"], + ["/-", "/"], + ["( ", "("], + [" )", ")"], + ["\r", " "], + ["\n", " "], + ]; + + // replace dash-like chars with dashes + for (var i=0; i<phonenr.length; i++) { + var c = phonenr.charAt(i); + if (this.token_dash.indexOf(c) >= 0) { + phonenr = phonenr.substr(0, i) + "-" + phonenr.substr(i+1); + } + } + + const MAXLOOP = 100; // safety bailout + var nChanged; + + nChanged = 1; + for (var j=0; nChanged > 0 && j < MAXLOOP; j++) { + nChanged = 0; + for (var i=0; i<substList.length; i++) { + var index; + while ((index = phonenr.indexOf(substList[i][0])) >= 0) { + phonenr = phonenr.substr(0, index) + substList[i][1] + phonenr.substr(index+substList[i][0].length); + nChanged++; + } + } + } + + return phonenr; +}, + + +convertVanityNr: function(phonenr) +{ + const tab_alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const tab_digit = "22233344455566677778889999"; + var newnr = ""; + for (var i=0; i<phonenr.length; i++) { + var c = phonenr.charAt(i); + var index = tab_alpha.indexOf(c); + if (index >= 0) c = tab_digit.substr(index, 1); + newnr += c; + } + return newnr; +}, + + +reject: function(str, reason) +{ + if (objTelifyPrefs.fDebug == false) return; + var msg = "Telify: reject '"+str+"' reason: "+reason; + objTelifyUtil.logmsg(msg); +}, + + +basechar_tab: [ + String.fromCharCode(0xa0) + + String.fromCharCode(0x2013) + + String.fromCharCode(0x2014) + + String.fromCharCode(0x2212), + " ---" +], + + +basechar: function(c) +{ + var index = this.basechar_tab[0].indexOf(c); + if (index >= 0) c = this.basechar_tab[1].charAt(index); + return c; +}, + + +telifyTextNode: function(node) +{ + if (node == null) return 0; + var text = node.data; + var len = text.length; + if (len < this.digits_min) return 0; + var hlFactor = objTelifyPrefs.highlight/200.0; + + for (var i=0; i<len; i++) { + var c = text.charAt(i); + + if (this.token_trigger.indexOf(c) < 0) continue; + + c = this.basechar(c); + + var str = "" + c; + var strlen = 1; + var last_c = c; + var ndigits = (objTelifyUtil.isdigit(c) ? 1 : 0); + var index; + var fStartsWithCountryCode = false; + var CCfromPattern = null; + + // gather allowed chars + while (strlen < len-i) { + c = text.charAt(i+strlen); + c = this.basechar(c); + if ((c == '+' && ndigits == 0) || (this.token_part.indexOf(c) >= 0)) { + if (c == last_c && c!=' ') break; + } else { + if (!objTelifyUtil.isdigit(c)) break; + ndigits++; + } + str += c; + strlen++; + last_c = c; + } + + // check against digit count min value + if (ndigits < this.digits_min) { + this.reject(str, "less than "+this.digits_min+" digits"); + i += strlen - 1; continue; + } + + // check allowed prev token + if (i > 0) { + var prev_c = text.charAt(i-1); + if (this.token_disallowed_prev.indexOf(prev_c) >= 0) { + this.reject(str, "unallowed previous token (reject list)"); + i += strlen - 1; continue; + } + if ((prev_c >= 'a' && prev_c <= "z") || (prev_c >= 'A' && prev_c <= "Z")) { + this.reject(str, "unallowed previous token (letter)"); + i += strlen - 1; continue; + } + } + + // check if phone number starts with country code + for (var j=0; j<telify_country_data.length; j++) { + var cclen = telify_country_data[j][0].length; + if (cclen < 2 || cclen > 4) continue; + var pattern = telify_country_data[j][0].substr(1); + var plen = pattern.length; + if (str.substr(0, plen) != pattern) continue; + var c = str.charAt(plen); + if (this.token_sep.indexOf(c) < 0) continue; + fStartsWithCountryCode = true; + break; + } + + // check against special local patterns + for (var j=0; j<this.inclLocalList.length; j++) { + var res = this.inclLocalList[j][0].exec(str); + if (res) {CCfromPattern = this.inclLocalList[j][1]; break;} + } + + // check if phone number starts with allowed token + if (CCfromPattern == null && fStartsWithCountryCode == false && this.token_start.indexOf(str.charAt(0)) < 0) { + this.reject(str, "unallowed start token (reject list)"); + i += strlen - 1; continue; + } + + // trim chars at end of string up to an unmatched opening bracket + index = -1; + for (var j=strlen-1; j>=0; j--) { + c = str.charAt(j); + if (c == ')') break; + if (c == '(') {index = j; break;} + } + if (index == 0) continue; + if (index > 0) { + str = str.substr(0, index); + strlen = str.length; + } + + // check against digit count max value (after we have removed unnecessary digits) + if (objTelifyUtil.countDigits(str) > this.digits_max) { + this.reject(str, "more than "+this.digits_max+" digits"); + i += strlen - 1; continue; + } + + // trim non-digit chars at end of string + while (str.length > 0) { + c = str.charAt(str.length-1); + if (!objTelifyUtil.isdigit(c)) { + str = str.substr(0, str.length-1); + strlen--; + } else break; + } + + // check allowed post token + var post_c = text.charAt(i+strlen); + if (post_c) { + if (this.token_disallowed_post.indexOf(post_c) >= 0) { + this.reject(str, "unallowed post token (reject list)"); + i += strlen - 1; continue; + } + if ((post_c >= 'a' && post_c <= "z") || (post_c >= 'A' && post_c <= "Z")) { + this.reject(str, "unallowed post token (letter)"); + i += strlen - 1; continue; + } + } + + // check if this is just a number in braces + // first check for unnecessary opening braces + if (str.substr(0, 1) == "(" && str.indexOf(")") < 0) { + str = str.substr(1); + i++; + strlen--; + // now check if it still starts with allowed token + if (this.token_start.indexOf(str.charAt(0)) < 0) { + this.reject(str, "unallowed start token (after brace removal)"); + i += strlen - 1; + continue; + } + } + + // check against blacklist patterns (date, time ranges etc.) + index = -1; + for (var j=0; j<this.exclPatternList.length; j++) { + var res = this.exclPatternList[j].exec(str); + if (res) {index = j; break;} + } + if (index >= 0) {this.reject(str, "blacklisted pattern #"+index); i += strlen - 1; continue;} + + + // ---------------------------------------------------------------- + + var display = this.formatPhoneNr(str); + var href = objTelifyUtil.stripNumber(display); + if (fStartsWithCountryCode) href = "+"+href; + //if (CCfromPattern) href = CCfromPattern + href; + + // insert link into DOM + + var node_prev = content.document.createTextNode(text.substr(0, i)); + var node_after = content.document.createTextNode(text.substr(i+strlen)); + + //alert("match="+str); + + var node_anchor = content.document.createElement("a"); + + if (hlFactor > 0.0) { + var color = objTelifyUtil.parseColor(this.getNodeColor(node)); + if (color == null) color = new Array(0,0,0); + var bgcolor = objTelifyUtil.parseColor(this.getNodeBackgroundColor(node)); + if (bgcolor == null) bgcolor = new Array(255,255,255); + for (var i=0; i<3; i++) { + color[i] = color[i] + hlFactor * (this.hilite_color[i] - color[i]); + bgcolor[i] = bgcolor[i] + hlFactor * (this.hilite_bgcolor[i] - bgcolor[i]); + } + var style = "color:#"+objTelifyUtil.color2hex(color)+";background-color:#"+objTelifyUtil.color2hex(bgcolor)+";-moz-border-radius:3px"; + node_anchor.setAttribute("style", style); + } + + node_anchor.setAttribute("title", objTelifyPrefs.telStrings.getString('link_title')); + node_anchor.setAttribute("class", "telified"); + node_anchor.setAttribute("nr", href); + node_anchor.setAttribute("href", objTelifyUtil.createDialURL(href)); + + var node_text = content.document.createTextNode(str); + node_anchor.appendChild(node_text); + + var parentNode = node.parentNode; + parentNode.replaceChild(node_after, node); + parentNode.insertBefore(node_anchor, node_after); + parentNode.insertBefore(node_prev, node_anchor); + + return 1; + } + + return 0; +}, + + +recurseNode: function(node) +{ + if (node == null) return 0; // safety + if (node.nodeType == Node.TEXT_NODE) { + return this.telifyTextNode(node); + } else { + var nChanged = 0; + //objTelifyUtil.logmsg("node type="+node.nodeType+" "+node.tagName+" (childs:"+node.childNodes.length+")"); + if (node.nodeType == Node.ELEMENT_NODE) { + var tagName = node.tagName.toLowerCase(); + if (objTelifyPrefs.excludedTags.indexOf(tagName) >= 0) return 0; + } + for (var i=0; i<node.childNodes.length; i++) { + nChanged += this.recurseNode(node.childNodes[i]); + } + if (node.contentDocument) { + nChanged += this.recurseNode(node.contentDocument.body); + node.contentDocument.addEventListener("click", objTelify.onClick, false); + } + } + return nChanged; +}, + + +parsePage: function(event) +{ + if (!objTelifyPrefs.fActive) return; + //objTelifyUtil.logmsg("eventPhase: "+event.eventPhase+"\n"+content.document.URL); + if (content.document.body == null) return; + if (event && event.eventPhase != 1) return; + + var host = objTelifyUtil.getHost(); + if (host && objTelifyPrefs.excludedHosts.indexOf(host) >= 0) return; + + //if (content.document.body.getAttribute('telified') == 1) return; + //content.document.body.setAttribute('telified', 1); + +/* + var nChanged = 0; + var duration = (new Date()).getTime(); + nChanged = objTelify.recurseNode(content.document.body); + duration = (new Date()).getTime() - duration; + var label = "Telify\n" + objTelifyPrefs.telStrings.getString('converted') + ": " + nChanged + " (" + duration + " ms)"; + document.getElementById("idTelify_statusicon").setAttribute("tooltiptext", label); +*/ + + window.setTimeout("objTelify.recurseNode(content.document.body)", 0); + + content.document.addEventListener("click", objTelify.onClick, false); +}, + + +init: function(event) +{ + window.addEventListener('load', objTelify.init, false); + objTelifyPrefs.initTelifyPrefs(); + objTelify.setStatus(); + getBrowser().addEventListener("load", objTelify.parsePage, true); + document.getElementById("contentAreaContextMenu").addEventListener("popupshowing", objTelify.modifyPopup, false); + objTelifyUtil.addScheme("tel"); + objTelifyUtil.localizeCountryData(); + objTelifyUtil.getAddonVersion(); +} + +}; + + +window.addEventListener('load', objTelify.init, false); + diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/util.js b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/util.js new file mode 100644 index 0000000000..838cd91afb --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/util.js @@ -0,0 +1,516 @@ +/* +Creative Commons License: Attribution-No Derivative Works 3.0 Unported +http://creativecommons.org/licenses/by-nd/3.0/ +(c)2009 Michael Koch +*/ + +var objTelifyUtil = { + +getBrowser: function() +{ + var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator); + var mainWindow = wm.getMostRecentWindow("navigator:browser"); + var browser = mainWindow.getBrowser(); + return browser; +}, + + +getAddonVersion: function() +{ + var gExtensionManager = Components.classes["@mozilla.org/extensions/manager;1"] + .getService(Components.interfaces.nsIExtensionManager); + return gExtensionManager.getItemForID("{6c5f349a-ddda-49ad-bdf0-326d3fe1f938}").version; +}, + + +createDialURL: function(nr) +{ + var url; + if (nr.charAt(0) == '+') { + if (objTelifyPrefs.idd_prefix.length > 0) { + nr = objTelifyPrefs.idd_prefix + nr.substr(1); + } else if (objTelifyPrefs.hrefType == objTelifyPrefs.HREFTYPE_CUSTOM && !objTelifyPrefs.fDontEscapePlus) { + nr = "%2B" + nr.substr(1); + } + } + if (objTelifyPrefs.hrefType == objTelifyPrefs.HREFTYPE_CUSTOM) { + url = objTelifyPrefs.custom_url; + url = objTelifyUtil.replaceRefs(url, 0, nr); + for (var i=1; i<objTelifyPrefs.NUM_CUSTOM_PARAMS+1; i++) { + url = objTelifyUtil.replaceRefs(url, i, objTelifyPrefs.custom_param[i]); + } + } else { + url = objTelifyPrefs.protoList[objTelifyPrefs.hrefType]+":"+nr; + } + return url; +}, + + +token_href: "+0123456789", + +stripNumber: function(phonenr) +{ + var newnr = ""; + for (var i=0; i<phonenr.length; i++) { + var c = phonenr.charAt(i); + if (this.token_href.indexOf(c) >= 0) newnr += c; + } + return newnr.substr(0, objTelify.digits_max); +}, + + +code2ndd_hashtable: null, + +create_code2ndd_hashtable: function() +{ + this.code2ndd_hashtable = new Hashtable(); + for (var i=0; i<telify_country_data.length; i++) { + if (telify_country_data[i][0] == "") continue; + this.code2ndd_hashtable.put(telify_country_data[i][0], telify_country_data[i][3]); + } +}, + + +prefixNumber: function(prefix, nr, sep) +{ + if (prefix == null || prefix == "") return this.stripNumber(nr); + if (this.code2ndd_hashtable == null) this.create_code2ndd_hashtable(); + var ndd = this.code2ndd_hashtable.get(prefix); + if ((ndd.length > 0) && (nr.substr(0, ndd.length) == ndd)) nr = nr.substr(ndd.length); + return this.stripNumber(prefix) + sep + this.stripNumber(nr); +}, + + +trim: function(s) +{ + s = s.replace(/^\s*(.*)/, "$1"); + s = s.replace(/(.*?)\s*$/, "$1"); + return s; +}, + + +localizeCountryData: function() +{ +/* + for (var i=0; i < telify_country_data.length; i++) { + for (var j=0; j<telify_country_locale.length; j++) { + if (telify_country_data[i][1] == telify_country_locale[j][0]) { + telify_country_data[i][1] = telify_country_locale[j][1]; + break; + } + } + } +*/ + var hashtable = new Hashtable(); + for (var i=0; i<telify_country_locale.length; i++) { + hashtable.put(telify_country_locale[i][0], telify_country_locale[i][1]); + } + for (var i=0; i<telify_country_data.length; i++) { + var value = hashtable.get(telify_country_data[i][1]); + if (value) telify_country_data[i][1] = value; + } +}, + + +tld_hashtable: null, + +create_tld_hashtable: function() +{ + this.tld_hashtable = new Hashtable(); + for (var i=0; i<telify_country_data.length; i++) { + if (telify_country_data[i][2] == "") continue; + var tld_list = telify_country_data[i][2].toLowerCase().split(","); + for (var j=0; j<tld_list.length; j++) { + tld_list[j] = this.trim(tld_list[j]); + this.tld_hashtable.put(tld_list[j], telify_country_data[i][0]); + } + } +}, + + +tld2cc: function(tld) +{ + if (this.tld_hashtable == null) this.create_tld_hashtable(); + return this.tld_hashtable.get(tld); +}, + + +splitPhoneNr: function(nr) +{ + var index = -1; + var maxlen = 0; + var idd_list = ["00", "011"]; + var oldnr = nr; + + if (nr.charAt(0) != '+') { + for (var i=0; i<idd_list.length; i++) { + if (nr.substr(0, idd_list[i].length) == idd_list[i]) { + nr = "+" + nr.substr(idd_list[i].length); + break; + } + } + } + if (nr.charAt(0) != '+') return [null, oldnr]; + for (var i=0; i<telify_country_data.length; i++) { + if (nr.substr(0, telify_country_data[i][0].length) == telify_country_data[i][0]) { + if (telify_country_data[i][0].length > maxlen) { + index = i; + maxlen = telify_country_data[i][0].length; + } + } + } + if (index >= 0) { + var cc = telify_country_data[index][0]; + return [cc, nr.substr(cc.length)]; + } + return [null, oldnr]; +}, + + +code2name_hashtable: null, + +create_code2name_hashtable: function() +{ + this.code2name_hashtable = new Hashtable(); + for (var i=0; i<telify_country_data.length; i++) { + if (telify_country_data[i][0] == "") continue; + var name = telify_country_data[i][1]; + var prev = this.code2name_hashtable.get(telify_country_data[i][0]); + if (prev) name = prev + ", " + name; + this.code2name_hashtable.put(telify_country_data[i][0], name); + } +}, + + +getCountryListString: function(prefix) +{ + if (this.code2name_hashtable == null) this.create_code2name_hashtable(); + return this.code2name_hashtable.get(prefix); +}, + + +getHost: function() +{ + try { + return content.document.location.host.toLowerCase(); + } catch (e) { + return null; + } +}, + + +getHostTLD: function() +{ + var host = this.getHost(); + if (host) { + var index = host.lastIndexOf('.'); + if (index >= 0) { + var tld = host.substr(index+1); + if (tld.length) return tld; + } + } + return null; +}, + + +MB_MASK: 0xff, MB_OK: 1, MB_CANCEL: 2, +MB_ICON_MASK: 0xff00, MB_ICON_INFO: 0, MB_ICON_WARNING: 0x0100, MB_ICON_ERROR: 0x0200, MB_ICON_ASK: 0x0300, + +showMessageBox: function(title, msg, flags) +{ + var argObj = {title: title, msg: msg, flags: flags, fResult: true}; + window.openDialog("chrome://telify/content/messagebox.xul", "dlgTelifyMessageBox", "centerscreen,chrome,modal", argObj).focus(); + return argObj.fResult; +}, + + +consoleService: null, + +logmsg: function(msg) { + if (this.consoleService == null) { + this.consoleService = Components.classes["@mozilla.org/consoleservice;1"]; + this.consoleService = this.consoleService.getService(Components.interfaces.nsIConsoleService); + } + this.consoleService.logStringMessage(msg); +}, + + +logerror: function(msg) { + Components.utils.reportError(msg); +}, + + +arrayRemove: function(a, v) +{ + for (var i=0; i<a.length; i++) { + if (a[i] == v) { + a.splice(i, 1); + i--; + } + } +}, + + +replaceRefs: function(string, nr, param) +{ + var index; + while ((index = string.indexOf("$"+nr)) >= 0 && string.charAt(index-1) != '\\') { + string = string.substr(0, index) + param + string.substr(index+2); + } + return string; +}, + + +substArgs: function(text) +{ + var newText = ""; + for (var i=1; i<arguments.length && i<10; i++) { + for (var j=0; j<text.length; j++) { + var c = text.charAt(j); + if (c == '$') { + c = text.charAt(j+1); + if (c >= '1' && c <= '9') { + var index = c - '0'; + if (index < arguments.length) { + newText += arguments[index]; + } else { + this.logerror("substArgs("+text+"): argument for $"+index+" missing"); + } + j++; + } else { + newText += c; + } + } else { + newText += c; + } + } + } + return newText; +}, + + +setIdAttr: function(name, value) +{ + for (var i=2; i<arguments.length; i++) { + var e = document.getElementById(arguments[i]); + if (e) { + e.setAttribute(name, value); + } else { + this.logerror("unknown element '"+arguments[i]+"'"); + } + } +}, + + +countDigits: function(text) +{ + var count = 0; + for (var i=0; i<text.length; i++) { + var c = text.charAt(i); + if (c >= '0' && c <= '9') count++; + } + return count; +}, + + +isdigit: function(c) +{ + return ("0123456789".indexOf(c) >= 0); +}, + + +trimInt: function(value, min, max) +{ + if (value < min) return min; + if (value > max) return max; + return value; +}, + + +parseColor: function(text) +{ + var exp, res, color; + + if (text == null) return null; + + exp = /^rgb *\( *(\d{1,3}) *, *(\d{1,3}) *, *(\d{1,3}) *\)$/; + res = exp.exec(text); + if (res) { + color = new Array(parseInt(res[1]), parseInt(res[2]), parseInt(res[3])); + for (var i=0; i<3; i++) { + if (color[i] < 0) color[i] = 0; + if (color[i] > 255) color[i] = 255; + } + return color; + } + + exp = /^#?([\da-f]{2})([\da-f]{2})([\da-f]{2})$/i; + res = exp.exec(text); + if (res) { + color = new Array(parseInt(res[1], 16), parseInt(res[2], 16), parseInt(res[3], 16)); + return color; + } + + exp = /^#?([\da-f])([\da-f])([\da-f])$/i; + res = exp.exec(text); + if (res) { + color = new Array(parseInt(res[1], 16), parseInt(res[2], 16), parseInt(res[3], 16)); + for (var i=0; i<3; i++) color[i] = color[i]*16+color[i]; + return color; + } + + return null; +}, + + +color2hex: function(color) +{ + var hex; + + if (color == null || color.length != 3) return ""; + for (var i=0, hex=""; i<3; i++) { + var d = "0"+Math.floor(color[i]).toString(16); + hex += d.substr(d.length - 2, 2); + } + return hex; +}, + + +esc2xml: function(string) +{ + var substList = [ + ["&", "&"], // here be dragons: must be first element in list + ["<", "<"], + [">", ">"], + ["\'", "'"], + ["\"", """], + ["Ä", "Ä"], + ["Ö", "Ö"], + ["Ü", "Ü"], + ["ä", "ä"], + ["ö", "ö"], + ["ü", "ü"], + ["ß", "ß"], + ]; + + for (var i=0; i<substList.length; i++) { + var index; + while ((index = string.indexOf(substList[i][0])) >= 0) { + string = string.substr(0, index) + substList[i][1] + string.substr(index+substList[i][0].length); + } + } + + return string; +}, + + +iso2utf8: function(s) +{ + s = s.split(""); + for (var i=0; i<s.length; i++) { + var c = s[i].charCodeAt(0); + if (c > 127) s[i] = String.fromCharCode(0xc0 | ((c >> 6) & 3)) + String.fromCharCode(0x80 | (c & 0x3f)); + } + return s.join(""); +}, + +addScheme: function(scheme) +{ + var createNC = function(aProperty) {return "http://home.netscape.com/NC-rdf#" + aProperty;}; + + var RDF = Components.classes["@mozilla.org/rdf/rdf-service;1"].getService(); + var IRDFService = RDF.QueryInterface(Components.interfaces.nsIRDFService); + + var ContainerUtils = Components.classes["@mozilla.org/rdf/container-utils;1"].getService(); + var IRDFContainerUtils = ContainerUtils.QueryInterface(Components.interfaces.nsIRDFContainerUtils); + + var Container = Components.classes["@mozilla.org/rdf/container;1"].createInstance(); + var IRDFContainer = Container.QueryInterface(Components.interfaces.nsIRDFContainer); + + const mimeTypes = "UMimTyp"; + var fileLocator = Components.classes["@mozilla.org/file/directory_service;1"].getService(Components.interfaces.nsIProperties); + var file = fileLocator.get(mimeTypes, Components.interfaces.nsIFile); + var ioService = Components.classes["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService); + var fileHandler = ioService.getProtocolHandler("file").QueryInterface(Components.interfaces.nsIFileProtocolHandler); + var datasource = IRDFService.GetDataSource(fileHandler.getURLSpecFromFile(file)); + var irds = datasource.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource); + + var about, property, value; + + about = IRDFService.GetResource("urn:schemes"); + property = IRDFService.GetResource(createNC("Protocol-Schemes")); + value = IRDFService.GetResource("urn:schemes:root"); + datasource.Assert(about, property, value, true); + + about = IRDFService.GetResource("urn:schemes:root"); + if (IRDFContainerUtils.IsSeq(datasource, about) == false) { + datasource.Assert(about, null, null, true); + IRDFContainerUtils.MakeSeq(datasource, about); + } + IRDFContainer.Init(datasource, about); + var element = IRDFService.GetResource("urn:scheme:"+scheme); + if (IRDFContainer.IndexOf(element) < 0) { + IRDFContainer.AppendElement(element); + } + + about = IRDFService.GetResource("urn:scheme:"+scheme); + property = IRDFService.GetResource(createNC("value")); + value = IRDFService.GetLiteral(scheme); + datasource.Assert(about, property, value, true); + property = IRDFService.GetResource(createNC("handlerProp")); + value = IRDFService.GetResource("urn:scheme:handler:"+scheme) + datasource.Assert(about, property, value, true); + + about = IRDFService.GetResource("urn:scheme:handler:"+scheme); + property = IRDFService.GetResource(createNC("alwaysAsk")); + value = IRDFService.GetLiteral("true"); + datasource.Assert(about, property, value, true); + property = IRDFService.GetResource(createNC("useSystemDefault")); + value = IRDFService.GetLiteral("false"); + datasource.Assert(about, property, value, true); +/* + property = IRDFService.GetResource(createNC("possibleApplication")); + value = IRDFService.GetResource("urn:scheme:possibleApplication:tel"); + datasource.Assert(about, property, value, true); + + about = IRDFService.GetResource("urn:scheme:possibleApplication:tel"); + property = IRDFService.GetResource(createNC("prettyName")); + value = IRDFService.GetLiteral("Nicht konfiguriert"); + datasource.Assert(about, property, value, true); + property = IRDFService.GetResource(createNC("uriTemplate")); + value = IRDFService.GetLiteral("urn:handler:web:http://www.mike-koch.de"); + datasource.Assert(about, property, value, true); +*/ + irds.Flush(); +} + + +/* + <RDF:Description RDF:about="urn:schemes"> + <NC:Protocol-Schemes RDF:resource="urn:schemes:root"/> + </RDF:Description> + + <RDF:Seq RDF:about="urn:schemes:root"> + <RDF:li RDF:resource="urn:scheme:webcal"/> + <RDF:li RDF:resource="urn:scheme:mailto"/> + <RDF:li RDF:resource="urn:scheme:callto"/> + <RDF:li RDF:resource="urn:scheme:tel"/> + </RDF:Seq> + + <RDF:Description RDF:about="urn:scheme:tel" NC:value="tel"> + <NC:handlerProp RDF:resource="urn:scheme:handler:tel"/> + </RDF:Description> + + <RDF:Description RDF:about="urn:scheme:handler:tel" NC:alwaysAsk="true"> + <NC:externalApplication RDF:resource="urn:scheme:externalApplication:tel"/> + </RDF:Description> + + <RDF:Description RDF:about="urn:scheme:externalApplication:tel" + NC:prettyName="3GP_Converter.exe" + NC:path="C:\Programme\3GP_Converter033\3GP_Converter.exe" /> + +*/ + + +}; + diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/warn32.png b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/content/warn32.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f6551d940eb76b48597f3f9bf09e2a3395b090 GIT binary patch literal 2125 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 literal 0 HcmV?d00001 diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/de-DE/country_locale.js b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/de-DE/country_locale.js new file mode 100644 index 0000000000..57e1c7035d --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/de-DE/country_locale.js @@ -0,0 +1,158 @@ +// caveat: save as UTF-8 +var telify_country_locale = [ +['U.S. Virgin Islands', 'Amerikanische Jungferninseln'], +['Northern Mariana Islands', 'Nördliche Marianen'], +['American Samoa', 'Amerikanisch-Samoa'], +['Canada', 'Kanada'], +['Antigua and Barbuda', 'Antigua und Barbuda'], +['British Virgin Islands', 'Britische Junferninseln'], +['Cayman Islands', 'Kaimaninseln'], +['Dominican Republic', 'Dominikanische Republik'], +['Jamaica', 'Jamaika'], +['Saint Kitts and Nevis', 'Saint Kitts und Nevis'], +['Saint Lucia', 'St. Lucia'], +['Saint Vincent and the Grenadines', 'St. Vincent und die Grenadinen'], +['Trinidad and Tobago', 'Trinidad und Tobago'], +['Turks and Caicos Islands', 'Turks- und Caicosinseln'], +['Egypt', 'Ägypten'], +['Morocco', 'Marokko'], +['Algeria', 'Algerien'], +['Tunisia', 'Tunesien'], +['Libya', 'Libyen'], +['Mauritania', 'Mauretanien'], +['Ivory Coast', 'Elfenbeinküste'], +['Ghana', 'Gana'], +['Chad', 'Tschad'], +['Central African Republic', 'Zentralafrikanische Republik'], +['Cameroon', 'Kamerun'], +['Cape Verde', 'Kap Verde'], +['São Tomé and Príncipe', 'São Tomé und Príncipe'], +['Equatorial Guinea', 'Äquatorialguinea'], +['Gabon', 'Gabun'], +['Congo (Republic)', 'Kongo (Republik)'], +['Congo (Democratic Republic)', 'Kongo (Demokratische Republik)'], +['Ascension Island', 'Ascension'], +['Seychelles', 'Seychellen'], +['Rwanda', 'Ruanda'], +['Ethiopia', 'Äthiopien'], +['Djibouti', 'Dschibuti'], +['Kenya', 'Kenia'], +['Tanzania', 'Tansania'], +['Mozambique', 'Mosambik'], +['Zambia', 'Sambia'], +['Madagascar', 'Madagaskar'], +['Zimbabwe', 'Simbabwe'], +['Botswana', 'Botsuana'], +['Swaziland', 'Swasiland'], +['Comoros', 'Komoren'], +['South Africa', 'Südafrika'], +['Saint Helena', 'St.Helena'], +['Faroe Islands', 'Färöer'], +['Greenland', 'Grönland'], +['Greece', 'Griechenland'], +['Netherlands', 'Niederlande'], +['Belgium', 'Belgien'], +['France', 'Frankreich'], +['Spain', 'Spanien'], +['Luxembourg', 'Luxemburg'], +['Ireland', 'Irland'], +['Iceland', 'Island'], +['Albania', 'Albanien'], +['Cyprus (South)', 'Zypern (Süden)'], +['Finland', 'Finnland'], +['Bulgaria', 'Bulgarien'], +['Hungary', 'Ungarn'], +['Lithuania', 'Litauen'], +['Latvia', 'Lettland'], +['Estonia', 'Estland'], +['Moldova', 'Moldawien'], +['Armenia', 'Armenien'], +['Nagorno-Karabakh', 'Bergkarabach'], +['Nagorno-Karabakh (Mobile)', 'Bergkarabach (Handynetz)'], +['Belarus', 'Weißrussland'], +['Kosovo (Mobile)', 'Kosovo (Handynetz)'], +['Serbia', 'Serbien'], +['Croatia', 'Kroatien'], +['Slovenia', 'Slowenien'], +['Kosovo (Mobile)', 'Kosovo (Handynetz)'], +['Bosnia and Herzegovina', 'Bosnien und Herzegowina'], +['Macedonia', 'Mazedonien'], +['Italy and Vatican City', 'Italien und Vatikanstadt'], +['Romania', 'Rumänien'], +['Switzerland', 'Schweiz'], +['Czech Republic', 'Tschechien'], +['Slovakia', 'Slowakei'], +['Austria', 'Österreich'], +['United Kingdom', 'Großbritannien'], +['Denmark', 'Dänemark'], +['Sweden', 'Schweden'], +['Norway', 'Norwegen'], +['Poland', 'Polen'], +['Germany', 'Deutschland'], +['Falkland Islands', 'Falklandinseln'], +['Saint-Pierre and Miquelon', 'Saint-Pierre und Miquelon'], +['Mexico', 'Mexiko'], +['Cuba', 'Kuba'], +['Argentina', 'Argentinien'], +['Brazil', 'Brasilien'], +['Colombia', 'Kolumbien'], +['Bolivia', 'Bolivien'], +['French Guiana', 'Französisch-Guayana'], +['Suriname', 'Surinam'], +['Netherlands Antilles', 'Niederländische Antillen'], +['Malaysia', 'Malaisia'], +['Australia', 'Australien'], +['Indonesia', 'Indonesien'], +['Philippines', 'Philippinen'], +['New Zealand', 'Neuseeland'], +['Singapore', 'Singapur'], +['East Timor', 'Ost-Timor'], +['Australian external territories', 'Australische Außengebiete'], +['Papua New Guinea', 'Papua-Neuguinea'], +['Solomon Islands', 'Salomonen'], +['Fiji', 'Fidschi'], +['Wallis and Futuna', 'Wallis und Futuna'], +['Cook Islands', 'Cook-Inseln'], +['Niue Island', 'Niue'], +['New Caledonia', 'Neukaledonien'], +['French Polynesia', 'Französisch-Polynesien'], +['Micronesia', 'Mikronesien'], +['Marshall Islands', 'Marshallinseln'], +['Russia', 'Russland'], +['Kazakhstan', 'Kasachstan'], +['South Korea', 'Südkorea'], +['North Korea', 'Nordkorea'], +['Hong Kong', 'Hongkong'], +['Macau', 'Macao'], +['Cambodia', 'Kambodscha'], +['Inmarsat (Atlantic East)', 'Inmarsat (Ostatlantik)'], +['Inmarsat (Pacific)', 'Inmarsat (Pazifik)'], +['Inmarsat (Indian)', 'Inmarsat (Indien)'], +['Inmarsat (Atlantic West)', 'Inmarsat (Westatlantik)'], +['Bangladesh', 'Bangladesch'], +['Global Mobile Satellite System', 'Globales mobiles Satellitensystem'], +['International Networks', 'Internationale Netzwerke'], +['Turkey', 'Türkei'], +['Cyprus (North)', 'Zypern (Nord)'], +['India', 'Indien'], +['Maldives', 'Malediven'], +['Lebanon', 'Libanon'], +['Jordan', 'Jordanien'], +['Syria', 'Syrien'], +['Iraq', 'Irak'], +['Saudi Arabia', 'Saudi Arabien'], +['Yemen', 'Jemen'], +['United Arab Emirates', 'Vereinigte Arabische Emirate'], +['Bahrain', 'Barain'], +['Qatar', 'Katar'], +['Bhutan', 'Butan'], +['Mongolia', 'Mongolei'], +['Tajikistan', 'Tadschikistan'], +['Azerbaijan', 'Aserbaidschan'], +['Georgia', 'Georgien'], +['Kyrgyzstan', 'Kirgisistan'], +['Uzbekistan', 'Usbekistan'], +['Guantanamo Bay', 'Guantanamo'], +['Midway Island', 'Midway Inseln'], +['Vatican City', 'Vatikanstadt'], +]; diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/de-DE/custom_preset.js b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/de-DE/custom_preset.js new file mode 100644 index 0000000000..7f248be61d --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/de-DE/custom_preset.js @@ -0,0 +1,8 @@ +/* (c)2009 Michael Koch +*/ + +/* name, url, parameter #1, parameter #2, parameter #3 */ +var telify_custom_preset = [ + ["", "", "Parameter #1", "Parameter #2", "Parameter #3"], + ["Vorlage für snom-Telefone", "http://$1/command.htm?number=$0&outgoing_uri=$2", "Telefon-IP", "Ausgehende URI", ""], +]; diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/de-DE/lang.dtd b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/de-DE/lang.dtd new file mode 100644 index 0000000000..797fd04dcf --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/de-DE/lang.dtd @@ -0,0 +1,39 @@ +<!ENTITY menu.edit_number "Telefonnummer bearbeiten"> +<!ENTITY menu.selection "Rufnummernauswahl"> +<!ENTITY menu.config "Einstellungen"> +<!ENTITY menu.onlinehelp "Online-Hilfe"> +<!ENTITY dialog.edit.title "Telefonnummer bearbeiten"> +<!ENTITY dialog.edit.code "Vorwahl"> +<!ENTITY dialog.edit.country "Land"> +<!ENTITY dialog.edit.dial "Wählen"> +<!ENTITY dialog.config.title "Telify-Einstellungen"> +<!ENTITY dialog.config.general "Allgemeine Einstellungen"> +<!ENTITY dialog.config.custom "Eigene URL"> +<!ENTITY dialog.config.about "Info"> +<!ENTITY dialog.config.replaces "Ersetzt"> +<!ENTITY dialog.config.in_template "in der Vorlage"> +<!ENTITY dialog.config.empty_url "Vorlage unten eingeben oder aus der Liste auswählen"> +<!ENTITY dialog.config.idd_prefix "Ersetze '+' durch"> +<!ENTITY dialog.config.hreftype "Verwendetes Protokoll"> +<!ENTITY dialog.config.hreftype0 "tel:"> +<!ENTITY dialog.config.hreftype1 "callto:"> +<!ENTITY dialog.config.hreftype2 "skype:"> +<!ENTITY dialog.config.hreftype3 "sip:"> +<!ENTITY dialog.config.hreftype_custom "Eigene URL"> +<!ENTITY dialog.config.dialcc "Bei vorhandener Landesvorwahl"> +<!ENTITY dialog.config.dialcc_menu "Öffne Menu"> +<!ENTITY dialog.config.dialcc_direct "Wähle direkt"> +<!ENTITY dialog.config.highlight "Texthervorhebung"> +<!ENTITY dialog.config.highlight0 "Keine"> +<!ENTITY dialog.config.highlight1 "Leicht"> +<!ENTITY dialog.config.highlight2 "Mittel"> +<!ENTITY dialog.config.highlight3 "Stark"> +<!ENTITY dialog.config.num_history "Anzahl der Nummerneinträge"> +<!ENTITY dialog.config.statusicon "Status-Icon anzeigen"> +<!ENTITY dialog.config.statusicon0 "Nein"> +<!ENTITY dialog.config.statusicon1 "Ja"> +<!ENTITY dialog.config.opentype "Öffne Link"> +<!ENTITY dialog.config.opentype0 "im Hintergrund"> +<!ENTITY dialog.config.opentype1 "in einem neuen Fenster"> +<!ENTITY dialog.config.opentype2 "in einem neuen Tab ohne Fokus"> +<!ENTITY dialog.config.opentype3 "in einem neuen Tab mit Fokus"> diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/de-DE/lang.properties b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/de-DE/lang.properties new file mode 100644 index 0000000000..52c37336e6 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/de-DE/lang.properties @@ -0,0 +1,13 @@ +leave_blank= +converted=Konvertiert +telify_active=Telify ist aktiv +telify_inactive=Telify ist inaktiv +telify_activate=Telify aktivieren +telify_deactivate=Telify deaktivieren +call_arg=$1 anrufen +host_active_arg=Auf $1 aktivieren +host_inactive_arg=Auf $1 deaktivieren +link_title=Wählbare Rufnummer +country_code=Landesvorwahl +empty_url=Vorlage unten eingeben oder aus der Liste auswählen +phonenr_tmpl=[TelNr] diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/de-DE/locale.js b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/de-DE/locale.js new file mode 100644 index 0000000000..539c6126e6 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/de-DE/locale.js @@ -0,0 +1,25 @@ +/* (c)2009 Michael Koch +*/ + +var objTelifyLocale = { + +openOnlineHelp: function() +{ + var browser = objTelifyUtil.getBrowser(); + var tab = browser.addTab("http://www.codepad.de/de/download/firefox-add-ons/telify.html"); + browser.selectedTab = tab; +}, + +msgNumberTemplateMissing: function() +{ + return "Ihre Vorlage enthält keinen Platzhalter für die Telefonnummer (d.h. '$0') und wird deshalb keine Telefonnummer übermitteln. " + + "Wollen Sie das wirklich?"; +}, + +msgUnknownProtocol: function() +{ + return "Im diesem System ist keine Anwendung installiert, die sich für das verwendete Protokoll registriert hat. " + + "Bitte stellen Sie in der Telify-Konfiguration ein geeignetes Protokoll ein oder installieren Sie eine geeignete Anwendung."; +} + +} diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/en-US/country_locale.js b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/en-US/country_locale.js new file mode 100644 index 0000000000..8d0143c5c1 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/en-US/country_locale.js @@ -0,0 +1,3 @@ +var telify_country_locale = [ +/* for en-US this is empty */ +]; diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/en-US/custom_preset.js b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/en-US/custom_preset.js new file mode 100644 index 0000000000..e8d3b6be97 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/en-US/custom_preset.js @@ -0,0 +1,8 @@ +/* (c)2009 Michael Koch +*/ + +/* name, url, parameter #1, parameter #2, parameter #3 */ +var telify_custom_preset = [ + ["", "", "Parameter #1", "Parameter #2", "Parameter #3"], + ["snom phones template", "http://$1/command.htm?number=$0&outgoing_uri=$2", "Telefon-IP", "Ausgehende URI", ""], +]; diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/en-US/lang.dtd b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/en-US/lang.dtd new file mode 100644 index 0000000000..0af8755f0e --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/en-US/lang.dtd @@ -0,0 +1,38 @@ +<!ENTITY menu.edit_number "Edit phone number"> +<!ENTITY menu.selection "Phone number selection"> +<!ENTITY menu.config "Preferences"> +<!ENTITY menu.onlinehelp "Online Help"> +<!ENTITY dialog.edit.title "Edit phone number"> +<!ENTITY dialog.edit.code "Code"> +<!ENTITY dialog.edit.country "Country"> +<!ENTITY dialog.edit.dial "Dial"> +<!ENTITY dialog.config.title "Telify Preferences"> +<!ENTITY dialog.config.general "General Settings"> +<!ENTITY dialog.config.custom "Custom URL"> +<!ENTITY dialog.config.about "About"> +<!ENTITY dialog.config.replaces "Replaces"> +<!ENTITY dialog.config.in_template "in template string"> +<!ENTITY dialog.config.idd_prefix "Replace '+' with"> +<!ENTITY dialog.config.hreftype "Used protocol"> +<!ENTITY dialog.config.hreftype0 "tel:"> +<!ENTITY dialog.config.hreftype1 "callto:"> +<!ENTITY dialog.config.hreftype2 "skype:"> +<!ENTITY dialog.config.hreftype3 "sip:"> +<!ENTITY dialog.config.hreftype_custom "Custom URL"> +<!ENTITY dialog.config.dialcc "When number has country code"> +<!ENTITY dialog.config.dialcc_menu "open menu"> +<!ENTITY dialog.config.dialcc_direct "dial directly"> +<!ENTITY dialog.config.highlight "Text highlighting"> +<!ENTITY dialog.config.highlight0 "None"> +<!ENTITY dialog.config.highlight1 "Light"> +<!ENTITY dialog.config.highlight2 "Medium"> +<!ENTITY dialog.config.highlight3 "Strong"> +<!ENTITY dialog.config.num_history "Number of recent country codes"> +<!ENTITY dialog.config.statusicon "Status icon"> +<!ENTITY dialog.config.statusicon0 "Hide"> +<!ENTITY dialog.config.statusicon1 "Show"> +<!ENTITY dialog.config.opentype "Open link"> +<!ENTITY dialog.config.opentype0 "silently in the background"> +<!ENTITY dialog.config.opentype1 "in a new window"> +<!ENTITY dialog.config.opentype2 "in a new tab without focus"> +<!ENTITY dialog.config.opentype3 "in a new tab with focus"> diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/en-US/lang.properties b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/en-US/lang.properties new file mode 100644 index 0000000000..ce7ed93d49 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/en-US/lang.properties @@ -0,0 +1,13 @@ +leave_blank= +converted=Converted +telify_active=Telify is active +telify_inactive=Telify is inactive +telify_activate=Activate Telify +telify_deactivate=Deactivate Telify +call_arg=Call $1 +host_active_arg=Activate on $1 +host_inactive_arg=Deactivate on $1 +link_title=phone number +country_code=Country Code +empty_url=Enter template below or choose from the list +phonenr_tmpl=[phonenr] diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/en-US/locale.js b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/en-US/locale.js new file mode 100644 index 0000000000..5240f8585d --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/chrome/locale/en-US/locale.js @@ -0,0 +1,25 @@ +/* (c)2009 Michael Koch +*/ + +var objTelifyLocale = { + +openOnlineHelp: function() +{ + var browser = objTelifyUtil.getBrowser(); + var tab = browser.addTab("http://www.codepad.de/en/download/firefox-add-ons/telify.html"); + browser.selectedTab = tab; +}, + +msgNumberTemplateMissing: function() +{ + return "Your template does not contain a placeholder for the phone number (i.e. '$0') and will therefore not transmit a phone number. " + + "Do you really want to continue?"; +}, + +msgUnknownProtocol: function() +{ + return "No application is installed which registered itself for the used protocol. " + + "Please configure a suitable protocol in the Telify preferences or install a suitable application."; +} + +} diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/defaults/preferences/preferences.js b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/defaults/preferences/preferences.js new file mode 100644 index 0000000000..b0bb58542d --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/defaults/preferences/preferences.js @@ -0,0 +1,18 @@ +pref("telify.settings.blacklist", ""); +pref("telify.settings.highlight", 25); +pref("telify.settings.debug", false); +pref("telify.settings.active", true); +pref("telify.settings.exclude", "a,applet,map,select,script,textarea"); +pref("telify.settings.statusicon", true); +pref("telify.settings.linktype", 0); +pref("telify.settings.colsortcc", 1); +pref("telify.settings.num_history", 5); +pref("telify.settings.idd_prefix", ""); +pref("telify.settings.custom_url", ""); +pref("telify.settings.custom_tmpl", 0); +pref("telify.settings.custom_param1", ""); +pref("telify.settings.custom_param2", ""); +pref("telify.settings.custom_param3", ""); +pref("telify.settings.custom_opentype", 3); +pref("telify.settings.dont_escape_plus", false); +pref("telify.settings.dial_cc_direct", false); diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/install.rdf b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/install.rdf new file mode 100644 index 0000000000..5128cb4f70 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/mozilla-telify-sflphone/usr/share/telify/install.rdf @@ -0,0 +1,37 @@ +<?xml version="1.0"?> +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + <Description about="urn:mozilla:install-manifest"> + <em:id>{6c5f349a-ddda-49ad-bdf0-326d3fe1f938}</em:id> + <em:extension>true</em:extension> + <em:iconURL>chrome://telify/content/icon32.png</em:iconURL> + <em:version>0.4.7.3</em:version> + <em:creator>Michael Koch</em:creator> + <em:homepageURL>http://www.codepad.de/</em:homepageURL> + <em:optionsURL>chrome://telify/content/config.xul</em:optionsURL> + + <em:localized> + <Description> + <em:locale>de-DE</em:locale> + <em:name>Telify</em:name> + <em:description>Erzeugt klickbare Links aus Telefonnummern</em:description> + </Description> + </em:localized> + + <em:localized> + <Description> + <em:locale>en-US</em:locale> + <em:name>Telify</em:name> + <em:description>Converts telephone numbers into clickable links</em:description> + </Description> + </em:localized> + + <!-- Firefox --> + <em:targetApplication> + <Description> + <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> + <em:minVersion>3.0</em:minVersion> + <em:maxVersion>3.6.*</em:maxVersion> + </Description> + </em:targetApplication> + </Description> +</RDF> \ No newline at end of file diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/postinst b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/postinst new file mode 100755 index 0000000000..1039df3268 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/postinst @@ -0,0 +1,16 @@ +#!/bin/bash + +gconftool-2 --direct --config-source xml::/etc/gconf/gconf.xml.defaults -t string -s /desktop/gnome/url-handlers/tel/command "/usr/bin/sflphone-handler %s" +gconftool-2 --direct --config-source xml::/etc/gconf/gconf.xml.defaults -s /desktop/gnome/url-handlers/tel/needs_terminal false -t bool +gconftool-2 --direct --config-source xml::/etc/gconf/gconf.xml.defaults -t bool -s /desktop/gnome/url-handlers/tel/enabled true + +gconftool-2 --direct --config-source xml::/etc/gconf/gconf.xml.defaults -t string -s /desktop/gnome/url-handlers/callto/command "/usr/bin/sflphone-handler %s" +gconftool-2 --direct --config-source xml::/etc/gconf/gconf.xml.defaults -s /desktop/gnome/url-handlers/callto/needs_terminal false -t bool +gconftool-2 --direct --config-source xml::/etc/gconf/gconf.xml.defaults -t bool -s /desktop/gnome/url-handlers/callto/enabled true + +gconftool-2 --direct --config-source xml::/etc/gconf/gconf.xml.defaults -t string -s /desktop/gnome/url-handlers/sip/command "/usr/bin/sflphone-handler %s" +gconftool-2 --direct --config-source xml::/etc/gconf/gconf.xml.defaults -s /desktop/gnome/url-handlers/sip/needs_terminal false -t bool +gconftool-2 --direct --config-source xml::/etc/gconf/gconf.xml.defaults -t bool -s /desktop/gnome/url-handlers/sip/enabled true + +exit 0 + diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/rules b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/rules new file mode 100755 index 0000000000..d002bc2839 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/rules @@ -0,0 +1,56 @@ +#!/usr/bin/make -f + +# Uncomment this to turn on verbose mode. +export DH_VERBOSE=1 + +VERSION="1.0" + +configure: configure-stamp +configure-stamp: + dh_testdir + touch configure-stamp + +build: configure-stamp build-stamp +build-stamp: + dh_testdir + + echo ${DIR} + umask 0022 + mkdir -p tmp/telify + unzip telify-${VERSION}-fx.xpi -d tmp/telify + touch build-stamp + +clean: + dh_testdir + dh_testroot + rm -f build-stamp configure-stamp + dh_clean + + rm -rf tmp + +install: build + dh_testdir + dh_testroot + dh_prep + dh_installdirs + dh_install + +# Build architecture-independent files here. +binary-indep: build install + dh_testdir + dh_testroot + dh_installchangelogs -i + dh_link -i + dh_compress -XMPL -i + dh_fixperms -i + dh_installdeb -i + dh_gencontrol -i + dh_md5sums -i + dh_builddeb -i + +# Build architecture-dependent files here. +binary-arch: build install +# We have nothing to do by default. + +binary: binary-indep binary-arch +.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/tools/build-system/launchpad/mozilla-telify-sflphone/debian/watch b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/watch new file mode 100644 index 0000000000..5836cddd95 --- /dev/null +++ b/tools/build-system/launchpad/mozilla-telify-sflphone/debian/watch @@ -0,0 +1,2 @@ +version=3 +http://www.codepad.de/en/download/firefox-add-ons/telify.html /download/Telify-(.*)-fx-tb.xpi diff --git a/tools/build-system/launchpad/sflphone-daemon-video/debian/changelog b/tools/build-system/launchpad/sflphone-daemon-video/debian/changelog new file mode 100644 index 0000000000..4d80576014 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon-video/debian/changelog @@ -0,0 +1,3585 @@ +sflphone-daemon-video (1.1.0-rc20120607~ppa1~SYSTEM) SYSTEM; urgency=low + + ** SNAPSHOT 1.1.0-rc20120607~ppa1~SYSTEM ** + + * * #12071: audiopreferences: fix make check + * * #12071: cleanup + * #12071: Lower expat dependency version + * * #12085: alsa: fix ringtone update bug, cleanup + * #12071: Fix pulseaudio compilation error + * #12071: Make pulseaudio optional at configuration time + * * #12091: daemon: issue warning if falling back to ALSA + * video: fixed make check + * *#12085: alsa: don't segfault on snd_pcm_avail_update error + * #12070: Add --without-pulse option + * * #12055: video: fix build for older libav + * [ #11886 ] cleanup + * [ #11886 ] Add basic reverse peer naming support + * [ #12008 ] Implement GUI part + * * #12012: video: fix some regressions + * * #12002: yamlparser: don't wipe out config if going from normal + build to --enable-video + * [ #12008 ] Add ConfigurationManager::getRingtoneList() + * * #12002: video: fix config file serialization/deserialization + * * #11987: managerimpl: fix bugs in conference when removing + participants + * * #11987: manager: fixed transfer from conference + * * #11987: mainbuffer: cleanup logging + * * #11979: pulse: fixed mismatched device list + * * #11971: audiolayer: fix bugs with getDeviceList + * * #11960: manager: validate conference earlier when processing + participants + * * #11960: manager: fixed segfault on transfer from conference + * * #11966: IP2IP: make alias consistently IP2IP + * * #11965: sipvoiplink: add more error checking in SDP negotiation + * * #11964: mainbuffer/ringbuffer: cleanup API + * sdp: remove unused variable warning + * * #11941: video: fix deprecated libav_api warnings + * * #11949: pulselayer: fix bug in getDeviceList + * video: whitespace fixes + * * #11951: video: fixed threading issues for ucommon Thread + * [#11848] Properly disable testPulseConnect + * [#11848] Disable pulseConnect test + * sdp: cleanup + * sdp: cleanup + * * #11860: mainbuffer: remove dead and/or buggy code + * * #11851: sdp: fixed gcc type narrowing warnings + * * #11851: audiostream: fixed gcc type narrowing warnings + * * #11841: don't put code with side effects in assert() + * * #11840: audiortp: remove some global symbols/variables + * * #11828: audiofile: fix broken build + * * #11828: audioloop: don't shadow sampleRate variable in derived + classes + * * #11818: gnome: stop daemon on SIGTERM, SIGINT or SIGHUP + * * #11499: daemon should also quit gracefully on SIGHUP + * * #11813: daemon configure should fail if expat is not installed + * #10304: updatePlaybackScale dbus method uses int 32 bit for size and + position (allow for 24 days long recording playback) + * #10304: Add time lable for seekslider + * * #11780: sip: don't use abort or leak calls on error and don't + restrict SDP size to 1000 bytes in transaction_request_cb + * * #11735: daemon: added timestamp start to call details + * * #10304: historyitem: added operator > defined in terms of operator + < + * * #11252: historyitem: added missing unistd.h header + * * #11252: daemon: removed deprecated zrtp code + * * #11728: yaml: check that nodes are valid before using them. + * * #10797: send DTMF over RTP as per RFC2833 + * * #11706: managerimpl: added unsetCurrentCall method + * * #11698: daemon: fix build for c++11 + * * #10304: historyitem: file_exists need not be a member method + * * #11499: managerimpl: don't crash if signal and dbus try and finish + the manager at the same time + * #10304: Prevent from storing removed files in history + * * #11499: daemon: Exit cleanly on SIGINT or SIGTERM + * * #10226: audiocodecfactory: use array instead of vector for codec + name lookup + * * #10226: audiocodecfactory: make codec loading stricter + * #10304: RCecale positions and size values for playback recording + * #11530: Make sure that only appropriate configuration option are + parsed for IP2IP calls + * * 11480: video: disabled by default + * * #11459: history: protect historyitems vector with mutex + * * #11448: fix video preferences for empty camera list + * #10304: Implemented playback seek in gnome client + * * #11269: video: fix codec per account management + * #10304: Implemented playback scale in gnome client + * Fix includes for gcc 4.7 + * * #11269: make clearer distinction between codecs and audiocodecs + * * #11269: merged master into video + * * #10296: managerimpl: more usage of getCallFromCallID + * * #10296: verify that calls exists before trying to join them in a + conference + * * #11208: bump version numbers for release 1.1.0 + * managerimpl: rename ManagerImpl::serialize/unserialize -> + join_string/split_string + * Fix warnings in resampler test + * * #10732: sipvoiplink: fix code that validates IP address + * Add historyChanged signal, better than managing it client side + * #10795: fix sipaccount deserialisation broken + * #10736: implement getConferenceId dbus method given a call id + * #10736: do not use iterator in daemon when joining conferences + * #10736: Fix joining conferences in daemon + * * #10736: gnome: fix crash on restart with active conference + * managerimpl: removed unused pulselayer.h header + * Save history everytime it change, prevent the file never to be saved + in some senario (SIG, crash, ASSERT, etc) + * * #10320: manager: check that participants are unique before joining + * #10335: Add a noise suppressor for incoming rtp streams + * * #10322: sip: registration state should not be always set to + ErrorAuth on error + * #10220: Fix recording thread does not exit when hanging up while + recording + * * #9903: fix includes for new ccrtp + * #10230: Get back default mainbuffer sampling rate to 8kHz, no need + of decoding noise suppressor + * #10230: Use a different samplerate converter for rtp encoding and + decoding + * * #9903: create DynamicPayloadFormat on stack, initialize earlier + * * #9903: audiorecorder: initialize buffer to silence, not random + data + * #10230: Test for triangular and sine signals + * #10230: Add resampling unit test + * * #10230: DTMF sample rate should come from main buffer, it should + not be hardcoded + * * #10213: audiolayer: create samplerateconverter on the stack + * * #10213: audiolayer: cleanup + * * #10213: increase resample buffer size, and check output size when + resampling + * * #10095: sipvoiplink: check pointers before using them + * #9981: IP2IP calls based on ip address instead of sip: + * * #10213: speex codecs should initialize their own parameters + * * #10213: account: removed redundant cast + * * #9832: removed extra printf + * * #10172: include -sflphone in recording file name + * * #9832: cleanup logging in tests + * * #10096: srtp: use vectors to simplify key/salt manipulation + * #10096: add case for non-srtp calls + * [ #10121 ] Sync the KDE with daemon, fix a few issues and implement + a recorded call player + * #9980: make keep registration optional as there is different + behavior on different registrar + * #10096: use c++ arrays to store keys in srtp sesssion + * * #10018: renamed registration related keys in dbus + * #10096: Fix onhold/offhold srtp + * * #8586: fixed make distcheck + * * #9832: logger: don't hide logging if NDEBUG is present + * #10096: Reinit crypto context when required on INVITE request + * * #10111: Fixes segfault on empty config file + * #100096: Set in/out queue crypto context at initialization, not when + starting the thread + * #10096: Update srtp key generation when holding/unholding a call + * * #9831: logger: removed extraneous carriage-return character + * * #10095: sipvoiplink: validate pointers before using them + * * #10094: renamed config/config.{h,cpp} config/sfl_config.{h,cpp} + * * #10090: fix segfault in transaction_state_changed_cb + * * #9832: audio_rtp_record_handler: cleanup logging + * * #9832: pulse: cleanup logging + * * #9832: cleanup logging + * * #9832: cleanup logging + * * #9832: dbus: fix logging + * * #9832: config: cleanup logging + * * #9832: remove unused header + * * #9832: manager: fix logging + * * #9832: audio: fix logging + * * #9832: audio: fix logging + * * #9832: zrtp: cleanup logging + * * #9832: AudioZRTPSession: cleanup logging + * * #9832: AudioSRTPSession: fix logging + * * #9832: cleanup logging + * * #9832: AudioRtpSession: cleanup logging + * * #9832: AudioRtpFactory: cleanup logging + * * #9832: codecs: fix logging + * * #9832: alsa: fix logging + * * #9832: audio: clean up logging + * * #9832: AudioRecord: cleanup logging + * * #9832: Fix logging in Manager + * * #9832: new logging macros + * * #9979: ulaw: fixed unused var warning + * * #10039: sipvoiplink: use references to avoid unnecessary parameter + validation + * * #10039: Fixed segfault on failed registration + * * #9979: codecs: fixed unused variable warnings + * * #9979: Don't do runtime assertions on data. + * * #10016: SDP: removed verbose debuggin + * #10016: Crypto context deletion are now managed inside the library + * * #10016: srtp: cleanup + * * #10016: SDES: fix uninitialized value bug, use const char* + * * 100016: don't double free crypto contexts, and don't improperly + copy CryptoSuiteDefinitions + * * #100016: cleanup crypto contexts in audio_srtp_session + * * #9979: removed unused methods from audicodec + * * #9979: ulaw: normalize types + * * #9979: cleanup + * * #9979: Alaw: cleanup + * * #9979: removed duplicate/superfluous code and type issues from + g722 + * * #9979: AudioRtpRecord: let AudioRtpRecord handle fadeIn internally + * #9980: Fix registration timer and transport shutdown on 401, default + registration timer to 3600 + * * #9979: use std::tr1::array instead of plain array for audio + buffers + * * #9969: set loose routing param when creating route set + * #9975: Fix account registration status display + * * #9969: SIP: initialize body earlier + * * #9969: sip: get received and rport fields if present in OK + * * #9968: fixed segfault in transaction callback + * #9898: make sure account are unregistered when sflphone quit, add + timeout on pending transaction + * #9910: fix contact header in outgoing request if via parameter are + present + * * #9910: SIP: use rport from VIA header if present + * * #9910: SipTransport: pass parameters by const reference + * #9910: fix sending call with new transport + * yaml: remove verbose debug messages + * * #9911: sipvoiplink: fixed "unused variable" warning + * #9910: create new udp transport to fix registration failure with 606 + error & received parameter + * * #9910: SIP: use pjsip error codes instead of magic numbers + * * #9911: SIP Transports must be cached by IP:port + * * #9910: SIP: cleanup + * * #9905: SipTransport: address has to stay on stack to be valid + * #9910: Update parse received parameter on 606 registration error + * * #9911: simplify network manager state reporting + * #9902: Fix SIPTest for IP to IP call + * #9911: Fix network manager crashes + * #9902: Move logic for ip2ip call in SIPVoIPLink + * #9902: Move logic for ip2ip call in SIPVoIPLink + * * #9910: fix 606 error code nomenclature + * * #9905: fixed address initialization in createUdpTransport + * * #9903: cleanup + * #9902: Log failure cause when new outgoing call fail + * * #9898: properly initialize ports + * #9898: Unregister account when leaving sflphone + * iax: create iaxvoiplink on stack + * account: removed unused methods + * * #9847: don't use assertions for input coming from DBus + * * #9897: audiorecord cleanup + * * #9897: audiorecord: cleanup, removed unused methods + * #9897: Initialize and fallback recording path in home directory if + not valid + * * #9871: SipTransport: hide more implementation + * * #9871: SipTransport: refactor SIP transport creation + * * #9871: disable STUN for account if STUN setup failed + * * #9847: check pointer before using it + * #9871: Fallback on normal upd transport when stun resolution fails + * Revert "#9871: Fallback on normal upd transport when stun resolution + fails" + * #9871: Fallback on normal upd transport when stun resolution fails + * pulse: cleanup + * * #9847: removed outdated README file + * * #9847: use references instead of pointers where possible + * * #9847: pass call by reference where possible + * * #9847: audiolayer: fixed typo + * * #9847: SIPVoipLink: gracefully handle invalid pointers + * * #9847: check that transport is initialized + * * #9847: SDP: avoid buffer overflow + * * #9847: fixed segfault on bad call invite + * * #9847: SDP: don't use assertions for runtime errors + * * #9847: handle invalid remote session gracefully + * * #9851: fixed segfault on stun socket cleanup + * * #8586: fixed warnings + * * #9849: added missing sstream header + * #9623: add required TLS certificates for testing purpose + * #9623: fixed tls registration + * #9623: fix storing tls port in config for normal account + * #9623: Allow all account to change tls listener port (not only + IP2IP) + * #9623: Allow for changing interface / port for tls transport + * #9623: Open TLS listener on selected interface + * #9833: remove unused debug + * #9833: handlingEvents_ must be initialized to true when starting iax + thread + * #9830: Remove create_route_set from sipvoiplink + * #9831: Fix sip transport port number + * #9830: move sip header parsing function in sip_utils + * Revert "* #8586: don't restore and save test files" + * * #8586: fixed make distcheck + * #9623: fixed tls registration + * * #8586: don't restore and save test files + * * #8586: refactored yaml code + * #9623: fix storing tls port in config for normal account + * #9623: Allow all account to change tls listener port (not only + IP2IP) + * #9623: Allow for changing interface / port for tls transport + * #9623: Open TLS listener on selected interface + * * #8586: added missing tests + * #9833: remove unused debug + * #9833: handlingEvents_ must be initialized to true when starting iax + thread + * #9830: Remove create_route_set from sipvoiplink + * #9831: Fix sip transport port number + * #9830: move sip header parsing function in sip_utils + * * #8977: removed unnecessary AC_CANONICAL macros from configure.ac + * * #8977: use actual PJSIP linking flags from pjproject/build.mak + * * #9774: sipvoiplink's destructor should not be public + * dbus: cleanup + * * #9774: make sure sipvoiplink is destroyed before accounts are + unloaded + * * #9777: don't use deprecated auto_ptr + * * #9778: removed AC_CHECK_FUNCS calls + * * #9782: fix warnings in tests + * * #9782: sip/sdp: fix emptiness checks + * * #9782: sdes_negotiator: fix iterator usage and set dangling + pointers to 0 + * * #9782: initialize all vars in iaxvoiplink + * * #9782: use fstreams instead of fscanf + * * #9782: yamlnode: fixed iterator usage + * * #9782: yamlemitter: fix iterator usage + * * #9782: yamlnode: make some methods const + * * #9782: initialize all member vars in constructor + * * #9782: Tone::interpolate should be const + * * #9782: mainbuffer: get rid of unused vars + * * #9782: GainControl::limit should be const + * * #9782: fix ARRAYSIZE check + * * #9782: use nanosleep instead of usleep + * * #9782: fixed "inefficient emptiness test" cppcheck warning + * * #9782: initialize dcblockers vars in constructor + * * #9779: dropped CELT support + * * #9750: moved sfl_data_format.h -> sfl_types.h + * * #9750: refactored global.h + * * #9736: restored command line options to daemon + * tests: cleanup + * * #8586: make distcheck was missing a header + * tests: cleanup + * * #9731: use all caps for application-wide constants + * tests: cleanup + * * #9730: cc++: enforce better checks in headers + * * #9730: builds against libccrtp1 + * * #9572: sipvoiplink: fixed typo + * * #9572: fixed threading issues with ccrtp2 + * * #9572: manager: pass config filename by const reference + * * #9572: Replace utilspp singleton implementation + * * #9571: regenerated config.{guess,sub} file to fix FTBFS on + armel/armhf. + * * #9665: siptransport: fixed udp_transport_start calls + * #9620 Add test SIP account in configuration sample (test/sflphoned- + sample.yml) + * * #9641: audiortp: Fixed CryptoContext management + * * #9641: fixed another memory leak in audio_srtp_session + * * #9641: audiosrtpsession: fixed memory leak, simplified memory + management + * * #9641: avoid dynamic memory allocs/raw pointer usage in audio rtp + stack + * * #9641: get rid of getType/RtpMethod logic + * fixed typo + * #9572: make sflphone compile with libccrtp 2 + * * #9490: fixed registration state change callback that was crashing + client + * * #9547: fixed warnings in SipTransport header + * #9547: Add SipTransport class + * #9547: Extract all the transport layer from SIPVoIPLink to new + SipTransport Class + * #9547: Destroy the STUN resolver in Transport shutdown + * sipvoiplink: removed erroneous FIXME + * sipvoiplink: cleanup + * #9547: Destroy the STUN resolver if server name change + * sipvoiplink: fix warning about variable shadowing + * #8320: Rename declared exception to avoid parameter shadowing + * #8320: Send signal to client on stun failure + * #8320: Use the same API for all transport creation (UDP, STUN, TLS) + * * #9509: use vector for credential info + * * #9508: fixes segfault in manager by changing order in which + destructors are called + * #8320: add dbus signal for stun failure + * #8320: Use two different variables for status and return statement + in stun's on_status_cb + * * #9490: removed resolve_once parameter that was causing a segfault + * #8320: make the retransmission callback to be rescheduled on error + * HookPreference: cleanup + * daemon: hookpreference: cleanup + * iaxvoiplink: terminate() doesn't have to be virtual + * sipvoiplink: functions need not be static if they are in an + anonymous namespace + * * #9037: moved CHECK macro into separate header + * * #9037: cleanup error handling/checking in video threads + * * #9037: video: cleanup + * * #9037: only signal receiving_video_event for rtp sessions + * * #9037: shared memory moved out of video_receive_thread + * * #9381: daemon: fixed make check for video + * * #9381: YAML_LIBS must be explicitly set in AC_SEARCH_LIBS macro + * * #9381: reverted yaml check + * * #9381: fix celt plugin compilation on fedora + * * #9381: use PKG_CHECK_MODULES to test for yaml + * * #9381: use autoconf macros and AC_SEARCH_LIBS + * * #9381: use AC_SEARCH_LIBS, AC_CHECK_LIB + * ringtonetest: cleanup + * configurationtest: cleanup + * instantmessagingtest.cpp: cleanup + * mainbuffertest: cleanup + * tests: cleanup + * #8320: Make sure stun keep alive is enabled + * call: push answer logic into call classes + * sipaccount: simplify IP2IP code + * sipaccount: avoid segfault if sipaccount is NULL + * sipaccount: cleanup + * #8084: Fix get sip header segfault when stun transport selected + * * #9037: created shared_memory class + * #8084: Init stun port with default valueas defined by RFC 3489 + * #9046: Move IP2IP_PROFILE global definition inside SIPAccount class + * #9045: fix Changing the account expire is not taken applied in + daemon + * vidoe_receive_thread: cleanup + * * #8968: suppress unusedFunction warnings for functions that are + actually used + * * #8821: fixed unit tests + * #8821: Renamed account map keys for consistency + * * #8968: audiorecord: added debug, clarified wave header creation + * * #8968: added debug message to get rid of "unused struct member" + warning + * * #8968: manager: create History on the stack + * * #9026: sfl::InstantMessaging is now a namespace + * * #8698: managerimpl: removed unused method isWaitingCall + * * #9008: don't include yaml headers in serializable.h + * * #9008: cleanup account map initialization + * refactor accountmap initialization + * #9020: fix config file not generated when no account created + * * #8968: audiorecord: removed unused getSndSamplingRate + * * #8968: config: removed getConfigTreeItemIntValue + * * #8968: recordable: removed unused getRecFileId + * * #8968: removed unused Codec::getMimeType method + * * #8968: manage lifetime of IMModule with auto_ptr + * * #8968: history: removed unused method + * * #8968: managerimpl: removed unused method + * * #8968: config: removed unused methods + * * #8968: managerimpl: removed unused getConfigBool/Int methods + * * #8968: networkmanager: cleanup + * * #8968: managerimpl: removed unused getConfig + * * #8968: managerimpl: Manage telephoneTone_ with auto_ptr. + * * #8968: history: fix memory leak upon exception + * * #8968: AudioFile: initialize filepath earlier + * * #8968: audiofile: fix memory leak on exception + * * #8968: audiocodec: removed unused getChannel method + * * #8968: use auto_ptr for dtmfKey + * * #8968: use vector instead of dynamically allocated int array + * * #8968: sdp.h: pass paramter by reference + * * #8968: sipvoiplink: avoid C-style pointer casting + * * #8968: yaml: avoid C-style pointer casts + * * #8968: pulselayer: avoid C-style pointer casting + * * #8968: recordable: removed unused getRecordingSmplRate method + * * #8968: alsalayer: use preincrement for iterators + * * #8968: config: removed unused method saveConfigTree + * * #8968: mainbuffer: preincrement iterators + * * #8977: history: added #include <fstream> + * * #8968: don't leak memory on exception + * * #8968: pulselayer: avoid C-style pointer casting + * * #8968: Member variables must be initialized in AudioSrtpSession + constructor + * * #8968: fix potential memory leak in audiorecord + * * #8968: Pass function parameter 'item' by const reference. + * * #8969: fixed memory leaks in sdes_negotiator + * video: cleanup + * * #8940: removed video test source for now + * #8763 Fix doxygen generation + * #8763 Generate Doxygen with Hudson + * * #8940: videosendthread: cleanup + * sipvoiplink: cleanup + * fileutils: cleanup + * #8335 Fix default transport initialization on 5062, 5064 + * #8762: update mute for mic only, fix remove slide for pulseaudio + * #8672: Add linear to decibel conversion functions in audio layer + * #8672: Implement audio gain management in pulseaudio + * #8671: Move audio gain management in audiolayer + * * #8542: create symbolic link properly + * * #8613: make check should fail early if another sflphone is running + * #8449: Update version 1.0.2 + * * #8545: fixed error case + * * #8545: fixed broken ringtone + * * #8586: fixed make dist + * sipvoiplink: use static_cast instead of reinterpret_cast if possible + * * #8542: test for .git existence before moving pre-commit hook + * * #8542: autogen.sh should not require git + * eventthread: cleanup + * * #8542: removed trailing whitespace from tree + * * #8357: added disable video option to client + * siptest: cleanup + * * #8521: use avcodec_open2 instead of deprecated avcodec_open, if + available + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Thu, 07 Jun 2012 16:08:15 -0400 + +sflphone-common (1.0.0-rc20110930~ppa1~SYSTEM) SYSTEM; urgency=low + + ** SNAPSHOT 1.0.0-rc20110930~ppa1~SYSTEM ** + + * update kde .gitignore + * Fix bug in volume widget + * More polishing for release + * Bump version to 1.0.0 + * [#7023] Add the ability to load an abstract contact backend in the + library to resolve more data, polish code + * [#7021] More cleanup for release + * Cleanup + * [#7021] Refactor KDE client dbus handling, add a missing call in + daemon and port the DataEngine to the new API + * Remove some annoying debug + * merge language scripts + * remove obsolete 'VERSION' files + * update install instructions + * Add missing translations to gnome + * language update + * Revert "Don't reference count DBus clients, exit core immediately + when one of them request it" + * Don't reference count DBus clients, exit core immediately when one + of them request it + * [7021] Add contact abstraction support + * [#7121] Polishing library (over). Indentation, spacing and naming + are now consistent + * codecs: link to libccrtp, don't use logger + * Fix a daemon bug + * [#7038] Fix adding contact + * * #7037 : stop audio stream after all calls have been hanged up + * [#7025] Add full support for bookmark + * SFLPhone KDE do not destroy history anymore + * Fix config skeleton + * Close the daemon once and for all, no more automatic respawning + * Fix "unregistered account" bug (I hope so) + * Close SFLPhone at the right place, it still respawn, I don't know + why + * Remove dead code + * Fix regressions introduced in the last commit + * Dead code elimination 1/3 + * Fix bug, add "add contact" option, fix warning + * * #7019: Fix IAX codec negociation + * Remove or comment unnecessary/unhelpful debug output + * Fix "same as local" account setting, fix IP2IP LED color + * Add support for some more advanced config options and add missing + config dialog icons + * Fix crash with noise suppressor + * Alternative can now be selected from the call view context menu + * Add drag and drop support, initial context menu and fix 3 bugs in + the account dialog + * Add basic history drag and drop support + * Complete contact support is back + * * #6991 : fix IAX problems + * Fix IAX accounts being disabled by default + * Revert "deb: forge -g flags for pjsip" + * * #5884: Disable debug code in pjsip + * echo suppressor : more assertions + * Don't let the daemon think crypto is enabled when it's not + * Simplify ToneList + * Some progress on contact support + * Remove unused getRegistrationCount() + * remove annoying debug + * revert SIP bit of e27e5c39bad27bae28f574eb2cba7717e8956229 + * Simplify CallManager::placeCallFirstAccount + * Fix crash on hold + * * #6905 : SIP refactor + * gnome client: be sure key exchange is set correctly + * Move code into createSipTransport + * Fix account registration on start + * ManagerImpl::registerAccounts(): simplify + * * #5884: don't mess with pjsip threads in echo suppressor + * * #6905 : simplify udp/stun/tls pjsip transport creation + * Restore and improve support for Call history + * fix launchpad build + * SIPVoIPLink: simplify / refactor + * Fix libwidget linking + * SIP: simplify + * IM : simplify + * gnome: remove some debug + * AudioRtpFactory::stop() cannot fail + * * #6905: simplify SIP code + * pjlib: fix build without SSLv2, fix warnings + * Port history to the new syntax + * Test a dock widget based implementation for contact and history + * Disable SSLv2 support from pjsip and sflphone + * deb: forge -g flags for pjsip + * Fix deb packaging to get debug symbols + * remove debug + * pjproject: update to last stable release (1.10) + * Require gtk >= 2.20 and glib >= 2.24 + * tlsadvanceddialog: simplify + * * #6902 : fix errors spotted by -DGSEAL_ENABLE + * Update daemon dbus XML and port KDE config backend from dbus to + local + * Remove unused but set variables + * * #6929 : fix IM widget, cleanup + * Unconditionally enable debug symbols + * Should fix many KDE issues + * * #6886 : hitting backspace on empty number have no side effects + * * #6905 : fix AudioCodecFactory access in optimized builds (-O > 0) + * Remove unsupported and broken jaunty/karmic packages + * * #6902 : avoid using some gtk deprecated functions + * Update dbus introspection files + * * #6904: removed unused contactmanager + * * #6903 : use correct dbus-cxx package name + * * #6902: don't use individual gtk headers + * Fix a segfault when config is not present + * Merge latest (0.9.13) KDE code. This version is not yet ready for + git master, but better than the previous one + * addressbook : simplify + * * #5659 : sflphone-plugins doesn't depend on libedataserverui + * * #5659 : addressbook doesn't use libedataserverui + * gnome client doesn't depend on evolution + * * #5695: addressbook: simplify + * * #5695: addressbook : remove AddrBookHandle from plugin + * * #5695 : addressbook : remove unused stuff in the client + * * #5695 : addressbook : remove unused stuff, use static mutex + * gnome client doesn't use evolution + * gnome: use proper API to set GTK_CAN_FOCUS + * * #6897: removed unused focus state vars/callbacks + * gnome: fix calls to sflphone_fill_codec_list_per_account + * * #6623: gnome: don't leak in mainwindow + * gnome: mainwindow whitespace cleanup + * gnome: actions.c parameter doesn't have to be a double pointer + * * #6895: fix memleaks, cleanup in accountconfigdialog + * * #6893: fixes segfault in client on clean history + * * #6894: fix leaks, cleanup in sflnotify + * daemon: fixed prints in main + * * #6892: simplify, fix leaks in dialpad + * * #6887: audiopreference creates audio layer + * * #6660: use const char * const, not std::string for globally + visible constants + * * #6852: Preferences now solely responsible for audiolayer creation. + * * #6860: refactor uimanager, also fixes #6865 + * * #6853: hangup as soon as all digits have been deleted + * * #6852: alsa: retry if device is busy + * * #6852: audiolayer creation depends only on preference.audioApi + * * #6850: gnome: fix build for gtk < 2.22.0 + * cleanup in iax + * alsa: typo + * pulse: if we can't peek in audio input, we can't drop samples + * * #6849: show error window if codecs are missing, instead of dying + * EchoCancel: unused, remove + * * #6629 : use number of samples as arguments for audio filters + * * #6629 : remove unused Algorithm interface + * * #6629 : use helper to call alsa functions and display error msgs + * Remove unused type + * * #6841: fix some error handling + * * #6629: simplify AlsaLayer::alsa_set_params() + * Get gdk key definition from header + * * #6828: Replace raw key codes by gdk defines + * remove some debug, enhance some other + * mainbuffer: simplify + * * #6561 : fix phantom call after transfer + * Conference Participant set : simplify + * SIPCall: remove unused functions, make invite session public + * * #6229 : remove malloc/free from pulse audio loop + * * #6629 : simplify pulse callbacks + * * #6629 + * Simplify widgets + * * #6629 : keep the correct audio module when frequency changes + * * #6751: fixed erroneous debug msgs + * callable_obj.h: removed unneeded pthread header + * alsalayer: cleanup + * * #6629: Always restart audio driver when changing parameters (ALSA + only) + * gnome GUI: don't block in DBus signal errorAlert() + * * #6629 : simplify AudioLayer creation + * * #6629 : remove unused and unconfigurable frameSize from audiolayer + * * #6629 : remove unused error message from audio layer + * Fix logic error when switching audio API + * Remove unused AudioProcessing class + * AudioRtpRecordHandler::initNoiseSuppress() : use noiseSuppress + directly + * * #6629 : use DC blocker directly in audio layers + * * #6629 : clean AudioLayer + * * #6629 : don't store mainbuffer inside audiolayer + * * #6629 : correct AudioLayer::notifyincomingCall() + * * #6554: cleanup, refactoring in sipvoiplink + * * #6554: cleanup in iaxvoiplink + * * #6554: throw exception in getSIPCall if pointer is NULL + * * #6554: make some methods of sipvoiplink static + * * #6655: cleanup in managerimpl + * * #6554: refactoring, fix memleaks in sipvoiplink + * * #6478: remove throw specs, cleanup in voiplink + * * #6629 : remove unused AudioDevice + * * #6655: removed more dependencies from managerimpl + * * #6744: simplified numbercleaner + * conference : remove one prototype + * * #6743: fix ip2ip + * Don't give glib warnings if icons are not found + * gnome: fixed includes + * Codec.h: removed unused function + * * #6742 : clean dbus & icons + * * #6699: refactor/cleanup accounts + * icons: cleanup + * timer : use second precision, not millisecond + * calltree_update_clock : use correct type, returns something + * * #6737: fixed typo in dbus call + * * #6737: removed tests for removed API + * * #6737: dbus: fixed bug from merge + * * #6737: cleanup in accountlist + * * #6737: cleanup in dbus + * * #6740 : fix history double free + * * #6740 : remove time updating thread from calls + * * #6737 : use c99 for client + * * #6738 : make history loading faster + * sipvoiplink : don't crash on transfers + * fixed typo + * Remove unused file + * Don't build networkmanager.cpp at all if NM is disabled + * _debug* -> _debug + * * #6554 : simplify sipvoiplink + * hudson: added -x to git clean command + * added git clean to hudson script + * audiocodecfactory: cleanup + * * #6718: refactored setTlsSettings into SIPAccount + * * #6718: removed more unused methods + * * #6718: refactored confmanager code into sipaccount + * remove unused functions + * * #6718: confmanager: removed more unused methods + * AudioCodecFactory : cleanup + * #6697 : Turn callableElement struct into union + * * #6718: confmanager: removed more unused methods + * * #6718: confmanager: removed more unused methods + * * #6718: removed unused dbus methods, refactoring + * * #6699: accounts: cleanup/refactoring + * * #6699: refactoring, cleanup in accounts + * * #6699: more account cleanup + * remove unused autoconf variable + * * #6714: fixed hudson script + * make distclean in hudson + * added || exit 1 to run_tests.sh call + * * #6714: fixed make distcheck for sflphone-plugins + * * #6714: fixed make distcheck for gnome client + * * #6714: fixed make distcheck for daemon + * git: #6698 split the main .gitignore file + * gnome: gpointer is already a pointer + * gnome: calltab_init: use calloc instead of malloc + * * #6699: more account cleanup + * * #6699: cleanup account + * * #6554 : more *voiplink cleanup + * * #6558 : more sipvoiplink simplification + * * #6558: saner loadSIPLocalIP prototype + * gnome: #6623 clean calllists + * * #6692: more audiolayer cleanup + * * #6692: cleanup/refactoring in audiolayers + * * #6692: more forward declarations, AudioThread->AlsaThread + * * #6692: audiolayer cleanup + * * #6692: alsalayer cleanup + * * #6558 : remove account creator + * * #6558 : clean sipvoiplink + * * #6554 : cleanup sipvoiplink + * audiortp: cleanup + * * #6657 : fix launchpad builds for good + * * #6675 : send RTP dtmf events only once + * * #6655: more cleanup + * AudioRtpSession::updateSessionMedia() : simplify + * * #6655: more cleanup in managerimpl + * * #6655: removed more code, cleanup + * * #6655: more cleanup, fixed infinite loop + * * #6655: removed more unused files + * * #6655: removed unused mutex + * * #6655 removed more unused code + * * #6655: removed unused methods + * * #6655: cleanup in main + * * #6663: fixed segfault when off hold from transfer + * * #6658: user's active codec selection is respected + * * #6660: static global string should be static const char* const + class member + * * #6659: use g_strcmp0, not strcmp for vals that may be null + * callable_obj: fix double free + * calltree_display_call_info() : simplify + * * #6657: Fix launchpad builds + * Logger::log() : simplify + * AudioRtpSession : privatize members + * * #6655: more constness, cleaned up/simplified methods + * * #6654: call DBus::_init_threading so that dbus-c++ to make it + threadaware + * set default credentials on account creation + * AudioCodecFactory::scanCodecDirectory() : simplify and correct + * * #6623: fixed typos + * * #6623: fixed more leaks + * * #6623: fixed more leaks + * * #6623: fixed more leaks, don't print codec name if null + * * #6623: more leaks fixed in client + * * #6623: fix more leaks, fixed some warnings + * * #6623: fixed leak in history + * updated gitignore + * initialize dbus dispatcher correctly + * Fix tests, hudson doesn't have a dbus daemon running + * remove unused code + * removeCall() : simplify , fix leak + * stopRtpThread() : simplify + * *CurrentCall : simplify + * Fix memleak + * fix serialization of audio api (pulse / alsa) + * account map : simplify + * remove call from callmap before terminating it, avoid use after free + * * #6630 : don't make DBusManager a singleton + * call: return confID by value + * add back history code deleted by error + * history : reverse logic + * simplify history serialization and remove some debug + * remove annoying debug + * * #6464 : replace cerr with _error + * * #6464: replace cout with logger macros + * replace printf() with logger macros + * update .gitignore + * remove unused function + * update eclipse projects + * uimanager_new() : simplify + * rename directories + * celt: simplify a bit + * Fix CELT configure.ac test + * * #6612 : template speex codecs + * * #6623: refactored conference obj + * * #6623: refactored callable object, removed leaks + * * #6623: more cleanup, fix leaks, make global vars static and rename + them + * * #6623: calltree: fixed memleaks, simplified code. + * audiolayer: init pointer members + * manager: catch exception on invalid hangup + * * #6623: don't leak on calls to create_new_call + * * #6611 : clarify codecs prototypes + * ringtones : .au and .ul files are both ulaw + * * #6611 : make sure samplerate converters are called correctly + * ManagerImpl::switchAudioManager() : simplify + * * #6623: fixed more leaks + * * #6623: fixed more leaks + * * #6623: fixed more leaks + * * #6623: fixed leak, line-endings in imwidget + * * #6627: zero-initialize pointers if they're going to be deleted + * * #6628: don't leak calls on exceptions + * Revert "audiortp: call join after calling stop on RtpThread" + * sflphone-client: more constness + * audiortp: call join after calling stop on RtpThread + * * #6625: return 0 on successful completion + * * #6624: fix segfault on servercallfailure + * * #6621: Fixed double free, unlock mutex in ManagerImpl::terminate + * * #6220: remove audio stream when peer hangs up + * * #6596: AudioSymmetricSession shouldn't self-delete + * resampler: grow internal buffers dynamically + * merge up and down sampling => resampling + * Leave test directory unchanged when running make check + * audio algorithms : remove unused prototype + * ringtone: detect codec from file extension + * *AudioFile : simplify + * * #6596: create local SDP on the stack, not the heap + * * #6596: don't call Ost::Thread::terminate from dtor + * audiofile: cleanup (samplerate -> unsigned) + * remove unused func + * samplerateconverter: cleanup + * RingBuffer::Put() : remove unused return value + * MainBuffer::putData() : remove unused return argument + * audiolayer::putMain() : remove unused func + * AudioLayer::putUrgent() : remove unused return value + * * #6618: delete any remaining ringbuffers in destructor + * RingBuffer::availForPut() : remove + * * #6617: return from main rather than calling exit + * MainBuffer::availForPut(): remove + * RingBuffer: simplify + * alsa : remove write only variable + * fix memcpy declaration + * bcopy(src, dst) -> memcpy(dst, src) + * RingBuffer::Get() : remove constant volume argument + * return a copy of the call ID, not just a reference. + * MainBuffer::getDataById() : remove volume argument (always 100) + * MainBuffer::getData() : remove constant volume argument + * RingBuffer::Put() : remove constant volume argument + * MainBuffer::putData() : remove constant (=100) volume argument + * audiolayer: remove constant _defaultvolume + * AudioRtpRecordHandler / AudioRtpSession : simplify + * mainbuffer: fix test + * iaxvoiplink : simplify + * sip registration callback: fix a dbus crash + * MainBuffer: simplify + * AudioRtpFactory: return cached type of rtp session. The rtp session + can have disappeared if the call was put on hold + * AudioRtpFactory: remove unused setters + * Fix launchpad builds + * * #6611 : remove unused bandwidth codec information + * * #6611: AudioCodec: remove useless/unused setters + * make sure buffer string is initialized correctly + * * #6596: declare certain destructors virtual + * audiolayer : cleanup + * Simplify doc build rules + * * #6270: don't build dbus-api doc with make, should require make all + * configure.ac: cleanup + * Remove copy of dbus-c++ from libs/ + * * #6596: stop clock thread when peer hangs up + * removed unused Fmtp.h + * * #6595: more logical initialization order + * * #6600 : fix account creation + * * #6601 : fix configure.ac tests + * remove unused variable + * Don't mix stack and heap based allocations + * Fix copyright (2009, 2008, 2009 -> 2008, 2009) + * Fix warnings found by clang + * * #6595: fix initialization order for AudioRTP + * * #6592: removed typedef std::string CallID + * * #6586: implement local g_slist_free_full for older glib versions + * * #6579: fix memory leaks in client (there's a lot left) + * ShortcutPreferences::setShortcuts() : simplify + * Fix merge + * * #6548: remove call to non thread-safe strerror() + * AudioRtpFactory: each instance is associated to exactly one SipCall + * create_audiocodecs_configuration() : make static + * * #6269 : refactor AudioRtpSession + * Fix AudioSymmetricRtpSession.h inclusion guard (cherry picked from + commit c3081dce1cc1370d6d3558a4c4ef5cfac0d21caf) + * * #6269: Rename AudioRtpSession to AudioSymmetricRtpSession + * * #6574: Don't exit when connection to pulseaudio server fails + * accountconfigdialog.h : remove some stuff from header + * * #6560: fix configuration test + * Fix warning in test + * * #6560: don't hide password entry in security tab + * * #6560: set initial password for SIP accounts + * * #6506: remove useless pointer indirection + * * 6560: password is now specific to IAX accounts + * * #6560 : actually use, store, restore, transmit SIP credentials + * * #6560: YamlEmitter: serialize sequences + * YamlEmitterException: typo + * ManagerImpl::computeMd5HashFromCredential() : simplify, fix memleak + * * #6561: invite_session_state_changed_cb() : simplify + * * #6561: More useful debug in VoIPLink::removeCall + * * #6561 : fix ghost call reappearing in GUI after transfer + * while -> for (make the code smaller) + * * #6558 : Account::loadConfig() : move IAX code to IAXAccount + * IAXVoIPLink::getAccountPtr : simplify + * * #6554 : access the SIPVoIPLink directly, not per account + * SIPVoIPLink is instanciated only once and is not associated to a + single account + * yamlnode: use const references when possible (still some left to do) + * Account::_accountID: constify + * VoIPLink: simplify, remove unused method + * hudson test : no need to call run_tests.sh anymore + * Remove AccountID type and AccountNULL define + * Make check runs the test (no need to call run_tests.sh manually + anymore) + * gnome GUI: Fix tests + * Revert "Move registration information from SIPAccount to + SIPVoIPLink" + * * #6392: pluginmanagertest: fix warnings reported by valgrind + * * #6547 : remove unused exceptions + * * #6547: CallManagerException: use runtime exceptions + * * #6547: InstantMessageException: use runtime exceptions + * * #6547: do not throw exceptions if some settings are not present in + config file + * * #6547: YamlParserException: use runtime exceptions + * * #6547: VoipLinkException: use runtime exceptions + * * #6547: YamlEmitterException: use runtime exceptions + * * #6547: DTMFException: use runtime exceptions + * * #6547: AudioFile: use runtime exceptions + * * 6547: AudioZRtpSession: remove impossible error case + * * #6547 : AudioRtpSession: remove impossible error case + * * #6547: AudioZrtp: use runtime exceptions + * * #6408 : send authenticationUsername to GUI + * * #6408 : store/restore authenticationUsername from config file + * SIPAccount: simplify + * Move registration information from SIPAccount to SIPVoIPLink + * SIPAccount::getAccountDetails : simplify + * * #6540: yaml parser: simplify + * sdp.cpp : fix a warning + * * #6540: yaml parser : remove std::string typedefs + * * #6540: Simplify yaml unserialization + * * #6540 : add a Conf::ScalarNode constructor for booleans + * setAccountDetails(): simplify + * * #6408: store authentication username in daemon + * * #6408: Be able to set the authentication username in the GUI + * * #6507 : do not crash if the program is not sflphoned + * Fix tests + * macroify SIPAccount::unserialize() + * Move all .cpp files from sflphoned target to libsflphone.la, except + main.c + * main() : simplify, return positive error codes + * * #6507 : find codecs dir in build directory + * * #6392: Sdp: move clean functions to destructor + * AlsaLayer::adjustVolume() : simplify + * alsalayer : reduce indentation + * malloc/free -> new/delete + * malloc/free -> new[]/delete[] + * malloc/free -> new/delete + * AudioSrtpSession: simplify base64 encoding + * * #6392: Initialize std::string from pj_str_t correctly + * * #6392: AudioRtpSession: Initialize remote port + * Audio settings : Initialize _echoCancelTailLength and + _echoCancelDelay(0) + * Initialize variable + * YamlParserException : fix use of stack variable after it has been + deallocated + * * #6392: fix memory leak in history + * * #6392 AudioCodec : fix memory leak + * * #6392 : fix memory leak in sip account + * * #6408: clean up sipaccount (cosmetics mostly) + * sipaccount.cpp serialize() : reduce number of lines + * * #6392: invalid memory access + * * #6392 : fix invalid memory access + * * #6479: merged useful code from MimeParameters into Codec interface + * * #6462: fixed hangup on IP2IP call + * added run_daemon.sh script + * test: remove unused variable + * Remove functions only used by a failing test (cherry picked from + commit fcf718cb75de7f1882dc61c07bb8d300dfa10f85) + * * #6360 : make client tests build (cherry picked from commit + 028b2835f040e51ab8ab979b32732b07b8798fce) + * * #6360 : fix warnings in check_global test (cherry picked from + commit 9e2bd6a7496dd64f6f48595e385760019aab1193) + * * 6360: updated API calls in tests, but they're not building yet + (cherry picked from commit 548f6f0f919b43772a3e9c667e5e292791281795) + * Fixed include in tests (cherry picked from commit + aeadc7525c1e31f936670ac8b02f0bcf387c38a8) + * Remove unused variables and functions + * IAX: fix warnings (cherry picked from commit + fd7a113a11cac2cd9a7c36929e88ad28195c4c35) + * Remove unused DEBUG define which interferes with logger.h (cherry + picked from commit b2f72b91d0f43cb1dd94d138882a8caa9c841c24) + * * #6392: no need to check for account NULLity since it is + dereferenced above + * * #6392: fix a memory leak, replace by stack allocation + * * #6392: remove a variable assignement which confuses cppcheck + * process_conference_participant_from_serialized() : remove unused + function + * * #6392: s/free/g_free/ + * * #6392: fix a memory leak in abookfactory_load_module() + * * #6392: remove generate_call_id() used only once + * * #6392: fix memory leak (opendir() without closedir()) + * * #6392: AudioRecorder(): ensures mbuffer is set + * Remove SFLPHONED_VERSION from global.h, use autoconf PACKAGE_VERSION + * #6298: Cleanup + * #6331: Fix deleting ringtone file after call have been answered + * * #6330: merged user_cfg into headers + * #6298: Fix conference recording file update at conference end + * #6298: Fix record file name serialization for conference + * * #6295: cleanup of codec hierarchy + * #6298: Fix gtk warnings + * * #6300: added script to run tests + * #6109: Add recording playback for conference + * * #6300: tests do not require an installed sflphone + * * #6295: re-removed clone methods + * #6109: Fix gtk_critical warnings for incoming calls + * #6109: Fix GTK_CRITICAL warning + * #6109: Fix icons when history is not activated + * #6109: Fix warnings + * #6109: Implement stop recorded file playback signal + * Revert "* #6295: removed unused clone method" + * * #6295: removed unused clone method + * * #6296: removed non existant file from Makefile.am + * #6109: Stop fileplayback for outgoing call + * #6109: Implement stop recording playback button + * Fix binding names errors in dbus introspection file + * #6109: Implement playback recorded file callback in client + * #6109: Store recorded file path on client side + * #6109: Add dbus methods for call recording playback + * * #6290: remove unused classes from utilspp + * * #6288: cleanup sdp + * * #6288: fix exception usage + * * #6288: simplify SdpException + * * #6288: cleanup in sdp.cpp/h + * #6109: Only display playback button if record file is set and valid + * * 6290: updated configure.ac to remove functor Makefile + * * #6290, #6289: removed unused classes from utilspp, fixed make + check + * #6109: Add button for history playback of recorded file + * * #6289: removed unused observer class + * * #6282: forward declare sdpMedia in sdp.h + * * #6281: renamed setCallAudioLocal->setCallMediaLocal + * #6183: Handle conference with more tahn two calls + * #6183: Fix history icons when calling back a conference from history + * #6183: Fix icons inconsistencies in history for conference hang up + * #6183: Fix toolbar actions when selecting a conference in history + * #6183: Fix conference serialization + * #6268: Serialize only calls + * * #6269: removed useless type testing + * ignore some files in test/ + * * #6268: Remove dead class AudioSymmetricRtpSession + * #6251: Do not had history calls in calllist when loading history + file + * #6251: Fix insertion in history map in before saving history file in + daemon + * #6251: Fix history unit tests + * #6251: Order the list before serailization, get rid of the hashtable + in history + * #6251: Implement history serialization using a list wether than a + map + * * #6253: remove external audioport from header, make all members + private + * * #6253: don't store external local audio port (used for NAT) in + Call + * #6251: Add start_time timestamp in history serialization + * #6251: Fix call insertion in conference items + * #6233: Fix serialized account list terminated with a ";" character + * #6238: Fix draggable history calls into current calls + * #6233: Fix toolbar updates + * #6233: Fix history + * * #6235: remove pyc files from git tree + * #6233: Handle cases when one or manuy calls are unreachable in + createConfFomrParticipantList + * #6233: Handle wrong numbers in createConferenceFromParticipantList + * #6231: Fix drag-n-drop issue + * * #6173 : move sippxml in tools + * #6231: Fix merging issue + * #6183: Implement conference unserialize + * * #6212: remove extraneous flags from globals.mak + * #6183: Unserialize conference data in conference + * #6183: Add account information in request for conference call from + history + * #5755: Add -ldl to liker in sflphone-client-gnome + * #5755: Fix fedora 15 compilation issue + * #6183: Serialize conference participant phone number and account + * #6183: Add conference timestamp in serialization + * * #6186: don't include global.h, just logger.h + * #6183: Fix saving history to file + * #6183: Fix removing call from calllist + * * #6184: remove pointers to Manager from AudioRtpSessions + * #6183: Calling calltree_add_call explicitely for history + * #6183: Ability to store conference inside history tab queue + * * 6181: remove unused API from sipcall + * #6171: Implment nreCallCreated callback + * #6167: Fix participant list NULL ending + * #6149: First draft of conference creation from history + * #6149: Fix multiple call/conf selection callbacks ... + * #6129: Fix place_call function called twice for pressing enter + action + * #6129: Fix double click action for history + * #6149: Add dbus call for creating conference from history + * #6129: Fix placing call from history and addressbook (still need to + fix icon) + * * #6148: removed unused AudioRtpFactory constructor + * * #6145: remove unused isAudioStarted + * * #6145: remove unused isAudioStarted + * #6129: Add conference into history, fix call/conference selection + * * #6143: don't use getType outside of serialization methods + * * #6132: forward declarations instead of includes + * * #6132: add constness, remove redundant "inline" keywords + * #6129: Add timestamp to conference object to order history entries + * * #6128: remove unused forward declarations from header + * * #6127: make noncopyable class actually noncopyable + * * #6125: don't include AudioRtpFactory in sipcall.h + * #6123: Fix alsa ringback audio file + * #6123: Fix raw audio file loading problem + * #6109: Fix daemon plugin manager unit test + * #6109: Fix history manager unit tests + * #6109: Recording filename in daemon and client for history items + + serialization + * #6109: Refactor AudioFile to play recorded call + * * #6104: AudioCodec moved to sfl namespace + * * #6099: remove active flags from codec classes + * #6095: Add notification-daemon as a runtime dependencies for rpm + packages + * #6095: Fix fedora 15 compilation in MineParameters.h + * #6095: Declare static variable explicitely for client + * #6095: Add logs to build OSC build machine + * * #6098: global variables should have file-scope to avoid name + conflicts + * #6095: Fix compilation error for Fedora 15 + * #6095: Update SFLphone version to 0.9.14 + * #6095: Add specification file in opensusse build service for + sflphone-plugins + * #6073: Fix sflphone-plugins build on launchpad + * #6093: Rename CodecDescriptor for AudioCodecFactory + * * #6089: fix warnings in make check + * * #6086: renamed codecs methods to audio_codecs + * * #6085: renamed codec related dbus calls to audio_codec + * #6065: Remove g_print from client, use DEBUG instead + * #6065: Add actions name for addressbook + * * #6085: renamed codecs* widgets/functions audiocodecs* + * #6065: Fix Addressbook runtime warnings + * #6065: Replace Codecs tab for Audio in account preference dialog + * #6065: Fix "transfert" typo + * #6065: Fix addressbook action runtime warning in uimanager + * * #6082: fixes make check by adding libcrypto libs to test + dependencies + * #6073: Rename plugin/addressbook folders for addressbook/evolution + in sflphone-plugins + * #6074: Removed AC_SUBST from configure.ac when using + PKG_CHECK_MODULE + * #6073: Fix sflphone-plugins package build + * #6073: Fix sflphone-common build + * #6065: Fix runtime gtk warning when initializing searchbar without + addressbook + * #6063: Fix mozilla-tellify gitignore + * #6063: Remove stream copy file using ifdef macro + * * #6012: fix make dist for sflphone-common + * #6063: Update .gitignore file + * #6058: Fix base64 encoding related warnings + * #6056: Fix SdpException handling + * #6055: Fix unknown pargma warning for gcc <= 4.5 + * * #5949: test gcc version before disabling unused-but-set warning + * #6054: Fix addressbook plugin compilation warning + * #6048: Fix uimanager static initialization + * #6046: Fix addressbook factory static initialization of member + addrbook + * #5979: Fix implicit function declaration warning + * #6042: Fixed discarding qualifier warnings in client + * #6041: Fix instant messaging unhandled case warning + * #5994: Implement set current addressbook name and search type in + addressbook plugin + * #5994: add rules for launchpad packaging of addressbook plugin + * #5994: Fix addressbook plugin configuration loading + * #6027: Fix addressbook enabled test from configuration + * #6027: No need of gnomedoc related macros in addressbook plugin + * #6027: Add NEWS file required for build + * #6027: Add addressbook plugin autogen.sh script + * #6027: Remove plugins from client + * #6027: Add sflphone-plugins folder at project's root level + * #5994: Move addressbook folder from contacts to plugin folder + * * #6011: removed unused Makefiles + * * #6010: remove unused headers + * * #5952: fix "string constant to char*" warnings + * * #6009 fixed warnings + * * #6003: finished cleanup of account classes + * * #6003, #6004: cleanup of account classes, defaultAccount no longer + global + * * #6000: fix memory leak of args object + * * #5998: removed using namespace std from networkmanager + * * #5998: removed "using namespace std" from ZrtpSessionCallback + * * #5998: removed using namespacestd from AudioZrtpSession.h + * * #5998: remove "using namespace std" from auriorecord.h and + MimeParameters.h + * * #5998: remove using namespace std in main + * * #5998: removed "using namespace std" from logger + * * #5949: test gcc version before disabling unused-but-set warning + * #5994: Installation of addressbook plugin + * #5979: Implement codec full addressbook search from plugin + * #5979: Implement addressbook factory and plugin + * * #5981: unused webwidget removed + * #5966: Account config synchronization fix (for stun) + * #5954: Handle media name exception + * #5954: Fix audio codec name display in client + * #5954: Clean up getSessionMedia methods + * * #5957: getRecordingSmplRate returns a value + * #5954: Clean up getCurrentCodec methods + * * #5950: remove "converting to non-pointer type 'int' from NULL" + warnings + * #5915: Full gain control version + * * #5949: remove more unused variable warnings + * * #5949: remove unused/unused-but-set variable warnings + * * #5949: show_preferences_dialog returns a success value + * * #5946: cleanup of include directives, undefined function + * * #5515: comment out SSLv2 calls in pjsip + * #5915: Implement different slope for attack tme and release time for + gain control + * #5915: use only one input signal for gain control (removed output + buffer) + * #5921: Fix no audio after holding a conference + * #5916: Add gaincontrol files + * #5916: Implement FFMPEG/CCRTP video streaming prototype + * #5903: Fix call transfer during a conference + * #5915: implement rms detector, first order averager, limiter for + gain control + * #5914: Fix call transfer when no notification request is required + * #5899: Fix conference right-click segfault + * #5884: temporary fix segfault in pjsip memory pool + * #5883: Fix compilation issues on maverick and lucid + * #5755: Fix fedora 15 compilation without patching ccrtp + * [#5855] Make echo canceller optional + * #5855: Fix echo suppression activation/deactivation + * #5855: Implement pjsip echo canceller + * #5814: Speex initialization function uses samples, not bytes + * #5814: Test using more unbalanced signals + * #5814: Fix buffer size for long echo length or long echo delay + * #5814: Adjust level for echo cancellation at runtime + * #5814: Process noise reduction before echo cancelling + * #5814: Implement speex post echo canceller processing + * #5814: Dump echo cancel file to disk + * #5814: Add parameters for echo cancel + * #5809: Add configuration parameters + * #5809: Implement speex echo canceller in audio rtp session + * #5814: Code cleanup + * #5814: Fix conf creation with several incomming ringing calls + * #5814: Fix conf creation segfault when dragging a call on hold on a + ringing call + * #5809: Added unit test for echo cancellation and implemented + "process" virtual method + * #5709: Add always recording option in configuration + * #5709: Add always recording option in audio conference panel + * #5709: Add core functionnality for always recording (missing config + options) + * #5769: Fix conference participant handling (detach/attach) and hold + actions + * #5747: Fix recording icons and state for conference when adding new + participant + * #5769: Code cleanup + * #5769: Fix hangup unsent calls + * #5769: Fix remove/add additional participant to conference + * 5769: Several fixes concerning confererence handling + * #5769: Fix compilation error + * [#5769] Fix audio streams binding in main buffer + * #5769: Removed access to audio mixer from audio layer + * #5765: Fix audio crash for illformated wavefiles + * #5765: Add maximum iteration for finding fmt and data "chunck" + * #5589: Fix compilation of libnotify under + * #5757: Fix abort signal when receiving INFO + * #5747: Add usersDetached.svg + * #5747: Handle offhold action for recording conference + * #5747: Fix off hold action for conferences + * #5747: Implement update conference in record action in calltree + * #5747: Add new icons for recording conferences + * #5747: Add recording state for conferences + * [#5738] Remove getAudioDriver call from manager (replace by + _audiodriver var) + * [#5738] Refactor mutex protecting audiolayer + * [#5737] Fix HD conference recording + * [#5730] Fix start audio session after changing sampling rate + * [#5714] Fix enter keyboard event for addressbbok and history + * [5695] Fix addressbook combo box update when no addressbook selected + * [#5695] Fix addressbook initialization and search bar update + * [#5695] Add mutex for books_data in addressbook to protect async + calls + * [#5695] Get back addressbook open from uri + * [#5695] Fix absolute addressbook URI for local addressbooks + * [#5695] Implement libebook 3.0 interface + * [#5571] Better logic for hangup (for case where call have not been + sent yet) + * [#5571] Update error handling in voip links + * [#5571] Fix compile time warnings + * [#5696] Fix installation dependencies for Natty + * [#5669] Add mention that sflphone.org is for testing only + * [#5693] Add natty in teh dput.conf file + * [#5690] Remove not useful logs + * [#5670] Use dynamic payload type for rtp dtmf + * [#5668] Clean up sflphone configuration logging + * [#5668] Fix hook checkbox configuration update + * [#5666] Fix unit tests + * [#5666] Manage event subscription + * [#5666] Emit bye request when subscription is terminated + * [#5666] Bye request should be sent after event subscription + notification is done on transfer + * [#5666] Make reinvite method static (to be called in pjsip + callbacks) + * [#5666] Hangup Call in manager for AccountNULL and IP2IP + * [#5589] Use PKG_CHECK_MODULE for every client's dependencies + * [#5623] Enlarge initial size of pjsip memory pool for calls (16k) + * [#5564] Fix audio recording resampling for g722 + * [#5571] Move attribute handling for onhold/offhold actions in SDP + session + * [#5571] Codec negotiation refactored and unittested + * [#5571] Implement tests + * [#5571] Implement pjsip negociator + * [#5571] Fix unit tests + * [#5571] Add Fmtp.h to repository + * [#5571] Integrate mime types and codec factory + * [#5571] Handle exception when SDP negotiation fails + * [#5570] Add sflphoned-sample.yml in repository + * [#5564]: Implement stereo to mono mixing for rigntone + * [#5342] Update audio stream initialization + * [#5514] Restore test ni historytest suite + * [#5514] Fix + * [#5514] Disable test_create_history_path + * [#5514] use pulseaudio in sample config file + * [#5514] Fix test: load history from file + * [#5514] Do not use X + * [#5513] Make unit tests compile successfully + * [#3947] Enable unit tests in Jenkins + * [#5454] Fix build system to handle new version number + * [#5454] Update languages from launchpad + * [#5454] Add --without-celt in OpenSuse build service + * [#5454] Change version number + * [#5331] Added first SDP session tests + * [#5273] Update nightly build version tags to conform dpkg rules + * [#5211] Refactor send register method for iaxvoiplink and + sipvoiplink + * [#3950] Remove call being transfered from calltree + * [#5211] Use appropriate memory pool for transport selector + * [#5211] Fix strict aliasing rules warning in pjsip + * [#5211] Bring back pjsip shutting down sleep to 1000 ms + * [#5211] Fix registration callback segfault when closing the + application + * [#5211] Use the dialog memory pool for Route header in INVITE + request + * [#5211] Add temporary memory pool for findLocalAddressFromUri and + findLocalPortFromUri + * [#5211] Use individual memory pool for dtmfs + * [#5211] SipVoipLink refactoring + * [#3950] Attended transfer for conference calls + * [#5284] Fix DNS resolution for Route with specified port number + * [#5284] Some code cleanup + * [#3947] Fix typo in hudson script + * [#5284] Added sip route to REGISTER, INVITE, BYE request, plus DNS + resolution + * [#5266] Use RTP dtmf as default + * [#5284] Added pjsip_process_route_set after setting routes in regc + structure + * [#5286] Fix parsing error due to long configuration file (removed + max event) + * [#5286] Fix false test in configuration emmiter + * [#5286] Code cleanup + * [#5286] Updated exception handling in configuration system + * [#4969] Fix put SRTP call on hold + * [#3950] Add debug messages + * [#3950] Ability to perform an attended transfer + * [#5276] Fix initialization problem in g722 + * [#3950] Add replace header in SIPVoIPLink::transferWithReplaces + method + * [#3950] Implemented attended method in SIPVoIPLink + * [#3950] Cleanup transaction request received callback + * [#3950] Implement dummy attended transfer in gnome-client + * [#5249] Fix audio samplerate update algorithm for g722 + * [#5249] Fix uninitialized variable used in conditional jumps + * [#5249] Fix conditional jump error in audiolayer (uninitialized + value) + * [#5267] Use autoconf 2.65 as a requirement (instead of 2.67) + * [#5267] Restore manual pjsip configuration and compilation + * [#5267] Autodetect celt version (0.9.1, 0.7.1) + * [#5267] Fix deprecated macros in gnome client configure.ac + * [#5267] Update configuration for libcelt-dev + * [#5267] Fix build autoconf and automake + * [#5227] Deactivate automatic call to astyle after compilation + * [#5242] Hangup every calls before leaving + * [#5237] Will now nightly-build for natty, Karmic deprecated + * [#5229] Use inner class for rtp thread instead of inheritance + * [#5211] Move mainbuffer unbind call in rtp final method + * [#5211] Initialize sip call memory pool using 16 kb + * [#5211] Use call memory pool in session reinvite + * [#5211] Add debug messages + * [#5211] Use and internal pool for calls + * [#5211] Reduce pjsip memory pool usage for stateless error messages + * [#5211] Refactor call deletion + * [#5212] + * [#5208] Refactor codec management for accounts + * [#5168] Remove printf from codec's encode & decode method + * [#5168] Fix celt compilation on launchpad + * [#5168] Fix sflphoned compilation warnings in audiocodec.h + * [#[#5168] Must keep the g722 specific RTP rate to avoid incoming + packet timeout + * [#5168] Fix static/dynamic payload rtp session update + * [#5168] Throw SIPVoipLink Error if codec not instantiated in new + outgoing call + * [#5168] Fix dynamic/static codec payload type ambiguity + * [#5169] Fix doubled IP2IP profile when no config file + * [#4867] Add gtkinfobar in configuration panel + * [#4867] Disable input/output/ringtone selection when using default + alsa plugin + * [#4952] Patches for possible buffer overflows + * [$4885] Fix schemas problem + * [#4885] sflphone-client-gnome.schemas not present during build + * [#4885] Add gconf shemas directories in opensuse build system + * [#4885] Add file/folder ownership for opensuse-factory build system + * [#4906] Fix opensuse-factory build + * [#4885] Update name dependency for libedataserver + * [#4885] Fix non-void function without return in dbus-c++ + * [#4895] Update language translation + * [#4896] Update session timestamp when updating media + * [#4896] Reapply RTP hack for G722 payload type + * [#4896] Update recording sampling rate when updating codec + * [#4897] Save codecs in config for each configuration changes + * [#4895] Do not save config when sflphone quit + * [#4885] Update date for copyright + * [#4885] Deactivate siptest that require more than one sipp instance + * [#4879] Remove inmcoming call notification from IAX + * [#4885] Some cleanup + * [#4874] Add setCancel immediate/deffered for ost::Thread + * [#4879] Fix incoming call notification + * [#4878] Set keyboard focus on searchbar when selecting addressbook + * [#4874] Fixed compilation warning + * [#4874] Fixed compilation warning in sipvoiplink + * [#4874] Fix compile time warning in RTP record handler + * [#4874] Fix conditional jump in SDP + * [#4874] Fix conditional jump based on uninitialized value + * [#4874] Store call id within rtp thread context + * [#4874] Fixed conditional jump based on uninitialised value in + conference + * [#4871] Fix default account fetching + * [#4870] Delete RTP session when Refusing an incoming call + * Restore IP to IP call + * [#4857] Fix audio codec negotiation problem + * [#3947] Adjust ressources allocated to compilation + * [#3947] Disable unit tests in Hudson + * [#4305] Free mutex only when really quiting SFLphone + * [#4859] Update copyright to 2011 in every source file + * [#3218] Character '.' stripped by the caller engine + * [#4854] Fix typos, desktop entry + * [#4847] Apply RTP modification to ZRTP session + * [#4852] Update Karmic and Lucid dependencies + * [#4852] Add Libedataserver and libedataserverui as gnome client + dependencies + * [#4852] Add authentication mechanism for EDS + * [#4851] Fix segfault when closing pulseaudio layer too rapidly + * [#4808] Some otehr cleanup + * [#4808] Made some cleanup + * [#4808] Added mutex in rtp session for codecs and noise process + * [#4847] Update audio processing when updating RTP media + * [#4842] Add support for linking with gold/ld --no-add-needed + * [#4808] Make update g722 related static/dynamic payload logic + * [#4827] Upper limit on the number of contacts to import from EDS is + hard-coded to 500 + * [#4808] Fix put call on/off hold + * [#4808] Implement early RTP start for incoming calls + * [#4808] Audio stream is no longer start within RTP session. + * [#4808] Removed coupling between audio layer and and RTP session + * [#4702] Start audio rtp session as soon as it is created + * [#4702] Init timestamp to 0 + * #4702: Send RTP packets immediately, no need of outgoing queue + * [#4784] Update dbus-c++ version from gitorious + * [#4702] Update RTP timeouts + * [#4702] Lengthen RTP timeouts + * [PATCH] Fixed compatibility with old libtool versions. + * [PATCH] Accept older libebook (Maemo 5 has 1.4.2) + * [PATCH] Fixed double-free error in preferences dialog + * [PATCH] Fixed building of sflphone-common on Maemo5 + * [PATCH] Improved Gnome client initialization error handling. 1. It + no longer segfaults when sflphoned isn't available. 2. User is + provided with GUI error dialog. + * [PATCH] Improved autogen.sh scripts 1. They do not require bash + anymore 2. Added workaround for Debian bug #565663 3. Replaced + manual autotools invocations with single autoreconf call 4. Non-zero + return status on failure + * Revert "[#4468] libtool <= 2.2 doesn't have LT_INIT macro so + AC_PROG_LIBTOOL should be used instead." + * Revert "[#4468] Libebook 1.4 is sufficient" + * Revert "[#4468] Apply big path on dbus communication system" + * [#4468] Apply big path on dbus communication system + * [#4468] Libebook 1.4 is sufficient + * [#4468] libtool <= 2.2 doesn't have LT_INIT macro so AC_PROG_LIBTOOL + should be used instead. + * [#4639] Fix determining default addressbook if this property is not + set in gconf + * [#4639] Fix memory leaks in Addressbook + * [#4637] Fix opening default addressbook at sflphone init + * [#4622] Free yaml events while parsing configuration file + * [#4623] Fix conditional jumps based on uninitialized variable + * [#4622] Fix leaks in yaml serialization engine + * [#4616] Fix addressbook warnings + * [#4514] Adjust RTP timestamp + * #4527: Rename Karmic libyaml and Celt package in debian control file + * #4495: Rework addressbook opening loop + * [#4524] Increment RTP count when sending data + * [#4524] DO NOT start RTP session twice + * [#4367] Use PKG_CHECK_MODULE for celt + * [#4367] Fedora package celt as celt (not libcelt) + * [#4367] Astyling + * [#4367] Update .po files + * [#4367] Fix segfault in gensin + * [#4354] Make celt a direct dependency on launchpad opensuse build + service + * [#4367] Make celt a required package, option --without-celt valid + * [#4367] Fix zrtp timestamping error + * [#4367] Fix audio zrtp timing + * [#4367] Dispatch ZRTP packets + * [#4367] Fix segfault when unloading account map + * [#4367] Fix zrtp session + * [#4367] Implement on packet receive + * [#4367] use symetric audio rtp session, not dual + * [#4367] Reduce packet receive/sent timeout + * [#4367] Reduce RTP timeouts + * [#4367] Move speaker data receive + * [#4367] Move speaker data receive + * [#4367] Move receive speaker data method + * [#4367] Remove debug in rtp session + * [#4367] Fix g722 codec clock rate + * [#4367] Fix noise suppression initialization + * [#4367] Fix segfault in RTP mic fadein method + * [#4367] Refactor mic data encoding in rtp session + * [#4367] Implement RTP main loop + * [#4367] Fix compilation problem + * [#4367] Fix AudioRtpclass using TRTPSessionBase + * [#4367] Fix AudioRtpSession putDtmfEvent shadowing + * [#4367] Fix AudioRtpSession putDtmfEvent shadowing + * [#4367] Refactor RTP session (phase 2) + * [#4367] Refactor RTP session (phase 1) + * [#4367] Remove Redeclaration of SymetricAudioRtpSession in + rtpfactory + * [#4265] Add continue statement in for loop for invalid addressbook + * [#4261] Makes addressbook initialization more robust + * [#4257] Add maverick in build system + * [#4233] Add sdp related unit tests + * [#4233] Add condition and signal in two incoming call test + * [#4243] Fix segfault in AudioSrtpSession + * [#4243] Fix memory leak in AudioSrtpSession + * [#4243] Make audio srtp optional in for incoming call + * [#4243] Add boolean variable to make sure remote crypto context + initialized only once + * [#4243] Add documentation to AudioSrtpSession + * [#4243] Use 80 bits authentication tags by default + * [#4243] Init audio srtp remote crypto context in + call_on_media_update + * [#4243] Move SDP negotiastion in mod_on_rx_request + * [#4243] Implement initLocalCryptoInfo to be called at different + momment + * [#4243] Init init local crypto context in when initializing audiortp + * [#4243] Change key length according to sdes negociation + * [#4243] Associate callid to accountid for incoming calls + * [#4242] Fix no SDES keys in IP2IP calls + * [#4242] Fix no SDES keys in IP2IP calls + * [#4233] Test for call on/off hold + * [#4233] Add two incoming call test + * [#4233] + * [#4233] Add 2 outgoing simultaneous call unit tests + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Fri, 30 Sep 2011 13:51:04 -0400 + +sflphone-common (0.9.7~rc1~ppa1~SYSTEM) SYSTEM; urgency=low + + ** 0.9.7~rc1~ppa1~SYSTEM ** + + * [#2462] Set explicitly the transport on incoming call too + * [#2462] fix typo + * [#2462] Use different address for SDP and call IP + * [#2462] Use published address in SIP-SDP + * [#2181] Fixed changelog files + * [#2181] Updated spec file + * [#2402] Fix pointer to int conversion warning (atoi) + * [#2402] Remove daemon warnings, make indent + * [#2459] Make sure the stream is opened when the call is answered + * [#2402] Add conference related picture in documentation + * [#2443] Not much ... + * [#2399] Fix dialing display problem + * [#2450] Fix incoming call already in conference crash + * [#2399] Display peer name on the first line and peer number on the + second + * [#2450] Handle 403 FORBIDDEN when refused + * [#2447] Bind offHold/onHold actions to button in gtk client + * [#2447] Bind hangup action to button for conference + * [#2447] Add conference action in gtk client's ToolBar + * [#2381] Disable the password hashing in config file + * [#2402] Cleanup + * [#2366] Set callback to null when deleting Pulseaudio streams + * [#1313] Fix main buffer unit test + * [#1313] Fix audio layer unit test + * [#2315] Hide pw in security tab, display when editing, sync with + basic tab + * [#1313] UnitTest change AudioRtpSession for AudioSymetricRtpSession + instance + * [#2402] Code cleanup + * [#2444] Add debug to catch occasional crash when loading client's + config + * [#2444] Add debug info to catch occasional crash when loading config + dialog + * [#2402] Restore Call menu translations + * [#2403] Use the published address if checked in GUI + * [#2442] Add protection test in sdp + * [#1841] Reapply pjsip patch concerning DNS SRV resolution + * [#2384] Tags incoming call as direct SIP call, if applicable + * [#2402] Change the monkey face + * [#2315] Enable user to display password in clear text + * [#2434] Force optimization level at 2 + * [#2284] Fix dbus_get_all_ip_interface compilation warnings + * [#2431] Popup main window on incoming if applicable + * [$2402] Fix simple warnings + * [#2402] Fix implicit variable init order in LibraryManagerException + * [#2402] Fixing implicit variable initialization warnings in + AudioRtpSession + * [#2402] Revert atoi change, fixing codec list doubled entries + * [#2402] Fix gpointer to gint conversion + * [#2402] Fix pointer casting to integer different size warning in + codec list + * [#2402] Fix warning discarting qualifiers from pointer target + * [#2402] Fix gtk tree view assignement from incompatible type warning + * [#1669] Fix audio recording folder utf-8 non compatibility issue + * [#2414] Clean up debugs + * [#2414] Use transport set in iptoip Account and update it frm + preference + * [#2348] Use macro N_() to mark ui.xml strings as translatable + * [#2414] Rename getSipAddress/setSipAddress functions + * [#2407] Fix volume controls display + * [#2407] Fixes dialpad + * [#2383] Set ip to ip config when clicking apply button + * [#2404] Update call-to script - Maxime Chambreuil + * [#2405] Client handles unknown call in current state as well + * [#2383] Add DBUS signal to send IPtoIP local address and port as + string + * [#2383] Add Ip to IP config change apply call back + * Clonflict + * [#2402] Code cleanup + * [#2383] Do the same for IPtoIP (init localn ip with first in the + list) + * [#2383] Use first interface in the list if local addresss is not + defined + * [#2403] Clean up unuseful addresses/ports + * [#2403] Use the IP profile SIP port as global SIP port + * [#2383] Fix dbus_get_all_ip_interface warnings + * [#2383] Take into account sameAsLocal when loading published address + * [#2383] Tsake into account sameAsLocal option when saving published + address + * [#2383] Update local ip address in ip to ip config + * [#2383] Save ip 2 ip local port in config + * [#2406] Update toolbar at startup + * [#2284] Remove redefinition warnings + speex warnings + * [#2383] Fix security table in account config + * [#2383] Save ip 2 ip network interface parameters in config + * [#2403] Restore sip transport selector + * [#2383] Fix filling the Localt IP Address on account creation + * [#2383] Fix Gtk-Critical when checking STUN + * [#2383] Fix reopening account configuration display issue + * [#2383] Load IPtoIP local address and port in preference iptoiptab + * [#2383] Add LocalAddress and Localport in Preference IpToIp tab + * [#2403] Use the address and port associated to the account as often + as possible + * [#1753] Removed pjsip generated files + * [#1753] Removed remaining milenage lib references + * [#2383] Add _publishedSameasLocal variable in sipaccount + * [#2383] Add PUBLISHED_SAMEAS_LOCAL variable in config + * [#2383] Fix stun set active or not when opening config + * [#2181] Added RPM 64bits dbus patch + * [#2402] Code indentation + * [#2313] Force $(HOME).cache directory creation at startup + * [#2383] Separate network interface and published address in account + config + * [#2400] Change dbus service installation path to libdir + * [#2382] Move TLS related published address options in security tab + * [#2382] Indent accountconfigdialog.c + * [#2181] Install libdbus-c++ in $pkglib instead of $lib + * [#1753] Remove ILBC code and disable it by default in the configure + * [#1753] Remove milenage directory + * [#2382] Fix switching interaface instabilities + * [#2396] Save local ip in account creation wizard + * [#2284] Remove warning on hold + * [#2387] Fixes history searching and filtering + * [#1215] Add samplerate display in the GUI + * [#1663] Voicemail icon reflects voice messages + * [#2395] Fix account registration ( specifically with callcentric) + * [#2386] Strip "sip:" on incoming call, fixing history call back + * [#2181] Updated spec files + * [#1215] Display codec name in calltree instead of status bar + * [#2390] Move back nbCalls and stopStream higher in refuseCall + * [#2392] Fix ringtone during call in IAX + * [#2391] Stop audio streams when there is 0 calls only + * [#2391] Add debug when call state is not valid + * [#2390] Clear returns in IAXvoipLink::sendAudioFromMic() method + * [#2380] Fixing IncomingCallNotification not regular + * [#2339] Query conference at client startup + * [#2339] Working conference querying at startup + * [#2339] Add conference in call tree + * [#2339] Primitives to query conferences at client startup + * [#2320] Add account selection in history + * [#2355] Temporary solution: do not delete pointer when removing + account + * [#2380] Change algorithm in AudioRtp to trigger an + IncomingCallNotification + * [#2274] Comment sdebug in MainBuffer flush method + * [#2274] Add flushMain() in ManagerImpl::addStream + * [#2274] Add getBufferID() method in ring buffer + * [#2274] Fix warning, comment debug in ringbuffer's flush method + * [#2274] Use AudioLayer flushMain() and flushUrgent() in ALSA + * [#2274] Clean up unused variable warning + * [#2274] Protect minbudffer pointer on flushing + * [#2274] Fix playATone method which writing empty buffer in urgent + ringbuffer + * [#2274] Use audio layer flushUrgent and flushMain in createStreams + * [#2274] Use flush audio calls from audiolayer + * [#2274] Flush when peer answered call + * [#2375] Flush main buffer in iax when answering a call + * [#2274] Parse displayname using c++ string method + * [#2375] Flush main buffer when off holding calls + * [#2375] Flush main buffer mon RTP startup + * [#2376] Use now Pulseaudio module-cork-music-on-phone + * Updated OSC packaging + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Fri, 20 Nov 2009 14:00:02 -0500 + +sflphone-common (0.9.7~beta~ppa1~SYSTEM) SYSTEM; urgency=low + + ** 0.9.7~beta~ppa1~SYSTEM ** + + * [#1933] Cleanup debug + * [#1933] Clean up debug + * Fix mic + * [#1933] Set the IAx format earlier + * [#1933] Move IAX sendAudioFromMic outside if (call) statement + * [#1933] Fix startstream when offhold in iax and add debug concerning + codec neg. + * [#2371] sflphone_notify_voice_mail: minor gettext message formatting + cleanup + * [#2371] select_account_cb: properly gettextize status message + * [#2371] show_account_list_config_dialog: properly gettextize status + message + * INSTALL: Minor tidyup of core install guide + * Add /sflphone-client-gnome/src/icons/Makefile to .gitignore + * [#2181] Updated OpenSUSE files (tmp) + * [#1933] Add debug for codec negociation for iax + * [#1933] Get rid of getMicAvail and getMicData in audiolayer (not + used anymore) + * [#1933] Add "audio codec not determined" error in IAX + * [#1933] Test flush data + * [#1933] Do not need to start audio stream in iax anymore + * [#1933] Protecting pointer + * [#2284] Remove more compilation/execution warnings + * [#2284] Cleanup debug in client, use DEBUG instead of g_print + * [#2284] Clean up uimanager + * [#2370] Remove warnings + * [#2366] Clean up other debug + * [#2366] Clean up debug + * [#2366] Call pa_xfree explicitely in writeToSpeaker + * [#2284] Remove address book warnings + * [#2365] Fixes bad cast + * [#2352] Fix continuous ringing when peer hangup and call not yet + answered + * [#2181] Added version support + * [#2181] Fixed some minor issues + * [#2360] Moved MainBuffer from AudioLayer to ManagerImpl + * [#2352] Makes getMainBuffer() everywhere + * [#2352] Use 50 sec latency on pulseaudio stream creation + * [#2352] Add alsa debug + * [#2359] Update repository documentation + * [#2354] Move pulseaudio disconnectAudioStream after stopping main + loop + * [#2352] Adjust nb byte copied in pulseaudio according to + writeableSize + * [#2352] Specify pulseaudio tlength parameters using pa_usec_to_bytes + * [#2322] Convert italian translation to UTF-8 + * [#2357] Fixes window size + * [#2357] Display only actionnable tool item + * [#2333] Update streams parameters + * [#2347] Use GNOME user settings for Menu and Toolbar appareance + * [#2349] Load/Save properly audio params + * [#2322] Update translations from Launchpad + * [#2181] Added Francois Marier script + * [#2350] Remove non-valid test + * [#2181] Updated launchpad packaging + * [#2333] Fix Pulseaudio Capture + * [#2333] Use pulseaudio ADJUST_LATENCY flag and ALSA RT-SCHEDULING + * [#2333] Pulseaudio Interpolate timing + * [#2333] Change (again) Pulseaudio settings to fit logiteck usb hdw + requirement + * [#2333] Adjust pulseaudio fragment size to 4096 (max sflphone's + frames per buffer) + * [#2284] Remove recurrent compilation warning (g++ linker problem) + * [#2333] Safer Audiostream parameters + * [#2333] Fix alsa playback to reduce underrun + * [#2333] Better audiostream parameters + * [#2181] Updated version management + * [#2333] Exclusive test in playback loop + * [#2181] Updated build system + * [#2333] Less underrun with these value + * [#2333] Update playback audiostream parameters + * [#2333] Lengthen the audio buffer reduce number of underrun in + pulseaudio + * [#2333] Add ALSA recovery functions for underrun (begin) + * [#2333] Add pa_stream_trigger in pulse audio underrun callabck + * [#2048] Reduce prebuffering in pulseaudio (which affect incomming + calls' plbck) + * [#2316] Do not display any icons to the right on the history tab + * [#2333] Comment pa_stream_trigger in pulseaudio underrun + * [#2333] Modify pulseaudio streams parameters + * [#2318] Fix transfer tool button double signal + * [#2181] Updated + * [#2333] Fix ALSA ringtone + * [#2333] Flush all main buffer before starting audio + * [#2333] Open/Close Alsa thread between calls while there is no audio + * [#2333] Add debug message and test condition on starting playback + and capture + * [#2181] Fixed gnome client makefile + * [#2181] Updated + * [#2308] Remove getTelephoneTone debug + * [#2308] Change plughw for default in ALSA + * [#2308] Oups, forgot to change function name in audiolayertest.cpp + * [#2308] Cleanup in pulseaudio code (debug, function name) + * [#2308] Fix pulseaudio stream closing assertion failure + * [#2308] Moved pulseaudio mainloop locking from AudioStream + disconnect stream + * [2308] Fix latency at the beginning of a call, when playing DTMF and + wehn starting tone + * [#2181] Updated karmic + * [#2317] [#2319] Fix address book toggle button contextual behaviour + * [#2308] Stop stream when refusing a call + * [#2308] Stop pulseaudio stream when peer hungup + * [#2308] Fix tone and ringtone + * [#2312] Display the STUN entry widget when opening the tab + * [#2308] Implement two different callbacks for capture/playback in + pulseaudio + * [#2309] Open/close pulseaudio connections in startStream/stopStream + * [2308] Leave pulseaudio stream running, do not cork/uncork them + anymore + * [#2295] Set gtk file chooser to None if nothing is set in + configuration + * [#1976] Add codec and conference documentation + * [#2209] Fix recording in regard of resamling + * [#2297] Update .gitignore + * [#2297] Update translation files + * [#2297] Add reference to our coding standards + * [#2297] Remove old docbook code + * [#2296] Reinit tls account settings after modification + * [#2253] Add DcBlocker class to remove capture's dc offset + * [#2034] Fixes for TLS transport to initialize + * [#2284] Add silent build rule + client clean warnings + * [#2274] Fix unserialize history items in cilent at startup + * [#2274] Complete display name parsing and displaying + * [#2274] Parse the Display Name in sip INVITE message + * [#2050] Fix capture volume control in ALSA + * [#1970] Volume controls disable when using pulseaudio + * [#1970] Disable volume controls when using pulseaudio + * [#2277] Fix direct ip2ip ZRTP enabling/disabling in ip2ip + preferences + * [#2181] Added launchpad debian files + * [#2181] Added spec files for OSC + * [#2274] Set display name for "Contact" sip header as the hostname + * [#2181] Fixed daemon issues + * [#2181] Fixed gnome client issues + * [#1976] Remove warnings - need to fix the transfer + * [#2006] Add init is_rec variable in ManagerImpl + * [#2006] Update codec display on call selection + * [#2006] Restore double click actions in history and contact calltree + (GTK) + * [#2176] use XDG_CACHE_HOME when initializing sfl.zid file + * [#1976] Fix calltree switching from history + * [#2209] (Re)Fix cache for zid + * [#2209] Clean up debug messages + * [#2209] Clean debug messages + * [#2209] Fix trasnfering a call during a conference + * [#2209] Speex decode must return the number of bytes + * [#2209] Change frameSize speex 32kHz + * [#2209] Fix speex codec framesize + * [#2209] Reinit converterSamplingRate in RTP sessions + * [#2209] Change speex ultra wide band framesize + * [#1747] Add pixmap data + * [#2252] Fix Receiving a server error 488 crashes the callee + * [#2209] Fix iax low rate packate sending + * [#2209] Clean up debug messages + * [#2209] Add resampling changes for IAX + * [#2209] Clean up resampling code + * [#2209] Fix latency introduced by pulseaudio + * [#2209] Fix initialization of mainbuffer's internal sampling rate + * [#2176] Fix upsampling buffer size in audiolayer + * [#2209] Add dynamic converter sampling rate in audiortp sessions + * [#1747] Fixes runtime warnings + * [#1747] Remove from repo + * [#1747] register our icons to be used as stock icons + * [#2209] Fix number of byte in alsa's write to speaker + * [#2209] Fix putting non-resampled data in RTP's mainbuffer + * [#2209] Add alsa resampler + * [#2209] Add a samplerate converter in PulseLayer + * [#2209] Add mainbuffer's internal sampling rate and flushall method + * [#2176] Add mainbuffer stateInfo debug method + * [#2209] Resampling is optimal using SRC_LINEAR not SRC_FASTEST + * [#2176] Remove debug recordings + * [#2176] Fix Holding a conference participant on new calls + * [#2224] Add confID in callable object + * [#2176] Fix putting onhold a call participating to a conference when + pressing new call + * [#2176] Reset auidio buffers when adding streams (rtp, audiolayer) + * [#1976] Use xml to describe toolbars - Add a naviguation toolbar + * [#2176] Remove conference default_id in joinParticipant + * [#2176] Display error message in alsa's snd_pcm_avail_update call + * [#2176] Alsa mic avail data debug + * [#2176] Add some debug message for mic loss problem + * [#2176] Flush mic ring buffer when offholding a call + * [#2176] Reset ringbuffers' readpointer when adding main participant + * [#2176] Fix getAvailData algorithm + * [#2176] Reset ringbuffer's readpointer when adding a new participant + to a conference + * [#1744] Regex object renamed to Pattern. Previous attempt at + providing + * [#2176] Fix detach main participant problem when adding new one + * [#1976] Use right domain to translate + * [#1976] Add xml menu description + * [#2176] Store a list of confernece participant in client + * [#2176] Fix add participant, joinparticipant methods + * [#2181] Do not install dbus-c++ headers + add return value + * [#2176] Fix minor call handling instabilities + * [#2174] Fix incoming IP call contact address + * [#2211] Add test to protect NULL pointer + * [#1163] Add Advanced account configuration section + * [#2176] Add some usefull comments and debugging info + * [#2176] Add conditions to display security icons in conference + * [#2176] Fix detaching one participant while keeping communication to + others + * [#2176] Reenable userActive.svg in call tree + * [#2176] Make user active blue (not red) + * [#2176] Fix user active picture + * [#2176] Fix "hidden" merge conflict in sipvoiplink + * [#2176] Remove iax audio stream on peer hungup + * [#2174] Multiple UDP transports functional (TESTED with 2 accounts + and 3 calls) + * [#2176] Fix fix audio stream binding in iax + * [#2174] Create a default UDP transport + use tp selector for dialogs + also + * [#2176] Register iax audio stream in mainbuffer + * [#2176] Fix getAudioCodecName in IAXvoipLink + * [#2176] Fix iax account init + * [#2176] Handle multiple account using the same sip transport + * [#2165] Add .png files + * [#2176] Small fixes concerning dtmf + * [#2176] Fix make uninstall in codecs + * [#2174] remove stund makefile generation + * [#2176] Add conference lock + * [#2174] Add transport selector for multiple accounts + * [#2176] Change userActive picture from red to blue + * [#2176] Fix security pixbuff in calltree + * [#2176] Replace sfl.zid in .cache/sflphone instead of .sflphone + * [#2176] Fix add call description + * [#2176] Remove detach button from toolbar + * [#2176] Fix calltree call description state and state code in + conferences + * [#2176] Fix pulse audio double free + * [#2176] Fix conference selection + * [#2174] Clean up - remove stun settings in client network + configuration panel + * [#2174] Remove voviva stun code + * [#2174] Rsolve STUN with pjsip - DO NOT WORK + * [#2165] Add user svg + * [#2165] Debugging sip call failed + * [#929] Link against uuid if installed + * Oops + * Fixed bugs related to libsexy (with GTK < 2.16) + * [#929] Remove uuid-dev dependency in the core + * [#2165] Debugging no negociated codecs at communicatio start + * [#2165] Fix calltree bug (gtktreestore instead of gtkliststore) + * [#2165] Fix several merge problems + * Updated opensuse packaging script + * [#1163] Add missing figures + * [#1163] Update INSTALL file + * [#2165] Fix IAX + * [#2165] Add recordabe interface + * [#2165] Finish recording refactoring for call (not for conference) + * [#2165] Enable speaker recording for two different calls + simultanously + * [#2165] Implement call recording using the Recordable interface + * [#2165] Add get and set to AudioLayer's audio recorder + * [#2165] Add class recordable from which inherit call and conference + * [#2006] Fix G722 and Speex 8khz codec conferencing + * [#2006] add recording of audio buffers + * [#1163] Add general settings section + * [#1163] Fixes makefile error + * [#2006] Fix some minor issues + * [#2006] Drag a conference call on another conference call + (difference conferences) + * [#2006] Fix dragging a conference on itself + * [#1744] Integrating some of the needed regular expression patterns + in order + * COmplete call features + * [#1744] Added support for named subgroup in the Regex object. Also, + new + * [#1744] Adds thread safety features, compile() and setPattern() + methods to the Regex class. + * [#1744] Fix inconsistency in the finditer method from the last + commit. + * [#1744] Added regex pattern object built on top of libpcre. To be + used + * [#1744] Initial commit towards implementing RFC4568. Unimplemented + in the + * [#2157] Hide "security" and "advanced" tabs for IAX under account + * [#1163] Add call features section + * [#2006] Add joinConference capabilities + * [#2006] Add dbus joinConference signal + * [#2006] Drag a conference call onto a conference to add it + * [#1163] Add addressbook section + * [#2006] Drag a conference call onto a single call to create a + conference + * [#2006] Expand rows automatically + * [#2006] Add minimal multiple conference handling + * [#2006] Add atached/detached conference icons + * [#2006] Add function processRemainingParticipant + * [#2006] Deep refactoring, fix hangup bug + * [#1163] Update documentation - Accounts part + * [#1976] Integrate user doc to gnome client build system + * [#2122] Remove double inclusion in dbus-c++/src/Makefile.am + * Remove pjproject version number + * [#2006] Fix peerHungup + * [#1976] Make Yelp accessible from the GNOME client (need to install + the sflphone.xml first) + * [#2006] Fix multiconferencing hangup + * [#2006] Fix hangup calls in a conference + * [#2150] Make IAx2 reappear + * [#2006] Fix detach participant on multiple call + * [#2006] Can remove rining call from a conference + * [#2006] Reinit confID when removing a participant + * [#2006] Remove get isCurrentCAll in hangup/peerhungup (SipVoipLink) + * [#2006] Fix refuse call + * [#2006] Fix answerring incoming call + * [#2006] Refactor conference's participant list + * [#2101] Re-integrate test compilation in main build system + * [#2101] Make the test directory compile + * [#2136] Restore history functionality + * [#2006] Fix binding main participant to himself + * [#2006] Fix add current/incoming/onHold participant to an existing + conference + * [#2006] Fix add incoming calls to an already created conference + * [#2006] Fix remove stream + * [#2006] Fix detachParticipant/removeParticipant switchCall ids + * [#2006] Fix adding a call in conference having state "CURRENT" + * [#2006] Remove/add main participant from conferences + * [#2006] Hold/unHold conference + * [#2006] Detach a partcipant from drag n drop + * [#2006] Hangup a conference + * [#2006] Add hold/unhold conference dbus messages + * [#2034] gtk-ui fix under the "basic" tab. + * [#2006] Fix dragging calls on conference calls + * [#2006] Fix detach participant from a conference + * [#2034] Added default message is status bar under the account config + dialog + * [#2112] Fix a crashed caused when a non-md5 password was sent to + pjsip. + * [#2006] Detach participant by ID + * [#2006] Fix addParticipant method in managerImpl to handle + incoming/answered calls + * [#2006] Add addParticipant method in managerimpl and related dbus + messages + * [#2111] Added the ability to configure zrtp on sip.sflphone.org from + * [#2106] Fixed problem in the account assistant under gtk-ui. Also, + assistant.c + * [#2006] Fix dragging a conference call on another conference call + (same conference) + * [#1904] Small UI fix. Assistant was moved from "Call" to "Edit" + menu. + * [#1904] Fix a wrong label under gtk-ui. + * [#2034] Renaming and source code splitting. + * [#2034] Status bar added to account window to better reflect the + registration + * [#2006] Make calltree_remove_call recursive (for GtkTreeStore) + * [#1110] Small gtk-UI fix in the account window (alignment). + * [#2006] Fix remove conference, display children which are still + active + * [#2006] Recursive function call in calltree_update_call + * [#2006] Add multilayered capabilities to calltree (GtkTreeStore) + * [#2006] Implement remove conference in calltree + * [#2034] Now useless as Direct Ip calls settings moved under + Preferences. + * [#2034] Edit/add buttons were set insensitive all the time under + gtk-ui. + * [#1887] Information about the state of the current SIP call is + displayed + * [#2006] Add call tree remove callback + * [#2006] Fix create_conference function + * [#2006] Update conference_added_cb to add new conference to the list + * [#812] Added new tab under GTK-ui Preferences. Moving Direct Ip + Calls from + * [#2121] Disable temporarily test compilation + * [#2006] Fix conferencelist to handle conference_obj_t instead of + gchar + * [#2006] Add conference_obj structure + * [#2121] Update version + * [#2006] Fix conference selection + * [#2101] Use the new source tree to fetch the right object files + * [#2006] Add conference in calltree + * [#2006] Add Dbus signal conference added/removed/changed + * [#2006] Add getConferenceDetails call on dbus + * [#1904] Registration expire now appears as a spin box under gtk-ui. + * [#812] Fixing a segmentation fault caused by a non-existing account + ID + * [#2006] Add getConfList method over dbus + * [#2006] Add a conferencelist data structure in client-gnome + * [#812] Defaults value are now sent if a non-existing account is + requested + * [#2006] Add sflphone action sflphone_join_participant + * [#2006] Fix buffer read pointer problem deletion + * [pjsip] Attempt at fixing via header incompatibility with + Freeswitch. + * [#1797] forget something + * [#2006] Add call new state conferencing in deamon + * [#2006] Remove addParticipant method for conference, use + joinParticipant only + * [#1163] Update INSTALL documentation + * [#812] Msec/sec values were not taken into account. + * [#1797] Make pjproject-1.4 compile + * [#2006] Add Detach participant method + * [#2006] Dragndrop fully functional with INCOMING and HOLD call + * [#1797] Add pjproject-1.4 + * [#1797] Remove pjproject-1.0.3 + * [#2006] Get call state in conference related function + * [#2006] Add joinParticipant (conference) method in ManagerImpl + * [#2006] Add joinConference DBUS message + * [#2006] Store the previously selected call_id on dragndrop + * [#2006] Fix GValue pointer unref in selection callback + * [#2006] Store dragged call_id + * [#2006] Update drag_data_received_cb callback to manipulate CallIDs + * [#2006] Add dragndrop signals + * [#2006] Set calltree reordable + * [#812] Adds the ability to create a TLS listener in case the user + requests + * [#812] Adds the ability to configure local/published address from + * [#1883] Move switchCall in onHoldCall function + * [#812] Deals with the published address/port problem when + integrating TLS. + * [#1883] Switch call id in managerimpl when peerHungUp + * [#1883] Switch call id before hangup + * [#1883] Add usefull and permanent debug info for conference + cretion/deletion + * [#812] Fix various segmentation faults related to Direct IP kind of + calls. + * [#1883] Fix deletion of std::map elements using iterators + * [#2014] Add libzrtpcpp build dependency + * [#1883] Still some for loop test ambiguity (while loop instead) + * [#1883] Fix for loop initial test ambiguity (use while loop instead) + * [#1883] We must discard data in urgent ring buffer if data is get in + mainbuf + * [#1883] Fix availForGet same id for ringbuffer and readpointer + * [#812] Match "sips" as a Direct IP Call when the user enter a sip + uri + * [#812] Fix segmentation fault related to SIP URI creation. + * [#812] Towards integrating multiple tls listeners at the same time. + This + * [#1883] Add debug messages in conference and fix mainbufferTest + * [#812] gkt-ui fix. Private key must be fed as a filename and not as- + is. + * [#812] TLS integration within sipvoiplink and pjsip. Also, + configure.ac + * [#1883] Fix Alsa/Pulse mallocation + * [#1883] Fix data corruption in AudioRtp's micData buffer + * [#812] Full dbus integration for all the tls related options under + gtk-ui. + * [#1883] Fix memory leaks in audiortp session + * [#1883] Fix mem leaks in audio rtp + * [#812] Fix setAccountDetails where TLS_ENABLE was set to the value + * [#812] Small gtk-ui fix. + * [#811][#812] Small gtk-ui fix. + * [#812] Introduced a mechanism for configuration files that makes + possible + * [#812] New dbus bindings added. Also, configuration compliance was + enforced + * [#1881] Remove default buffer from MainBuffer (update unit-tests) + * [#1881] Add ring buffer read pointer tests + * [#1883] Fix issues in ringbuffer reader pointers + * [#2034] Implementing a new configuration dialogue for TLS transport + settings + * [#1883] Add some usefull debug and safety checks + * [#2028] Notify the client with libnotify when the zrtp negotiation + failed. + * [#811] Harmless no to throw an exception, an makes the application + less + * [#2028] A minidialog is showed to the user under sflphone-client- + gnome + * Removed useless file. + * Ignoring Makefile in src/widget + * [#2027] Fix segmentation fault when showMessage callback is called + after + * [#2026] keyExchange was set to ZRTP instead of "1" + * [#2024] Fix the wrong summary at the end of the assistant. + * [#1883] Fix mnagerimpl conference map insertion + * [#1883] Add Mutexes in MainBuffer + * [#811] Gtk ui was not presenting the right information about zrtp + for + * [#2023] security icons were not installed in sflphone-client-gnome. + * [#2021] Fix a mistake in the readme from sflphone-common that gives + wrong + * [#811] The current SRTP mode was not properly displayed for the + IP2IP + * [#1743] Re-implementation of the "automatically remove error dialogs + [...]" + * [#2017] [#2019] Fix the inability to dial a number and place a + registered + * [#811] Final re-integration of ZRTP support in the main branch from + 0.9.6 + * [#1883] Fix map insertion methods + * [#811] Combo box now is now set to the active key exchange method + * [#811] ZRTP options now configurable back again from the Gtk UI. + IP2IP + * Updated hostname for git clone + * [#1883] Add minimal functionalities to create a conference + * [#811] re-integration of all the methods and signals on dbus. + ManagerImpl + * [#811] Got out of a precarious position were nothing would compile. + * [#1976] Build documentation squeleton with docbook + * [#1883] Add sflphone-client "addParticipant" button for conference + * [#1994] Better organize the source directory structure. New + subdirectories + * [#1883] Add a simple Conference class + * [#1882] Use static audio buffer in Pulse and ALSA layer (instead of + malloc) + * [#811] First commit toward re-integration and refactoring of ZRTP + * [#1882] Flush RTP ring buffer before entering mainloop + * [#1882] Fixed MainBuffer::UnBinCallID() in case there is no + ringbuffer + * [#1882] Test (and fixe) high level conference and mixing + functionalities + * [#1772] Apply patch to compile on fedora (sent by Marcin + Zajączkowski <mszpak@wp.pl>) + * [#1882] Update Bind, unBind call_id in MainBuffer + * [#1959] This adds the ability to store password as an MD5 Hash in + the + * [#1538] Fixes rules compilation + * [#1930][#1931] Fixed a mistake (again) related to index and + credential count + * [#1753] Remove ILBC from pjproject - Hacks in pjsip + * [#1930][#1931] Credential was not selected properly using realm + * [#1882] Finilize multiple reading pointer in RingBuffer + * [#1538] Remove configure from autogen.sh to respect debian upstream + authors policy + * [#1773] Remove generated files from repo + * [#1791] Use XDG_CACHE_HOME to save pid file + * [#1791] Fixes path to save history + * [#1791] Fix debian installation scripts + * [#1930][#1931] Settings are now taken into account in the server. + * [#1882] Add ringbuffer default ring buffer pointer in methods + involving mStart + * [#1882] Add default ringbuffer pointer + * [#1882] Add RingBuffer multiple read pointer basic functionnalities + * [#1882] Fix MainBuffer flushData unit test + * [#1930][#1931] Ability to save and retreive the configuration from + * [#1882] Added Multiple CallID mapping to MainBuffer + * [#1791] Not much + * [#1791] If XDG env variables are not null but empty, use default + ones + * [#1791] Make XDG_CONFIG_HOME writable + * [#1930][#1931] Partial commit. Not working yet. Cannot delete + account + * [#1881] Fixed alsa capture latency problem + * [#1881] Fixed Alsa capture temporarily + * [#1930] [#1931] Partial unbroken commit providing the ability to + * [#1881] MainBuffer implemented in AudioLayer/AudioRTP + * [#1881] Add discard and flush unit-tests + * [#1881] Add discard and flush functionnalites to MainRingBuffer + * [#1881] Add availForGet in MainBuffer + * [#1881] Add availForPut function to MainBuffer + * [#1880] Remove AudioRTP* pointer from SipVoIP (reapered while + merging master) + * [#1881] Add a map between call id and coresponding ring buffer + * [#1855] Refresh pot file and upload on Launchpad + * [#1881] MainBuffe now robust to false ids on getData and putData + * [#1881] Fix big big big memory leak + * [#1881] Add getData and putData to mainBuffer + * [#1881] Unit-test basic ring buffer functionnaities + * [#1881] Add class MainBuffer and basic buffer creation unit-tests + * [#1880] Fix call transfer (step2) issues + * [#1880] Moved AudioRtp* pointer from SIPVoIPLink to SIPCall class + * [#1791] Add postinst script to keep user data when migrating + config/history file + * [#1797] Make pjsip compile + * [#1777] Code indentation + * [#1791] Use XDG_DATA_HOME and XDG_CONFIG_HOME for sflphonedrc and + history + unit tests + * [#1746] Useless space does not appear anymore when volume sliders + and + * [#1643] GtkCheckMenuItem is used instead of icons for elements in + the + * [#1110] [#1668] STUN parameters are now located in the preferences, + under + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Fri, 06 Nov 2009 11:23:15 -0500 + +sflphone-common (0.9.6-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6 ** + + * Documentation on echo test + * [redmine_down] codec names not displayed in total + * [redmine_down] crash when hanging up a dialing call because tries to + add it to history whereas no starttime + * [#1927] alternate every time screen changed to call history + * [#1886] clean code + * [#1886] debug messages when loading history removed + * [redmine_down] sflphone-kde icons + * [#1855] Update language files + * [#1502] Update version number + * [redmine_down] setHistory at close + * [#redmine_down] Handle PJ_DECLINE_SC as failure + * [#1923] Fix segmentation fault when adding a new account + * [#1923] Check on iterator before setting the config + * [#1904] Added mnemonic to tabs in sflphone-client-gnome. + * [#1905] The daemon was not sending the currentSelectedCodec signal + on dbus when answering a call. + * [#1922] Default values set to all account details + * [#1886] Spinbox reg expire enables apply, and address book is not + visible when disabled + * [#1905] Bug fix for segmentation fault caused by an empty string, + * [#1910] Warnings in test directory + * [#1919] Error fixed + * [#1855] Update russian translation - Hussein Abdallah + * [#1910] Remove files + * [#1919] fixed + * [#1777] Code indentation + * [#1918] fixed + * [#1917] fixed + * [#1910] Remove warnings compilation in src + * [#1886] removed AccountListModel in configskeleton + * [#1914] + * [#1911] check previous and new port + * [#1910] Remove compilation warnings in src/dbus and src/history + * [#1910] Remove compilation warnings in src/audio + * [1855] Update german translation - Sven Werlen + * [#1909] removed + * [#1906] Done + * [#1904] The registration expire value is now configurable from the + * Cleaned up debug messages. + * [#1886] separated initCallItem in two functions + * [#1886] reversed error in commit + * [#1886] clean debug + * [#1886] changed Name of classes and files + * [#1886] clean + * [#1870] In call_state_cb (dbus.c:126), _time_stop was overridden by + the actual time. + * [#1884] Added some new gpg flags to prevent tty warnings + * [#1886] Clean audio config dialog + * [#1886] No more compile warnings. + 1 comm + * [#1872] Check if the user input is smaller than PJ_MAX_HOSTNAME. + * [#1886] + * [#1785] Fixed build when no new commit + * [#1852] If chosen by the user, the hostname can now be solved and + used + * [#1871] * and # inverted back + * [#1869] Conditional compilation that checks if + * [#1309] removed test in main + * [#1425] Put actions in SFLPhone window class instead of ui view, + made a separate toolbar for screens. + + -- SFLphone Automatic Build System <team@sflphone.org> Mon, 27 Jul 2009 09:53:00 -0400 + +sflphone-common (0.9.6~rc2-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6~rc2 ** + + * [#1755] Remove generated file + * [#1753] restore ilbc ... + * [#1866] Methods getSipPort and setSipPort now have an effect on the + * [#1753] make pjsip compile without ilbc. Use ./autogen.sh --disable- + ilbc-codec + * [#1855] Fix error in russian translation + * [#1805] Remove the old flawed signal mechanism which was failing in + * [#1855] Refresh translation + * Spanish translation finished + po README files updated + echo's in + copy-in-clients + * [#1850] Yun made the chinese HK-CN translation + * [#1848] Fix transfer interface bug + * [#1862] At install, kde client installs only french translation file + * [#1841] A new fallback mechanism was added to the internal resolver + in PJSIP. + * Started AccountList model/view + * [#1855] Remove po subdir in Makefile.am + * [#1855] Fix typo error in sflphone-client-gnome + * [#1855] Do not generate Makefile in sflphone-common/po + * [#1855] Copy translation files into both clients dirs + * [#1855] Remove po dir from sflphone-common + * Comments added + * [#1860] mailbox->voicemail... + * make scripts executable + * [#1855] French translation + * [#1855] Chinese zh_HK partially filled... + * [#1859] An unnamed pipe monitored by poll() was added. When we want + to + * [#1855] Sven completed the first part of the german translation + * [#1855] Cantonese manually filled for already translated, almost + equal strings + * [#1855] Merge russian translation + * [#1855] Spanish manually filled for already translated, almost equal + strings + * [#1855] Update german translation in ./lang/de + * [#1858] This problem was fixed by removing a useless line in + * [#1855] merged existing translations in lang/ sflphone.po's + * [#1842] [#1843] An attempt at improving the expected behaviour that + can't + * [#1855] added po folder in gnome client and scripts for copying from + common lang folder to clients + * [#1853] Edit before call does nothing on call history + * Put most language entries possible in common. From 300 to 250 + entries. Stays underscores problem. Scripts for copy in clients. + * commit to merge master + * [#1825] Changed "Bad authentification" to "Authentication Failed". + * common po files + * [#1753] Remove ILBC from pjproject + + -- SFLphone Automatic Build System <team@sflphone.org> Fri, 17 Jul 2009 19:12:44 -0400 + +sflphone-common (0.9.6~rc1-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6~rc1 ** + + * Update some version number + * [#1792] Creates .sflphone directory with permission 600. Also, + "chmod 600" after + * [#1810] GUI is now notified that the call failed. Also, a segfault + was + * [#1816] Address book search disabled when disabled address book and + enabled it back plus button stays triggered + * codeclistmodel + asynchronous loading of address book + + enable/disable address book + * [#1810] Now checking SDP answer after 200 OK. Still need to + implement full + * [#1794] Can't use the interface during a call + * Updated translation files + * Russian translation integrated + * Codec list model/view started. + * [#1807] Add configure.ac in pjproject-1.0.3 + * [#1787] closeRtpSession added in some places where it should have + been + * Use Item class for contacts and accounts + * Comments + clean code + * [#1794] Improved debug messages + * [#1805] Replaced the old and unreliable mecanism that was was + waiting for + * [#1794] Can't use the interface during a call + * [#1787] For those cases where no registered SIP account is + configured + * [#1797] Make pjsip compile + * [#1787] Minor changes. Removed useless commented line. Changed order + of + * [#1777] Code indentation + * [#1797] Update package generation with new pjsip version + * [#1798] Does not hang up when the call is building up + * [#1797] Update .gitignore with new pjsip version + * [#1797] Remove generated files from repo + * [#1797] Main build system now uses pjproject-1.0.3 + * [#1797] Add pjproject-1.0.3 + * [#1797] Remove pjproject-1.0.2 + * [#1796] Computing time optimization (samplerate conversion) + * [#1787] _audiortp->start() moved away from offhold(), + SIPCallAnswered() + * [#1312] Added new states for calls initialized by other clients + * [#1795] Crashes when adding a new account, checking it and applying + * [#1782] Missing icons + * [#1793] KDE client compilation problem + * Fake ringtone files can no longer be set. + * indentation + * [#1312] Able to fetch to differentiate incoming/ringing call state + * [#1784] Use DESTDIR variable in po Makefile - fix language file + installation + * [#1785] Fixed typo + * [#1785] Fixed changelog update + * [#1759] ./autogen.sh --prefix=/usr --with-debug to use optimization + level 0 + * [#1773] Changed snapshot naming convention + * [#1773] Removed gpg agent use, added repository cache cleaning + * [#1759] Use optimization level 0 for repository, 2 for packages + * [#1777] Code indentation/formatting + * Translated new features in french + * [#1785] Added missing changelog entry + * [#1781] Window title is SFLPhone + * [#1777] Add code indentation/formatting in the buil system + * [#1774] Can't set voicemail number in KDE account creation wizard + * [#1775] Can't modify account information for account created with + the wizard + * [#1771] Add a "Default" button in context menu to disable chosen + prior account + * [#1705] + * [#1224] Remove generated file from the repo + * [#1224] Remove generated file from the repo + * [#1762] distclean target should remove kconfig generated files + (settings.h, settings.cpp). Rename them? + * [#1761] clear history button should really clear history + * Dialpad works. + * Implemented Dialpad widget instead of building it in main view. + * Removed last occurence of the old config dialog, that made the build + crash. + * [#1755] Do not consider G722 as a dynamic payload elsewhere than in + RTP layer + * [#1753] Remove ilbc Makefile generation + * [#1756] Implement a kde configuration dialog with kconfig xt and + kconfigdialog class + * [#1755] fix audiocodec folder parsing problem + * [#1450] Reinit timestamp comparison in RTP, create session in + newOutgoingCall + * [#1753] Remove milenage third party code from pjsip + * New Config Dialog integrated in GUI.(without codecs) + * [#1753] Remove ILBC codec + * kconfig started, tr2i18n -> i18n, icons folder, accountList changed + * [#1705] Fixed Audio RTP thread creation/start + * [#1714] Fix codec negociation result handling + * [#1678] Fix audiortp payload setting + * [#1678] Put bac putData method in rtp + * [#1669] gtk_file_chooser_get_filename() support UTF-8 by default + * [#1735] Add conditions to sdp update call if call declined + * [#1737] substr of recordings destination folder to remove "file://" + should be done in client rather than in daemon + * [#1731] Enlarge audio stream buffer size + * [#1714] Missing true + * [#1317] Fixed Mandriva timeout + * [#1317] Changed tag convention + * [#1317] Cleaned git-dch + + -- SFLphone Automatic Build System <team@sflphone.org> Fri, 10 Jul 2009 15:49:56 -0400 + +sflphone-common (0.9.6~beta-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6~beta ** + + * spec files for mandriva and opensuse updated with buildrequires + libqt4-dev >=4.3 + * [#1700] Cannot build on ubuntu 8.10 and a few other distribs + * [#1502] Update version number where applicable + * [#1642] Update client icons + * [#1450] Clean up useless debug and comments in sipvoiplink and + audiortp + * [#1450] Remove Semaphore object in AudioRtp thread deletion + * [#1450] Audio RTP init now synchronized with Sip/SDP + * [#1693] kde client crashes when changing codecs order/activation + * [#1450] Deep refactoring of audiortp + * [#1450] setRtpSessionRemoteIp + * [#1689] getCallList at start + * [#1224] Change path in package files + * [#1450] Audio RTP initialized only once, payload and remote ip set + at runtime + * [#1450] Add setRtpSessionMedia and setRtpSessionRemoteIp address + * [#1642] Make GNOME GUI fresher and younger ;) + * [#1686] Status bar displaying used account + * added sflphone-kde icon so that it compiles + * [#1659] Ending a call causes the daemon to crash + * corrected introspection XMLs, po files... + * [#1211] g722 media descriptor in codecDescriptor + * [#1310] Install sflphoned in $(prefix)/lib/sflphone + * [#1502] Do not install test binaries and dbus utilitaries + * [#1224] hack for pjsip build system! + * [#1224] Remove pjsip binaries from repo + * [#1224] Upgrade to pjsip 1.0.2 + * [#1658] About SFLphone (bugs) + * [#1658] About SFLphone + * [#1660] Displaying all dialed numbers in a call + * Tested status bar. + * [#790] Optimize pulse audio streams parameters + * [#1678] Some usefull debug messages for mutex/semaphore deadlock + problem + * [#1669] Add/remove some usefull/unusefull debug + * [#1665] Fix latency related to pulse audio stream openning/closing + * [#1457] Make the menus and panels accessible in french + * [#1457] Improve broken keyboard accessibility in menus and conf + panels + * [#961] Instanciate only once the searchbar icons + * [#961] Restore transfer fonction + * [#961] Filter on the history type OK + * [#961] Fix compilation problems on hardy/intrepid + * [#1157] Commit missing files + * [#790] Reduce number of start/stop streams call on pulse audio + * [#1639] kde client crashes when no account registered + * [#1620] Fix the searchbar + * [#1620] Get back caltree as it was during gtkcritical area + * [#1620] Add history filter reinit function + * [#1335] Add a missing label in address book preferences + * [#1561] Update russian translation - Hussein Abdallah + * [#1605] Fix edit menu french translation + * [#961] Enable to search in the history according to the call type + * [#1449] Searchbar does not work anymore + * [#961] Add popup menu on the entry primary icon for history + * [#1317] Fixed KDE client package dependency + * [#936] speex 32 khz integration completed + * [#936] Use 320 frame size + * [#936] Test using a frame size at 320 smpls + * [#1214] Enable / Disable history + * [#1607] Fix compilation problem for ubuntu 8.10 (libsexy) + * [#1313] Implement processDataEncode processDataDecode in audiortp + * [#1613] codec list order can't be set + * Better handling of localisation + added languages + corrected + warnings + begginning of new config dialog with kconfig + 14px + account leds + * [#1214] Save and load history according to the limit timestamp + + unit tests + * [1609] Fix call number copy/paste feature + * [1607] Restore clear action icon in searchbar + * [#936] Try to decode using 1280 samples + * [#936] Add some debug + * [#936] Add .cpp file + * [#936] Oops Forgot speex 32 khz + * [#1214] Add configuration panel for history + D-Bus calls + * [#1313] Test rtp thread function, frame size, nbbytes, resampling + * [#790] Flush audio data before closing audio streams + * [#1214] History displays local time + * [#1214] Skip empty field on display + * [#1214] Associate an account to an history entry + * [#1342] Get addressbook options sensitive/non-sensitive + * [#1211] Clean up and comments + * [#1211] Get back to 20 ms framesize + * [#1211] Use sendImmediate instead of putData in RTP + * [#1211] Fix nb byte available in RTP + * [#1211] Clear condition on maxNbSamples in RTP + * [#1211] Fix max byte available in RTP session + * [#1211] G722: Use 160 samples per frame instead of 320 + * [#1211] Test using a dynamic payload + * [#1211] Test using a dynamic payload type + * [#1211] Rename size variable (nb_samples, nb_bytes) + * [#1211] Test g722 ip-to-ip sending twice the data lenth + * [#1211] Test g722 ip-to-ip + * [#1214] Do not select an history item by default at startup + * [#1214] Remove some compilation warnings + * [#1214] Handle empty field - remove g_print + * [#1214] Add each history item only once + * [#1214] Handle call timestamps properlier + * [#1214] Do not need timestamp files anymore + * [#1214] Use the saved date for history entry + * Clean up + * [#1214] Client doesn't crash if the D-Bus call fails + * [#1214] Client is able to save its history - still some glitches + * [#1211] Forgot 16000 for g722 + * [#1211] G722 initialization + * [#1214] Save name/number, successfully load the history if no fields + are empty + * [#1499] Fixed destination directory bug + * [#1214] Restore all the functionalities; peer name/number way more + easy to handle !! + * [#1214] Add callable_object instead of call_t, refactoring + * [#1211] Test with polycom soundstation 16000 + * [#1211] Remove C like inline function in g722 codec + * [#1342] Finalize gnome client preference window formating + * [#1214] Retrieve the history when the gnome client startsup + * [#1306] Implement localization for KDE client + * [#1593] enable accounts apply button when account checked/unchecked + * [#1214] Implement the dbus calls on server side + * [#1214] Add serialized/unserialized functions to pass data on DBUS + * [#1342] Formating gnome client configuration windows + * [#1214] Save sucessfully a map of history items + * [#1499] Removed multiple jobs compilation for KDE client (2) + * [#1214] Load history from file into memory, add unit tests + * [#1534] Throws a length_error exception in case URL exceeds + std::string max_size + * [#1499] Removed multiple jobs compilation for KDE client + * [#1565] make account leds smaller + * [1430] Fix dbus debug + * [#1562] crashes when trying to change item of a call of state "OVER" + * [#1116] Fix compilation bug + * [#1317] Added mandriva and opensuse-11 64 bits + * [#1108] Add messges in main window concerning transfer success + failure + * [#1116] Fix compilation problems + * [#1211] g722 Makefile + * [#1108] Client side transferFailed/trasferSucceded signals handling + * [#1211] G722 mostly completed, + * [#1555] make bigger toolbar (24x24) + * [#1551] remove default mailbox number in wizard and disable mailbox + button when first account doesn't have mailbox number + * [#1342] Re-add sflphone manpages + * [#1116] Fix compilation on non-jaunty distros + * [#1317] Fixed opensuse startup sleep + * [#1108] Add a signal in the client to notify successful or failed + transfer + * [#1108] Dbus signals concerning call transfer success/failure + * [#1317] Added opensuse to automatic build system + * [#1223] Fix manpages bug + * [#1060] german translation glitch + * Clean up some gnome client warnings + * [#1547] replace ugly account leds by beautiful icons + * [#1548] add close button that hides windowand just hide on clicking + the cross + * [#1549] put introspec XMLs in the client's source + * [#1312] Implement getCallList D-BUS method + * [#1116] Clear text in history and contacts + * [#1499] KDE integration + * [#1469] Modify header linkers in dbus-c++'s Makefile.am's + * [#1469] Remove examples folder from dbus-c++ + * [#1214] History integration in build system; unit test squeleton + * [#1317] Cleaning + * [#1469] Remove configure stuff in dbus-c++ + * [#1469] Add unofficial mainline dbus-c++ + * [#1469] Remove dbus-c++ from freedesktop + * [#1430] Bring account changed signal/callback back to normal + * [#1060] Update german translation - Sven Werlen + * [#1430] Add marshaller one string define + * [#1430] Send account change signal broadcast using account id + * [#1430] Remove condition on setRegistrationState, cause stun to + crash + * [#1317] Centralized version handling + * [#1317] Fixed version number on sfl-git-dch + * [#1317] Refactoring for new distributions + * [#1215] Fix account order at startup if latency + * [#1088] Restore sip dns srv + * [#1214] Add squeleton for history manager + * [#1430] Add accout id to accout changed method + * [#1430] No connectionStatusNotification (account changed) if no + changes + * [#1538] Add COPYING file + * [#1430] Add audio rtp thread tests + * [#1317] Changed version detection + * [#1538] Document license in libs/stund + * [#1317] Added version files + * [#1538] Apply François patches - debian packages + * [#1317] Updated spec files + * add files + * [#1538] Apply François patches - debian packages + * [#1535] Change program file structure (directory src...) + * [#1317] Updated build system scripts + * [#1317] Cleaning + * [#1317] Copied introspect files to gnome client + * [#1317] Added opensuse to build-system : first-shot + * [#1317] Remove spec files from configure + * [#1317] Added missing prefix + * removed debug for daemon account fix + * [#1430] Add a connection reference which most likely belong to + libdbus + * [#1430] Use shared connection instead of private + * make daemon find the account, added userMatch + * Clean code, add comments... + * [#1317] Fixed packaging rules + * [#1317] Updated autogen + * Updated autogen.sh for pjsip + * [#1526] Set accounts order + * [#1317] Fixed pjsip lib dirs + * [#1317] Updated debian packaging for new pjsip configuration script + * [#1317] Switch to autogenerated guess and sub files + * [#1317] Updated pjsip inclusion in build system + * [#1317] Replaced pjsip guess and sub files + * [#1317] Fixed compilation issues on opensuse 11 + * [#1505] account list seem to crash the application when clicking + Apply very fast... + * [#1456] Add a flag to be replaced in the control files + * [#1456] Added version dependancy handling + * put account alias in AccountWidgetItem rather than in the item with + " " before. + * [#1034] The KDE client should start sflphoned if it is not started + * [#1500] Handle options for notifications and display on incoming + call. + * [#1443] Client should not crash when receive an unexpected + stateChanged signal + * [#1403] Do not stop the notification anymore + * [#1456] Added version dependancy handling + * [#1426] Daemon crashes when get alsa plugin + * [#1422] Improved error messages + * commit for merge + * [#1424] Change logo in tray icon and put a different one when + incoming call + * [#1425] first part done, window title... + * [#1413] add manpages creating and installing in build system + * [#1417] The client should start the account creation wizard if + started for the first time (if config file doesn't exist) + * [#1421] Make volume bars horizontal when dialpad is hidden. + * Changed main window title and fixed a mistake in sflphone_const.h + * [#1412] make debian package building work + * changelog changed. + * Changed addAccount method in gnome client. + * Debian and man folders added. + * [#1388] Change project name from sflphone_kde to sflphone-client-kde + * Better handle of kabc check. + * [#1351] Automatic generation of dbus interfaces in makefile + generated by cmake + * [#1307] Implement "edit before call" in history and address book. + * [#1344] change action_call label in call history from "call" to + "call back". + * [#1308] Implement Hook feature in kde client + * Improved build system. + * #1219 : Add address book configuration page + * Better handling of registration to the daemon. + * #1039 : Add tray icon in kde. + * Issue no 1216 : Double click on item in history or address book + causes call. + * display peer name in call list and call history when called from + address book. + * Address book functionnal with photo displayed. + * Help menu kde available but actions disappeared. All fonctions in + view. + * Address book functionnal but ugly and making its own sort in the + complete address book. + * Account choice on right click, clean out includes, page address + book, fixed bugs... + * Wizard, double click, context menu... + * Removed sflphone_kde.kdevelop.filelist + * Added account creation wizard and translated interface in english. + * Transfer functionnal but ugly. + * transfer not functionnal + * Bug fixed : unholding (UNHOLD_CURRENT, UNHOLD_RECORD) + * Commit functional for push. With install.sh + * Before merge. + * Problem with enable accounts. Account display increased. + * Functional with codec order working , playDTMF. + * Commit functional. + * sflphone_kde/build added in .gitignore. + * complete commit for checkout previous. + * Commit before checkout previous version to check the display + bug(little font everywhere...) + * Functionnal client. Rest : history icons, config icons and + functionalities + * commit before merge asavard for isRecording. + * Call and Automate fusion done and seems to work. + * Commiting before putting Automate class in Call class. + * Functionnal main window without recording, history, voicemail, kio + widgets. + * client kde avec kdevelop. + * Config Dialog almost finished. + * Base of QT client + + -- SFLphone Automatic Build System <team@sflphone.org> Tue, 23 Jun 2009 11:12:06 -0400 + +sflphone-common (0.9.5-SYSTEM) SYSTEM; urgency=low + + ** 0.9.5 release ** + + * [#1060] FIx bug in chinese translation + * [#1313] git add rtpTest.cpp rtpTest.h + * [#1313] Add init/close rtp tests + * [#1313] Basic instanciation of the rtp layer + * [#1449] Gtk-Critical concerning history filters and new calls + * [#1400] Make the match with the hostname instead of username + * [#1324] Change status bar label for "Using %s (%s)" + * [#1403] Icon size: 60x60 px + * [#1403] Do not remove notification, improve icon quality + * [#1403] Add smaller icon for gnome notifications + * [#1403] Prevent crash when hangup && no notification + * [#1403] Remove all actions on notifications; code refactoring + * [#1451] Use stun.sflphone.org as default STUN server + * [#1060] New po files - need to be translated + * [#1060] Update french translation - Rebuild template file + * [#1456] Add a flag to be replaced in the control files + * [#1454] Make cppunit optional; remove from build deps in control + files + * [#1401] Add libexpat1-dev dependency in control files + * [#1448] Take off these ugly debug messages + * [#1448] fixed getTelephoneTone and getTelephoneFile() called + repeatedly + * [#1406] add liblog4c-dev in build-depends + * [#1409] Restore .desktop icon + + -- SFLphone Automatic Build System <team@sflphone.org> Mon, 25 May 2009 11:34:40 -0400 + +sflphone-common (0.9.5-SYSTEM~rc2) SYSTEM; urgency=low + + ** 0.9.5 rc2 ** + + * [#1422] Improved error message + * [#1402] Fix pjsip build + * [#1404] Clear GTK-Critical Bug at client startup + * [#1422] Added automatic VM shutdown when building on more than one + VM + * [#1422] Fixed some issues with new changelog generation script + * [#1422] Moved distribution update to specific file + * [#1422] Dropped git-dch, replace by home made implementation + * [#1402] Fix pjsip build + * [#1404] Clear GTK-Critical Bug at client startup + * Changes for name based dbus connection + * Clean changelogs + * [#1343] Gnome: Implement a callback system to handle focus on + different widgets + * Debus Session + * Refactoring Python code, PEP8 + * [#1430] Get back dbus_g_proxy_new_for_name + * [#1430] Get back DBUS_BUS_SESSION type + * [#1430] Dbus fixed owner message binding + * Second test with DBUS owner + * [#1404] Gnome -> Preferences -> Hooks + * [#1404] Gnome -> Preferences -> Recordings + * [#1404] Call History + * [#1404] Gnome -> Preferences -> Address Book + * [#1404] IF the first notification option disable the second + notification + * Dbus with fixed owner does not automatically start the deamon + * Add codec debug tests in pysflphone + * [#1407] Some print info + * [#1407] Add a scenario to pick_up action + * Test client dbus connection to a fixed owner + * Add python dbus test suite + * [#1161] Modified version handling in build system + * [#1314] Test pulse audio and audio streams connect and disconnect + * [#1402] Add info message after configure + * [#1402] Build the daemon with the local pjsip library (vs the + installed one) + * [#1009] Fix Codec Sampling Rate set to zeros + * [#1314] Add mutex to pulse layer audio streams + * [#1314] Refactoring pulseaudio stream to test connect disconnect + * [#1314] Refactoring of pulselayer to test conect/disconnect + * Add debug messages in debus calls concerning account + * [#1314] Add some return values to audio init functions + * [#1406] add liblog4c-dev in build-depends + * [#1409] Restore .desktop icon + * Bug #1405: Fix strings as requested. + * Bug #1404: Fix strings in preferences panel. + + -- SFLphone Automatic Build System <team@sflphone.org> Tue, 19 May 2009 12:08:03 -0400 + +sflphone-common (0.9.5-0ubuntu1~rc1) SYSTEM; urgency=low + + [ SFLphone Project ] + * [#1262] Updated changelogs for version 0.9.5-0ubuntu1 Snapshot 2009- + 05-05 + + [ Emmanuel Milou ] + * Add some python CLI client code; not really functional + * [#1108] Fix peerHungup method for IP to IP call + + [ Alexandre Savard ] + * [#1108] Correct setting of SIP contact for direct IP call + * [#1108] SIP user agent handles incoming REFER + + [ Emmanuel Milou ] + * Remove website from repository + * Update translation + + [ Alexandre Savard ] + * Sflphone icon's tooltip changed for "configured" instead of + "registered" + + [ Emmanuel Milou ] + * Update translation + + [ Sflphone Project ] + + -- Sflphone Project <sflphone@mtl.savoirfairelinux.net> Tue, 05 May 2009 19:16:09 -0400 + +sflphone-common (0.9.5-0ubuntu1~beta) SYSTEM; urgency=low + + [ Julien Bonjean ] + * Updated Eclipse stuff + * Improved addressbook config window + * Added sflphone Eclipse stuff + * Implemented addressbook list server side + * Moved dbus stuff in dbus directory + * Updated addressbook configuration + + [ Emmanuel Milou ] + * Remove unuseful installation scripts. Use apt-get build-dep sflphone + instead + * fix bug #1090 + + [ Alexandre Savard ] + * defining speex 16khz + + [ Emmanuel Milou ] + * Remove unuseful file from build system + * Start dns srv resolver + + [ Alexandre Savard ] + * Basic ogg/vorbis initialization + + [ Emmanuel Milou ] + * Handle incoming IP-to-IP invite correctly + + [ Alexandre Savard ] + * speex wideband 16000 + + [ Emmanuel Milou ] + * Better handling of incoming IP to IP call + * DNS SRV resolution functional + * Implement IAX2 incoming URL + * Allow user to make IP call without any accounts configured + * Add a contextual menu to edit a number from the contacts tab + * Add comments, tooltip and new button to the contextual menu + * add delete event, migrate to GTK 2.16 for sexy icons + * Resolve ticket #1118 + * Update suse spec file + * Add phone number cleanup functions, unit tests and panel + configuration + * Add pertinent test that fails + * fix dependencies for suse package + * Add contextual edit menu in history - #1120 + + [ Alexandre Savard ] + * Temporary comit: make speex wideband (16 khz) + * Temporary: shared object for speex narrow band + * Temporary: speex narrowband and wideband coexist + + [ Julien Bonjean ] + * Fixed bug when no book selected + * Fixed addressbook related compilation warnings + * Fixed GTK client remaining compilation warnings + * Fixed segfault when book removed since last sflphone run + * Fixed bug when book is unreachable (ldap error) + + [ Alexandre Savard ] + * Fix codec list in audio config window + * Active/inactive speex codec by payload + + [ Julien Bonjean ] + * Updated gitignore + * Added some comments + + [ Emmanuel Milou ] + * Add callto: handler script for browsers and al. + * Integrate test compilation in the daemon build-system + + [ Julien Bonjean ] + * Fixed g_object_unref warning for pixbuf + * Cleaned too verbose output + * Fixed toolbar update warning + * Added support for asynchornous books open (first shot) + + [ Emmanuel Milou ] + * Add a DBus call to fetch the call details from a call ID - Ticket + #928 + + [ Julien Bonjean ] + * Improved async open books + * Fixed bug #1139 + + [ Emmanuel Milou ] + * Add a way to save account order + * commit missing files + + [ Julien Bonjean ] + * Introduced log4c (ticket #1162) + + [ Emmanuel Milou ] + * Load/save account order functionnal - ticket #813 + + [ Alexandre Savard ] + * Add CELT codec (#1143) + * Make celt frame size 256 (*1143) + + [ Julien Bonjean ] + * Switched everything to log4c (ticket #1162) + * Updated eclipse settings + + [ Emmanuel Milou ] + * Restore adding account - ticket #1172 + * Add liblog4c dependecy - ticket #1179 + + [ Alexandre Savard ] + * Double maxAvailByte for frame size in rtp (#1143) + + [ Emmanuel Milou ] + * Add User-Agent SIP header - Ticket #1173 + + [ Julien Bonjean ] + * Fixed autoresize issue (#708) + + [ Emmanuel Milou ] + * Remove libcppuint dependency for the debian packages + * Look for libsexy only if gtk version < 2.16 - Ticket #1116 + * Remove libsexy dependency for jaunty. ticket #1116 + + [ Julien Bonjean ] + * Introduced unit tests (#1146) + * Updated gitignore + * Fixed Makefile (#1146) + + [ Emmanuel Milou ] + * [TICKET #1112] Add a test on the voice buffer to send through iax + packets + * Remove doublon in dependencies + * Remove warnings from the client test framework + * Update version number to 0.9.5~beta + * Update build-package script + * Add check dependency in build-deps control file field + * Create debian files for the new sflphone-client-gnome + * [TICKET #1212] Add Replaces field in control files + * [TICKET #1212] Fix manpages installation path + * [TICKET #1212] Add maintainer scripts to create alternatives + * [#1212] Update the manpages generation - edit preinst maintainer + script + * [#1212] Fix reference error in manpage + * [#1212] Add missing files on the client side + * [#1212] Fix debian docs files - no TODO file + * [1212] Fix manpage creation problem + * [#1220] Generate client-side glue files and marshaller at + compilation time + * [#1220] Generate server-side glue files at compilation time + * [#1212] Change binary name to sflphone-client-gnome + * [#1212] Update .gitignore to fit the new working tree + * [#1220] Explicitly generate glue files before building the library + * [#1220] Compile dbus directory before audio + * [#1212] Create sflphone-common at the root of the repository + * [#1212] Re-add pjproject + * [#1212] Remove Makefile from repo + * [#1220] Fix Makefile.am + * [#1212] New working directory functional + * [#1212] Update .gitignore + * [#1212] Hack to make pjsip compile.. + * [#1220] Use non-installed binary for dbusxx-xml2cpp + * [#1212] Add descriptive files, remove unuseful scripts from tools/ + + [ Alexandre Savard ] + * Restore speex codecs + * add frame size for celt (#1143) + * add framesize to codec, independant from audiolayer (#1143) + * use codec frame size in rtp (#1143) + * compute fixed_codec_framesize (#1143) + * do not resample if not required (#1143) + * add condition on resampling for decoder (#1143) + * add a condition on bytesAvail == 0 from mic data + * no maximum in rtp decode (#1143) + * compute maximum for decoding (#1143) + + [ Emmanuel Milou ] + * [#1146] Implement unitary tests on the client-side + + [ Alexandre Savard ] + * use float instead of int to compute max nb of sample (#1143) + * add nbSampleMax for unresampled data (#1143) + * make thread sleep during 5 ms insead of 20 (#1143) + * use unix usleep (#1143) + * 50 usecond thread!!!!! (#1143) + * try with the smallest compression (#1143) + * use timer set at framesize (#1143) + + [ Emmanuel Milou ] + * [#1161] Restore changelog version + + [ Alexandre Savard ] + * Remove celt stuff + + [ Emmanuel Milou ] + * [#1161] Update changelog + * [#1220] Add Conflicts: sflphone in debian control files + * [#1179] Add liblog4c3 runtime dependency + * [#1212] FIx typo error in dependency list for itnrepid + * [#1212] FIx .desktop file to point on the right exec + * [#1212] Modify changelog replacing tag + + [ Sflphone Project ] + * "[#1262] Updated changelogs for version 0.9.5-0ubuntu1~beta" + + [ Emmanuel Milou ] + * [#1212] restore changelogs + + [ Sflphone Project ] + * [#1262] Updated changelogs for version 0.9.5-0ubuntu1 Snapshot 2009- + 04-27 + + [ Emmanuel Milou ] + * [#1212] restore changelogs + + [ Sflphone Project ] + * [#1262] Updated changelogs for version 0.9.5-0ubuntu1~beta + + [ Emmanuel Milou ] + * [#1212] restore changelogs + + [ Sflphone Project ] + + -- Sflphone Project <sflphone@mtl.savoirfairelinux.net> Mon, 27 Apr 2009 16:57:00 -0400 + +sflphone-common (0.9.4-0ubuntu2) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Restore speex and GSM detection + + [ Emmanuel Milou ] + * Fix bug #1090 + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 8 Apr 2009 11:29:15 -0500 + +sflphone (0.9.4-0ubuntu1) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Integrate DBus-c++ and libiax2 in the main build system + * Clean up in the working repository + * Reorder hooks configuration panel + * Protect case when no codecs are active + * Fix some return values + * Add unitary tests for the hook manager (premisces) + + [Yun Liu] + * Update chinese translation + + [Sven Werlen] + * Update german translation + + [Hussein Abdallah] + * Update russian translation + + [Maxime Chambreuil] + * Update spanish translation + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 3 Apr 2009 18:29:15 -0500 + + +sflphone (0.9.4-rc1) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Fix bug while trying to hold/unhold several simultaneous call + * Improve address book build system + * Implement SIP url popup on incoming call + * Improve GTK+ panel configuration + [ Julien Bonjean ] + * GTK+ client refactoring + * GTK+ clean up + * Address book improvment + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 27 Mar 2009 18:29:15 -0500 + +sflphone (0.9.4-0beta1) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Display codec used during conversation on the GUI + * Enable/disable STUN parameters at runtime + * Refactor search bar use + [ Emmanuel Milou ] + * Build system fixes + * Implement SIP re-invite + * Implement IP to IP call + [ Julien Bonjean ] + * Integrate GNOME address book based on evolution data server + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 20 Mar 2009 18:29:15 -0500 + + +sflphone (0.9.3-0ubuntu3) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Both playback and record streams in PA_STREAM_CORKED (pulseaudio) + * Use PLUGHW device for ALSA capture + * Functional IAX and SIP recording for voicemail + * Use the less CPU-consuming interpolator algorithm for resampling + * Display in GTK GUI the codec used in conversation + * GTK GUI use ASCII instread of utf-8 + * Add record menus in GTK GUI + * Put on hold when dialing a new number + * AccountID's are saved in the history + + [ Emmanuel Milou ] + * Integrate DBUS C++, libiax2 in the git repository + * Update website + * Use libspeexdsp only if available on the system + * Updated .gitignore file + + [Cyrille Béraud] + * Account assistant manager improvment + * Add an email request when creating a new account to receive voicemails + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Sat, 14 Feb 2009 13:29:15 -0500 + +sflphone (0.9.3-0ubuntu2) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Add compilation note in README + * Use default ALSA plugin for capture + * Fix the ALSA capture problem one more time + * Clean up debug messages in dbus.c + * Add libspeexdsp dependency + * Remove implicit declaration compilation warnings + * Fix links in the website, add release note + * Change capture for the website front page + * Add alsa devel dependency in build-depends control file field + * Clean up, indentation, try to handle latency problems in iax/pulseaudio + * Remove pjsip generated files from the repo + * Use the previous declared curAlias function in accountwindow + * Fix bug in history call duration when the call fails + * Remove runtime warning in the GTK+ client + * Add librsvg2-common dependency to load SVG under KDE + * Refresh .gitignore + * Update locales files + french translation + * Add configuration panel for future noise reduction + * Add configuration panel for audio record module + * Daemon less verbose; accounts don't try to access STUn options anymore + * Fix typo in configwindow + * Add content in the official website + * use a GTK_STOCK icon for the record button + * Complete description text in the assistant manager + * Add libtool flags in client configure.ac + * Remove unuseful dependency (snd) + * Fix SIP transfer problems + * Remove previous version of PJSIP from the repo + * Upgrade PJSIP to version 1.0.1 + * Add the new website source in the repository + * Use libspeexdsp for silence detection only if available + + [ Loïc Faure-Lacroix ] + * Ajout du logo gpl3 + * Ajout des images + * Ajout de la section screenshot pour le site + * Ajout du favicon dans le header + * Modification des cartes + + [ Alexandre Savard ] + * Clean up <speex/libspeexdsp> + * Small cleanup + * Save Wave fixed + * Fix new call button when recording + * libspeexdsp added + * Recording: default home folder at startup + * Minor changes to config window + * IAX recording fixed + * Set / get recording path, still need some GTK for client + * AudioRecord file name format + * Now recording in HOME folder + + [ Cyrille Béraud ] + * Fix bug in reqaccount.c + + [ Maxime Chambreuil ] + * Update spanish translation + + [Yun Liu ] + * Update chinese translation + + [ Hussein Abdallah ] + * Update russian translation + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Sat, 14 Feb 2009 13:29:15 -0500 + +sflphone (0.9.3-0ubuntu1) SYSTEM; urgency=low + + * Remove debug + * Join thread before leaving + * Fix implicit declaration in reqaccount + * Add REST code to build the request to server + * Fix GValue initialization warnings + * Update version number, fix implicit declaration, fix GTK markup + warnings + * Apply patch to create custom SIP account from our own server + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 06 Feb 2009 19:17:32 -0500 + +sflphone (0.9.2-2ubuntu9) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Speex audio codec preprocessing initialization + * peer hung up segmentation fault solved + * Stop recording when transfering + * Terminate only one call + * Add isRecording() function + * Fix call_icon GTK client + * Fix SIPCallClose() function, recorded file now close properly + * Function terminateSIPCall added in sipvoiplink and managerimpl + * Fix thread destructor + * setRecordingOption function implement in audiorecord + * Record now implemented in Call class + * Record interface complete (on hold erase previous recording) + * Added recButton in client + * Added: record button related icons + * Record button added + * Overload AudioRecord::recData to get mic and speaker data mixed + * Recording now in audiortp::run() method + * Audio recording working in AudioRTP: receiveSessionForSpeaker + * Open/close a wave file when pulse audio stream start/stop + + [ Emmanuel Milou ] + * Fix path for GTK+ icons; clean up + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Thu, 05 Feb 2009 18:27:53 -0500 + +sflphone (0.9.2-2ubuntu8) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Update changelogs + * Fix bug in merge and in Makefile.am + * Terminate only one call + * Disable PJsip shutdown when changing STUN parameters + * Function terminateSIPCall added in sipvoiplink and managerimpl + * Add a timer to the alsa thread to not jam the CPU load + * Fix bug in sipvoiplink.cpp + * Clean shutdown of pulseaudio on quiting + * Fix DTMF at first start with Pulseaudio + * Remove zeroconf from the build system + * Add a library manager + exception handling + * Clean up in the working directory + * Better handling of capture XRUNs + * Restore mic adjust volume on ALSA layer + * Protect device ALSA operation if not opened + * Fix the switching layer bug + * Use dynamic_cast<> to use audiolayer-specific methods + * Open the audio devices only once at startup + * Refactoring of the ALSA part + * Functional plug-in manager + * Use a C++ thread to handle tones and DTMF in ALSA + * Restore IAXVoIPLink, restore Mutex + * Make the plugins registering against the plugin manager + * Migrate to 1->N relationship between voiplink and accounts + * API plugin for registration + * Use C++ thread in SIP, move everything in sipvoiplink + * Complete singleton pattern for the plugin manager + * Add -Wno-return-type compilation flag to remove warnings; Update + version number in configure.ac + * Add the dynamic loading for the plugin framework; integate unittest + + [ Yun Liu ] + * Update rpm spec file + * modify build package script and spec file for suse + + [ Alexandre Savard ] + * Add audiorecorder plugin and testaudiorecorder + * Add audio Recording class, edit global.h + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 04 Feb 2009 14:00:30 -0500 + +sflphone (0.9.2-2ubuntu7) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Update changelog to 0.9.2-6 + * Fix some dbus-glib implementation details on the client side + * Init history after dbus initialization + * Add error checking in useragent; Clean sipvoiplink + * Prevent crash when trying to call an empty number + * Set the volume of the playback stream to PA_VOLUME_NORM at startup + * Fix GTK+ generic value double initialization + * Fix jaunty control file dependency problems + * Fix jaunty control file dependency problems + + [ Yun Liu ] + * Fix bug ticket # 137 + * Tolerant to gsm library of OpenSuse 11 + + [ Sven Werlen ] + * Update german translation + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 23 Jan 2009 17:48:13 -0500 + +sflphone (0.9.2-2ubuntu6) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Migrate STUN configuration to the main config window + * Update french translation + * Other tiny memory leaks + * Fix memory leak in sampleconverter.cpp + * Generate packages from the release branch + * update the build package script + * modify the control files with architecture=any + * Remove valgring uninitialized value + * IAX and SIP use the same global variables to set account + configuration ; fix broken code + + [ Maxime Chambreuil ] + * Update spanish translation + + [ Hussein Abdallah ] + * Update russian translation + + [ Yun Liu ] + * Update translation files + * Fix the bug when user uncheck the account which fails in the + previous registration + * Add stun error status + * Fix bug ticket #143 + * Script for auto-install dependencies + * Fix bug ticket #140 + * Fix bug ticket 141 + * Fix the reregister process when user change the details of an + account + + -- Emmanuel Milou <manu@sulfur.inside.savoirfairelinux.net> Fri, 16 Jan 2009 18:19:05 -0500 + +sflphone (0.9.2-2ubuntu5) SYSTEM; urgency=low + + * Fix memory leak in the pulseaudio callback + * Update debian package generation script + * Warnings removal in GTK+ client + * Clean adjust volume method in alsalayer + * Plug the sflphone playback volume control to the pulseaudio volume + manager + * Display the date in history according to the current locale + * Generate the changelog according to the git commit messages + * Complete header in chinese translation file + * Use the right gpg key to sign the packages + * add debian jaunty jackalope support + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 14 Jan 2009 21:17:20 -0500 + +sflphone (0.9.2-2ubuntu4) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * add german translation + + [ Yun Liu ] + * Fix GUI crash in Ubuntu8.10 64bit system + + -- Yun Liu <yun.liu@savoirfairelinux.com> Thu, 08 Jan 2009 13:08:51 -0500 + +sflphone (0.9.2-2ubuntu3) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * The main thread synchronizes the ringtone thread + * disable custom ringtone for the ALSA layer + * Fix the Makefile.am in man directory, add a SEE ALSO section + + [ Yun Liu ] + * Fix daemon crash caused by the previous patch ( for bug ticket #129) + + -- Yun Liu <yun.liu@savoirfairelinux.com> Tue, 06 Jan 2009 16:18:38 -0500 + +sflphone (0.9.2-2ubuntu2) SYSTEM; urgency=low + + * Fix bug ticket #129 + + -- Yun Liu <yun.liu@savoirfairelinux.com> Wed, 5 Jan 2009 15:54:53 -0500 + +sflphone (0.9.2-2ubuntu1) SYSTEM; urgency=low + + * Migrate from eXosip library to pjsip + * Add multiple SIP accounts support + * Fix ringtones problems + * Add a pulseaudio support + * Improve audio quality with ALSA + * Add chinese translation + * Improve spanish translation + * Migrate to a maintained C++ DBus bindings + * Clean and improve the build system + * Add build-dependency on Perl because we need pod2man to generate manpages + + -- Yun Liu <yun.liu@savoirfairelinux.com> Wed, 26 Nov 2008 09:47:53 -0500 + +sflphone (0.9.1) unstable; urgency=low + * Add a search tool in the history + * Migrate some gtk_entry_new to sexy_icon_entry_new + * Bug fix (Ticket #78): The voicemail password isn't displayed anymore in + the history tab + * Add the SIP registration expire value in the user file. + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Thu, 22 May 2008 11:14:25 -0500 + +sflphone (0.9.0) unstable; urgency=low + * Add history features + * Call date + * Call duration + * Mouse events in the history tab + * Smooth switch from the history tab to the calls tab + * Remove most of GTK-Critical warnings + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 13 May 2008 16:58:25 -0500 + +sflphone (0.9-2008-06-06) unstable; urgency=low + * Audio bug correction: capture stopped after a few minutes of conversation + with USB Plantronics sound card + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Tue, 06 May 2008 16:58:25 -0500 + +sflphone (0.9-2008-05-06) unstable; urgency=low + * Bug correction: account creation with the assistant + * GTK+ warnings removal + * libnotify warnings removal + * Remove aliasing on the SFLphone logo + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Mon, 05 May 2008 16:58:25 -0500 + +sflphone (0.9) unstable; urgency=low + * Clean dependencies ( removal of libboost ) + * Several GTK improvement and updates + -account window + -configuration window + * Migrate from GtkCheckMenuItem to GtkImageMenuItem + * ALSA standard I/O transfers: MMAP instead of R/W + * Fix speex audio quality + * IAX2 protocol + -Fix hold/unhold situation + -Add on hold music + * SIP protocol + -Ringtone on incoming call + -Fix transfer situation + * Add desktop notification ( libnotify ) + * Improve the system tray icon behaviour + * Improve registration error handling + * Register/unregister from the account window takes effect without starting back SFLphone + * Compilation warnings removal + * Call history + * Add an account configuration wizard + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 30 Apr 2008 16:58:25 -0500 + +sflphone (0.8.2) unstable; urgency=low + * Internationalization of the GTK GUI + * English / French + * STUN support + * Slight modifications of the graphical interface ( tooltips, dialpad, ...) + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 21 Mar 2008 11:37:53 -0500 diff --git a/tools/build-system/launchpad/sflphone-daemon-video/debian/compat b/tools/build-system/launchpad/sflphone-daemon-video/debian/compat new file mode 100644 index 0000000000..7ed6ff82de --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon-video/debian/compat @@ -0,0 +1 @@ +5 diff --git a/tools/build-system/launchpad/sflphone-daemon-video/debian/control b/tools/build-system/launchpad/sflphone-daemon-video/debian/control new file mode 100644 index 0000000000..a42e722c2b --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon-video/debian/control @@ -0,0 +1,20 @@ +Source: sflphone-daemon-video +Maintainer: SavoirFaireLinux Inc <julien.bonjean@savoirfairelinux.com> +Section: gnome +Priority: optional +Build-Depends: debhelper (>= 7.0.50), libgcc1, autoconf, automake, libpulse-dev, libsamplerate0-dev, libccrtp-dev, libgsm1-dev, libspeex-dev, libtool, libdbus-1-dev, libasound2-dev, libopus-dev, libspeexdsp-dev, libexpat1-dev, libzrtpcpp-dev, libssl-dev, libgnutls-dev, libpcre3-dev, libyaml-cpp-dev, libboost-dev, libdbus-c++-dev, libsndfile1-dev, libavcodec-dev, libavformat-dev, libswscale-dev, libavdevice-dev, libavutil-dev, libudev-dev, libpjproject-dev, libsrtp-dev, libjack-dev, libvpx-dev +Standards-Version: 3.7.3 + +Package: sflphone-daemon-video +Priority: optional +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends}, libavcodec56 (>= 6:11~beta1) | libavcodec-extra-56 (>= 6:11~beta1) | libavcodec54 | libavcodec-extra-54, libavdevice55 (>= 6:11~beta1) | libavdevice53 | libavdevice-extra-53, libavformat56 (>= 6:11~beta1) | libavformat54 | libavformat-extra-54, libswscale3 (>= 6:11~beta1) | libswscale2 | libswscale-extra-2, libavutil54 (>= 6:11~beta1) | libavutil52 | libavutil-extra-52 +Replaces: sflphone, sflphone-common-video +Conflicts: sflphone-common, sflphone-daemon, sflphone-data +Provides: sflphone-common-video +Homepage: http://www.sflphone.org +Description: SIP and IAX2 compatible softphone - Core with video support + SFLphone is meant to be a robust enterprise-class desktop phone. + SFLphone is released under the GNU General Public License. + SFLphone is being developed by the global community, and maintained by + Savoir-faire Linux, a Montreal, Quebec, Canada-based Linux consulting company. diff --git a/tools/build-system/launchpad/sflphone-daemon-video/debian/copyright b/tools/build-system/launchpad/sflphone-daemon-video/debian/copyright new file mode 100644 index 0000000000..90f090efff --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon-video/debian/copyright @@ -0,0 +1,28 @@ +This package was debianized by Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> on +Fri, 3 Apr 2009 09:47:53 -0500. + +It was downloaded from the git repository of SFLphone: git://sflphone.org/git/sflphone.git + +Upstream Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + +Copyright: + +Savoir-Faire Linux Inc. + +License: + +This software is copyright (c) 2004-2011 Savoir-Faire Linux inc. + +You are free to distribute this software under the terms of +the GNU General Public License version 3. +On Debian systems, the complete text of the GNU General Public +License can be found in the file `/usr/share/common-licenses/GPL'. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklyn St, Fifth Floor, Boston, MA 02110-1301, USA. diff --git a/tools/build-system/launchpad/sflphone-daemon-video/debian/cron.d b/tools/build-system/launchpad/sflphone-daemon-video/debian/cron.d new file mode 100644 index 0000000000..d11e611777 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon-video/debian/cron.d @@ -0,0 +1,4 @@ +# +# Regular cron jobs for the sflphone package +# +0 4 * * * root sflphone_maintenance diff --git a/tools/build-system/launchpad/sflphone-daemon-video/debian/dirs b/tools/build-system/launchpad/sflphone-daemon-video/debian/dirs new file mode 100644 index 0000000000..93e7926139 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon-video/debian/dirs @@ -0,0 +1,9 @@ +usr/bin +usr/lib +usr/lib/sflphone +usr/share/applications +usr/share/dbus-1/services +usr/share/sflphone/ringtones +usr/share/locale +usr/share/doc +usr/share/man diff --git a/tools/build-system/launchpad/sflphone-daemon-video/debian/docs b/tools/build-system/launchpad/sflphone-daemon-video/debian/docs new file mode 100644 index 0000000000..0f8394ba76 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon-video/debian/docs @@ -0,0 +1,5 @@ +NEWS +README +TODO +ChangeLog +AUTHORS diff --git a/tools/build-system/launchpad/sflphone-daemon-video/debian/manpages b/tools/build-system/launchpad/sflphone-daemon-video/debian/manpages new file mode 100644 index 0000000000..0b7e5f1c26 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon-video/debian/manpages @@ -0,0 +1 @@ +debian/sflphone-daemon/usr/share/man/man1/sflphoned.1 diff --git a/tools/build-system/launchpad/sflphone-daemon-video/debian/postinst b/tools/build-system/launchpad/sflphone-daemon-video/debian/postinst new file mode 100644 index 0000000000..5ac10f4f25 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon-video/debian/postinst @@ -0,0 +1,56 @@ +#!/bin/bash +# postinst script for sflphone-common +# +# see: dh_installdeb(1) + +# Script to copy and move, if exists, configuration file sflphonedrc and history in the XDG directory +# Freedesktop specifications: http://standards.freedesktop.org/basedir-spec/latest/ + +set -e + +INST_CONFIG="$HOME/.sflphone/sflphonedrc"; +INST_DATA="$HOME/.sflphone/history"; +INST_CACHE="$HOME/.sflphone/sfl.pid"; + +NEW_INST_CONFIG= +NEW_INST_DATA= +NEW_INST_CACHE= + +# Set the XDG CONFIG directory to the default one or to the path set in the environment variable +if [ -z $XDG_CONFIG_HOME ]; then + NEW_INST_CONFIG=$HOME"/.config/sflphone/"; # This is the standard path +else + NEW_INST_CONFIG=$XDG_CONFIG_HOME; +fi; + +# Set the XDG DATA directory to the default one or to the path set in the environment variable +if [ -z $XDG_DATA_HOME ]; then + NEW_INST_DATA=$HOME"/.local/share/sflphone/"; # This is the standard path +else + NEW_INST_DATA=$XDG_DATA_HOME; +fi; + +# Move the configuration file +if [ -f $INST_CONFIG ] ; then + echo "Moving the configuration file into $NEW_INST_CONFIG directory"; + if [ ! -d $NEW_INST_CONFIG ]; then + mkdir $NEW_INST_CONFIG; + fi + mv $INST_CONFIG $NEW_INST_CONFIG; +fi + +# Move the history +if [ -f $INST_DATA ] ; then + echo "Moving the history file into $NEW_INST_DATA directory"; + if [ ! -d $NEW_INST_DATA ]; then + mkdir $NEW_INST_DATA; + fi + mv $INST_DATA $NEW_INST_DATA; +fi + +# Remove the directory +# rmdir $HOME"/.sflphone"; + +echo "You may remove the $HOME/.sflphone, the application won't use it anymore, but the XDG directories instead. Thank you."; + +exit 0 diff --git a/tools/build-system/launchpad/sflphone-daemon-video/debian/postrm b/tools/build-system/launchpad/sflphone-daemon-video/debian/postrm new file mode 100644 index 0000000000..e6107444fa --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon-video/debian/postrm @@ -0,0 +1,34 @@ +#!/bin/sh +# postrm script for sflphone +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * <postrm> `remove' +# * <postrm> `purge' +# * <old-postrm> `upgrade' <new-version> +# * <new-postrm> `failed-upgrade' <old-version> +# * <new-postrm> `abort-install' +# * <new-postrm> `abort-install' <old-version> +# * <new-postrm> `abort-upgrade' <old-version> +# * <disappearer's-postrm> `disappear' <overwriter> +# <overwriter-version> +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +if [ "$1" = "purge" ] +then + + # remove the user config file + rm -f $HOME/.sflphone/sflphonedrc + +fi + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/tools/build-system/launchpad/sflphone-daemon-video/debian/preinst b/tools/build-system/launchpad/sflphone-daemon-video/debian/preinst new file mode 100644 index 0000000000..6d04e97b45 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon-video/debian/preinst @@ -0,0 +1,16 @@ +#!/bin/sh +# postrm script for sflphone +# +# see: dh_installdeb(1) + +set -e + +package=sflphone + +case "$1" in + install|upgrade) + # Clear the old dbus-c++ and iax2 if presents + ;; +esac + +exit 0 diff --git a/tools/build-system/launchpad/sflphone-daemon-video/debian/rules b/tools/build-system/launchpad/sflphone-daemon-video/debian/rules new file mode 100755 index 0000000000..03eb67e044 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon-video/debian/rules @@ -0,0 +1,91 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 +export DH_OPTIONS + +package=sflphone-daemon-video + +CXX = g++-4.0 +CFLAGS = -Wall -g +DEB_INSTALL_MANPAGES_sflphone_daemon_video = sflphoned.1 + +configure: configure-stamp +configure-stamp: + dh_testdir + # Add here commands to configure the package. + # build iax and opendht with contrib since they are not packaged + cd contrib && mkdir -p native && cd native && ../bootstrap && make .iax && make .dht && cd ../.. + ./autogen.sh + ./configure --prefix=/usr + touch configure-stamp + +#Architecture +build: build-arch + +build-arch: build-arch-stamp +build-arch-stamp: configure-stamp + + # Add here commands to compile the arch part of the package. + $(MAKE) + touch $@ + +clean: + dh_testdir + dh_testroot + rm -f build-arch-stamp configure-stamp + # Add here commands to clean up after the build process. + [ ! -f Makefile ] || $(MAKE) distclean + +ifneq "$(wildcard /usr/share/misc/config.sub)" "" + cp -f /usr/share/misc/config.sub config.sub +endif +ifneq "$(wildcard /usr/share/misc/config.guess)" "" + cp -f /usr/share/misc/config.guess config.guess +endif + dh_clean + +install: install-arch + +install-arch: + dh_testdir + dh_testroot + dh_clean -k -s + dh_installdirs -s + # Add here commands to install the arch part of the package into + # debian/tmp. + $(MAKE) DESTDIR=$(CURDIR)/debian/$(package) install + rm -rf $(CURDIR)/debian/$(package)/usr/include + dh_install -s + +binary-common: + dh_testdir + dh_testroot + dh_installchangelogs ChangeLog + dh_installdocs + dh_installexamples +# dh_installman + dh_link + dh_compress + dh_fixperms + dh_makeshlibs + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +# Build architecture dependant packages using the common target. +binary-arch: build-arch install-arch + $(MAKE) -f debian/rules DH_OPTIONS=-s binary-common + +override_dh_strip: + +binary: binary-arch +.PHONY: build clean binary-arch binary install install-arch configure override_dh_strip diff --git a/tools/build-system/launchpad/sflphone-daemon/debian/changelog b/tools/build-system/launchpad/sflphone-daemon/debian/changelog new file mode 100644 index 0000000000..a6cb2908c9 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon/debian/changelog @@ -0,0 +1,3585 @@ +sflphone-daemon (1.1.0-rc20120607~ppa1~SYSTEM) SYSTEM; urgency=low + + ** SNAPSHOT 1.1.0-rc20120607~ppa1~SYSTEM ** + + * * #12071: audiopreferences: fix make check + * * #12071: cleanup + * #12071: Lower expat dependency version + * * #12085: alsa: fix ringtone update bug, cleanup + * #12071: Fix pulseaudio compilation error + * #12071: Make pulseaudio optional at configuration time + * * #12091: daemon: issue warning if falling back to ALSA + * video: fixed make check + * *#12085: alsa: don't segfault on snd_pcm_avail_update error + * #12070: Add --without-pulse option + * * #12055: video: fix build for older libav + * [ #11886 ] cleanup + * [ #11886 ] Add basic reverse peer naming support + * [ #12008 ] Implement GUI part + * * #12012: video: fix some regressions + * * #12002: yamlparser: don't wipe out config if going from normal + build to --enable-video + * [ #12008 ] Add ConfigurationManager::getRingtoneList() + * * #12002: video: fix config file serialization/deserialization + * * #11987: managerimpl: fix bugs in conference when removing + participants + * * #11987: manager: fixed transfer from conference + * * #11987: mainbuffer: cleanup logging + * * #11979: pulse: fixed mismatched device list + * * #11971: audiolayer: fix bugs with getDeviceList + * * #11960: manager: validate conference earlier when processing + participants + * * #11960: manager: fixed segfault on transfer from conference + * * #11966: IP2IP: make alias consistently IP2IP + * * #11965: sipvoiplink: add more error checking in SDP negotiation + * * #11964: mainbuffer/ringbuffer: cleanup API + * sdp: remove unused variable warning + * * #11941: video: fix deprecated libav_api warnings + * * #11949: pulselayer: fix bug in getDeviceList + * video: whitespace fixes + * * #11951: video: fixed threading issues for ucommon Thread + * [#11848] Properly disable testPulseConnect + * [#11848] Disable pulseConnect test + * sdp: cleanup + * sdp: cleanup + * * #11860: mainbuffer: remove dead and/or buggy code + * * #11851: sdp: fixed gcc type narrowing warnings + * * #11851: audiostream: fixed gcc type narrowing warnings + * * #11841: don't put code with side effects in assert() + * * #11840: audiortp: remove some global symbols/variables + * * #11828: audiofile: fix broken build + * * #11828: audioloop: don't shadow sampleRate variable in derived + classes + * * #11818: gnome: stop daemon on SIGTERM, SIGINT or SIGHUP + * * #11499: daemon should also quit gracefully on SIGHUP + * * #11813: daemon configure should fail if expat is not installed + * #10304: updatePlaybackScale dbus method uses int 32 bit for size and + position (allow for 24 days long recording playback) + * #10304: Add time lable for seekslider + * * #11780: sip: don't use abort or leak calls on error and don't + restrict SDP size to 1000 bytes in transaction_request_cb + * * #11735: daemon: added timestamp start to call details + * * #10304: historyitem: added operator > defined in terms of operator + < + * * #11252: historyitem: added missing unistd.h header + * * #11252: daemon: removed deprecated zrtp code + * * #11728: yaml: check that nodes are valid before using them. + * * #10797: send DTMF over RTP as per RFC2833 + * * #11706: managerimpl: added unsetCurrentCall method + * * #11698: daemon: fix build for c++11 + * * #10304: historyitem: file_exists need not be a member method + * * #11499: managerimpl: don't crash if signal and dbus try and finish + the manager at the same time + * #10304: Prevent from storing removed files in history + * * #11499: daemon: Exit cleanly on SIGINT or SIGTERM + * * #10226: audiocodecfactory: use array instead of vector for codec + name lookup + * * #10226: audiocodecfactory: make codec loading stricter + * #10304: RCecale positions and size values for playback recording + * #11530: Make sure that only appropriate configuration option are + parsed for IP2IP calls + * * 11480: video: disabled by default + * * #11459: history: protect historyitems vector with mutex + * * #11448: fix video preferences for empty camera list + * #10304: Implemented playback seek in gnome client + * * #11269: video: fix codec per account management + * #10304: Implemented playback scale in gnome client + * Fix includes for gcc 4.7 + * * #11269: make clearer distinction between codecs and audiocodecs + * * #11269: merged master into video + * * #10296: managerimpl: more usage of getCallFromCallID + * * #10296: verify that calls exists before trying to join them in a + conference + * * #11208: bump version numbers for release 1.1.0 + * managerimpl: rename ManagerImpl::serialize/unserialize -> + join_string/split_string + * Fix warnings in resampler test + * * #10732: sipvoiplink: fix code that validates IP address + * Add historyChanged signal, better than managing it client side + * #10795: fix sipaccount deserialisation broken + * #10736: implement getConferenceId dbus method given a call id + * #10736: do not use iterator in daemon when joining conferences + * #10736: Fix joining conferences in daemon + * * #10736: gnome: fix crash on restart with active conference + * managerimpl: removed unused pulselayer.h header + * Save history everytime it change, prevent the file never to be saved + in some senario (SIG, crash, ASSERT, etc) + * * #10320: manager: check that participants are unique before joining + * #10335: Add a noise suppressor for incoming rtp streams + * * #10322: sip: registration state should not be always set to + ErrorAuth on error + * #10220: Fix recording thread does not exit when hanging up while + recording + * * #9903: fix includes for new ccrtp + * #10230: Get back default mainbuffer sampling rate to 8kHz, no need + of decoding noise suppressor + * #10230: Use a different samplerate converter for rtp encoding and + decoding + * * #9903: create DynamicPayloadFormat on stack, initialize earlier + * * #9903: audiorecorder: initialize buffer to silence, not random + data + * #10230: Test for triangular and sine signals + * #10230: Add resampling unit test + * * #10230: DTMF sample rate should come from main buffer, it should + not be hardcoded + * * #10213: audiolayer: create samplerateconverter on the stack + * * #10213: audiolayer: cleanup + * * #10213: increase resample buffer size, and check output size when + resampling + * * #10095: sipvoiplink: check pointers before using them + * #9981: IP2IP calls based on ip address instead of sip: + * * #10213: speex codecs should initialize their own parameters + * * #10213: account: removed redundant cast + * * #9832: removed extra printf + * * #10172: include -sflphone in recording file name + * * #9832: cleanup logging in tests + * * #10096: srtp: use vectors to simplify key/salt manipulation + * #10096: add case for non-srtp calls + * [ #10121 ] Sync the KDE with daemon, fix a few issues and implement + a recorded call player + * #9980: make keep registration optional as there is different + behavior on different registrar + * #10096: use c++ arrays to store keys in srtp sesssion + * * #10018: renamed registration related keys in dbus + * #10096: Fix onhold/offhold srtp + * * #8586: fixed make distcheck + * * #9832: logger: don't hide logging if NDEBUG is present + * #10096: Reinit crypto context when required on INVITE request + * * #10111: Fixes segfault on empty config file + * #100096: Set in/out queue crypto context at initialization, not when + starting the thread + * #10096: Update srtp key generation when holding/unholding a call + * * #9831: logger: removed extraneous carriage-return character + * * #10095: sipvoiplink: validate pointers before using them + * * #10094: renamed config/config.{h,cpp} config/sfl_config.{h,cpp} + * * #10090: fix segfault in transaction_state_changed_cb + * * #9832: audio_rtp_record_handler: cleanup logging + * * #9832: pulse: cleanup logging + * * #9832: cleanup logging + * * #9832: cleanup logging + * * #9832: dbus: fix logging + * * #9832: config: cleanup logging + * * #9832: remove unused header + * * #9832: manager: fix logging + * * #9832: audio: fix logging + * * #9832: audio: fix logging + * * #9832: zrtp: cleanup logging + * * #9832: AudioZRTPSession: cleanup logging + * * #9832: AudioSRTPSession: fix logging + * * #9832: cleanup logging + * * #9832: AudioRtpSession: cleanup logging + * * #9832: AudioRtpFactory: cleanup logging + * * #9832: codecs: fix logging + * * #9832: alsa: fix logging + * * #9832: audio: clean up logging + * * #9832: AudioRecord: cleanup logging + * * #9832: Fix logging in Manager + * * #9832: new logging macros + * * #9979: ulaw: fixed unused var warning + * * #10039: sipvoiplink: use references to avoid unnecessary parameter + validation + * * #10039: Fixed segfault on failed registration + * * #9979: codecs: fixed unused variable warnings + * * #9979: Don't do runtime assertions on data. + * * #10016: SDP: removed verbose debuggin + * #10016: Crypto context deletion are now managed inside the library + * * #10016: srtp: cleanup + * * #10016: SDES: fix uninitialized value bug, use const char* + * * 100016: don't double free crypto contexts, and don't improperly + copy CryptoSuiteDefinitions + * * #100016: cleanup crypto contexts in audio_srtp_session + * * #9979: removed unused methods from audicodec + * * #9979: ulaw: normalize types + * * #9979: cleanup + * * #9979: Alaw: cleanup + * * #9979: removed duplicate/superfluous code and type issues from + g722 + * * #9979: AudioRtpRecord: let AudioRtpRecord handle fadeIn internally + * #9980: Fix registration timer and transport shutdown on 401, default + registration timer to 3600 + * * #9979: use std::tr1::array instead of plain array for audio + buffers + * * #9969: set loose routing param when creating route set + * #9975: Fix account registration status display + * * #9969: SIP: initialize body earlier + * * #9969: sip: get received and rport fields if present in OK + * * #9968: fixed segfault in transaction callback + * #9898: make sure account are unregistered when sflphone quit, add + timeout on pending transaction + * #9910: fix contact header in outgoing request if via parameter are + present + * * #9910: SIP: use rport from VIA header if present + * * #9910: SipTransport: pass parameters by const reference + * #9910: fix sending call with new transport + * yaml: remove verbose debug messages + * * #9911: sipvoiplink: fixed "unused variable" warning + * #9910: create new udp transport to fix registration failure with 606 + error & received parameter + * * #9910: SIP: use pjsip error codes instead of magic numbers + * * #9911: SIP Transports must be cached by IP:port + * * #9910: SIP: cleanup + * * #9905: SipTransport: address has to stay on stack to be valid + * #9910: Update parse received parameter on 606 registration error + * * #9911: simplify network manager state reporting + * #9902: Fix SIPTest for IP to IP call + * #9911: Fix network manager crashes + * #9902: Move logic for ip2ip call in SIPVoIPLink + * #9902: Move logic for ip2ip call in SIPVoIPLink + * * #9910: fix 606 error code nomenclature + * * #9905: fixed address initialization in createUdpTransport + * * #9903: cleanup + * #9902: Log failure cause when new outgoing call fail + * * #9898: properly initialize ports + * #9898: Unregister account when leaving sflphone + * iax: create iaxvoiplink on stack + * account: removed unused methods + * * #9847: don't use assertions for input coming from DBus + * * #9897: audiorecord cleanup + * * #9897: audiorecord: cleanup, removed unused methods + * #9897: Initialize and fallback recording path in home directory if + not valid + * * #9871: SipTransport: hide more implementation + * * #9871: SipTransport: refactor SIP transport creation + * * #9871: disable STUN for account if STUN setup failed + * * #9847: check pointer before using it + * #9871: Fallback on normal upd transport when stun resolution fails + * Revert "#9871: Fallback on normal upd transport when stun resolution + fails" + * #9871: Fallback on normal upd transport when stun resolution fails + * pulse: cleanup + * * #9847: removed outdated README file + * * #9847: use references instead of pointers where possible + * * #9847: pass call by reference where possible + * * #9847: audiolayer: fixed typo + * * #9847: SIPVoipLink: gracefully handle invalid pointers + * * #9847: check that transport is initialized + * * #9847: SDP: avoid buffer overflow + * * #9847: fixed segfault on bad call invite + * * #9847: SDP: don't use assertions for runtime errors + * * #9847: handle invalid remote session gracefully + * * #9851: fixed segfault on stun socket cleanup + * * #8586: fixed warnings + * * #9849: added missing sstream header + * #9623: add required TLS certificates for testing purpose + * #9623: fixed tls registration + * #9623: fix storing tls port in config for normal account + * #9623: Allow all account to change tls listener port (not only + IP2IP) + * #9623: Allow for changing interface / port for tls transport + * #9623: Open TLS listener on selected interface + * #9833: remove unused debug + * #9833: handlingEvents_ must be initialized to true when starting iax + thread + * #9830: Remove create_route_set from sipvoiplink + * #9831: Fix sip transport port number + * #9830: move sip header parsing function in sip_utils + * Revert "* #8586: don't restore and save test files" + * * #8586: fixed make distcheck + * #9623: fixed tls registration + * * #8586: don't restore and save test files + * * #8586: refactored yaml code + * #9623: fix storing tls port in config for normal account + * #9623: Allow all account to change tls listener port (not only + IP2IP) + * #9623: Allow for changing interface / port for tls transport + * #9623: Open TLS listener on selected interface + * * #8586: added missing tests + * #9833: remove unused debug + * #9833: handlingEvents_ must be initialized to true when starting iax + thread + * #9830: Remove create_route_set from sipvoiplink + * #9831: Fix sip transport port number + * #9830: move sip header parsing function in sip_utils + * * #8977: removed unnecessary AC_CANONICAL macros from configure.ac + * * #8977: use actual PJSIP linking flags from pjproject/build.mak + * * #9774: sipvoiplink's destructor should not be public + * dbus: cleanup + * * #9774: make sure sipvoiplink is destroyed before accounts are + unloaded + * * #9777: don't use deprecated auto_ptr + * * #9778: removed AC_CHECK_FUNCS calls + * * #9782: fix warnings in tests + * * #9782: sip/sdp: fix emptiness checks + * * #9782: sdes_negotiator: fix iterator usage and set dangling + pointers to 0 + * * #9782: initialize all vars in iaxvoiplink + * * #9782: use fstreams instead of fscanf + * * #9782: yamlnode: fixed iterator usage + * * #9782: yamlemitter: fix iterator usage + * * #9782: yamlnode: make some methods const + * * #9782: initialize all member vars in constructor + * * #9782: Tone::interpolate should be const + * * #9782: mainbuffer: get rid of unused vars + * * #9782: GainControl::limit should be const + * * #9782: fix ARRAYSIZE check + * * #9782: use nanosleep instead of usleep + * * #9782: fixed "inefficient emptiness test" cppcheck warning + * * #9782: initialize dcblockers vars in constructor + * * #9779: dropped CELT support + * * #9750: moved sfl_data_format.h -> sfl_types.h + * * #9750: refactored global.h + * * #9736: restored command line options to daemon + * tests: cleanup + * * #8586: make distcheck was missing a header + * tests: cleanup + * * #9731: use all caps for application-wide constants + * tests: cleanup + * * #9730: cc++: enforce better checks in headers + * * #9730: builds against libccrtp1 + * * #9572: sipvoiplink: fixed typo + * * #9572: fixed threading issues with ccrtp2 + * * #9572: manager: pass config filename by const reference + * * #9572: Replace utilspp singleton implementation + * * #9571: regenerated config.{guess,sub} file to fix FTBFS on + armel/armhf. + * * #9665: siptransport: fixed udp_transport_start calls + * #9620 Add test SIP account in configuration sample (test/sflphoned- + sample.yml) + * * #9641: audiortp: Fixed CryptoContext management + * * #9641: fixed another memory leak in audio_srtp_session + * * #9641: audiosrtpsession: fixed memory leak, simplified memory + management + * * #9641: avoid dynamic memory allocs/raw pointer usage in audio rtp + stack + * * #9641: get rid of getType/RtpMethod logic + * fixed typo + * #9572: make sflphone compile with libccrtp 2 + * * #9490: fixed registration state change callback that was crashing + client + * * #9547: fixed warnings in SipTransport header + * #9547: Add SipTransport class + * #9547: Extract all the transport layer from SIPVoIPLink to new + SipTransport Class + * #9547: Destroy the STUN resolver in Transport shutdown + * sipvoiplink: removed erroneous FIXME + * sipvoiplink: cleanup + * #9547: Destroy the STUN resolver if server name change + * sipvoiplink: fix warning about variable shadowing + * #8320: Rename declared exception to avoid parameter shadowing + * #8320: Send signal to client on stun failure + * #8320: Use the same API for all transport creation (UDP, STUN, TLS) + * * #9509: use vector for credential info + * * #9508: fixes segfault in manager by changing order in which + destructors are called + * #8320: add dbus signal for stun failure + * #8320: Use two different variables for status and return statement + in stun's on_status_cb + * * #9490: removed resolve_once parameter that was causing a segfault + * #8320: make the retransmission callback to be rescheduled on error + * HookPreference: cleanup + * daemon: hookpreference: cleanup + * iaxvoiplink: terminate() doesn't have to be virtual + * sipvoiplink: functions need not be static if they are in an + anonymous namespace + * * #9037: moved CHECK macro into separate header + * * #9037: cleanup error handling/checking in video threads + * * #9037: video: cleanup + * * #9037: only signal receiving_video_event for rtp sessions + * * #9037: shared memory moved out of video_receive_thread + * * #9381: daemon: fixed make check for video + * * #9381: YAML_LIBS must be explicitly set in AC_SEARCH_LIBS macro + * * #9381: reverted yaml check + * * #9381: fix celt plugin compilation on fedora + * * #9381: use PKG_CHECK_MODULES to test for yaml + * * #9381: use autoconf macros and AC_SEARCH_LIBS + * * #9381: use AC_SEARCH_LIBS, AC_CHECK_LIB + * ringtonetest: cleanup + * configurationtest: cleanup + * instantmessagingtest.cpp: cleanup + * mainbuffertest: cleanup + * tests: cleanup + * #8320: Make sure stun keep alive is enabled + * call: push answer logic into call classes + * sipaccount: simplify IP2IP code + * sipaccount: avoid segfault if sipaccount is NULL + * sipaccount: cleanup + * #8084: Fix get sip header segfault when stun transport selected + * * #9037: created shared_memory class + * #8084: Init stun port with default valueas defined by RFC 3489 + * #9046: Move IP2IP_PROFILE global definition inside SIPAccount class + * #9045: fix Changing the account expire is not taken applied in + daemon + * vidoe_receive_thread: cleanup + * * #8968: suppress unusedFunction warnings for functions that are + actually used + * * #8821: fixed unit tests + * #8821: Renamed account map keys for consistency + * * #8968: audiorecord: added debug, clarified wave header creation + * * #8968: added debug message to get rid of "unused struct member" + warning + * * #8968: manager: create History on the stack + * * #9026: sfl::InstantMessaging is now a namespace + * * #8698: managerimpl: removed unused method isWaitingCall + * * #9008: don't include yaml headers in serializable.h + * * #9008: cleanup account map initialization + * refactor accountmap initialization + * #9020: fix config file not generated when no account created + * * #8968: audiorecord: removed unused getSndSamplingRate + * * #8968: config: removed getConfigTreeItemIntValue + * * #8968: recordable: removed unused getRecFileId + * * #8968: removed unused Codec::getMimeType method + * * #8968: manage lifetime of IMModule with auto_ptr + * * #8968: history: removed unused method + * * #8968: managerimpl: removed unused method + * * #8968: config: removed unused methods + * * #8968: managerimpl: removed unused getConfigBool/Int methods + * * #8968: networkmanager: cleanup + * * #8968: managerimpl: removed unused getConfig + * * #8968: managerimpl: Manage telephoneTone_ with auto_ptr. + * * #8968: history: fix memory leak upon exception + * * #8968: AudioFile: initialize filepath earlier + * * #8968: audiofile: fix memory leak on exception + * * #8968: audiocodec: removed unused getChannel method + * * #8968: use auto_ptr for dtmfKey + * * #8968: use vector instead of dynamically allocated int array + * * #8968: sdp.h: pass paramter by reference + * * #8968: sipvoiplink: avoid C-style pointer casting + * * #8968: yaml: avoid C-style pointer casts + * * #8968: pulselayer: avoid C-style pointer casting + * * #8968: recordable: removed unused getRecordingSmplRate method + * * #8968: alsalayer: use preincrement for iterators + * * #8968: config: removed unused method saveConfigTree + * * #8968: mainbuffer: preincrement iterators + * * #8977: history: added #include <fstream> + * * #8968: don't leak memory on exception + * * #8968: pulselayer: avoid C-style pointer casting + * * #8968: Member variables must be initialized in AudioSrtpSession + constructor + * * #8968: fix potential memory leak in audiorecord + * * #8968: Pass function parameter 'item' by const reference. + * * #8969: fixed memory leaks in sdes_negotiator + * video: cleanup + * * #8940: removed video test source for now + * #8763 Fix doxygen generation + * #8763 Generate Doxygen with Hudson + * * #8940: videosendthread: cleanup + * sipvoiplink: cleanup + * fileutils: cleanup + * #8335 Fix default transport initialization on 5062, 5064 + * #8762: update mute for mic only, fix remove slide for pulseaudio + * #8672: Add linear to decibel conversion functions in audio layer + * #8672: Implement audio gain management in pulseaudio + * #8671: Move audio gain management in audiolayer + * * #8542: create symbolic link properly + * * #8613: make check should fail early if another sflphone is running + * #8449: Update version 1.0.2 + * * #8545: fixed error case + * * #8545: fixed broken ringtone + * * #8586: fixed make dist + * sipvoiplink: use static_cast instead of reinterpret_cast if possible + * * #8542: test for .git existence before moving pre-commit hook + * * #8542: autogen.sh should not require git + * eventthread: cleanup + * * #8542: removed trailing whitespace from tree + * * #8357: added disable video option to client + * siptest: cleanup + * * #8521: use avcodec_open2 instead of deprecated avcodec_open, if + available + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Thu, 07 Jun 2012 16:08:15 -0400 + +sflphone-daemon (1.0.0-rc20110930~ppa1~SYSTEM) SYSTEM; urgency=low + + ** SNAPSHOT 1.0.0-rc20110930~ppa1~SYSTEM ** + + * update kde .gitignore + * Fix bug in volume widget + * More polishing for release + * Bump version to 1.0.0 + * [#7023] Add the ability to load an abstract contact backend in the + library to resolve more data, polish code + * [#7021] More cleanup for release + * Cleanup + * [#7021] Refactor KDE client dbus handling, add a missing call in + daemon and port the DataEngine to the new API + * Remove some annoying debug + * merge language scripts + * remove obsolete 'VERSION' files + * update install instructions + * Add missing translations to gnome + * language update + * Revert "Don't reference count DBus clients, exit core immediately + when one of them request it" + * Don't reference count DBus clients, exit core immediately when one + of them request it + * [7021] Add contact abstraction support + * [#7121] Polishing library (over). Indentation, spacing and naming + are now consistent + * codecs: link to libccrtp, don't use logger + * Fix a daemon bug + * [#7038] Fix adding contact + * * #7037 : stop audio stream after all calls have been hanged up + * [#7025] Add full support for bookmark + * SFLPhone KDE do not destroy history anymore + * Fix config skeleton + * Close the daemon once and for all, no more automatic respawning + * Fix "unregistered account" bug (I hope so) + * Close SFLPhone at the right place, it still respawn, I don't know + why + * Remove dead code + * Fix regressions introduced in the last commit + * Dead code elimination 1/3 + * Fix bug, add "add contact" option, fix warning + * * #7019: Fix IAX codec negociation + * Remove or comment unnecessary/unhelpful debug output + * Fix "same as local" account setting, fix IP2IP LED color + * Add support for some more advanced config options and add missing + config dialog icons + * Fix crash with noise suppressor + * Alternative can now be selected from the call view context menu + * Add drag and drop support, initial context menu and fix 3 bugs in + the account dialog + * Add basic history drag and drop support + * Complete contact support is back + * * #6991 : fix IAX problems + * Fix IAX accounts being disabled by default + * Revert "deb: forge -g flags for pjsip" + * * #5884: Disable debug code in pjsip + * echo suppressor : more assertions + * Don't let the daemon think crypto is enabled when it's not + * Simplify ToneList + * Some progress on contact support + * Remove unused getRegistrationCount() + * remove annoying debug + * revert SIP bit of e27e5c39bad27bae28f574eb2cba7717e8956229 + * Simplify CallManager::placeCallFirstAccount + * Fix crash on hold + * * #6905 : SIP refactor + * gnome client: be sure key exchange is set correctly + * Move code into createSipTransport + * Fix account registration on start + * ManagerImpl::registerAccounts(): simplify + * * #5884: don't mess with pjsip threads in echo suppressor + * * #6905 : simplify udp/stun/tls pjsip transport creation + * Restore and improve support for Call history + * fix launchpad build + * SIPVoIPLink: simplify / refactor + * Fix libwidget linking + * SIP: simplify + * IM : simplify + * gnome: remove some debug + * AudioRtpFactory::stop() cannot fail + * * #6905: simplify SIP code + * pjlib: fix build without SSLv2, fix warnings + * Port history to the new syntax + * Test a dock widget based implementation for contact and history + * Disable SSLv2 support from pjsip and sflphone + * deb: forge -g flags for pjsip + * Fix deb packaging to get debug symbols + * remove debug + * pjproject: update to last stable release (1.10) + * Require gtk >= 2.20 and glib >= 2.24 + * tlsadvanceddialog: simplify + * * #6902 : fix errors spotted by -DGSEAL_ENABLE + * Update daemon dbus XML and port KDE config backend from dbus to + local + * Remove unused but set variables + * * #6929 : fix IM widget, cleanup + * Unconditionally enable debug symbols + * Should fix many KDE issues + * * #6886 : hitting backspace on empty number have no side effects + * * #6905 : fix AudioCodecFactory access in optimized builds (-O > 0) + * Remove unsupported and broken jaunty/karmic packages + * * #6902 : avoid using some gtk deprecated functions + * Update dbus introspection files + * * #6904: removed unused contactmanager + * * #6903 : use correct dbus-cxx package name + * * #6902: don't use individual gtk headers + * Fix a segfault when config is not present + * Merge latest (0.9.13) KDE code. This version is not yet ready for + git master, but better than the previous one + * addressbook : simplify + * * #5659 : sflphone-plugins doesn't depend on libedataserverui + * * #5659 : addressbook doesn't use libedataserverui + * gnome client doesn't depend on evolution + * * #5695: addressbook: simplify + * * #5695: addressbook : remove AddrBookHandle from plugin + * * #5695 : addressbook : remove unused stuff in the client + * * #5695 : addressbook : remove unused stuff, use static mutex + * gnome client doesn't use evolution + * gnome: use proper API to set GTK_CAN_FOCUS + * * #6897: removed unused focus state vars/callbacks + * gnome: fix calls to sflphone_fill_codec_list_per_account + * * #6623: gnome: don't leak in mainwindow + * gnome: mainwindow whitespace cleanup + * gnome: actions.c parameter doesn't have to be a double pointer + * * #6895: fix memleaks, cleanup in accountconfigdialog + * * #6893: fixes segfault in client on clean history + * * #6894: fix leaks, cleanup in sflnotify + * daemon: fixed prints in main + * * #6892: simplify, fix leaks in dialpad + * * #6887: audiopreference creates audio layer + * * #6660: use const char * const, not std::string for globally + visible constants + * * #6852: Preferences now solely responsible for audiolayer creation. + * * #6860: refactor uimanager, also fixes #6865 + * * #6853: hangup as soon as all digits have been deleted + * * #6852: alsa: retry if device is busy + * * #6852: audiolayer creation depends only on preference.audioApi + * * #6850: gnome: fix build for gtk < 2.22.0 + * cleanup in iax + * alsa: typo + * pulse: if we can't peek in audio input, we can't drop samples + * * #6849: show error window if codecs are missing, instead of dying + * EchoCancel: unused, remove + * * #6629 : use number of samples as arguments for audio filters + * * #6629 : remove unused Algorithm interface + * * #6629 : use helper to call alsa functions and display error msgs + * Remove unused type + * * #6841: fix some error handling + * * #6629: simplify AlsaLayer::alsa_set_params() + * Get gdk key definition from header + * * #6828: Replace raw key codes by gdk defines + * remove some debug, enhance some other + * mainbuffer: simplify + * * #6561 : fix phantom call after transfer + * Conference Participant set : simplify + * SIPCall: remove unused functions, make invite session public + * * #6229 : remove malloc/free from pulse audio loop + * * #6629 : simplify pulse callbacks + * * #6629 + * Simplify widgets + * * #6629 : keep the correct audio module when frequency changes + * * #6751: fixed erroneous debug msgs + * callable_obj.h: removed unneeded pthread header + * alsalayer: cleanup + * * #6629: Always restart audio driver when changing parameters (ALSA + only) + * gnome GUI: don't block in DBus signal errorAlert() + * * #6629 : simplify AudioLayer creation + * * #6629 : remove unused and unconfigurable frameSize from audiolayer + * * #6629 : remove unused error message from audio layer + * Fix logic error when switching audio API + * Remove unused AudioProcessing class + * AudioRtpRecordHandler::initNoiseSuppress() : use noiseSuppress + directly + * * #6629 : use DC blocker directly in audio layers + * * #6629 : clean AudioLayer + * * #6629 : don't store mainbuffer inside audiolayer + * * #6629 : correct AudioLayer::notifyincomingCall() + * * #6554: cleanup, refactoring in sipvoiplink + * * #6554: cleanup in iaxvoiplink + * * #6554: throw exception in getSIPCall if pointer is NULL + * * #6554: make some methods of sipvoiplink static + * * #6655: cleanup in managerimpl + * * #6554: refactoring, fix memleaks in sipvoiplink + * * #6478: remove throw specs, cleanup in voiplink + * * #6629 : remove unused AudioDevice + * * #6655: removed more dependencies from managerimpl + * * #6744: simplified numbercleaner + * conference : remove one prototype + * * #6743: fix ip2ip + * Don't give glib warnings if icons are not found + * gnome: fixed includes + * Codec.h: removed unused function + * * #6742 : clean dbus & icons + * * #6699: refactor/cleanup accounts + * icons: cleanup + * timer : use second precision, not millisecond + * calltree_update_clock : use correct type, returns something + * * #6737: fixed typo in dbus call + * * #6737: removed tests for removed API + * * #6737: dbus: fixed bug from merge + * * #6737: cleanup in accountlist + * * #6737: cleanup in dbus + * * #6740 : fix history double free + * * #6740 : remove time updating thread from calls + * * #6737 : use c99 for client + * * #6738 : make history loading faster + * sipvoiplink : don't crash on transfers + * fixed typo + * Remove unused file + * Don't build networkmanager.cpp at all if NM is disabled + * _debug* -> _debug + * * #6554 : simplify sipvoiplink + * hudson: added -x to git clean command + * added git clean to hudson script + * audiocodecfactory: cleanup + * * #6718: refactored setTlsSettings into SIPAccount + * * #6718: removed more unused methods + * * #6718: refactored confmanager code into sipaccount + * remove unused functions + * * #6718: confmanager: removed more unused methods + * AudioCodecFactory : cleanup + * #6697 : Turn callableElement struct into union + * * #6718: confmanager: removed more unused methods + * * #6718: confmanager: removed more unused methods + * * #6718: removed unused dbus methods, refactoring + * * #6699: accounts: cleanup/refactoring + * * #6699: refactoring, cleanup in accounts + * * #6699: more account cleanup + * remove unused autoconf variable + * * #6714: fixed hudson script + * make distclean in hudson + * added || exit 1 to run_tests.sh call + * * #6714: fixed make distcheck for sflphone-plugins + * * #6714: fixed make distcheck for gnome client + * * #6714: fixed make distcheck for daemon + * git: #6698 split the main .gitignore file + * gnome: gpointer is already a pointer + * gnome: calltab_init: use calloc instead of malloc + * * #6699: more account cleanup + * * #6699: cleanup account + * * #6554 : more *voiplink cleanup + * * #6558 : more sipvoiplink simplification + * * #6558: saner loadSIPLocalIP prototype + * gnome: #6623 clean calllists + * * #6692: more audiolayer cleanup + * * #6692: cleanup/refactoring in audiolayers + * * #6692: more forward declarations, AudioThread->AlsaThread + * * #6692: audiolayer cleanup + * * #6692: alsalayer cleanup + * * #6558 : remove account creator + * * #6558 : clean sipvoiplink + * * #6554 : cleanup sipvoiplink + * audiortp: cleanup + * * #6657 : fix launchpad builds for good + * * #6675 : send RTP dtmf events only once + * * #6655: more cleanup + * AudioRtpSession::updateSessionMedia() : simplify + * * #6655: more cleanup in managerimpl + * * #6655: removed more code, cleanup + * * #6655: more cleanup, fixed infinite loop + * * #6655: removed more unused files + * * #6655: removed unused mutex + * * #6655 removed more unused code + * * #6655: removed unused methods + * * #6655: cleanup in main + * * #6663: fixed segfault when off hold from transfer + * * #6658: user's active codec selection is respected + * * #6660: static global string should be static const char* const + class member + * * #6659: use g_strcmp0, not strcmp for vals that may be null + * callable_obj: fix double free + * calltree_display_call_info() : simplify + * * #6657: Fix launchpad builds + * Logger::log() : simplify + * AudioRtpSession : privatize members + * * #6655: more constness, cleaned up/simplified methods + * * #6654: call DBus::_init_threading so that dbus-c++ to make it + threadaware + * set default credentials on account creation + * AudioCodecFactory::scanCodecDirectory() : simplify and correct + * * #6623: fixed typos + * * #6623: fixed more leaks + * * #6623: fixed more leaks + * * #6623: fixed more leaks, don't print codec name if null + * * #6623: more leaks fixed in client + * * #6623: fix more leaks, fixed some warnings + * * #6623: fixed leak in history + * updated gitignore + * initialize dbus dispatcher correctly + * Fix tests, hudson doesn't have a dbus daemon running + * remove unused code + * removeCall() : simplify , fix leak + * stopRtpThread() : simplify + * *CurrentCall : simplify + * Fix memleak + * fix serialization of audio api (pulse / alsa) + * account map : simplify + * remove call from callmap before terminating it, avoid use after free + * * #6630 : don't make DBusManager a singleton + * call: return confID by value + * add back history code deleted by error + * history : reverse logic + * simplify history serialization and remove some debug + * remove annoying debug + * * #6464 : replace cerr with _error + * * #6464: replace cout with logger macros + * replace printf() with logger macros + * update .gitignore + * remove unused function + * update eclipse projects + * uimanager_new() : simplify + * rename directories + * celt: simplify a bit + * Fix CELT configure.ac test + * * #6612 : template speex codecs + * * #6623: refactored conference obj + * * #6623: refactored callable object, removed leaks + * * #6623: more cleanup, fix leaks, make global vars static and rename + them + * * #6623: calltree: fixed memleaks, simplified code. + * audiolayer: init pointer members + * manager: catch exception on invalid hangup + * * #6623: don't leak on calls to create_new_call + * * #6611 : clarify codecs prototypes + * ringtones : .au and .ul files are both ulaw + * * #6611 : make sure samplerate converters are called correctly + * ManagerImpl::switchAudioManager() : simplify + * * #6623: fixed more leaks + * * #6623: fixed more leaks + * * #6623: fixed more leaks + * * #6623: fixed leak, line-endings in imwidget + * * #6627: zero-initialize pointers if they're going to be deleted + * * #6628: don't leak calls on exceptions + * Revert "audiortp: call join after calling stop on RtpThread" + * sflphone-client: more constness + * audiortp: call join after calling stop on RtpThread + * * #6625: return 0 on successful completion + * * #6624: fix segfault on servercallfailure + * * #6621: Fixed double free, unlock mutex in ManagerImpl::terminate + * * #6220: remove audio stream when peer hangs up + * * #6596: AudioSymmetricSession shouldn't self-delete + * resampler: grow internal buffers dynamically + * merge up and down sampling => resampling + * Leave test directory unchanged when running make check + * audio algorithms : remove unused prototype + * ringtone: detect codec from file extension + * *AudioFile : simplify + * * #6596: create local SDP on the stack, not the heap + * * #6596: don't call Ost::Thread::terminate from dtor + * audiofile: cleanup (samplerate -> unsigned) + * remove unused func + * samplerateconverter: cleanup + * RingBuffer::Put() : remove unused return value + * MainBuffer::putData() : remove unused return argument + * audiolayer::putMain() : remove unused func + * AudioLayer::putUrgent() : remove unused return value + * * #6618: delete any remaining ringbuffers in destructor + * RingBuffer::availForPut() : remove + * * #6617: return from main rather than calling exit + * MainBuffer::availForPut(): remove + * RingBuffer: simplify + * alsa : remove write only variable + * fix memcpy declaration + * bcopy(src, dst) -> memcpy(dst, src) + * RingBuffer::Get() : remove constant volume argument + * return a copy of the call ID, not just a reference. + * MainBuffer::getDataById() : remove volume argument (always 100) + * MainBuffer::getData() : remove constant volume argument + * RingBuffer::Put() : remove constant volume argument + * MainBuffer::putData() : remove constant (=100) volume argument + * audiolayer: remove constant _defaultvolume + * AudioRtpRecordHandler / AudioRtpSession : simplify + * mainbuffer: fix test + * iaxvoiplink : simplify + * sip registration callback: fix a dbus crash + * MainBuffer: simplify + * AudioRtpFactory: return cached type of rtp session. The rtp session + can have disappeared if the call was put on hold + * AudioRtpFactory: remove unused setters + * Fix launchpad builds + * * #6611 : remove unused bandwidth codec information + * * #6611: AudioCodec: remove useless/unused setters + * make sure buffer string is initialized correctly + * * #6596: declare certain destructors virtual + * audiolayer : cleanup + * Simplify doc build rules + * * #6270: don't build dbus-api doc with make, should require make all + * configure.ac: cleanup + * Remove copy of dbus-c++ from libs/ + * * #6596: stop clock thread when peer hangs up + * removed unused Fmtp.h + * * #6595: more logical initialization order + * * #6600 : fix account creation + * * #6601 : fix configure.ac tests + * remove unused variable + * Don't mix stack and heap based allocations + * Fix copyright (2009, 2008, 2009 -> 2008, 2009) + * Fix warnings found by clang + * * #6595: fix initialization order for AudioRTP + * * #6592: removed typedef std::string CallID + * * #6586: implement local g_slist_free_full for older glib versions + * * #6579: fix memory leaks in client (there's a lot left) + * ShortcutPreferences::setShortcuts() : simplify + * Fix merge + * * #6548: remove call to non thread-safe strerror() + * AudioRtpFactory: each instance is associated to exactly one SipCall + * create_audiocodecs_configuration() : make static + * * #6269 : refactor AudioRtpSession + * Fix AudioSymmetricRtpSession.h inclusion guard (cherry picked from + commit c3081dce1cc1370d6d3558a4c4ef5cfac0d21caf) + * * #6269: Rename AudioRtpSession to AudioSymmetricRtpSession + * * #6574: Don't exit when connection to pulseaudio server fails + * accountconfigdialog.h : remove some stuff from header + * * #6560: fix configuration test + * Fix warning in test + * * #6560: don't hide password entry in security tab + * * #6560: set initial password for SIP accounts + * * #6506: remove useless pointer indirection + * * 6560: password is now specific to IAX accounts + * * #6560 : actually use, store, restore, transmit SIP credentials + * * #6560: YamlEmitter: serialize sequences + * YamlEmitterException: typo + * ManagerImpl::computeMd5HashFromCredential() : simplify, fix memleak + * * #6561: invite_session_state_changed_cb() : simplify + * * #6561: More useful debug in VoIPLink::removeCall + * * #6561 : fix ghost call reappearing in GUI after transfer + * while -> for (make the code smaller) + * * #6558 : Account::loadConfig() : move IAX code to IAXAccount + * IAXVoIPLink::getAccountPtr : simplify + * * #6554 : access the SIPVoIPLink directly, not per account + * SIPVoIPLink is instanciated only once and is not associated to a + single account + * yamlnode: use const references when possible (still some left to do) + * Account::_accountID: constify + * VoIPLink: simplify, remove unused method + * hudson test : no need to call run_tests.sh anymore + * Remove AccountID type and AccountNULL define + * Make check runs the test (no need to call run_tests.sh manually + anymore) + * gnome GUI: Fix tests + * Revert "Move registration information from SIPAccount to + SIPVoIPLink" + * * #6392: pluginmanagertest: fix warnings reported by valgrind + * * #6547 : remove unused exceptions + * * #6547: CallManagerException: use runtime exceptions + * * #6547: InstantMessageException: use runtime exceptions + * * #6547: do not throw exceptions if some settings are not present in + config file + * * #6547: YamlParserException: use runtime exceptions + * * #6547: VoipLinkException: use runtime exceptions + * * #6547: YamlEmitterException: use runtime exceptions + * * #6547: DTMFException: use runtime exceptions + * * #6547: AudioFile: use runtime exceptions + * * 6547: AudioZRtpSession: remove impossible error case + * * #6547 : AudioRtpSession: remove impossible error case + * * #6547: AudioZrtp: use runtime exceptions + * * #6408 : send authenticationUsername to GUI + * * #6408 : store/restore authenticationUsername from config file + * SIPAccount: simplify + * Move registration information from SIPAccount to SIPVoIPLink + * SIPAccount::getAccountDetails : simplify + * * #6540: yaml parser: simplify + * sdp.cpp : fix a warning + * * #6540: yaml parser : remove std::string typedefs + * * #6540: Simplify yaml unserialization + * * #6540 : add a Conf::ScalarNode constructor for booleans + * setAccountDetails(): simplify + * * #6408: store authentication username in daemon + * * #6408: Be able to set the authentication username in the GUI + * * #6507 : do not crash if the program is not sflphoned + * Fix tests + * macroify SIPAccount::unserialize() + * Move all .cpp files from sflphoned target to libsflphone.la, except + main.c + * main() : simplify, return positive error codes + * * #6507 : find codecs dir in build directory + * * #6392: Sdp: move clean functions to destructor + * AlsaLayer::adjustVolume() : simplify + * alsalayer : reduce indentation + * malloc/free -> new/delete + * malloc/free -> new[]/delete[] + * malloc/free -> new/delete + * AudioSrtpSession: simplify base64 encoding + * * #6392: Initialize std::string from pj_str_t correctly + * * #6392: AudioRtpSession: Initialize remote port + * Audio settings : Initialize _echoCancelTailLength and + _echoCancelDelay(0) + * Initialize variable + * YamlParserException : fix use of stack variable after it has been + deallocated + * * #6392: fix memory leak in history + * * #6392 AudioCodec : fix memory leak + * * #6392 : fix memory leak in sip account + * * #6408: clean up sipaccount (cosmetics mostly) + * sipaccount.cpp serialize() : reduce number of lines + * * #6392: invalid memory access + * * #6392 : fix invalid memory access + * * #6479: merged useful code from MimeParameters into Codec interface + * * #6462: fixed hangup on IP2IP call + * added run_daemon.sh script + * test: remove unused variable + * Remove functions only used by a failing test (cherry picked from + commit fcf718cb75de7f1882dc61c07bb8d300dfa10f85) + * * #6360 : make client tests build (cherry picked from commit + 028b2835f040e51ab8ab979b32732b07b8798fce) + * * #6360 : fix warnings in check_global test (cherry picked from + commit 9e2bd6a7496dd64f6f48595e385760019aab1193) + * * 6360: updated API calls in tests, but they're not building yet + (cherry picked from commit 548f6f0f919b43772a3e9c667e5e292791281795) + * Fixed include in tests (cherry picked from commit + aeadc7525c1e31f936670ac8b02f0bcf387c38a8) + * Remove unused variables and functions + * IAX: fix warnings (cherry picked from commit + fd7a113a11cac2cd9a7c36929e88ad28195c4c35) + * Remove unused DEBUG define which interferes with logger.h (cherry + picked from commit b2f72b91d0f43cb1dd94d138882a8caa9c841c24) + * * #6392: no need to check for account NULLity since it is + dereferenced above + * * #6392: fix a memory leak, replace by stack allocation + * * #6392: remove a variable assignement which confuses cppcheck + * process_conference_participant_from_serialized() : remove unused + function + * * #6392: s/free/g_free/ + * * #6392: fix a memory leak in abookfactory_load_module() + * * #6392: remove generate_call_id() used only once + * * #6392: fix memory leak (opendir() without closedir()) + * * #6392: AudioRecorder(): ensures mbuffer is set + * Remove SFLPHONED_VERSION from global.h, use autoconf PACKAGE_VERSION + * #6298: Cleanup + * #6331: Fix deleting ringtone file after call have been answered + * * #6330: merged user_cfg into headers + * #6298: Fix conference recording file update at conference end + * #6298: Fix record file name serialization for conference + * * #6295: cleanup of codec hierarchy + * #6298: Fix gtk warnings + * * #6300: added script to run tests + * #6109: Add recording playback for conference + * * #6300: tests do not require an installed sflphone + * * #6295: re-removed clone methods + * #6109: Fix gtk_critical warnings for incoming calls + * #6109: Fix GTK_CRITICAL warning + * #6109: Fix icons when history is not activated + * #6109: Fix warnings + * #6109: Implement stop recorded file playback signal + * Revert "* #6295: removed unused clone method" + * * #6295: removed unused clone method + * * #6296: removed non existant file from Makefile.am + * #6109: Stop fileplayback for outgoing call + * #6109: Implement stop recording playback button + * Fix binding names errors in dbus introspection file + * #6109: Implement playback recorded file callback in client + * #6109: Store recorded file path on client side + * #6109: Add dbus methods for call recording playback + * * #6290: remove unused classes from utilspp + * * #6288: cleanup sdp + * * #6288: fix exception usage + * * #6288: simplify SdpException + * * #6288: cleanup in sdp.cpp/h + * #6109: Only display playback button if record file is set and valid + * * 6290: updated configure.ac to remove functor Makefile + * * #6290, #6289: removed unused classes from utilspp, fixed make + check + * #6109: Add button for history playback of recorded file + * * #6289: removed unused observer class + * * #6282: forward declare sdpMedia in sdp.h + * * #6281: renamed setCallAudioLocal->setCallMediaLocal + * #6183: Handle conference with more tahn two calls + * #6183: Fix history icons when calling back a conference from history + * #6183: Fix icons inconsistencies in history for conference hang up + * #6183: Fix toolbar actions when selecting a conference in history + * #6183: Fix conference serialization + * #6268: Serialize only calls + * * #6269: removed useless type testing + * ignore some files in test/ + * * #6268: Remove dead class AudioSymmetricRtpSession + * #6251: Do not had history calls in calllist when loading history + file + * #6251: Fix insertion in history map in before saving history file in + daemon + * #6251: Fix history unit tests + * #6251: Order the list before serailization, get rid of the hashtable + in history + * #6251: Implement history serialization using a list wether than a + map + * * #6253: remove external audioport from header, make all members + private + * * #6253: don't store external local audio port (used for NAT) in + Call + * #6251: Add start_time timestamp in history serialization + * #6251: Fix call insertion in conference items + * #6233: Fix serialized account list terminated with a ";" character + * #6238: Fix draggable history calls into current calls + * #6233: Fix toolbar updates + * #6233: Fix history + * * #6235: remove pyc files from git tree + * #6233: Handle cases when one or manuy calls are unreachable in + createConfFomrParticipantList + * #6233: Handle wrong numbers in createConferenceFromParticipantList + * #6231: Fix drag-n-drop issue + * * #6173 : move sippxml in tools + * #6231: Fix merging issue + * #6183: Implement conference unserialize + * * #6212: remove extraneous flags from globals.mak + * #6183: Unserialize conference data in conference + * #6183: Add account information in request for conference call from + history + * #5755: Add -ldl to liker in sflphone-client-gnome + * #5755: Fix fedora 15 compilation issue + * #6183: Serialize conference participant phone number and account + * #6183: Add conference timestamp in serialization + * * #6186: don't include global.h, just logger.h + * #6183: Fix saving history to file + * #6183: Fix removing call from calllist + * * #6184: remove pointers to Manager from AudioRtpSessions + * #6183: Calling calltree_add_call explicitely for history + * #6183: Ability to store conference inside history tab queue + * * 6181: remove unused API from sipcall + * #6171: Implment nreCallCreated callback + * #6167: Fix participant list NULL ending + * #6149: First draft of conference creation from history + * #6149: Fix multiple call/conf selection callbacks ... + * #6129: Fix place_call function called twice for pressing enter + action + * #6129: Fix double click action for history + * #6149: Add dbus call for creating conference from history + * #6129: Fix placing call from history and addressbook (still need to + fix icon) + * * #6148: removed unused AudioRtpFactory constructor + * * #6145: remove unused isAudioStarted + * * #6145: remove unused isAudioStarted + * #6129: Add conference into history, fix call/conference selection + * * #6143: don't use getType outside of serialization methods + * * #6132: forward declarations instead of includes + * * #6132: add constness, remove redundant "inline" keywords + * #6129: Add timestamp to conference object to order history entries + * * #6128: remove unused forward declarations from header + * * #6127: make noncopyable class actually noncopyable + * * #6125: don't include AudioRtpFactory in sipcall.h + * #6123: Fix alsa ringback audio file + * #6123: Fix raw audio file loading problem + * #6109: Fix daemon plugin manager unit test + * #6109: Fix history manager unit tests + * #6109: Recording filename in daemon and client for history items + + serialization + * #6109: Refactor AudioFile to play recorded call + * * #6104: AudioCodec moved to sfl namespace + * * #6099: remove active flags from codec classes + * #6095: Add notification-daemon as a runtime dependencies for rpm + packages + * #6095: Fix fedora 15 compilation in MineParameters.h + * #6095: Declare static variable explicitely for client + * #6095: Add logs to build OSC build machine + * * #6098: global variables should have file-scope to avoid name + conflicts + * #6095: Fix compilation error for Fedora 15 + * #6095: Update SFLphone version to 0.9.14 + * #6095: Add specification file in opensusse build service for + sflphone-plugins + * #6073: Fix sflphone-plugins build on launchpad + * #6093: Rename CodecDescriptor for AudioCodecFactory + * * #6089: fix warnings in make check + * * #6086: renamed codecs methods to audio_codecs + * * #6085: renamed codec related dbus calls to audio_codec + * #6065: Remove g_print from client, use DEBUG instead + * #6065: Add actions name for addressbook + * * #6085: renamed codecs* widgets/functions audiocodecs* + * #6065: Fix Addressbook runtime warnings + * #6065: Replace Codecs tab for Audio in account preference dialog + * #6065: Fix "transfert" typo + * #6065: Fix addressbook action runtime warning in uimanager + * * #6082: fixes make check by adding libcrypto libs to test + dependencies + * #6073: Rename plugin/addressbook folders for addressbook/evolution + in sflphone-plugins + * #6074: Removed AC_SUBST from configure.ac when using + PKG_CHECK_MODULE + * #6073: Fix sflphone-plugins package build + * #6073: Fix sflphone-daemon build + * #6065: Fix runtime gtk warning when initializing searchbar without + addressbook + * #6063: Fix mozilla-tellify gitignore + * #6063: Remove stream copy file using ifdef macro + * * #6012: fix make dist for sflphone-daemon + * #6063: Update .gitignore file + * #6058: Fix base64 encoding related warnings + * #6056: Fix SdpException handling + * #6055: Fix unknown pargma warning for gcc <= 4.5 + * * #5949: test gcc version before disabling unused-but-set warning + * #6054: Fix addressbook plugin compilation warning + * #6048: Fix uimanager static initialization + * #6046: Fix addressbook factory static initialization of member + addrbook + * #5979: Fix implicit function declaration warning + * #6042: Fixed discarding qualifier warnings in client + * #6041: Fix instant messaging unhandled case warning + * #5994: Implement set current addressbook name and search type in + addressbook plugin + * #5994: add rules for launchpad packaging of addressbook plugin + * #5994: Fix addressbook plugin configuration loading + * #6027: Fix addressbook enabled test from configuration + * #6027: No need of gnomedoc related macros in addressbook plugin + * #6027: Add NEWS file required for build + * #6027: Add addressbook plugin autogen.sh script + * #6027: Remove plugins from client + * #6027: Add sflphone-plugins folder at project's root level + * #5994: Move addressbook folder from contacts to plugin folder + * * #6011: removed unused Makefiles + * * #6010: remove unused headers + * * #5952: fix "string constant to char*" warnings + * * #6009 fixed warnings + * * #6003: finished cleanup of account classes + * * #6003, #6004: cleanup of account classes, defaultAccount no longer + global + * * #6000: fix memory leak of args object + * * #5998: removed using namespace std from networkmanager + * * #5998: removed "using namespace std" from ZrtpSessionCallback + * * #5998: removed using namespacestd from AudioZrtpSession.h + * * #5998: remove "using namespace std" from auriorecord.h and + MimeParameters.h + * * #5998: remove using namespace std in main + * * #5998: removed "using namespace std" from logger + * * #5949: test gcc version before disabling unused-but-set warning + * #5994: Installation of addressbook plugin + * #5979: Implement codec full addressbook search from plugin + * #5979: Implement addressbook factory and plugin + * * #5981: unused webwidget removed + * #5966: Account config synchronization fix (for stun) + * #5954: Handle media name exception + * #5954: Fix audio codec name display in client + * #5954: Clean up getSessionMedia methods + * * #5957: getRecordingSmplRate returns a value + * #5954: Clean up getCurrentCodec methods + * * #5950: remove "converting to non-pointer type 'int' from NULL" + warnings + * #5915: Full gain control version + * * #5949: remove more unused variable warnings + * * #5949: remove unused/unused-but-set variable warnings + * * #5949: show_preferences_dialog returns a success value + * * #5946: cleanup of include directives, undefined function + * * #5515: comment out SSLv2 calls in pjsip + * #5915: Implement different slope for attack tme and release time for + gain control + * #5915: use only one input signal for gain control (removed output + buffer) + * #5921: Fix no audio after holding a conference + * #5916: Add gaincontrol files + * #5916: Implement FFMPEG/CCRTP video streaming prototype + * #5903: Fix call transfer during a conference + * #5915: implement rms detector, first order averager, limiter for + gain control + * #5914: Fix call transfer when no notification request is required + * #5899: Fix conference right-click segfault + * #5884: temporary fix segfault in pjsip memory pool + * #5883: Fix compilation issues on maverick and lucid + * #5755: Fix fedora 15 compilation without patching ccrtp + * [#5855] Make echo canceller optional + * #5855: Fix echo suppression activation/deactivation + * #5855: Implement pjsip echo canceller + * #5814: Speex initialization function uses samples, not bytes + * #5814: Test using more unbalanced signals + * #5814: Fix buffer size for long echo length or long echo delay + * #5814: Adjust level for echo cancellation at runtime + * #5814: Process noise reduction before echo cancelling + * #5814: Implement speex post echo canceller processing + * #5814: Dump echo cancel file to disk + * #5814: Add parameters for echo cancel + * #5809: Add configuration parameters + * #5809: Implement speex echo canceller in audio rtp session + * #5814: Code cleanup + * #5814: Fix conf creation with several incomming ringing calls + * #5814: Fix conf creation segfault when dragging a call on hold on a + ringing call + * #5809: Added unit test for echo cancellation and implemented + "process" virtual method + * #5709: Add always recording option in configuration + * #5709: Add always recording option in audio conference panel + * #5709: Add core functionnality for always recording (missing config + options) + * #5769: Fix conference participant handling (detach/attach) and hold + actions + * #5747: Fix recording icons and state for conference when adding new + participant + * #5769: Code cleanup + * #5769: Fix hangup unsent calls + * #5769: Fix remove/add additional participant to conference + * 5769: Several fixes concerning confererence handling + * #5769: Fix compilation error + * [#5769] Fix audio streams binding in main buffer + * #5769: Removed access to audio mixer from audio layer + * #5765: Fix audio crash for illformated wavefiles + * #5765: Add maximum iteration for finding fmt and data "chunck" + * #5589: Fix compilation of libnotify under + * #5757: Fix abort signal when receiving INFO + * #5747: Add usersDetached.svg + * #5747: Handle offhold action for recording conference + * #5747: Fix off hold action for conferences + * #5747: Implement update conference in record action in calltree + * #5747: Add new icons for recording conferences + * #5747: Add recording state for conferences + * [#5738] Remove getAudioDriver call from manager (replace by + _audiodriver var) + * [#5738] Refactor mutex protecting audiolayer + * [#5737] Fix HD conference recording + * [#5730] Fix start audio session after changing sampling rate + * [#5714] Fix enter keyboard event for addressbbok and history + * [5695] Fix addressbook combo box update when no addressbook selected + * [#5695] Fix addressbook initialization and search bar update + * [#5695] Add mutex for books_data in addressbook to protect async + calls + * [#5695] Get back addressbook open from uri + * [#5695] Fix absolute addressbook URI for local addressbooks + * [#5695] Implement libebook 3.0 interface + * [#5571] Better logic for hangup (for case where call have not been + sent yet) + * [#5571] Update error handling in voip links + * [#5571] Fix compile time warnings + * [#5696] Fix installation dependencies for Natty + * [#5669] Add mention that sflphone.org is for testing only + * [#5693] Add natty in teh dput.conf file + * [#5690] Remove not useful logs + * [#5670] Use dynamic payload type for rtp dtmf + * [#5668] Clean up sflphone configuration logging + * [#5668] Fix hook checkbox configuration update + * [#5666] Fix unit tests + * [#5666] Manage event subscription + * [#5666] Emit bye request when subscription is terminated + * [#5666] Bye request should be sent after event subscription + notification is done on transfer + * [#5666] Make reinvite method static (to be called in pjsip + callbacks) + * [#5666] Hangup Call in manager for AccountNULL and IP2IP + * [#5589] Use PKG_CHECK_MODULE for every client's dependencies + * [#5623] Enlarge initial size of pjsip memory pool for calls (16k) + * [#5564] Fix audio recording resampling for g722 + * [#5571] Move attribute handling for onhold/offhold actions in SDP + session + * [#5571] Codec negotiation refactored and unittested + * [#5571] Implement tests + * [#5571] Implement pjsip negociator + * [#5571] Fix unit tests + * [#5571] Add Fmtp.h to repository + * [#5571] Integrate mime types and codec factory + * [#5571] Handle exception when SDP negotiation fails + * [#5570] Add sflphoned-sample.yml in repository + * [#5564]: Implement stereo to mono mixing for rigntone + * [#5342] Update audio stream initialization + * [#5514] Restore test ni historytest suite + * [#5514] Fix + * [#5514] Disable test_create_history_path + * [#5514] use pulseaudio in sample config file + * [#5514] Fix test: load history from file + * [#5514] Do not use X + * [#5513] Make unit tests compile successfully + * [#3947] Enable unit tests in Jenkins + * [#5454] Fix build system to handle new version number + * [#5454] Update languages from launchpad + * [#5454] Add --without-celt in OpenSuse build service + * [#5454] Change version number + * [#5331] Added first SDP session tests + * [#5273] Update nightly build version tags to conform dpkg rules + * [#5211] Refactor send register method for iaxvoiplink and + sipvoiplink + * [#3950] Remove call being transfered from calltree + * [#5211] Use appropriate memory pool for transport selector + * [#5211] Fix strict aliasing rules warning in pjsip + * [#5211] Bring back pjsip shutting down sleep to 1000 ms + * [#5211] Fix registration callback segfault when closing the + application + * [#5211] Use the dialog memory pool for Route header in INVITE + request + * [#5211] Add temporary memory pool for findLocalAddressFromUri and + findLocalPortFromUri + * [#5211] Use individual memory pool for dtmfs + * [#5211] SipVoipLink refactoring + * [#3950] Attended transfer for conference calls + * [#5284] Fix DNS resolution for Route with specified port number + * [#5284] Some code cleanup + * [#3947] Fix typo in hudson script + * [#5284] Added sip route to REGISTER, INVITE, BYE request, plus DNS + resolution + * [#5266] Use RTP dtmf as default + * [#5284] Added pjsip_process_route_set after setting routes in regc + structure + * [#5286] Fix parsing error due to long configuration file (removed + max event) + * [#5286] Fix false test in configuration emmiter + * [#5286] Code cleanup + * [#5286] Updated exception handling in configuration system + * [#4969] Fix put SRTP call on hold + * [#3950] Add debug messages + * [#3950] Ability to perform an attended transfer + * [#5276] Fix initialization problem in g722 + * [#3950] Add replace header in SIPVoIPLink::transferWithReplaces + method + * [#3950] Implemented attended method in SIPVoIPLink + * [#3950] Cleanup transaction request received callback + * [#3950] Implement dummy attended transfer in gnome-client + * [#5249] Fix audio samplerate update algorithm for g722 + * [#5249] Fix uninitialized variable used in conditional jumps + * [#5249] Fix conditional jump error in audiolayer (uninitialized + value) + * [#5267] Use autoconf 2.65 as a requirement (instead of 2.67) + * [#5267] Restore manual pjsip configuration and compilation + * [#5267] Autodetect celt version (0.9.1, 0.7.1) + * [#5267] Fix deprecated macros in gnome client configure.ac + * [#5267] Update configuration for libcelt-dev + * [#5267] Fix build autoconf and automake + * [#5227] Deactivate automatic call to astyle after compilation + * [#5242] Hangup every calls before leaving + * [#5237] Will now nightly-build for natty, Karmic deprecated + * [#5229] Use inner class for rtp thread instead of inheritance + * [#5211] Move mainbuffer unbind call in rtp final method + * [#5211] Initialize sip call memory pool using 16 kb + * [#5211] Use call memory pool in session reinvite + * [#5211] Add debug messages + * [#5211] Use and internal pool for calls + * [#5211] Reduce pjsip memory pool usage for stateless error messages + * [#5211] Refactor call deletion + * [#5212] + * [#5208] Refactor codec management for accounts + * [#5168] Remove printf from codec's encode & decode method + * [#5168] Fix celt compilation on launchpad + * [#5168] Fix sflphoned compilation warnings in audiocodec.h + * [#[#5168] Must keep the g722 specific RTP rate to avoid incoming + packet timeout + * [#5168] Fix static/dynamic payload rtp session update + * [#5168] Throw SIPVoipLink Error if codec not instantiated in new + outgoing call + * [#5168] Fix dynamic/static codec payload type ambiguity + * [#5169] Fix doubled IP2IP profile when no config file + * [#4867] Add gtkinfobar in configuration panel + * [#4867] Disable input/output/ringtone selection when using default + alsa plugin + * [#4952] Patches for possible buffer overflows + * [$4885] Fix schemas problem + * [#4885] sflphone-client-gnome.schemas not present during build + * [#4885] Add gconf shemas directories in opensuse build system + * [#4885] Add file/folder ownership for opensuse-factory build system + * [#4906] Fix opensuse-factory build + * [#4885] Update name dependency for libedataserver + * [#4885] Fix non-void function without return in dbus-c++ + * [#4895] Update language translation + * [#4896] Update session timestamp when updating media + * [#4896] Reapply RTP hack for G722 payload type + * [#4896] Update recording sampling rate when updating codec + * [#4897] Save codecs in config for each configuration changes + * [#4895] Do not save config when sflphone quit + * [#4885] Update date for copyright + * [#4885] Deactivate siptest that require more than one sipp instance + * [#4879] Remove inmcoming call notification from IAX + * [#4885] Some cleanup + * [#4874] Add setCancel immediate/deffered for ost::Thread + * [#4879] Fix incoming call notification + * [#4878] Set keyboard focus on searchbar when selecting addressbook + * [#4874] Fixed compilation warning + * [#4874] Fixed compilation warning in sipvoiplink + * [#4874] Fix compile time warning in RTP record handler + * [#4874] Fix conditional jump in SDP + * [#4874] Fix conditional jump based on uninitialized value + * [#4874] Store call id within rtp thread context + * [#4874] Fixed conditional jump based on uninitialised value in + conference + * [#4871] Fix default account fetching + * [#4870] Delete RTP session when Refusing an incoming call + * Restore IP to IP call + * [#4857] Fix audio codec negotiation problem + * [#3947] Adjust ressources allocated to compilation + * [#3947] Disable unit tests in Hudson + * [#4305] Free mutex only when really quiting SFLphone + * [#4859] Update copyright to 2011 in every source file + * [#3218] Character '.' stripped by the caller engine + * [#4854] Fix typos, desktop entry + * [#4847] Apply RTP modification to ZRTP session + * [#4852] Update Karmic and Lucid dependencies + * [#4852] Add Libedataserver and libedataserverui as gnome client + dependencies + * [#4852] Add authentication mechanism for EDS + * [#4851] Fix segfault when closing pulseaudio layer too rapidly + * [#4808] Some otehr cleanup + * [#4808] Made some cleanup + * [#4808] Added mutex in rtp session for codecs and noise process + * [#4847] Update audio processing when updating RTP media + * [#4842] Add support for linking with gold/ld --no-add-needed + * [#4808] Make update g722 related static/dynamic payload logic + * [#4827] Upper limit on the number of contacts to import from EDS is + hard-coded to 500 + * [#4808] Fix put call on/off hold + * [#4808] Implement early RTP start for incoming calls + * [#4808] Audio stream is no longer start within RTP session. + * [#4808] Removed coupling between audio layer and and RTP session + * [#4702] Start audio rtp session as soon as it is created + * [#4702] Init timestamp to 0 + * #4702: Send RTP packets immediately, no need of outgoing queue + * [#4784] Update dbus-c++ version from gitorious + * [#4702] Update RTP timeouts + * [#4702] Lengthen RTP timeouts + * [PATCH] Fixed compatibility with old libtool versions. + * [PATCH] Accept older libebook (Maemo 5 has 1.4.2) + * [PATCH] Fixed double-free error in preferences dialog + * [PATCH] Fixed building of sflphone-daemon on Maemo5 + * [PATCH] Improved Gnome client initialization error handling. 1. It + no longer segfaults when sflphoned isn't available. 2. User is + provided with GUI error dialog. + * [PATCH] Improved autogen.sh scripts 1. They do not require bash + anymore 2. Added workaround for Debian bug #565663 3. Replaced + manual autotools invocations with single autoreconf call 4. Non-zero + return status on failure + * Revert "[#4468] libtool <= 2.2 doesn't have LT_INIT macro so + AC_PROG_LIBTOOL should be used instead." + * Revert "[#4468] Libebook 1.4 is sufficient" + * Revert "[#4468] Apply big path on dbus communication system" + * [#4468] Apply big path on dbus communication system + * [#4468] Libebook 1.4 is sufficient + * [#4468] libtool <= 2.2 doesn't have LT_INIT macro so AC_PROG_LIBTOOL + should be used instead. + * [#4639] Fix determining default addressbook if this property is not + set in gconf + * [#4639] Fix memory leaks in Addressbook + * [#4637] Fix opening default addressbook at sflphone init + * [#4622] Free yaml events while parsing configuration file + * [#4623] Fix conditional jumps based on uninitialized variable + * [#4622] Fix leaks in yaml serialization engine + * [#4616] Fix addressbook warnings + * [#4514] Adjust RTP timestamp + * #4527: Rename Karmic libyaml and Celt package in debian control file + * #4495: Rework addressbook opening loop + * [#4524] Increment RTP count when sending data + * [#4524] DO NOT start RTP session twice + * [#4367] Use PKG_CHECK_MODULE for celt + * [#4367] Fedora package celt as celt (not libcelt) + * [#4367] Astyling + * [#4367] Update .po files + * [#4367] Fix segfault in gensin + * [#4354] Make celt a direct dependency on launchpad opensuse build + service + * [#4367] Make celt a required package, option --without-celt valid + * [#4367] Fix zrtp timestamping error + * [#4367] Fix audio zrtp timing + * [#4367] Dispatch ZRTP packets + * [#4367] Fix segfault when unloading account map + * [#4367] Fix zrtp session + * [#4367] Implement on packet receive + * [#4367] use symetric audio rtp session, not dual + * [#4367] Reduce packet receive/sent timeout + * [#4367] Reduce RTP timeouts + * [#4367] Move speaker data receive + * [#4367] Move speaker data receive + * [#4367] Move receive speaker data method + * [#4367] Remove debug in rtp session + * [#4367] Fix g722 codec clock rate + * [#4367] Fix noise suppression initialization + * [#4367] Fix segfault in RTP mic fadein method + * [#4367] Refactor mic data encoding in rtp session + * [#4367] Implement RTP main loop + * [#4367] Fix compilation problem + * [#4367] Fix AudioRtpclass using TRTPSessionBase + * [#4367] Fix AudioRtpSession putDtmfEvent shadowing + * [#4367] Fix AudioRtpSession putDtmfEvent shadowing + * [#4367] Refactor RTP session (phase 2) + * [#4367] Refactor RTP session (phase 1) + * [#4367] Remove Redeclaration of SymetricAudioRtpSession in + rtpfactory + * [#4265] Add continue statement in for loop for invalid addressbook + * [#4261] Makes addressbook initialization more robust + * [#4257] Add maverick in build system + * [#4233] Add sdp related unit tests + * [#4233] Add condition and signal in two incoming call test + * [#4243] Fix segfault in AudioSrtpSession + * [#4243] Fix memory leak in AudioSrtpSession + * [#4243] Make audio srtp optional in for incoming call + * [#4243] Add boolean variable to make sure remote crypto context + initialized only once + * [#4243] Add documentation to AudioSrtpSession + * [#4243] Use 80 bits authentication tags by default + * [#4243] Init audio srtp remote crypto context in + call_on_media_update + * [#4243] Move SDP negotiastion in mod_on_rx_request + * [#4243] Implement initLocalCryptoInfo to be called at different + momment + * [#4243] Init init local crypto context in when initializing audiortp + * [#4243] Change key length according to sdes negociation + * [#4243] Associate callid to accountid for incoming calls + * [#4242] Fix no SDES keys in IP2IP calls + * [#4242] Fix no SDES keys in IP2IP calls + * [#4233] Test for call on/off hold + * [#4233] Add two incoming call test + * [#4233] + * [#4233] Add 2 outgoing simultaneous call unit tests + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Fri, 30 Sep 2011 13:51:04 -0400 + +sflphone-daemon (0.9.7~rc1~ppa1~SYSTEM) SYSTEM; urgency=low + + ** 0.9.7~rc1~ppa1~SYSTEM ** + + * [#2462] Set explicitly the transport on incoming call too + * [#2462] fix typo + * [#2462] Use different address for SDP and call IP + * [#2462] Use published address in SIP-SDP + * [#2181] Fixed changelog files + * [#2181] Updated spec file + * [#2402] Fix pointer to int conversion warning (atoi) + * [#2402] Remove daemon warnings, make indent + * [#2459] Make sure the stream is opened when the call is answered + * [#2402] Add conference related picture in documentation + * [#2443] Not much ... + * [#2399] Fix dialing display problem + * [#2450] Fix incoming call already in conference crash + * [#2399] Display peer name on the first line and peer number on the + second + * [#2450] Handle 403 FORBIDDEN when refused + * [#2447] Bind offHold/onHold actions to button in gtk client + * [#2447] Bind hangup action to button for conference + * [#2447] Add conference action in gtk client's ToolBar + * [#2381] Disable the password hashing in config file + * [#2402] Cleanup + * [#2366] Set callback to null when deleting Pulseaudio streams + * [#1313] Fix main buffer unit test + * [#1313] Fix audio layer unit test + * [#2315] Hide pw in security tab, display when editing, sync with + basic tab + * [#1313] UnitTest change AudioRtpSession for AudioSymetricRtpSession + instance + * [#2402] Code cleanup + * [#2444] Add debug to catch occasional crash when loading client's + config + * [#2444] Add debug info to catch occasional crash when loading config + dialog + * [#2402] Restore Call menu translations + * [#2403] Use the published address if checked in GUI + * [#2442] Add protection test in sdp + * [#1841] Reapply pjsip patch concerning DNS SRV resolution + * [#2384] Tags incoming call as direct SIP call, if applicable + * [#2402] Change the monkey face + * [#2315] Enable user to display password in clear text + * [#2434] Force optimization level at 2 + * [#2284] Fix dbus_get_all_ip_interface compilation warnings + * [#2431] Popup main window on incoming if applicable + * [$2402] Fix simple warnings + * [#2402] Fix implicit variable init order in LibraryManagerException + * [#2402] Fixing implicit variable initialization warnings in + AudioRtpSession + * [#2402] Revert atoi change, fixing codec list doubled entries + * [#2402] Fix gpointer to gint conversion + * [#2402] Fix pointer casting to integer different size warning in + codec list + * [#2402] Fix warning discarting qualifiers from pointer target + * [#2402] Fix gtk tree view assignement from incompatible type warning + * [#1669] Fix audio recording folder utf-8 non compatibility issue + * [#2414] Clean up debugs + * [#2414] Use transport set in iptoip Account and update it frm + preference + * [#2348] Use macro N_() to mark ui.xml strings as translatable + * [#2414] Rename getSipAddress/setSipAddress functions + * [#2407] Fix volume controls display + * [#2407] Fixes dialpad + * [#2383] Set ip to ip config when clicking apply button + * [#2404] Update call-to script - Maxime Chambreuil + * [#2405] Client handles unknown call in current state as well + * [#2383] Add DBUS signal to send IPtoIP local address and port as + string + * [#2383] Add Ip to IP config change apply call back + * Clonflict + * [#2402] Code cleanup + * [#2383] Do the same for IPtoIP (init localn ip with first in the + list) + * [#2383] Use first interface in the list if local addresss is not + defined + * [#2403] Clean up unuseful addresses/ports + * [#2403] Use the IP profile SIP port as global SIP port + * [#2383] Fix dbus_get_all_ip_interface warnings + * [#2383] Take into account sameAsLocal when loading published address + * [#2383] Tsake into account sameAsLocal option when saving published + address + * [#2383] Update local ip address in ip to ip config + * [#2383] Save ip 2 ip local port in config + * [#2406] Update toolbar at startup + * [#2284] Remove redefinition warnings + speex warnings + * [#2383] Fix security table in account config + * [#2383] Save ip 2 ip network interface parameters in config + * [#2403] Restore sip transport selector + * [#2383] Fix filling the Localt IP Address on account creation + * [#2383] Fix Gtk-Critical when checking STUN + * [#2383] Fix reopening account configuration display issue + * [#2383] Load IPtoIP local address and port in preference iptoiptab + * [#2383] Add LocalAddress and Localport in Preference IpToIp tab + * [#2403] Use the address and port associated to the account as often + as possible + * [#1753] Removed pjsip generated files + * [#1753] Removed remaining milenage lib references + * [#2383] Add _publishedSameasLocal variable in sipaccount + * [#2383] Add PUBLISHED_SAMEAS_LOCAL variable in config + * [#2383] Fix stun set active or not when opening config + * [#2181] Added RPM 64bits dbus patch + * [#2402] Code indentation + * [#2313] Force $(HOME).cache directory creation at startup + * [#2383] Separate network interface and published address in account + config + * [#2400] Change dbus service installation path to libdir + * [#2382] Move TLS related published address options in security tab + * [#2382] Indent accountconfigdialog.c + * [#2181] Install libdbus-c++ in $pkglib instead of $lib + * [#1753] Remove ILBC code and disable it by default in the configure + * [#1753] Remove milenage directory + * [#2382] Fix switching interaface instabilities + * [#2396] Save local ip in account creation wizard + * [#2284] Remove warning on hold + * [#2387] Fixes history searching and filtering + * [#1215] Add samplerate display in the GUI + * [#1663] Voicemail icon reflects voice messages + * [#2395] Fix account registration ( specifically with callcentric) + * [#2386] Strip "sip:" on incoming call, fixing history call back + * [#2181] Updated spec files + * [#1215] Display codec name in calltree instead of status bar + * [#2390] Move back nbCalls and stopStream higher in refuseCall + * [#2392] Fix ringtone during call in IAX + * [#2391] Stop audio streams when there is 0 calls only + * [#2391] Add debug when call state is not valid + * [#2390] Clear returns in IAXvoipLink::sendAudioFromMic() method + * [#2380] Fixing IncomingCallNotification not regular + * [#2339] Query conference at client startup + * [#2339] Working conference querying at startup + * [#2339] Add conference in call tree + * [#2339] Primitives to query conferences at client startup + * [#2320] Add account selection in history + * [#2355] Temporary solution: do not delete pointer when removing + account + * [#2380] Change algorithm in AudioRtp to trigger an + IncomingCallNotification + * [#2274] Comment sdebug in MainBuffer flush method + * [#2274] Add flushMain() in ManagerImpl::addStream + * [#2274] Add getBufferID() method in ring buffer + * [#2274] Fix warning, comment debug in ringbuffer's flush method + * [#2274] Use AudioLayer flushMain() and flushUrgent() in ALSA + * [#2274] Clean up unused variable warning + * [#2274] Protect minbudffer pointer on flushing + * [#2274] Fix playATone method which writing empty buffer in urgent + ringbuffer + * [#2274] Use audio layer flushUrgent and flushMain in createStreams + * [#2274] Use flush audio calls from audiolayer + * [#2274] Flush when peer answered call + * [#2375] Flush main buffer in iax when answering a call + * [#2274] Parse displayname using c++ string method + * [#2375] Flush main buffer when off holding calls + * [#2375] Flush main buffer mon RTP startup + * [#2376] Use now Pulseaudio module-cork-music-on-phone + * Updated OSC packaging + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Fri, 20 Nov 2009 14:00:02 -0500 + +sflphone-daemon (0.9.7~beta~ppa1~SYSTEM) SYSTEM; urgency=low + + ** 0.9.7~beta~ppa1~SYSTEM ** + + * [#1933] Cleanup debug + * [#1933] Clean up debug + * Fix mic + * [#1933] Set the IAx format earlier + * [#1933] Move IAX sendAudioFromMic outside if (call) statement + * [#1933] Fix startstream when offhold in iax and add debug concerning + codec neg. + * [#2371] sflphone_notify_voice_mail: minor gettext message formatting + cleanup + * [#2371] select_account_cb: properly gettextize status message + * [#2371] show_account_list_config_dialog: properly gettextize status + message + * INSTALL: Minor tidyup of core install guide + * Add /sflphone-client-gnome/src/icons/Makefile to .gitignore + * [#2181] Updated OpenSUSE files (tmp) + * [#1933] Add debug for codec negociation for iax + * [#1933] Get rid of getMicAvail and getMicData in audiolayer (not + used anymore) + * [#1933] Add "audio codec not determined" error in IAX + * [#1933] Test flush data + * [#1933] Do not need to start audio stream in iax anymore + * [#1933] Protecting pointer + * [#2284] Remove more compilation/execution warnings + * [#2284] Cleanup debug in client, use DEBUG instead of g_print + * [#2284] Clean up uimanager + * [#2370] Remove warnings + * [#2366] Clean up other debug + * [#2366] Clean up debug + * [#2366] Call pa_xfree explicitely in writeToSpeaker + * [#2284] Remove address book warnings + * [#2365] Fixes bad cast + * [#2352] Fix continuous ringing when peer hangup and call not yet + answered + * [#2181] Added version support + * [#2181] Fixed some minor issues + * [#2360] Moved MainBuffer from AudioLayer to ManagerImpl + * [#2352] Makes getMainBuffer() everywhere + * [#2352] Use 50 sec latency on pulseaudio stream creation + * [#2352] Add alsa debug + * [#2359] Update repository documentation + * [#2354] Move pulseaudio disconnectAudioStream after stopping main + loop + * [#2352] Adjust nb byte copied in pulseaudio according to + writeableSize + * [#2352] Specify pulseaudio tlength parameters using pa_usec_to_bytes + * [#2322] Convert italian translation to UTF-8 + * [#2357] Fixes window size + * [#2357] Display only actionnable tool item + * [#2333] Update streams parameters + * [#2347] Use GNOME user settings for Menu and Toolbar appareance + * [#2349] Load/Save properly audio params + * [#2322] Update translations from Launchpad + * [#2181] Added Francois Marier script + * [#2350] Remove non-valid test + * [#2181] Updated launchpad packaging + * [#2333] Fix Pulseaudio Capture + * [#2333] Use pulseaudio ADJUST_LATENCY flag and ALSA RT-SCHEDULING + * [#2333] Pulseaudio Interpolate timing + * [#2333] Change (again) Pulseaudio settings to fit logiteck usb hdw + requirement + * [#2333] Adjust pulseaudio fragment size to 4096 (max sflphone's + frames per buffer) + * [#2284] Remove recurrent compilation warning (g++ linker problem) + * [#2333] Safer Audiostream parameters + * [#2333] Fix alsa playback to reduce underrun + * [#2333] Better audiostream parameters + * [#2181] Updated version management + * [#2333] Exclusive test in playback loop + * [#2181] Updated build system + * [#2333] Less underrun with these value + * [#2333] Update playback audiostream parameters + * [#2333] Lengthen the audio buffer reduce number of underrun in + pulseaudio + * [#2333] Add ALSA recovery functions for underrun (begin) + * [#2333] Add pa_stream_trigger in pulse audio underrun callabck + * [#2048] Reduce prebuffering in pulseaudio (which affect incomming + calls' plbck) + * [#2316] Do not display any icons to the right on the history tab + * [#2333] Comment pa_stream_trigger in pulseaudio underrun + * [#2333] Modify pulseaudio streams parameters + * [#2318] Fix transfer tool button double signal + * [#2181] Updated + * [#2333] Fix ALSA ringtone + * [#2333] Flush all main buffer before starting audio + * [#2333] Open/Close Alsa thread between calls while there is no audio + * [#2333] Add debug message and test condition on starting playback + and capture + * [#2181] Fixed gnome client makefile + * [#2181] Updated + * [#2308] Remove getTelephoneTone debug + * [#2308] Change plughw for default in ALSA + * [#2308] Oups, forgot to change function name in audiolayertest.cpp + * [#2308] Cleanup in pulseaudio code (debug, function name) + * [#2308] Fix pulseaudio stream closing assertion failure + * [#2308] Moved pulseaudio mainloop locking from AudioStream + disconnect stream + * [2308] Fix latency at the beginning of a call, when playing DTMF and + wehn starting tone + * [#2181] Updated karmic + * [#2317] [#2319] Fix address book toggle button contextual behaviour + * [#2308] Stop stream when refusing a call + * [#2308] Stop pulseaudio stream when peer hungup + * [#2308] Fix tone and ringtone + * [#2312] Display the STUN entry widget when opening the tab + * [#2308] Implement two different callbacks for capture/playback in + pulseaudio + * [#2309] Open/close pulseaudio connections in startStream/stopStream + * [2308] Leave pulseaudio stream running, do not cork/uncork them + anymore + * [#2295] Set gtk file chooser to None if nothing is set in + configuration + * [#1976] Add codec and conference documentation + * [#2209] Fix recording in regard of resamling + * [#2297] Update .gitignore + * [#2297] Update translation files + * [#2297] Add reference to our coding standards + * [#2297] Remove old docbook code + * [#2296] Reinit tls account settings after modification + * [#2253] Add DcBlocker class to remove capture's dc offset + * [#2034] Fixes for TLS transport to initialize + * [#2284] Add silent build rule + client clean warnings + * [#2274] Fix unserialize history items in cilent at startup + * [#2274] Complete display name parsing and displaying + * [#2274] Parse the Display Name in sip INVITE message + * [#2050] Fix capture volume control in ALSA + * [#1970] Volume controls disable when using pulseaudio + * [#1970] Disable volume controls when using pulseaudio + * [#2277] Fix direct ip2ip ZRTP enabling/disabling in ip2ip + preferences + * [#2181] Added launchpad debian files + * [#2181] Added spec files for OSC + * [#2274] Set display name for "Contact" sip header as the hostname + * [#2181] Fixed daemon issues + * [#2181] Fixed gnome client issues + * [#1976] Remove warnings - need to fix the transfer + * [#2006] Add init is_rec variable in ManagerImpl + * [#2006] Update codec display on call selection + * [#2006] Restore double click actions in history and contact calltree + (GTK) + * [#2176] use XDG_CACHE_HOME when initializing sfl.zid file + * [#1976] Fix calltree switching from history + * [#2209] (Re)Fix cache for zid + * [#2209] Clean up debug messages + * [#2209] Clean debug messages + * [#2209] Fix trasnfering a call during a conference + * [#2209] Speex decode must return the number of bytes + * [#2209] Change frameSize speex 32kHz + * [#2209] Fix speex codec framesize + * [#2209] Reinit converterSamplingRate in RTP sessions + * [#2209] Change speex ultra wide band framesize + * [#1747] Add pixmap data + * [#2252] Fix Receiving a server error 488 crashes the callee + * [#2209] Fix iax low rate packate sending + * [#2209] Clean up debug messages + * [#2209] Add resampling changes for IAX + * [#2209] Clean up resampling code + * [#2209] Fix latency introduced by pulseaudio + * [#2209] Fix initialization of mainbuffer's internal sampling rate + * [#2176] Fix upsampling buffer size in audiolayer + * [#2209] Add dynamic converter sampling rate in audiortp sessions + * [#1747] Fixes runtime warnings + * [#1747] Remove from repo + * [#1747] register our icons to be used as stock icons + * [#2209] Fix number of byte in alsa's write to speaker + * [#2209] Fix putting non-resampled data in RTP's mainbuffer + * [#2209] Add alsa resampler + * [#2209] Add a samplerate converter in PulseLayer + * [#2209] Add mainbuffer's internal sampling rate and flushall method + * [#2176] Add mainbuffer stateInfo debug method + * [#2209] Resampling is optimal using SRC_LINEAR not SRC_FASTEST + * [#2176] Remove debug recordings + * [#2176] Fix Holding a conference participant on new calls + * [#2224] Add confID in callable object + * [#2176] Fix putting onhold a call participating to a conference when + pressing new call + * [#2176] Reset auidio buffers when adding streams (rtp, audiolayer) + * [#1976] Use xml to describe toolbars - Add a naviguation toolbar + * [#2176] Remove conference default_id in joinParticipant + * [#2176] Display error message in alsa's snd_pcm_avail_update call + * [#2176] Alsa mic avail data debug + * [#2176] Add some debug message for mic loss problem + * [#2176] Flush mic ring buffer when offholding a call + * [#2176] Reset ringbuffers' readpointer when adding main participant + * [#2176] Fix getAvailData algorithm + * [#2176] Reset ringbuffer's readpointer when adding a new participant + to a conference + * [#1744] Regex object renamed to Pattern. Previous attempt at + providing + * [#2176] Fix detach main participant problem when adding new one + * [#1976] Use right domain to translate + * [#1976] Add xml menu description + * [#2176] Store a list of confernece participant in client + * [#2176] Fix add participant, joinparticipant methods + * [#2181] Do not install dbus-c++ headers + add return value + * [#2176] Fix minor call handling instabilities + * [#2174] Fix incoming IP call contact address + * [#2211] Add test to protect NULL pointer + * [#1163] Add Advanced account configuration section + * [#2176] Add some usefull comments and debugging info + * [#2176] Add conditions to display security icons in conference + * [#2176] Fix detaching one participant while keeping communication to + others + * [#2176] Reenable userActive.svg in call tree + * [#2176] Make user active blue (not red) + * [#2176] Fix user active picture + * [#2176] Fix "hidden" merge conflict in sipvoiplink + * [#2176] Remove iax audio stream on peer hungup + * [#2174] Multiple UDP transports functional (TESTED with 2 accounts + and 3 calls) + * [#2176] Fix fix audio stream binding in iax + * [#2174] Create a default UDP transport + use tp selector for dialogs + also + * [#2176] Register iax audio stream in mainbuffer + * [#2176] Fix getAudioCodecName in IAXvoipLink + * [#2176] Fix iax account init + * [#2176] Handle multiple account using the same sip transport + * [#2165] Add .png files + * [#2176] Small fixes concerning dtmf + * [#2176] Fix make uninstall in codecs + * [#2174] remove stund makefile generation + * [#2176] Add conference lock + * [#2174] Add transport selector for multiple accounts + * [#2176] Change userActive picture from red to blue + * [#2176] Fix security pixbuff in calltree + * [#2176] Replace sfl.zid in .cache/sflphone instead of .sflphone + * [#2176] Fix add call description + * [#2176] Remove detach button from toolbar + * [#2176] Fix calltree call description state and state code in + conferences + * [#2176] Fix pulse audio double free + * [#2176] Fix conference selection + * [#2174] Clean up - remove stun settings in client network + configuration panel + * [#2174] Remove voviva stun code + * [#2174] Rsolve STUN with pjsip - DO NOT WORK + * [#2165] Add user svg + * [#2165] Debugging sip call failed + * [#929] Link against uuid if installed + * Oops + * Fixed bugs related to libsexy (with GTK < 2.16) + * [#929] Remove uuid-dev dependency in the core + * [#2165] Debugging no negociated codecs at communicatio start + * [#2165] Fix calltree bug (gtktreestore instead of gtkliststore) + * [#2165] Fix several merge problems + * Updated opensuse packaging script + * [#1163] Add missing figures + * [#1163] Update INSTALL file + * [#2165] Fix IAX + * [#2165] Add recordabe interface + * [#2165] Finish recording refactoring for call (not for conference) + * [#2165] Enable speaker recording for two different calls + simultanously + * [#2165] Implement call recording using the Recordable interface + * [#2165] Add get and set to AudioLayer's audio recorder + * [#2165] Add class recordable from which inherit call and conference + * [#2006] Fix G722 and Speex 8khz codec conferencing + * [#2006] add recording of audio buffers + * [#1163] Add general settings section + * [#1163] Fixes makefile error + * [#2006] Fix some minor issues + * [#2006] Drag a conference call on another conference call + (difference conferences) + * [#2006] Fix dragging a conference on itself + * [#1744] Integrating some of the needed regular expression patterns + in order + * COmplete call features + * [#1744] Added support for named subgroup in the Regex object. Also, + new + * [#1744] Adds thread safety features, compile() and setPattern() + methods to the Regex class. + * [#1744] Fix inconsistency in the finditer method from the last + commit. + * [#1744] Added regex pattern object built on top of libpcre. To be + used + * [#1744] Initial commit towards implementing RFC4568. Unimplemented + in the + * [#2157] Hide "security" and "advanced" tabs for IAX under account + * [#1163] Add call features section + * [#2006] Add joinConference capabilities + * [#2006] Add dbus joinConference signal + * [#2006] Drag a conference call onto a conference to add it + * [#1163] Add addressbook section + * [#2006] Drag a conference call onto a single call to create a + conference + * [#2006] Expand rows automatically + * [#2006] Add minimal multiple conference handling + * [#2006] Add atached/detached conference icons + * [#2006] Add function processRemainingParticipant + * [#2006] Deep refactoring, fix hangup bug + * [#1163] Update documentation - Accounts part + * [#1976] Integrate user doc to gnome client build system + * [#2122] Remove double inclusion in dbus-c++/src/Makefile.am + * Remove pjproject version number + * [#2006] Fix peerHungup + * [#1976] Make Yelp accessible from the GNOME client (need to install + the sflphone.xml first) + * [#2006] Fix multiconferencing hangup + * [#2006] Fix hangup calls in a conference + * [#2150] Make IAx2 reappear + * [#2006] Fix detach participant on multiple call + * [#2006] Can remove rining call from a conference + * [#2006] Reinit confID when removing a participant + * [#2006] Remove get isCurrentCAll in hangup/peerhungup (SipVoipLink) + * [#2006] Fix refuse call + * [#2006] Fix answerring incoming call + * [#2006] Refactor conference's participant list + * [#2101] Re-integrate test compilation in main build system + * [#2101] Make the test directory compile + * [#2136] Restore history functionality + * [#2006] Fix binding main participant to himself + * [#2006] Fix add current/incoming/onHold participant to an existing + conference + * [#2006] Fix add incoming calls to an already created conference + * [#2006] Fix remove stream + * [#2006] Fix detachParticipant/removeParticipant switchCall ids + * [#2006] Fix adding a call in conference having state "CURRENT" + * [#2006] Remove/add main participant from conferences + * [#2006] Hold/unHold conference + * [#2006] Detach a partcipant from drag n drop + * [#2006] Hangup a conference + * [#2006] Add hold/unhold conference dbus messages + * [#2034] gtk-ui fix under the "basic" tab. + * [#2006] Fix dragging calls on conference calls + * [#2006] Fix detach participant from a conference + * [#2034] Added default message is status bar under the account config + dialog + * [#2112] Fix a crashed caused when a non-md5 password was sent to + pjsip. + * [#2006] Detach participant by ID + * [#2006] Fix addParticipant method in managerImpl to handle + incoming/answered calls + * [#2006] Add addParticipant method in managerimpl and related dbus + messages + * [#2111] Added the ability to configure zrtp on sip.sflphone.org from + * [#2106] Fixed problem in the account assistant under gtk-ui. Also, + assistant.c + * [#2006] Fix dragging a conference call on another conference call + (same conference) + * [#1904] Small UI fix. Assistant was moved from "Call" to "Edit" + menu. + * [#1904] Fix a wrong label under gtk-ui. + * [#2034] Renaming and source code splitting. + * [#2034] Status bar added to account window to better reflect the + registration + * [#2006] Make calltree_remove_call recursive (for GtkTreeStore) + * [#1110] Small gtk-UI fix in the account window (alignment). + * [#2006] Fix remove conference, display children which are still + active + * [#2006] Recursive function call in calltree_update_call + * [#2006] Add multilayered capabilities to calltree (GtkTreeStore) + * [#2006] Implement remove conference in calltree + * [#2034] Now useless as Direct Ip calls settings moved under + Preferences. + * [#2034] Edit/add buttons were set insensitive all the time under + gtk-ui. + * [#1887] Information about the state of the current SIP call is + displayed + * [#2006] Add call tree remove callback + * [#2006] Fix create_conference function + * [#2006] Update conference_added_cb to add new conference to the list + * [#812] Added new tab under GTK-ui Preferences. Moving Direct Ip + Calls from + * [#2121] Disable temporarily test compilation + * [#2006] Fix conferencelist to handle conference_obj_t instead of + gchar + * [#2006] Add conference_obj structure + * [#2121] Update version + * [#2006] Fix conference selection + * [#2101] Use the new source tree to fetch the right object files + * [#2006] Add conference in calltree + * [#2006] Add Dbus signal conference added/removed/changed + * [#2006] Add getConferenceDetails call on dbus + * [#1904] Registration expire now appears as a spin box under gtk-ui. + * [#812] Fixing a segmentation fault caused by a non-existing account + ID + * [#2006] Add getConfList method over dbus + * [#2006] Add a conferencelist data structure in client-gnome + * [#812] Defaults value are now sent if a non-existing account is + requested + * [#2006] Add sflphone action sflphone_join_participant + * [#2006] Fix buffer read pointer problem deletion + * [pjsip] Attempt at fixing via header incompatibility with + Freeswitch. + * [#1797] forget something + * [#2006] Add call new state conferencing in deamon + * [#2006] Remove addParticipant method for conference, use + joinParticipant only + * [#1163] Update INSTALL documentation + * [#812] Msec/sec values were not taken into account. + * [#1797] Make pjproject-1.4 compile + * [#2006] Add Detach participant method + * [#2006] Dragndrop fully functional with INCOMING and HOLD call + * [#1797] Add pjproject-1.4 + * [#1797] Remove pjproject-1.0.3 + * [#2006] Get call state in conference related function + * [#2006] Add joinParticipant (conference) method in ManagerImpl + * [#2006] Add joinConference DBUS message + * [#2006] Store the previously selected call_id on dragndrop + * [#2006] Fix GValue pointer unref in selection callback + * [#2006] Store dragged call_id + * [#2006] Update drag_data_received_cb callback to manipulate CallIDs + * [#2006] Add dragndrop signals + * [#2006] Set calltree reordable + * [#812] Adds the ability to create a TLS listener in case the user + requests + * [#812] Adds the ability to configure local/published address from + * [#1883] Move switchCall in onHoldCall function + * [#812] Deals with the published address/port problem when + integrating TLS. + * [#1883] Switch call id in managerimpl when peerHungUp + * [#1883] Switch call id before hangup + * [#1883] Add usefull and permanent debug info for conference + cretion/deletion + * [#812] Fix various segmentation faults related to Direct IP kind of + calls. + * [#1883] Fix deletion of std::map elements using iterators + * [#2014] Add libzrtpcpp build dependency + * [#1883] Still some for loop test ambiguity (while loop instead) + * [#1883] Fix for loop initial test ambiguity (use while loop instead) + * [#1883] We must discard data in urgent ring buffer if data is get in + mainbuf + * [#1883] Fix availForGet same id for ringbuffer and readpointer + * [#812] Match "sips" as a Direct IP Call when the user enter a sip + uri + * [#812] Fix segmentation fault related to SIP URI creation. + * [#812] Towards integrating multiple tls listeners at the same time. + This + * [#1883] Add debug messages in conference and fix mainbufferTest + * [#812] gkt-ui fix. Private key must be fed as a filename and not as- + is. + * [#812] TLS integration within sipvoiplink and pjsip. Also, + configure.ac + * [#1883] Fix Alsa/Pulse mallocation + * [#1883] Fix data corruption in AudioRtp's micData buffer + * [#812] Full dbus integration for all the tls related options under + gtk-ui. + * [#1883] Fix memory leaks in audiortp session + * [#1883] Fix mem leaks in audio rtp + * [#812] Fix setAccountDetails where TLS_ENABLE was set to the value + * [#812] Small gtk-ui fix. + * [#811][#812] Small gtk-ui fix. + * [#812] Introduced a mechanism for configuration files that makes + possible + * [#812] New dbus bindings added. Also, configuration compliance was + enforced + * [#1881] Remove default buffer from MainBuffer (update unit-tests) + * [#1881] Add ring buffer read pointer tests + * [#1883] Fix issues in ringbuffer reader pointers + * [#2034] Implementing a new configuration dialogue for TLS transport + settings + * [#1883] Add some usefull debug and safety checks + * [#2028] Notify the client with libnotify when the zrtp negotiation + failed. + * [#811] Harmless no to throw an exception, an makes the application + less + * [#2028] A minidialog is showed to the user under sflphone-client- + gnome + * Removed useless file. + * Ignoring Makefile in src/widget + * [#2027] Fix segmentation fault when showMessage callback is called + after + * [#2026] keyExchange was set to ZRTP instead of "1" + * [#2024] Fix the wrong summary at the end of the assistant. + * [#1883] Fix mnagerimpl conference map insertion + * [#1883] Add Mutexes in MainBuffer + * [#811] Gtk ui was not presenting the right information about zrtp + for + * [#2023] security icons were not installed in sflphone-client-gnome. + * [#2021] Fix a mistake in the readme from sflphone-daemon that gives + wrong + * [#811] The current SRTP mode was not properly displayed for the + IP2IP + * [#1743] Re-implementation of the "automatically remove error dialogs + [...]" + * [#2017] [#2019] Fix the inability to dial a number and place a + registered + * [#811] Final re-integration of ZRTP support in the main branch from + 0.9.6 + * [#1883] Fix map insertion methods + * [#811] Combo box now is now set to the active key exchange method + * [#811] ZRTP options now configurable back again from the Gtk UI. + IP2IP + * Updated hostname for git clone + * [#1883] Add minimal functionalities to create a conference + * [#811] re-integration of all the methods and signals on dbus. + ManagerImpl + * [#811] Got out of a precarious position were nothing would compile. + * [#1976] Build documentation squeleton with docbook + * [#1883] Add sflphone-client "addParticipant" button for conference + * [#1994] Better organize the source directory structure. New + subdirectories + * [#1883] Add a simple Conference class + * [#1882] Use static audio buffer in Pulse and ALSA layer (instead of + malloc) + * [#811] First commit toward re-integration and refactoring of ZRTP + * [#1882] Flush RTP ring buffer before entering mainloop + * [#1882] Fixed MainBuffer::UnBinCallID() in case there is no + ringbuffer + * [#1882] Test (and fixe) high level conference and mixing + functionalities + * [#1772] Apply patch to compile on fedora (sent by Marcin + Zajączkowski <mszpak@wp.pl>) + * [#1882] Update Bind, unBind call_id in MainBuffer + * [#1959] This adds the ability to store password as an MD5 Hash in + the + * [#1538] Fixes rules compilation + * [#1930][#1931] Fixed a mistake (again) related to index and + credential count + * [#1753] Remove ILBC from pjproject - Hacks in pjsip + * [#1930][#1931] Credential was not selected properly using realm + * [#1882] Finilize multiple reading pointer in RingBuffer + * [#1538] Remove configure from autogen.sh to respect debian upstream + authors policy + * [#1773] Remove generated files from repo + * [#1791] Use XDG_CACHE_HOME to save pid file + * [#1791] Fixes path to save history + * [#1791] Fix debian installation scripts + * [#1930][#1931] Settings are now taken into account in the server. + * [#1882] Add ringbuffer default ring buffer pointer in methods + involving mStart + * [#1882] Add default ringbuffer pointer + * [#1882] Add RingBuffer multiple read pointer basic functionnalities + * [#1882] Fix MainBuffer flushData unit test + * [#1930][#1931] Ability to save and retreive the configuration from + * [#1882] Added Multiple CallID mapping to MainBuffer + * [#1791] Not much + * [#1791] If XDG env variables are not null but empty, use default + ones + * [#1791] Make XDG_CONFIG_HOME writable + * [#1930][#1931] Partial commit. Not working yet. Cannot delete + account + * [#1881] Fixed alsa capture latency problem + * [#1881] Fixed Alsa capture temporarily + * [#1930] [#1931] Partial unbroken commit providing the ability to + * [#1881] MainBuffer implemented in AudioLayer/AudioRTP + * [#1881] Add discard and flush unit-tests + * [#1881] Add discard and flush functionnalites to MainRingBuffer + * [#1881] Add availForGet in MainBuffer + * [#1881] Add availForPut function to MainBuffer + * [#1880] Remove AudioRTP* pointer from SipVoIP (reapered while + merging master) + * [#1881] Add a map between call id and coresponding ring buffer + * [#1855] Refresh pot file and upload on Launchpad + * [#1881] MainBuffe now robust to false ids on getData and putData + * [#1881] Fix big big big memory leak + * [#1881] Add getData and putData to mainBuffer + * [#1881] Unit-test basic ring buffer functionnaities + * [#1881] Add class MainBuffer and basic buffer creation unit-tests + * [#1880] Fix call transfer (step2) issues + * [#1880] Moved AudioRtp* pointer from SIPVoIPLink to SIPCall class + * [#1791] Add postinst script to keep user data when migrating + config/history file + * [#1797] Make pjsip compile + * [#1777] Code indentation + * [#1791] Use XDG_DATA_HOME and XDG_CONFIG_HOME for sflphonedrc and + history + unit tests + * [#1746] Useless space does not appear anymore when volume sliders + and + * [#1643] GtkCheckMenuItem is used instead of icons for elements in + the + * [#1110] [#1668] STUN parameters are now located in the preferences, + under + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Fri, 06 Nov 2009 11:23:15 -0500 + +sflphone-daemon (0.9.6-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6 ** + + * Documentation on echo test + * [redmine_down] codec names not displayed in total + * [redmine_down] crash when hanging up a dialing call because tries to + add it to history whereas no starttime + * [#1927] alternate every time screen changed to call history + * [#1886] clean code + * [#1886] debug messages when loading history removed + * [redmine_down] sflphone-kde icons + * [#1855] Update language files + * [#1502] Update version number + * [redmine_down] setHistory at close + * [#redmine_down] Handle PJ_DECLINE_SC as failure + * [#1923] Fix segmentation fault when adding a new account + * [#1923] Check on iterator before setting the config + * [#1904] Added mnemonic to tabs in sflphone-client-gnome. + * [#1905] The daemon was not sending the currentSelectedCodec signal + on dbus when answering a call. + * [#1922] Default values set to all account details + * [#1886] Spinbox reg expire enables apply, and address book is not + visible when disabled + * [#1905] Bug fix for segmentation fault caused by an empty string, + * [#1910] Warnings in test directory + * [#1919] Error fixed + * [#1855] Update russian translation - Hussein Abdallah + * [#1910] Remove files + * [#1919] fixed + * [#1777] Code indentation + * [#1918] fixed + * [#1917] fixed + * [#1910] Remove warnings compilation in src + * [#1886] removed AccountListModel in configskeleton + * [#1914] + * [#1911] check previous and new port + * [#1910] Remove compilation warnings in src/dbus and src/history + * [#1910] Remove compilation warnings in src/audio + * [1855] Update german translation - Sven Werlen + * [#1909] removed + * [#1906] Done + * [#1904] The registration expire value is now configurable from the + * Cleaned up debug messages. + * [#1886] separated initCallItem in two functions + * [#1886] reversed error in commit + * [#1886] clean debug + * [#1886] changed Name of classes and files + * [#1886] clean + * [#1870] In call_state_cb (dbus.c:126), _time_stop was overridden by + the actual time. + * [#1884] Added some new gpg flags to prevent tty warnings + * [#1886] Clean audio config dialog + * [#1886] No more compile warnings. + 1 comm + * [#1872] Check if the user input is smaller than PJ_MAX_HOSTNAME. + * [#1886] + * [#1785] Fixed build when no new commit + * [#1852] If chosen by the user, the hostname can now be solved and + used + * [#1871] * and # inverted back + * [#1869] Conditional compilation that checks if + * [#1309] removed test in main + * [#1425] Put actions in SFLPhone window class instead of ui view, + made a separate toolbar for screens. + + -- SFLphone Automatic Build System <team@sflphone.org> Mon, 27 Jul 2009 09:53:00 -0400 + +sflphone-daemon (0.9.6~rc2-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6~rc2 ** + + * [#1755] Remove generated file + * [#1753] restore ilbc ... + * [#1866] Methods getSipPort and setSipPort now have an effect on the + * [#1753] make pjsip compile without ilbc. Use ./autogen.sh --disable- + ilbc-codec + * [#1855] Fix error in russian translation + * [#1805] Remove the old flawed signal mechanism which was failing in + * [#1855] Refresh translation + * Spanish translation finished + po README files updated + echo's in + copy-in-clients + * [#1850] Yun made the chinese HK-CN translation + * [#1848] Fix transfer interface bug + * [#1862] At install, kde client installs only french translation file + * [#1841] A new fallback mechanism was added to the internal resolver + in PJSIP. + * Started AccountList model/view + * [#1855] Remove po subdir in Makefile.am + * [#1855] Fix typo error in sflphone-client-gnome + * [#1855] Do not generate Makefile in sflphone-daemon/po + * [#1855] Copy translation files into both clients dirs + * [#1855] Remove po dir from sflphone-daemon + * Comments added + * [#1860] mailbox->voicemail... + * make scripts executable + * [#1855] French translation + * [#1855] Chinese zh_HK partially filled... + * [#1859] An unnamed pipe monitored by poll() was added. When we want + to + * [#1855] Sven completed the first part of the german translation + * [#1855] Cantonese manually filled for already translated, almost + equal strings + * [#1855] Merge russian translation + * [#1855] Spanish manually filled for already translated, almost equal + strings + * [#1855] Update german translation in ./lang/de + * [#1858] This problem was fixed by removing a useless line in + * [#1855] merged existing translations in lang/ sflphone.po's + * [#1842] [#1843] An attempt at improving the expected behaviour that + can't + * [#1855] added po folder in gnome client and scripts for copying from + common lang folder to clients + * [#1853] Edit before call does nothing on call history + * Put most language entries possible in common. From 300 to 250 + entries. Stays underscores problem. Scripts for copy in clients. + * commit to merge master + * [#1825] Changed "Bad authentification" to "Authentication Failed". + * common po files + * [#1753] Remove ILBC from pjproject + + -- SFLphone Automatic Build System <team@sflphone.org> Fri, 17 Jul 2009 19:12:44 -0400 + +sflphone-daemon (0.9.6~rc1-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6~rc1 ** + + * Update some version number + * [#1792] Creates .sflphone directory with permission 600. Also, + "chmod 600" after + * [#1810] GUI is now notified that the call failed. Also, a segfault + was + * [#1816] Address book search disabled when disabled address book and + enabled it back plus button stays triggered + * codeclistmodel + asynchronous loading of address book + + enable/disable address book + * [#1810] Now checking SDP answer after 200 OK. Still need to + implement full + * [#1794] Can't use the interface during a call + * Updated translation files + * Russian translation integrated + * Codec list model/view started. + * [#1807] Add configure.ac in pjproject-1.0.3 + * [#1787] closeRtpSession added in some places where it should have + been + * Use Item class for contacts and accounts + * Comments + clean code + * [#1794] Improved debug messages + * [#1805] Replaced the old and unreliable mecanism that was was + waiting for + * [#1794] Can't use the interface during a call + * [#1787] For those cases where no registered SIP account is + configured + * [#1797] Make pjsip compile + * [#1787] Minor changes. Removed useless commented line. Changed order + of + * [#1777] Code indentation + * [#1797] Update package generation with new pjsip version + * [#1798] Does not hang up when the call is building up + * [#1797] Update .gitignore with new pjsip version + * [#1797] Remove generated files from repo + * [#1797] Main build system now uses pjproject-1.0.3 + * [#1797] Add pjproject-1.0.3 + * [#1797] Remove pjproject-1.0.2 + * [#1796] Computing time optimization (samplerate conversion) + * [#1787] _audiortp->start() moved away from offhold(), + SIPCallAnswered() + * [#1312] Added new states for calls initialized by other clients + * [#1795] Crashes when adding a new account, checking it and applying + * [#1782] Missing icons + * [#1793] KDE client compilation problem + * Fake ringtone files can no longer be set. + * indentation + * [#1312] Able to fetch to differentiate incoming/ringing call state + * [#1784] Use DESTDIR variable in po Makefile - fix language file + installation + * [#1785] Fixed typo + * [#1785] Fixed changelog update + * [#1759] ./autogen.sh --prefix=/usr --with-debug to use optimization + level 0 + * [#1773] Changed snapshot naming convention + * [#1773] Removed gpg agent use, added repository cache cleaning + * [#1759] Use optimization level 0 for repository, 2 for packages + * [#1777] Code indentation/formatting + * Translated new features in french + * [#1785] Added missing changelog entry + * [#1781] Window title is SFLPhone + * [#1777] Add code indentation/formatting in the buil system + * [#1774] Can't set voicemail number in KDE account creation wizard + * [#1775] Can't modify account information for account created with + the wizard + * [#1771] Add a "Default" button in context menu to disable chosen + prior account + * [#1705] + * [#1224] Remove generated file from the repo + * [#1224] Remove generated file from the repo + * [#1762] distclean target should remove kconfig generated files + (settings.h, settings.cpp). Rename them? + * [#1761] clear history button should really clear history + * Dialpad works. + * Implemented Dialpad widget instead of building it in main view. + * Removed last occurence of the old config dialog, that made the build + crash. + * [#1755] Do not consider G722 as a dynamic payload elsewhere than in + RTP layer + * [#1753] Remove ilbc Makefile generation + * [#1756] Implement a kde configuration dialog with kconfig xt and + kconfigdialog class + * [#1755] fix audiocodec folder parsing problem + * [#1450] Reinit timestamp comparison in RTP, create session in + newOutgoingCall + * [#1753] Remove milenage third party code from pjsip + * New Config Dialog integrated in GUI.(without codecs) + * [#1753] Remove ILBC codec + * kconfig started, tr2i18n -> i18n, icons folder, accountList changed + * [#1705] Fixed Audio RTP thread creation/start + * [#1714] Fix codec negociation result handling + * [#1678] Fix audiortp payload setting + * [#1678] Put bac putData method in rtp + * [#1669] gtk_file_chooser_get_filename() support UTF-8 by default + * [#1735] Add conditions to sdp update call if call declined + * [#1737] substr of recordings destination folder to remove "file://" + should be done in client rather than in daemon + * [#1731] Enlarge audio stream buffer size + * [#1714] Missing true + * [#1317] Fixed Mandriva timeout + * [#1317] Changed tag convention + * [#1317] Cleaned git-dch + + -- SFLphone Automatic Build System <team@sflphone.org> Fri, 10 Jul 2009 15:49:56 -0400 + +sflphone-daemon (0.9.6~beta-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6~beta ** + + * spec files for mandriva and opensuse updated with buildrequires + libqt4-dev >=4.3 + * [#1700] Cannot build on ubuntu 8.10 and a few other distribs + * [#1502] Update version number where applicable + * [#1642] Update client icons + * [#1450] Clean up useless debug and comments in sipvoiplink and + audiortp + * [#1450] Remove Semaphore object in AudioRtp thread deletion + * [#1450] Audio RTP init now synchronized with Sip/SDP + * [#1693] kde client crashes when changing codecs order/activation + * [#1450] Deep refactoring of audiortp + * [#1450] setRtpSessionRemoteIp + * [#1689] getCallList at start + * [#1224] Change path in package files + * [#1450] Audio RTP initialized only once, payload and remote ip set + at runtime + * [#1450] Add setRtpSessionMedia and setRtpSessionRemoteIp address + * [#1642] Make GNOME GUI fresher and younger ;) + * [#1686] Status bar displaying used account + * added sflphone-kde icon so that it compiles + * [#1659] Ending a call causes the daemon to crash + * corrected introspection XMLs, po files... + * [#1211] g722 media descriptor in codecDescriptor + * [#1310] Install sflphoned in $(prefix)/lib/sflphone + * [#1502] Do not install test binaries and dbus utilitaries + * [#1224] hack for pjsip build system! + * [#1224] Remove pjsip binaries from repo + * [#1224] Upgrade to pjsip 1.0.2 + * [#1658] About SFLphone (bugs) + * [#1658] About SFLphone + * [#1660] Displaying all dialed numbers in a call + * Tested status bar. + * [#790] Optimize pulse audio streams parameters + * [#1678] Some usefull debug messages for mutex/semaphore deadlock + problem + * [#1669] Add/remove some usefull/unusefull debug + * [#1665] Fix latency related to pulse audio stream openning/closing + * [#1457] Make the menus and panels accessible in french + * [#1457] Improve broken keyboard accessibility in menus and conf + panels + * [#961] Instanciate only once the searchbar icons + * [#961] Restore transfer fonction + * [#961] Filter on the history type OK + * [#961] Fix compilation problems on hardy/intrepid + * [#1157] Commit missing files + * [#790] Reduce number of start/stop streams call on pulse audio + * [#1639] kde client crashes when no account registered + * [#1620] Fix the searchbar + * [#1620] Get back caltree as it was during gtkcritical area + * [#1620] Add history filter reinit function + * [#1335] Add a missing label in address book preferences + * [#1561] Update russian translation - Hussein Abdallah + * [#1605] Fix edit menu french translation + * [#961] Enable to search in the history according to the call type + * [#1449] Searchbar does not work anymore + * [#961] Add popup menu on the entry primary icon for history + * [#1317] Fixed KDE client package dependency + * [#936] speex 32 khz integration completed + * [#936] Use 320 frame size + * [#936] Test using a frame size at 320 smpls + * [#1214] Enable / Disable history + * [#1607] Fix compilation problem for ubuntu 8.10 (libsexy) + * [#1313] Implement processDataEncode processDataDecode in audiortp + * [#1613] codec list order can't be set + * Better handling of localisation + added languages + corrected + warnings + begginning of new config dialog with kconfig + 14px + account leds + * [#1214] Save and load history according to the limit timestamp + + unit tests + * [1609] Fix call number copy/paste feature + * [1607] Restore clear action icon in searchbar + * [#936] Try to decode using 1280 samples + * [#936] Add some debug + * [#936] Add .cpp file + * [#936] Oops Forgot speex 32 khz + * [#1214] Add configuration panel for history + D-Bus calls + * [#1313] Test rtp thread function, frame size, nbbytes, resampling + * [#790] Flush audio data before closing audio streams + * [#1214] History displays local time + * [#1214] Skip empty field on display + * [#1214] Associate an account to an history entry + * [#1342] Get addressbook options sensitive/non-sensitive + * [#1211] Clean up and comments + * [#1211] Get back to 20 ms framesize + * [#1211] Use sendImmediate instead of putData in RTP + * [#1211] Fix nb byte available in RTP + * [#1211] Clear condition on maxNbSamples in RTP + * [#1211] Fix max byte available in RTP session + * [#1211] G722: Use 160 samples per frame instead of 320 + * [#1211] Test using a dynamic payload + * [#1211] Test using a dynamic payload type + * [#1211] Rename size variable (nb_samples, nb_bytes) + * [#1211] Test g722 ip-to-ip sending twice the data lenth + * [#1211] Test g722 ip-to-ip + * [#1214] Do not select an history item by default at startup + * [#1214] Remove some compilation warnings + * [#1214] Handle empty field - remove g_print + * [#1214] Add each history item only once + * [#1214] Handle call timestamps properlier + * [#1214] Do not need timestamp files anymore + * [#1214] Use the saved date for history entry + * Clean up + * [#1214] Client doesn't crash if the D-Bus call fails + * [#1214] Client is able to save its history - still some glitches + * [#1211] Forgot 16000 for g722 + * [#1211] G722 initialization + * [#1214] Save name/number, successfully load the history if no fields + are empty + * [#1499] Fixed destination directory bug + * [#1214] Restore all the functionalities; peer name/number way more + easy to handle !! + * [#1214] Add callable_object instead of call_t, refactoring + * [#1211] Test with polycom soundstation 16000 + * [#1211] Remove C like inline function in g722 codec + * [#1342] Finalize gnome client preference window formating + * [#1214] Retrieve the history when the gnome client startsup + * [#1306] Implement localization for KDE client + * [#1593] enable accounts apply button when account checked/unchecked + * [#1214] Implement the dbus calls on server side + * [#1214] Add serialized/unserialized functions to pass data on DBUS + * [#1342] Formating gnome client configuration windows + * [#1214] Save sucessfully a map of history items + * [#1499] Removed multiple jobs compilation for KDE client (2) + * [#1214] Load history from file into memory, add unit tests + * [#1534] Throws a length_error exception in case URL exceeds + std::string max_size + * [#1499] Removed multiple jobs compilation for KDE client + * [#1565] make account leds smaller + * [1430] Fix dbus debug + * [#1562] crashes when trying to change item of a call of state "OVER" + * [#1116] Fix compilation bug + * [#1317] Added mandriva and opensuse-11 64 bits + * [#1108] Add messges in main window concerning transfer success + failure + * [#1116] Fix compilation problems + * [#1211] g722 Makefile + * [#1108] Client side transferFailed/trasferSucceded signals handling + * [#1211] G722 mostly completed, + * [#1555] make bigger toolbar (24x24) + * [#1551] remove default mailbox number in wizard and disable mailbox + button when first account doesn't have mailbox number + * [#1342] Re-add sflphone manpages + * [#1116] Fix compilation on non-jaunty distros + * [#1317] Fixed opensuse startup sleep + * [#1108] Add a signal in the client to notify successful or failed + transfer + * [#1108] Dbus signals concerning call transfer success/failure + * [#1317] Added opensuse to automatic build system + * [#1223] Fix manpages bug + * [#1060] german translation glitch + * Clean up some gnome client warnings + * [#1547] replace ugly account leds by beautiful icons + * [#1548] add close button that hides windowand just hide on clicking + the cross + * [#1549] put introspec XMLs in the client's source + * [#1312] Implement getCallList D-BUS method + * [#1116] Clear text in history and contacts + * [#1499] KDE integration + * [#1469] Modify header linkers in dbus-c++'s Makefile.am's + * [#1469] Remove examples folder from dbus-c++ + * [#1214] History integration in build system; unit test squeleton + * [#1317] Cleaning + * [#1469] Remove configure stuff in dbus-c++ + * [#1469] Add unofficial mainline dbus-c++ + * [#1469] Remove dbus-c++ from freedesktop + * [#1430] Bring account changed signal/callback back to normal + * [#1060] Update german translation - Sven Werlen + * [#1430] Add marshaller one string define + * [#1430] Send account change signal broadcast using account id + * [#1430] Remove condition on setRegistrationState, cause stun to + crash + * [#1317] Centralized version handling + * [#1317] Fixed version number on sfl-git-dch + * [#1317] Refactoring for new distributions + * [#1215] Fix account order at startup if latency + * [#1088] Restore sip dns srv + * [#1214] Add squeleton for history manager + * [#1430] Add accout id to accout changed method + * [#1430] No connectionStatusNotification (account changed) if no + changes + * [#1538] Add COPYING file + * [#1430] Add audio rtp thread tests + * [#1317] Changed version detection + * [#1538] Document license in libs/stund + * [#1317] Added version files + * [#1538] Apply François patches - debian packages + * [#1317] Updated spec files + * add files + * [#1538] Apply François patches - debian packages + * [#1535] Change program file structure (directory src...) + * [#1317] Updated build system scripts + * [#1317] Cleaning + * [#1317] Copied introspect files to gnome client + * [#1317] Added opensuse to build-system : first-shot + * [#1317] Remove spec files from configure + * [#1317] Added missing prefix + * removed debug for daemon account fix + * [#1430] Add a connection reference which most likely belong to + libdbus + * [#1430] Use shared connection instead of private + * make daemon find the account, added userMatch + * Clean code, add comments... + * [#1317] Fixed packaging rules + * [#1317] Updated autogen + * Updated autogen.sh for pjsip + * [#1526] Set accounts order + * [#1317] Fixed pjsip lib dirs + * [#1317] Updated debian packaging for new pjsip configuration script + * [#1317] Switch to autogenerated guess and sub files + * [#1317] Updated pjsip inclusion in build system + * [#1317] Replaced pjsip guess and sub files + * [#1317] Fixed compilation issues on opensuse 11 + * [#1505] account list seem to crash the application when clicking + Apply very fast... + * [#1456] Add a flag to be replaced in the control files + * [#1456] Added version dependancy handling + * put account alias in AccountWidgetItem rather than in the item with + " " before. + * [#1034] The KDE client should start sflphoned if it is not started + * [#1500] Handle options for notifications and display on incoming + call. + * [#1443] Client should not crash when receive an unexpected + stateChanged signal + * [#1403] Do not stop the notification anymore + * [#1456] Added version dependancy handling + * [#1426] Daemon crashes when get alsa plugin + * [#1422] Improved error messages + * commit for merge + * [#1424] Change logo in tray icon and put a different one when + incoming call + * [#1425] first part done, window title... + * [#1413] add manpages creating and installing in build system + * [#1417] The client should start the account creation wizard if + started for the first time (if config file doesn't exist) + * [#1421] Make volume bars horizontal when dialpad is hidden. + * Changed main window title and fixed a mistake in sflphone_const.h + * [#1412] make debian package building work + * changelog changed. + * Changed addAccount method in gnome client. + * Debian and man folders added. + * [#1388] Change project name from sflphone_kde to sflphone-client-kde + * Better handle of kabc check. + * [#1351] Automatic generation of dbus interfaces in makefile + generated by cmake + * [#1307] Implement "edit before call" in history and address book. + * [#1344] change action_call label in call history from "call" to + "call back". + * [#1308] Implement Hook feature in kde client + * Improved build system. + * #1219 : Add address book configuration page + * Better handling of registration to the daemon. + * #1039 : Add tray icon in kde. + * Issue no 1216 : Double click on item in history or address book + causes call. + * display peer name in call list and call history when called from + address book. + * Address book functionnal with photo displayed. + * Help menu kde available but actions disappeared. All fonctions in + view. + * Address book functionnal but ugly and making its own sort in the + complete address book. + * Account choice on right click, clean out includes, page address + book, fixed bugs... + * Wizard, double click, context menu... + * Removed sflphone_kde.kdevelop.filelist + * Added account creation wizard and translated interface in english. + * Transfer functionnal but ugly. + * transfer not functionnal + * Bug fixed : unholding (UNHOLD_CURRENT, UNHOLD_RECORD) + * Commit functional for push. With install.sh + * Before merge. + * Problem with enable accounts. Account display increased. + * Functional with codec order working , playDTMF. + * Commit functional. + * sflphone_kde/build added in .gitignore. + * complete commit for checkout previous. + * Commit before checkout previous version to check the display + bug(little font everywhere...) + * Functionnal client. Rest : history icons, config icons and + functionalities + * commit before merge asavard for isRecording. + * Call and Automate fusion done and seems to work. + * Commiting before putting Automate class in Call class. + * Functionnal main window without recording, history, voicemail, kio + widgets. + * client kde avec kdevelop. + * Config Dialog almost finished. + * Base of QT client + + -- SFLphone Automatic Build System <team@sflphone.org> Tue, 23 Jun 2009 11:12:06 -0400 + +sflphone-daemon (0.9.5-SYSTEM) SYSTEM; urgency=low + + ** 0.9.5 release ** + + * [#1060] FIx bug in chinese translation + * [#1313] git add rtpTest.cpp rtpTest.h + * [#1313] Add init/close rtp tests + * [#1313] Basic instanciation of the rtp layer + * [#1449] Gtk-Critical concerning history filters and new calls + * [#1400] Make the match with the hostname instead of username + * [#1324] Change status bar label for "Using %s (%s)" + * [#1403] Icon size: 60x60 px + * [#1403] Do not remove notification, improve icon quality + * [#1403] Add smaller icon for gnome notifications + * [#1403] Prevent crash when hangup && no notification + * [#1403] Remove all actions on notifications; code refactoring + * [#1451] Use stun.sflphone.org as default STUN server + * [#1060] New po files - need to be translated + * [#1060] Update french translation - Rebuild template file + * [#1456] Add a flag to be replaced in the control files + * [#1454] Make cppunit optional; remove from build deps in control + files + * [#1401] Add libexpat1-dev dependency in control files + * [#1448] Take off these ugly debug messages + * [#1448] fixed getTelephoneTone and getTelephoneFile() called + repeatedly + * [#1406] add liblog4c-dev in build-depends + * [#1409] Restore .desktop icon + + -- SFLphone Automatic Build System <team@sflphone.org> Mon, 25 May 2009 11:34:40 -0400 + +sflphone-daemon (0.9.5-SYSTEM~rc2) SYSTEM; urgency=low + + ** 0.9.5 rc2 ** + + * [#1422] Improved error message + * [#1402] Fix pjsip build + * [#1404] Clear GTK-Critical Bug at client startup + * [#1422] Added automatic VM shutdown when building on more than one + VM + * [#1422] Fixed some issues with new changelog generation script + * [#1422] Moved distribution update to specific file + * [#1422] Dropped git-dch, replace by home made implementation + * [#1402] Fix pjsip build + * [#1404] Clear GTK-Critical Bug at client startup + * Changes for name based dbus connection + * Clean changelogs + * [#1343] Gnome: Implement a callback system to handle focus on + different widgets + * Debus Session + * Refactoring Python code, PEP8 + * [#1430] Get back dbus_g_proxy_new_for_name + * [#1430] Get back DBUS_BUS_SESSION type + * [#1430] Dbus fixed owner message binding + * Second test with DBUS owner + * [#1404] Gnome -> Preferences -> Hooks + * [#1404] Gnome -> Preferences -> Recordings + * [#1404] Call History + * [#1404] Gnome -> Preferences -> Address Book + * [#1404] IF the first notification option disable the second + notification + * Dbus with fixed owner does not automatically start the deamon + * Add codec debug tests in pysflphone + * [#1407] Some print info + * [#1407] Add a scenario to pick_up action + * Test client dbus connection to a fixed owner + * Add python dbus test suite + * [#1161] Modified version handling in build system + * [#1314] Test pulse audio and audio streams connect and disconnect + * [#1402] Add info message after configure + * [#1402] Build the daemon with the local pjsip library (vs the + installed one) + * [#1009] Fix Codec Sampling Rate set to zeros + * [#1314] Add mutex to pulse layer audio streams + * [#1314] Refactoring pulseaudio stream to test connect disconnect + * [#1314] Refactoring of pulselayer to test conect/disconnect + * Add debug messages in debus calls concerning account + * [#1314] Add some return values to audio init functions + * [#1406] add liblog4c-dev in build-depends + * [#1409] Restore .desktop icon + * Bug #1405: Fix strings as requested. + * Bug #1404: Fix strings in preferences panel. + + -- SFLphone Automatic Build System <team@sflphone.org> Tue, 19 May 2009 12:08:03 -0400 + +sflphone-daemon (0.9.5-0ubuntu1~rc1) SYSTEM; urgency=low + + [ SFLphone Project ] + * [#1262] Updated changelogs for version 0.9.5-0ubuntu1 Snapshot 2009- + 05-05 + + [ Emmanuel Milou ] + * Add some python CLI client code; not really functional + * [#1108] Fix peerHungup method for IP to IP call + + [ Alexandre Savard ] + * [#1108] Correct setting of SIP contact for direct IP call + * [#1108] SIP user agent handles incoming REFER + + [ Emmanuel Milou ] + * Remove website from repository + * Update translation + + [ Alexandre Savard ] + * Sflphone icon's tooltip changed for "configured" instead of + "registered" + + [ Emmanuel Milou ] + * Update translation + + [ Sflphone Project ] + + -- Sflphone Project <sflphone@mtl.savoirfairelinux.net> Tue, 05 May 2009 19:16:09 -0400 + +sflphone-daemon (0.9.5-0ubuntu1~beta) SYSTEM; urgency=low + + [ Julien Bonjean ] + * Updated Eclipse stuff + * Improved addressbook config window + * Added sflphone Eclipse stuff + * Implemented addressbook list server side + * Moved dbus stuff in dbus directory + * Updated addressbook configuration + + [ Emmanuel Milou ] + * Remove unuseful installation scripts. Use apt-get build-dep sflphone + instead + * fix bug #1090 + + [ Alexandre Savard ] + * defining speex 16khz + + [ Emmanuel Milou ] + * Remove unuseful file from build system + * Start dns srv resolver + + [ Alexandre Savard ] + * Basic ogg/vorbis initialization + + [ Emmanuel Milou ] + * Handle incoming IP-to-IP invite correctly + + [ Alexandre Savard ] + * speex wideband 16000 + + [ Emmanuel Milou ] + * Better handling of incoming IP to IP call + * DNS SRV resolution functional + * Implement IAX2 incoming URL + * Allow user to make IP call without any accounts configured + * Add a contextual menu to edit a number from the contacts tab + * Add comments, tooltip and new button to the contextual menu + * add delete event, migrate to GTK 2.16 for sexy icons + * Resolve ticket #1118 + * Update suse spec file + * Add phone number cleanup functions, unit tests and panel + configuration + * Add pertinent test that fails + * fix dependencies for suse package + * Add contextual edit menu in history - #1120 + + [ Alexandre Savard ] + * Temporary comit: make speex wideband (16 khz) + * Temporary: shared object for speex narrow band + * Temporary: speex narrowband and wideband coexist + + [ Julien Bonjean ] + * Fixed bug when no book selected + * Fixed addressbook related compilation warnings + * Fixed GTK client remaining compilation warnings + * Fixed segfault when book removed since last sflphone run + * Fixed bug when book is unreachable (ldap error) + + [ Alexandre Savard ] + * Fix codec list in audio config window + * Active/inactive speex codec by payload + + [ Julien Bonjean ] + * Updated gitignore + * Added some comments + + [ Emmanuel Milou ] + * Add callto: handler script for browsers and al. + * Integrate test compilation in the daemon build-system + + [ Julien Bonjean ] + * Fixed g_object_unref warning for pixbuf + * Cleaned too verbose output + * Fixed toolbar update warning + * Added support for asynchornous books open (first shot) + + [ Emmanuel Milou ] + * Add a DBus call to fetch the call details from a call ID - Ticket + #928 + + [ Julien Bonjean ] + * Improved async open books + * Fixed bug #1139 + + [ Emmanuel Milou ] + * Add a way to save account order + * commit missing files + + [ Julien Bonjean ] + * Introduced log4c (ticket #1162) + + [ Emmanuel Milou ] + * Load/save account order functionnal - ticket #813 + + [ Alexandre Savard ] + * Add CELT codec (#1143) + * Make celt frame size 256 (*1143) + + [ Julien Bonjean ] + * Switched everything to log4c (ticket #1162) + * Updated eclipse settings + + [ Emmanuel Milou ] + * Restore adding account - ticket #1172 + * Add liblog4c dependecy - ticket #1179 + + [ Alexandre Savard ] + * Double maxAvailByte for frame size in rtp (#1143) + + [ Emmanuel Milou ] + * Add User-Agent SIP header - Ticket #1173 + + [ Julien Bonjean ] + * Fixed autoresize issue (#708) + + [ Emmanuel Milou ] + * Remove libcppuint dependency for the debian packages + * Look for libsexy only if gtk version < 2.16 - Ticket #1116 + * Remove libsexy dependency for jaunty. ticket #1116 + + [ Julien Bonjean ] + * Introduced unit tests (#1146) + * Updated gitignore + * Fixed Makefile (#1146) + + [ Emmanuel Milou ] + * [TICKET #1112] Add a test on the voice buffer to send through iax + packets + * Remove doublon in dependencies + * Remove warnings from the client test framework + * Update version number to 0.9.5~beta + * Update build-package script + * Add check dependency in build-deps control file field + * Create debian files for the new sflphone-client-gnome + * [TICKET #1212] Add Replaces field in control files + * [TICKET #1212] Fix manpages installation path + * [TICKET #1212] Add maintainer scripts to create alternatives + * [#1212] Update the manpages generation - edit preinst maintainer + script + * [#1212] Fix reference error in manpage + * [#1212] Add missing files on the client side + * [#1212] Fix debian docs files - no TODO file + * [1212] Fix manpage creation problem + * [#1220] Generate client-side glue files and marshaller at + compilation time + * [#1220] Generate server-side glue files at compilation time + * [#1212] Change binary name to sflphone-client-gnome + * [#1212] Update .gitignore to fit the new working tree + * [#1220] Explicitly generate glue files before building the library + * [#1220] Compile dbus directory before audio + * [#1212] Create sflphone-daemon at the root of the repository + * [#1212] Re-add pjproject + * [#1212] Remove Makefile from repo + * [#1220] Fix Makefile.am + * [#1212] New working directory functional + * [#1212] Update .gitignore + * [#1212] Hack to make pjsip compile.. + * [#1220] Use non-installed binary for dbusxx-xml2cpp + * [#1212] Add descriptive files, remove unuseful scripts from tools/ + + [ Alexandre Savard ] + * Restore speex codecs + * add frame size for celt (#1143) + * add framesize to codec, independant from audiolayer (#1143) + * use codec frame size in rtp (#1143) + * compute fixed_codec_framesize (#1143) + * do not resample if not required (#1143) + * add condition on resampling for decoder (#1143) + * add a condition on bytesAvail == 0 from mic data + * no maximum in rtp decode (#1143) + * compute maximum for decoding (#1143) + + [ Emmanuel Milou ] + * [#1146] Implement unitary tests on the client-side + + [ Alexandre Savard ] + * use float instead of int to compute max nb of sample (#1143) + * add nbSampleMax for unresampled data (#1143) + * make thread sleep during 5 ms insead of 20 (#1143) + * use unix usleep (#1143) + * 50 usecond thread!!!!! (#1143) + * try with the smallest compression (#1143) + * use timer set at framesize (#1143) + + [ Emmanuel Milou ] + * [#1161] Restore changelog version + + [ Alexandre Savard ] + * Remove celt stuff + + [ Emmanuel Milou ] + * [#1161] Update changelog + * [#1220] Add Conflicts: sflphone in debian control files + * [#1179] Add liblog4c3 runtime dependency + * [#1212] FIx typo error in dependency list for itnrepid + * [#1212] FIx .desktop file to point on the right exec + * [#1212] Modify changelog replacing tag + + [ Sflphone Project ] + * "[#1262] Updated changelogs for version 0.9.5-0ubuntu1~beta" + + [ Emmanuel Milou ] + * [#1212] restore changelogs + + [ Sflphone Project ] + * [#1262] Updated changelogs for version 0.9.5-0ubuntu1 Snapshot 2009- + 04-27 + + [ Emmanuel Milou ] + * [#1212] restore changelogs + + [ Sflphone Project ] + * [#1262] Updated changelogs for version 0.9.5-0ubuntu1~beta + + [ Emmanuel Milou ] + * [#1212] restore changelogs + + [ Sflphone Project ] + + -- Sflphone Project <sflphone@mtl.savoirfairelinux.net> Mon, 27 Apr 2009 16:57:00 -0400 + +sflphone-daemon (0.9.4-0ubuntu2) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Restore speex and GSM detection + + [ Emmanuel Milou ] + * Fix bug #1090 + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 8 Apr 2009 11:29:15 -0500 + +sflphone (0.9.4-0ubuntu1) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Integrate DBus-c++ and libiax2 in the main build system + * Clean up in the working repository + * Reorder hooks configuration panel + * Protect case when no codecs are active + * Fix some return values + * Add unitary tests for the hook manager (premisces) + + [Yun Liu] + * Update chinese translation + + [Sven Werlen] + * Update german translation + + [Hussein Abdallah] + * Update russian translation + + [Maxime Chambreuil] + * Update spanish translation + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 3 Apr 2009 18:29:15 -0500 + + +sflphone (0.9.4-rc1) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Fix bug while trying to hold/unhold several simultaneous call + * Improve address book build system + * Implement SIP url popup on incoming call + * Improve GTK+ panel configuration + [ Julien Bonjean ] + * GTK+ client refactoring + * GTK+ clean up + * Address book improvment + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 27 Mar 2009 18:29:15 -0500 + +sflphone (0.9.4-0beta1) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Display codec used during conversation on the GUI + * Enable/disable STUN parameters at runtime + * Refactor search bar use + [ Emmanuel Milou ] + * Build system fixes + * Implement SIP re-invite + * Implement IP to IP call + [ Julien Bonjean ] + * Integrate GNOME address book based on evolution data server + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 20 Mar 2009 18:29:15 -0500 + + +sflphone (0.9.3-0ubuntu3) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Both playback and record streams in PA_STREAM_CORKED (pulseaudio) + * Use PLUGHW device for ALSA capture + * Functional IAX and SIP recording for voicemail + * Use the less CPU-consuming interpolator algorithm for resampling + * Display in GTK GUI the codec used in conversation + * GTK GUI use ASCII instread of utf-8 + * Add record menus in GTK GUI + * Put on hold when dialing a new number + * AccountID's are saved in the history + + [ Emmanuel Milou ] + * Integrate DBUS C++, libiax2 in the git repository + * Update website + * Use libspeexdsp only if available on the system + * Updated .gitignore file + + [Cyrille Béraud] + * Account assistant manager improvment + * Add an email request when creating a new account to receive voicemails + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Sat, 14 Feb 2009 13:29:15 -0500 + +sflphone (0.9.3-0ubuntu2) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Add compilation note in README + * Use default ALSA plugin for capture + * Fix the ALSA capture problem one more time + * Clean up debug messages in dbus.c + * Add libspeexdsp dependency + * Remove implicit declaration compilation warnings + * Fix links in the website, add release note + * Change capture for the website front page + * Add alsa devel dependency in build-depends control file field + * Clean up, indentation, try to handle latency problems in iax/pulseaudio + * Remove pjsip generated files from the repo + * Use the previous declared curAlias function in accountwindow + * Fix bug in history call duration when the call fails + * Remove runtime warning in the GTK+ client + * Add librsvg2-common dependency to load SVG under KDE + * Refresh .gitignore + * Update locales files + french translation + * Add configuration panel for future noise reduction + * Add configuration panel for audio record module + * Daemon less verbose; accounts don't try to access STUn options anymore + * Fix typo in configwindow + * Add content in the official website + * use a GTK_STOCK icon for the record button + * Complete description text in the assistant manager + * Add libtool flags in client configure.ac + * Remove unuseful dependency (snd) + * Fix SIP transfer problems + * Remove previous version of PJSIP from the repo + * Upgrade PJSIP to version 1.0.1 + * Add the new website source in the repository + * Use libspeexdsp for silence detection only if available + + [ Loïc Faure-Lacroix ] + * Ajout du logo gpl3 + * Ajout des images + * Ajout de la section screenshot pour le site + * Ajout du favicon dans le header + * Modification des cartes + + [ Alexandre Savard ] + * Clean up <speex/libspeexdsp> + * Small cleanup + * Save Wave fixed + * Fix new call button when recording + * libspeexdsp added + * Recording: default home folder at startup + * Minor changes to config window + * IAX recording fixed + * Set / get recording path, still need some GTK for client + * AudioRecord file name format + * Now recording in HOME folder + + [ Cyrille Béraud ] + * Fix bug in reqaccount.c + + [ Maxime Chambreuil ] + * Update spanish translation + + [Yun Liu ] + * Update chinese translation + + [ Hussein Abdallah ] + * Update russian translation + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Sat, 14 Feb 2009 13:29:15 -0500 + +sflphone (0.9.3-0ubuntu1) SYSTEM; urgency=low + + * Remove debug + * Join thread before leaving + * Fix implicit declaration in reqaccount + * Add REST code to build the request to server + * Fix GValue initialization warnings + * Update version number, fix implicit declaration, fix GTK markup + warnings + * Apply patch to create custom SIP account from our own server + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 06 Feb 2009 19:17:32 -0500 + +sflphone (0.9.2-2ubuntu9) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Speex audio codec preprocessing initialization + * peer hung up segmentation fault solved + * Stop recording when transfering + * Terminate only one call + * Add isRecording() function + * Fix call_icon GTK client + * Fix SIPCallClose() function, recorded file now close properly + * Function terminateSIPCall added in sipvoiplink and managerimpl + * Fix thread destructor + * setRecordingOption function implement in audiorecord + * Record now implemented in Call class + * Record interface complete (on hold erase previous recording) + * Added recButton in client + * Added: record button related icons + * Record button added + * Overload AudioRecord::recData to get mic and speaker data mixed + * Recording now in audiortp::run() method + * Audio recording working in AudioRTP: receiveSessionForSpeaker + * Open/close a wave file when pulse audio stream start/stop + + [ Emmanuel Milou ] + * Fix path for GTK+ icons; clean up + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Thu, 05 Feb 2009 18:27:53 -0500 + +sflphone (0.9.2-2ubuntu8) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Update changelogs + * Fix bug in merge and in Makefile.am + * Terminate only one call + * Disable PJsip shutdown when changing STUN parameters + * Function terminateSIPCall added in sipvoiplink and managerimpl + * Add a timer to the alsa thread to not jam the CPU load + * Fix bug in sipvoiplink.cpp + * Clean shutdown of pulseaudio on quiting + * Fix DTMF at first start with Pulseaudio + * Remove zeroconf from the build system + * Add a library manager + exception handling + * Clean up in the working directory + * Better handling of capture XRUNs + * Restore mic adjust volume on ALSA layer + * Protect device ALSA operation if not opened + * Fix the switching layer bug + * Use dynamic_cast<> to use audiolayer-specific methods + * Open the audio devices only once at startup + * Refactoring of the ALSA part + * Functional plug-in manager + * Use a C++ thread to handle tones and DTMF in ALSA + * Restore IAXVoIPLink, restore Mutex + * Make the plugins registering against the plugin manager + * Migrate to 1->N relationship between voiplink and accounts + * API plugin for registration + * Use C++ thread in SIP, move everything in sipvoiplink + * Complete singleton pattern for the plugin manager + * Add -Wno-return-type compilation flag to remove warnings; Update + version number in configure.ac + * Add the dynamic loading for the plugin framework; integate unittest + + [ Yun Liu ] + * Update rpm spec file + * modify build package script and spec file for suse + + [ Alexandre Savard ] + * Add audiorecorder plugin and testaudiorecorder + * Add audio Recording class, edit global.h + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 04 Feb 2009 14:00:30 -0500 + +sflphone (0.9.2-2ubuntu7) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Update changelog to 0.9.2-6 + * Fix some dbus-glib implementation details on the client side + * Init history after dbus initialization + * Add error checking in useragent; Clean sipvoiplink + * Prevent crash when trying to call an empty number + * Set the volume of the playback stream to PA_VOLUME_NORM at startup + * Fix GTK+ generic value double initialization + * Fix jaunty control file dependency problems + * Fix jaunty control file dependency problems + + [ Yun Liu ] + * Fix bug ticket # 137 + * Tolerant to gsm library of OpenSuse 11 + + [ Sven Werlen ] + * Update german translation + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 23 Jan 2009 17:48:13 -0500 + +sflphone (0.9.2-2ubuntu6) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Migrate STUN configuration to the main config window + * Update french translation + * Other tiny memory leaks + * Fix memory leak in sampleconverter.cpp + * Generate packages from the release branch + * update the build package script + * modify the control files with architecture=any + * Remove valgring uninitialized value + * IAX and SIP use the same global variables to set account + configuration ; fix broken code + + [ Maxime Chambreuil ] + * Update spanish translation + + [ Hussein Abdallah ] + * Update russian translation + + [ Yun Liu ] + * Update translation files + * Fix the bug when user uncheck the account which fails in the + previous registration + * Add stun error status + * Fix bug ticket #143 + * Script for auto-install dependencies + * Fix bug ticket #140 + * Fix bug ticket 141 + * Fix the reregister process when user change the details of an + account + + -- Emmanuel Milou <manu@sulfur.inside.savoirfairelinux.net> Fri, 16 Jan 2009 18:19:05 -0500 + +sflphone (0.9.2-2ubuntu5) SYSTEM; urgency=low + + * Fix memory leak in the pulseaudio callback + * Update debian package generation script + * Warnings removal in GTK+ client + * Clean adjust volume method in alsalayer + * Plug the sflphone playback volume control to the pulseaudio volume + manager + * Display the date in history according to the current locale + * Generate the changelog according to the git commit messages + * Complete header in chinese translation file + * Use the right gpg key to sign the packages + * add debian jaunty jackalope support + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 14 Jan 2009 21:17:20 -0500 + +sflphone (0.9.2-2ubuntu4) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * add german translation + + [ Yun Liu ] + * Fix GUI crash in Ubuntu8.10 64bit system + + -- Yun Liu <yun.liu@savoirfairelinux.com> Thu, 08 Jan 2009 13:08:51 -0500 + +sflphone (0.9.2-2ubuntu3) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * The main thread synchronizes the ringtone thread + * disable custom ringtone for the ALSA layer + * Fix the Makefile.am in man directory, add a SEE ALSO section + + [ Yun Liu ] + * Fix daemon crash caused by the previous patch ( for bug ticket #129) + + -- Yun Liu <yun.liu@savoirfairelinux.com> Tue, 06 Jan 2009 16:18:38 -0500 + +sflphone (0.9.2-2ubuntu2) SYSTEM; urgency=low + + * Fix bug ticket #129 + + -- Yun Liu <yun.liu@savoirfairelinux.com> Wed, 5 Jan 2009 15:54:53 -0500 + +sflphone (0.9.2-2ubuntu1) SYSTEM; urgency=low + + * Migrate from eXosip library to pjsip + * Add multiple SIP accounts support + * Fix ringtones problems + * Add a pulseaudio support + * Improve audio quality with ALSA + * Add chinese translation + * Improve spanish translation + * Migrate to a maintained C++ DBus bindings + * Clean and improve the build system + * Add build-dependency on Perl because we need pod2man to generate manpages + + -- Yun Liu <yun.liu@savoirfairelinux.com> Wed, 26 Nov 2008 09:47:53 -0500 + +sflphone (0.9.1) unstable; urgency=low + * Add a search tool in the history + * Migrate some gtk_entry_new to sexy_icon_entry_new + * Bug fix (Ticket #78): The voicemail password isn't displayed anymore in + the history tab + * Add the SIP registration expire value in the user file. + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Thu, 22 May 2008 11:14:25 -0500 + +sflphone (0.9.0) unstable; urgency=low + * Add history features + * Call date + * Call duration + * Mouse events in the history tab + * Smooth switch from the history tab to the calls tab + * Remove most of GTK-Critical warnings + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 13 May 2008 16:58:25 -0500 + +sflphone (0.9-2008-06-06) unstable; urgency=low + * Audio bug correction: capture stopped after a few minutes of conversation + with USB Plantronics sound card + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Tue, 06 May 2008 16:58:25 -0500 + +sflphone (0.9-2008-05-06) unstable; urgency=low + * Bug correction: account creation with the assistant + * GTK+ warnings removal + * libnotify warnings removal + * Remove aliasing on the SFLphone logo + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Mon, 05 May 2008 16:58:25 -0500 + +sflphone (0.9) unstable; urgency=low + * Clean dependencies ( removal of libboost ) + * Several GTK improvement and updates + -account window + -configuration window + * Migrate from GtkCheckMenuItem to GtkImageMenuItem + * ALSA standard I/O transfers: MMAP instead of R/W + * Fix speex audio quality + * IAX2 protocol + -Fix hold/unhold situation + -Add on hold music + * SIP protocol + -Ringtone on incoming call + -Fix transfer situation + * Add desktop notification ( libnotify ) + * Improve the system tray icon behaviour + * Improve registration error handling + * Register/unregister from the account window takes effect without starting back SFLphone + * Compilation warnings removal + * Call history + * Add an account configuration wizard + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 30 Apr 2008 16:58:25 -0500 + +sflphone (0.8.2) unstable; urgency=low + * Internationalization of the GTK GUI + * English / French + * STUN support + * Slight modifications of the graphical interface ( tooltips, dialpad, ...) + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 21 Mar 2008 11:37:53 -0500 diff --git a/tools/build-system/launchpad/sflphone-daemon/debian/compat b/tools/build-system/launchpad/sflphone-daemon/debian/compat new file mode 100644 index 0000000000..7ed6ff82de --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon/debian/compat @@ -0,0 +1 @@ +5 diff --git a/tools/build-system/launchpad/sflphone-daemon/debian/control b/tools/build-system/launchpad/sflphone-daemon/debian/control new file mode 100644 index 0000000000..8275b9dc67 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon/debian/control @@ -0,0 +1,29 @@ +Source: sflphone-daemon +Maintainer: SavoirFaireLinux Inc <julien.bonjean@savoirfairelinux.com> +Section: gnome +Priority: optional +Build-Depends: debhelper (>= 7.0.50), libgcc1, autoconf, automake, libpulse-dev, libsamplerate0-dev, libccrtp-dev, libgsm1-dev, libspeex-dev, libtool, libdbus-1-dev, libasound2-dev, libopus-dev, libspeexdsp-dev, libexpat1-dev, libzrtpcpp-dev, libssl-dev, libgnutls-dev, libpcre3-dev, libyaml-cpp-dev, libboost-dev, libdbus-c++-dev, libsndfile1-dev, libpjproject-dev, libsrtp-dev, libjack-dev +Standards-Version: 3.7.3 + +Package: sflphone-daemon +Priority: optional +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Replaces: sflphone, sflphone-common +Provides: sflphone-common +Conflicts: sflphone-common-video, sflphone-daemon-video, sflphone-data +Homepage: http://www.sflphone.org +Description: SIP and IAX2 compatible softphone - Core + SFLphone is meant to be a robust enterprise-class desktop phone. + SFLphone is released under the GNU General Public License. + SFLphone is being developed by the global community, and maintained by + Savoir-faire Linux, a Montreal, Quebec, Canada-based Linux consulting company. + +Package: sflphone-daemon-dbg +Architecture: any +Section: debug +Priority: extra +Depends: + sflphone-daemon (= ${binary:Version}), + ${misc:Depends} +Description: debugging symbols for sflphone-daemon diff --git a/tools/build-system/launchpad/sflphone-daemon/debian/copyright b/tools/build-system/launchpad/sflphone-daemon/debian/copyright new file mode 100644 index 0000000000..90f090efff --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon/debian/copyright @@ -0,0 +1,28 @@ +This package was debianized by Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> on +Fri, 3 Apr 2009 09:47:53 -0500. + +It was downloaded from the git repository of SFLphone: git://sflphone.org/git/sflphone.git + +Upstream Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + +Copyright: + +Savoir-Faire Linux Inc. + +License: + +This software is copyright (c) 2004-2011 Savoir-Faire Linux inc. + +You are free to distribute this software under the terms of +the GNU General Public License version 3. +On Debian systems, the complete text of the GNU General Public +License can be found in the file `/usr/share/common-licenses/GPL'. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklyn St, Fifth Floor, Boston, MA 02110-1301, USA. diff --git a/tools/build-system/launchpad/sflphone-daemon/debian/cron.d b/tools/build-system/launchpad/sflphone-daemon/debian/cron.d new file mode 100644 index 0000000000..d11e611777 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon/debian/cron.d @@ -0,0 +1,4 @@ +# +# Regular cron jobs for the sflphone package +# +0 4 * * * root sflphone_maintenance diff --git a/tools/build-system/launchpad/sflphone-daemon/debian/dirs b/tools/build-system/launchpad/sflphone-daemon/debian/dirs new file mode 100644 index 0000000000..93e7926139 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon/debian/dirs @@ -0,0 +1,9 @@ +usr/bin +usr/lib +usr/lib/sflphone +usr/share/applications +usr/share/dbus-1/services +usr/share/sflphone/ringtones +usr/share/locale +usr/share/doc +usr/share/man diff --git a/tools/build-system/launchpad/sflphone-daemon/debian/docs b/tools/build-system/launchpad/sflphone-daemon/debian/docs new file mode 100644 index 0000000000..f1dd08af02 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon/debian/docs @@ -0,0 +1,6 @@ +NEWS +README +TODO +ChangeLog +AUTHORS + diff --git a/tools/build-system/launchpad/sflphone-daemon/debian/manpages b/tools/build-system/launchpad/sflphone-daemon/debian/manpages new file mode 100644 index 0000000000..0b7e5f1c26 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon/debian/manpages @@ -0,0 +1 @@ +debian/sflphone-daemon/usr/share/man/man1/sflphoned.1 diff --git a/tools/build-system/launchpad/sflphone-daemon/debian/postinst b/tools/build-system/launchpad/sflphone-daemon/debian/postinst new file mode 100644 index 0000000000..7cbd04ec65 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon/debian/postinst @@ -0,0 +1,56 @@ +#!/bin/bash +# postinst script for sflphone-daemon +# +# see: dh_installdeb(1) + +# Script to copy and move, if exists, configuration file sflphonedrc and history in the XDG directory +# Freedesktop specifications: http://standards.freedesktop.org/basedir-spec/latest/ + +set -e + +INST_CONFIG="$HOME/.sflphone/sflphonedrc"; +INST_DATA="$HOME/.sflphone/history"; +INST_CACHE="$HOME/.sflphone/sfl.pid"; + +NEW_INST_CONFIG= +NEW_INST_DATA= +NEW_INST_CACHE= + +# Set the XDG CONFIG directory to the default one or to the path set in the environment variable +if [ -z $XDG_CONFIG_HOME ]; then + NEW_INST_CONFIG=$HOME"/.config/sflphone/"; # This is the standard path +else + NEW_INST_CONFIG=$XDG_CONFIG_HOME; +fi; + +# Set the XDG DATA directory to the default one or to the path set in the environment variable +if [ -z $XDG_DATA_HOME ]; then + NEW_INST_DATA=$HOME"/.local/share/sflphone/"; # This is the standard path +else + NEW_INST_DATA=$XDG_DATA_HOME; +fi; + +# Move the configuration file +if [ -f $INST_CONFIG ] ; then + echo "Moving the configuration file into $NEW_INST_CONFIG directory"; + if [ ! -d $NEW_INST_CONFIG ]; then + mkdir $NEW_INST_CONFIG; + fi + mv $INST_CONFIG $NEW_INST_CONFIG; +fi + +# Move the history +if [ -f $INST_DATA ] ; then + echo "Moving the history file into $NEW_INST_DATA directory"; + if [ ! -d $NEW_INST_DATA ]; then + mkdir $NEW_INST_DATA; + fi + mv $INST_DATA $NEW_INST_DATA; +fi + +# Remove the directory +# rmdir $HOME"/.sflphone"; + +echo "You may remove the $HOME/.sflphone, the application won't use it anymore, but the XDG directories instead. Thank you."; + +exit 0 diff --git a/tools/build-system/launchpad/sflphone-daemon/debian/postrm b/tools/build-system/launchpad/sflphone-daemon/debian/postrm new file mode 100644 index 0000000000..70be710bd1 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon/debian/postrm @@ -0,0 +1,36 @@ +#!/bin/sh +# postrm script for sflphone +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * <postrm> `remove' +# * <postrm> `purge' +# * <old-postrm> `upgrade' <new-version> +# * <new-postrm> `failed-upgrade' <old-version> +# * <new-postrm> `abort-install' +# * <new-postrm> `abort-install' <old-version> +# * <new-postrm> `abort-upgrade' <old-version> +# * <disappearer's-postrm> `disappear' <overwriter> +# <overwriter-version> +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +if [ "$1" = "purge" ] +then + + # remove the user config file + rm -f $HOME/.sflphone/sflphonedrc + +fi + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 + + diff --git a/tools/build-system/launchpad/sflphone-daemon/debian/preinst b/tools/build-system/launchpad/sflphone-daemon/debian/preinst new file mode 100644 index 0000000000..6d04e97b45 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon/debian/preinst @@ -0,0 +1,16 @@ +#!/bin/sh +# postrm script for sflphone +# +# see: dh_installdeb(1) + +set -e + +package=sflphone + +case "$1" in + install|upgrade) + # Clear the old dbus-c++ and iax2 if presents + ;; +esac + +exit 0 diff --git a/tools/build-system/launchpad/sflphone-daemon/debian/rules b/tools/build-system/launchpad/sflphone-daemon/debian/rules new file mode 100755 index 0000000000..ff8ac6dcc2 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-daemon/debian/rules @@ -0,0 +1,93 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 +export DH_OPTIONS + +package=sflphone-daemon + +CXX = g++-4.0 +CFLAGS = -Wall -g +DEB_INSTALL_MANPAGES_sflphone_common = sflphoned.1 + +configure: configure-stamp +configure-stamp: + dh_testdir + # Add here commands to configure the package. + # build iax and opendht with contrib since they are not packaged + cd contrib && mkdir -p native && cd native && ../bootstrap && make .iax && make .dht && cd ../.. + ./autogen.sh + ./configure --prefix=/usr --disable-video + touch configure-stamp + +#Architecture +build: build-arch + +build-arch: build-arch-stamp +build-arch-stamp: configure-stamp + + # Add here commands to compile the arch part of the package. + $(MAKE) + touch $@ + +clean: + dh_testdir + dh_testroot + rm -f build-arch-stamp configure-stamp + # Add here commands to clean up after the build process. + [ ! -f Makefile ] || $(MAKE) distclean + +ifneq "$(wildcard /usr/share/misc/config.sub)" "" + cp -f /usr/share/misc/config.sub config.sub +endif +ifneq "$(wildcard /usr/share/misc/config.guess)" "" + cp -f /usr/share/misc/config.guess config.guess +endif + dh_clean + +install: install-arch + +install-arch: + dh_testdir + dh_testroot + dh_clean -k -s + dh_installdirs -s + # Add here commands to install the arch part of the package into + # debian/tmp. + $(MAKE) DESTDIR=$(CURDIR)/debian/$(package) install + rm -rf $(CURDIR)/debian/$(package)/usr/include + dh_install -s + +binary-common: + dh_testdir + dh_testroot + dh_installchangelogs ChangeLog + dh_installdocs + dh_installexamples + dh_installman + dh_link + dh_strip --dbg-package=sflphone-daemon-dbg + dh_compress + dh_fixperms + dh_makeshlibs + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb + +# Build architecture dependant packages using the common target. +binary-arch: build-arch install-arch + $(MAKE) -f debian/rules DH_OPTIONS=-s binary-common + +override_dh_strip: + dh_strip --dbg-package=sflphone-daemon-dbg + +binary: binary-arch +.PHONY: build clean binary-arch binary install install-arch configure override_dh_strip diff --git a/tools/build-system/launchpad/sflphone-gnome-video/debian/changelog b/tools/build-system/launchpad/sflphone-gnome-video/debian/changelog new file mode 100644 index 0000000000..cdaabfde15 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome-video/debian/changelog @@ -0,0 +1,3138 @@ +sflphone-gnome-video (1.1.0-rc20120607~ppa1~SYSTEM) SYSTEM; urgency=low + + ** SNAPSHOT 1.1.0-rc20120607~ppa1~SYSTEM ** + + * gnome: cleanup logging + * * #12061: gnome: fix video codec list display + * * #12050: gnome: fixed regression in active codec handling + * #11732: Do not align menu with other interface component + * * #11818: gnome: stop daemon on SIGTERM, SIGINT or SIGHUP + * #10304: updatePlaybackScale dbus method uses int 32 bit for size and + position (allow for 24 days long recording playback) + * #10304: Add time lable for seekslider + * * #10725: gnome: seekslider: fixed compiler warning due to missing + header + * * #11732: gnome:calltree: fix warning when searchbar is not created + * * #11252: daemon: removed deprecated zrtp code + * #10725: Consolidate test to determine if there is a recording, + remove log on NULL pointer + * #10725: Remove logging from stop callback in seekslider + * #10725: No need of playback related callbacks in uimanager + * #10725: Remove logging in seekslider when no selected call + * #11732: Align Icons with dialpad in gnome client + * #10304: Make sure that the playback won't be updated after reset + * #10304: Prevent playback widget reset from stoping dtmf + * #10304: Move Playback widget at the bottom of the main window + * #10304: Fix logic to determine outgoing/incoming calls for recording + icon + * * #11685: gnome: fixed memory leaks in config menus + * #10304: Fix reset seekslider that prevent scale to be updated at + first read + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Thu, 07 Jun 2012 16:04:59 -0400 + +sflphone-client-gnome (1.0.0-rc20110930~ppa1~SYSTEM) SYSTEM; urgency=low + + ** SNAPSHOT 1.0.0-rc20110930~ppa1~SYSTEM ** + + * update kde .gitignore + * Fix bug in volume widget + * More polishing for release + * Bump version to 1.0.0 + * [#7023] Add the ability to load an abstract contact backend in the + library to resolve more data, polish code + * [#7021] More cleanup for release + * Cleanup + * [#7021] Refactor KDE client dbus handling, add a missing call in + daemon and port the DataEngine to the new API + * Remove some annoying debug + * merge language scripts + * remove obsolete 'VERSION' files + * update install instructions + * Add missing translations to gnome + * language update + * Revert "Don't reference count DBus clients, exit core immediately + when one of them request it" + * Don't reference count DBus clients, exit core immediately when one + of them request it + * [7021] Add contact abstraction support + * [#7121] Polishing library (over). Indentation, spacing and naming + are now consistent + * codecs: link to libccrtp, don't use logger + * Fix a daemon bug + * [#7038] Fix adding contact + * * #7037 : stop audio stream after all calls have been hanged up + * [#7025] Add full support for bookmark + * SFLPhone KDE do not destroy history anymore + * Fix config skeleton + * Close the daemon once and for all, no more automatic respawning + * Fix "unregistered account" bug (I hope so) + * Close SFLPhone at the right place, it still respawn, I don't know + why + * Remove dead code + * Fix regressions introduced in the last commit + * Dead code elimination 1/3 + * Fix bug, add "add contact" option, fix warning + * * #7019: Fix IAX codec negociation + * Remove or comment unnecessary/unhelpful debug output + * Fix "same as local" account setting, fix IP2IP LED color + * Add support for some more advanced config options and add missing + config dialog icons + * Fix crash with noise suppressor + * Alternative can now be selected from the call view context menu + * Add drag and drop support, initial context menu and fix 3 bugs in + the account dialog + * Add basic history drag and drop support + * Complete contact support is back + * * #6991 : fix IAX problems + * Fix IAX accounts being disabled by default + * Revert "deb: forge -g flags for pjsip" + * * #5884: Disable debug code in pjsip + * echo suppressor : more assertions + * Don't let the daemon think crypto is enabled when it's not + * Simplify ToneList + * Some progress on contact support + * Remove unused getRegistrationCount() + * remove annoying debug + * revert SIP bit of e27e5c39bad27bae28f574eb2cba7717e8956229 + * Simplify CallManager::placeCallFirstAccount + * Fix crash on hold + * * #6905 : SIP refactor + * gnome client: be sure key exchange is set correctly + * Move code into createSipTransport + * Fix account registration on start + * ManagerImpl::registerAccounts(): simplify + * * #5884: don't mess with pjsip threads in echo suppressor + * * #6905 : simplify udp/stun/tls pjsip transport creation + * Restore and improve support for Call history + * fix launchpad build + * SIPVoIPLink: simplify / refactor + * Fix libwidget linking + * SIP: simplify + * IM : simplify + * gnome: remove some debug + * AudioRtpFactory::stop() cannot fail + * * #6905: simplify SIP code + * pjlib: fix build without SSLv2, fix warnings + * Port history to the new syntax + * Test a dock widget based implementation for contact and history + * Disable SSLv2 support from pjsip and sflphone + * deb: forge -g flags for pjsip + * Fix deb packaging to get debug symbols + * remove debug + * pjproject: update to last stable release (1.10) + * Require gtk >= 2.20 and glib >= 2.24 + * tlsadvanceddialog: simplify + * * #6902 : fix errors spotted by -DGSEAL_ENABLE + * Update daemon dbus XML and port KDE config backend from dbus to + local + * Remove unused but set variables + * * #6929 : fix IM widget, cleanup + * Unconditionally enable debug symbols + * Should fix many KDE issues + * * #6886 : hitting backspace on empty number have no side effects + * * #6905 : fix AudioCodecFactory access in optimized builds (-O > 0) + * Remove unsupported and broken jaunty/karmic packages + * * #6902 : avoid using some gtk deprecated functions + * Update dbus introspection files + * * #6904: removed unused contactmanager + * * #6903 : use correct dbus-cxx package name + * * #6902: don't use individual gtk headers + * Fix a segfault when config is not present + * Merge latest (0.9.13) KDE code. This version is not yet ready for + git master, but better than the previous one + * addressbook : simplify + * * #5659 : sflphone-plugins doesn't depend on libedataserverui + * * #5659 : addressbook doesn't use libedataserverui + * gnome client doesn't depend on evolution + * * #5695: addressbook: simplify + * * #5695: addressbook : remove AddrBookHandle from plugin + * * #5695 : addressbook : remove unused stuff in the client + * * #5695 : addressbook : remove unused stuff, use static mutex + * gnome client doesn't use evolution + * gnome: use proper API to set GTK_CAN_FOCUS + * * #6897: removed unused focus state vars/callbacks + * gnome: fix calls to sflphone_fill_codec_list_per_account + * * #6623: gnome: don't leak in mainwindow + * gnome: mainwindow whitespace cleanup + * gnome: actions.c parameter doesn't have to be a double pointer + * * #6895: fix memleaks, cleanup in accountconfigdialog + * * #6893: fixes segfault in client on clean history + * * #6894: fix leaks, cleanup in sflnotify + * daemon: fixed prints in main + * * #6892: simplify, fix leaks in dialpad + * * #6887: audiopreference creates audio layer + * * #6660: use const char * const, not std::string for globally + visible constants + * * #6852: Preferences now solely responsible for audiolayer creation. + * * #6860: refactor uimanager, also fixes #6865 + * * #6853: hangup as soon as all digits have been deleted + * * #6852: alsa: retry if device is busy + * * #6852: audiolayer creation depends only on preference.audioApi + * * #6850: gnome: fix build for gtk < 2.22.0 + * cleanup in iax + * alsa: typo + * pulse: if we can't peek in audio input, we can't drop samples + * * #6849: show error window if codecs are missing, instead of dying + * EchoCancel: unused, remove + * * #6629 : use number of samples as arguments for audio filters + * * #6629 : remove unused Algorithm interface + * * #6629 : use helper to call alsa functions and display error msgs + * Remove unused type + * * #6841: fix some error handling + * * #6629: simplify AlsaLayer::alsa_set_params() + * Get gdk key definition from header + * * #6828: Replace raw key codes by gdk defines + * remove some debug, enhance some other + * mainbuffer: simplify + * * #6561 : fix phantom call after transfer + * Conference Participant set : simplify + * SIPCall: remove unused functions, make invite session public + * * #6229 : remove malloc/free from pulse audio loop + * * #6629 : simplify pulse callbacks + * * #6629 + * Simplify widgets + * * #6629 : keep the correct audio module when frequency changes + * * #6751: fixed erroneous debug msgs + * callable_obj.h: removed unneeded pthread header + * alsalayer: cleanup + * * #6629: Always restart audio driver when changing parameters (ALSA + only) + * gnome GUI: don't block in DBus signal errorAlert() + * * #6629 : simplify AudioLayer creation + * * #6629 : remove unused and unconfigurable frameSize from audiolayer + * * #6629 : remove unused error message from audio layer + * Fix logic error when switching audio API + * Remove unused AudioProcessing class + * AudioRtpRecordHandler::initNoiseSuppress() : use noiseSuppress + directly + * * #6629 : use DC blocker directly in audio layers + * * #6629 : clean AudioLayer + * * #6629 : don't store mainbuffer inside audiolayer + * * #6629 : correct AudioLayer::notifyincomingCall() + * * #6554: cleanup, refactoring in sipvoiplink + * * #6554: cleanup in iaxvoiplink + * * #6554: throw exception in getSIPCall if pointer is NULL + * * #6554: make some methods of sipvoiplink static + * * #6655: cleanup in managerimpl + * * #6554: refactoring, fix memleaks in sipvoiplink + * * #6478: remove throw specs, cleanup in voiplink + * * #6629 : remove unused AudioDevice + * * #6655: removed more dependencies from managerimpl + * * #6744: simplified numbercleaner + * conference : remove one prototype + * * #6743: fix ip2ip + * Don't give glib warnings if icons are not found + * gnome: fixed includes + * Codec.h: removed unused function + * * #6742 : clean dbus & icons + * * #6699: refactor/cleanup accounts + * icons: cleanup + * timer : use second precision, not millisecond + * calltree_update_clock : use correct type, returns something + * * #6737: fixed typo in dbus call + * * #6737: removed tests for removed API + * * #6737: dbus: fixed bug from merge + * * #6737: cleanup in accountlist + * * #6737: cleanup in dbus + * * #6740 : fix history double free + * * #6740 : remove time updating thread from calls + * * #6737 : use c99 for client + * * #6738 : make history loading faster + * sipvoiplink : don't crash on transfers + * fixed typo + * Remove unused file + * Don't build networkmanager.cpp at all if NM is disabled + * _debug* -> _debug + * * #6554 : simplify sipvoiplink + * hudson: added -x to git clean command + * added git clean to hudson script + * audiocodecfactory: cleanup + * * #6718: refactored setTlsSettings into SIPAccount + * * #6718: removed more unused methods + * * #6718: refactored confmanager code into sipaccount + * remove unused functions + * * #6718: confmanager: removed more unused methods + * AudioCodecFactory : cleanup + * #6697 : Turn callableElement struct into union + * * #6718: confmanager: removed more unused methods + * * #6718: confmanager: removed more unused methods + * * #6718: removed unused dbus methods, refactoring + * * #6699: accounts: cleanup/refactoring + * * #6699: refactoring, cleanup in accounts + * * #6699: more account cleanup + * remove unused autoconf variable + * * #6714: fixed hudson script + * make distclean in hudson + * added || exit 1 to run_tests.sh call + * * #6714: fixed make distcheck for sflphone-plugins + * * #6714: fixed make distcheck for gnome client + * * #6714: fixed make distcheck for daemon + * git: #6698 split the main .gitignore file + * gnome: gpointer is already a pointer + * gnome: calltab_init: use calloc instead of malloc + * * #6699: more account cleanup + * * #6699: cleanup account + * * #6554 : more *voiplink cleanup + * * #6558 : more sipvoiplink simplification + * * #6558: saner loadSIPLocalIP prototype + * gnome: #6623 clean calllists + * * #6692: more audiolayer cleanup + * * #6692: cleanup/refactoring in audiolayers + * * #6692: more forward declarations, AudioThread->AlsaThread + * * #6692: audiolayer cleanup + * * #6692: alsalayer cleanup + * * #6558 : remove account creator + * * #6558 : clean sipvoiplink + * * #6554 : cleanup sipvoiplink + * audiortp: cleanup + * * #6657 : fix launchpad builds for good + * * #6675 : send RTP dtmf events only once + * * #6655: more cleanup + * AudioRtpSession::updateSessionMedia() : simplify + * * #6655: more cleanup in managerimpl + * * #6655: removed more code, cleanup + * * #6655: more cleanup, fixed infinite loop + * * #6655: removed more unused files + * * #6655: removed unused mutex + * * #6655 removed more unused code + * * #6655: removed unused methods + * * #6655: cleanup in main + * * #6663: fixed segfault when off hold from transfer + * * #6658: user's active codec selection is respected + * * #6660: static global string should be static const char* const + class member + * * #6659: use g_strcmp0, not strcmp for vals that may be null + * callable_obj: fix double free + * calltree_display_call_info() : simplify + * * #6657: Fix launchpad builds + * Logger::log() : simplify + * AudioRtpSession : privatize members + * * #6655: more constness, cleaned up/simplified methods + * * #6654: call DBus::_init_threading so that dbus-c++ to make it + threadaware + * set default credentials on account creation + * AudioCodecFactory::scanCodecDirectory() : simplify and correct + * * #6623: fixed typos + * * #6623: fixed more leaks + * * #6623: fixed more leaks + * * #6623: fixed more leaks, don't print codec name if null + * * #6623: more leaks fixed in client + * * #6623: fix more leaks, fixed some warnings + * * #6623: fixed leak in history + * updated gitignore + * initialize dbus dispatcher correctly + * Fix tests, hudson doesn't have a dbus daemon running + * remove unused code + * removeCall() : simplify , fix leak + * stopRtpThread() : simplify + * *CurrentCall : simplify + * Fix memleak + * fix serialization of audio api (pulse / alsa) + * account map : simplify + * remove call from callmap before terminating it, avoid use after free + * * #6630 : don't make DBusManager a singleton + * call: return confID by value + * add back history code deleted by error + * history : reverse logic + * simplify history serialization and remove some debug + * remove annoying debug + * * #6464 : replace cerr with _error + * * #6464: replace cout with logger macros + * replace printf() with logger macros + * update .gitignore + * remove unused function + * update eclipse projects + * uimanager_new() : simplify + * rename directories + * celt: simplify a bit + * Fix CELT configure.ac test + * * #6612 : template speex codecs + * * #6623: refactored conference obj + * * #6623: refactored callable object, removed leaks + * * #6623: more cleanup, fix leaks, make global vars static and rename + them + * * #6623: calltree: fixed memleaks, simplified code. + * audiolayer: init pointer members + * manager: catch exception on invalid hangup + * * #6623: don't leak on calls to create_new_call + * * #6611 : clarify codecs prototypes + * ringtones : .au and .ul files are both ulaw + * * #6611 : make sure samplerate converters are called correctly + * ManagerImpl::switchAudioManager() : simplify + * * #6623: fixed more leaks + * * #6623: fixed more leaks + * * #6623: fixed more leaks + * * #6623: fixed leak, line-endings in imwidget + * * #6627: zero-initialize pointers if they're going to be deleted + * * #6628: don't leak calls on exceptions + * Revert "audiortp: call join after calling stop on RtpThread" + * sflphone-client: more constness + * audiortp: call join after calling stop on RtpThread + * * #6625: return 0 on successful completion + * * #6624: fix segfault on servercallfailure + * * #6621: Fixed double free, unlock mutex in ManagerImpl::terminate + * * #6220: remove audio stream when peer hangs up + * * #6596: AudioSymmetricSession shouldn't self-delete + * resampler: grow internal buffers dynamically + * merge up and down sampling => resampling + * Leave test directory unchanged when running make check + * audio algorithms : remove unused prototype + * ringtone: detect codec from file extension + * *AudioFile : simplify + * * #6596: create local SDP on the stack, not the heap + * * #6596: don't call Ost::Thread::terminate from dtor + * audiofile: cleanup (samplerate -> unsigned) + * remove unused func + * samplerateconverter: cleanup + * RingBuffer::Put() : remove unused return value + * MainBuffer::putData() : remove unused return argument + * audiolayer::putMain() : remove unused func + * AudioLayer::putUrgent() : remove unused return value + * * #6618: delete any remaining ringbuffers in destructor + * RingBuffer::availForPut() : remove + * * #6617: return from main rather than calling exit + * MainBuffer::availForPut(): remove + * RingBuffer: simplify + * alsa : remove write only variable + * fix memcpy declaration + * bcopy(src, dst) -> memcpy(dst, src) + * RingBuffer::Get() : remove constant volume argument + * return a copy of the call ID, not just a reference. + * MainBuffer::getDataById() : remove volume argument (always 100) + * MainBuffer::getData() : remove constant volume argument + * RingBuffer::Put() : remove constant volume argument + * MainBuffer::putData() : remove constant (=100) volume argument + * audiolayer: remove constant _defaultvolume + * AudioRtpRecordHandler / AudioRtpSession : simplify + * mainbuffer: fix test + * iaxvoiplink : simplify + * sip registration callback: fix a dbus crash + * MainBuffer: simplify + * AudioRtpFactory: return cached type of rtp session. The rtp session + can have disappeared if the call was put on hold + * AudioRtpFactory: remove unused setters + * Fix launchpad builds + * * #6611 : remove unused bandwidth codec information + * * #6611: AudioCodec: remove useless/unused setters + * make sure buffer string is initialized correctly + * * #6596: declare certain destructors virtual + * audiolayer : cleanup + * Simplify doc build rules + * * #6270: don't build dbus-api doc with make, should require make all + * configure.ac: cleanup + * Remove copy of dbus-c++ from libs/ + * * #6596: stop clock thread when peer hangs up + * removed unused Fmtp.h + * * #6595: more logical initialization order + * * #6600 : fix account creation + * * #6601 : fix configure.ac tests + * remove unused variable + * Don't mix stack and heap based allocations + * Fix copyright (2009, 2008, 2009 -> 2008, 2009) + * Fix warnings found by clang + * * #6595: fix initialization order for AudioRTP + * * #6592: removed typedef std::string CallID + * * #6586: implement local g_slist_free_full for older glib versions + * * #6579: fix memory leaks in client (there's a lot left) + * ShortcutPreferences::setShortcuts() : simplify + * Fix merge + * * #6548: remove call to non thread-safe strerror() + * AudioRtpFactory: each instance is associated to exactly one SipCall + * create_audiocodecs_configuration() : make static + * * #6269 : refactor AudioRtpSession + * Fix AudioSymmetricRtpSession.h inclusion guard (cherry picked from + commit c3081dce1cc1370d6d3558a4c4ef5cfac0d21caf) + * * #6269: Rename AudioRtpSession to AudioSymmetricRtpSession + * * #6574: Don't exit when connection to pulseaudio server fails + * accountconfigdialog.h : remove some stuff from header + * * #6560: fix configuration test + * Fix warning in test + * * #6560: don't hide password entry in security tab + * * #6560: set initial password for SIP accounts + * * #6506: remove useless pointer indirection + * * 6560: password is now specific to IAX accounts + * * #6560 : actually use, store, restore, transmit SIP credentials + * * #6560: YamlEmitter: serialize sequences + * YamlEmitterException: typo + * ManagerImpl::computeMd5HashFromCredential() : simplify, fix memleak + * * #6561: invite_session_state_changed_cb() : simplify + * * #6561: More useful debug in VoIPLink::removeCall + * * #6561 : fix ghost call reappearing in GUI after transfer + * while -> for (make the code smaller) + * * #6558 : Account::loadConfig() : move IAX code to IAXAccount + * IAXVoIPLink::getAccountPtr : simplify + * * #6554 : access the SIPVoIPLink directly, not per account + * SIPVoIPLink is instanciated only once and is not associated to a + single account + * yamlnode: use const references when possible (still some left to do) + * Account::_accountID: constify + * VoIPLink: simplify, remove unused method + * hudson test : no need to call run_tests.sh anymore + * Remove AccountID type and AccountNULL define + * Make check runs the test (no need to call run_tests.sh manually + anymore) + * gnome GUI: Fix tests + * Revert "Move registration information from SIPAccount to + SIPVoIPLink" + * * #6392: pluginmanagertest: fix warnings reported by valgrind + * * #6547 : remove unused exceptions + * * #6547: CallManagerException: use runtime exceptions + * * #6547: InstantMessageException: use runtime exceptions + * * #6547: do not throw exceptions if some settings are not present in + config file + * * #6547: YamlParserException: use runtime exceptions + * * #6547: VoipLinkException: use runtime exceptions + * * #6547: YamlEmitterException: use runtime exceptions + * * #6547: DTMFException: use runtime exceptions + * * #6547: AudioFile: use runtime exceptions + * * 6547: AudioZRtpSession: remove impossible error case + * * #6547 : AudioRtpSession: remove impossible error case + * * #6547: AudioZrtp: use runtime exceptions + * * #6408 : send authenticationUsername to GUI + * * #6408 : store/restore authenticationUsername from config file + * SIPAccount: simplify + * Move registration information from SIPAccount to SIPVoIPLink + * SIPAccount::getAccountDetails : simplify + * * #6540: yaml parser: simplify + * sdp.cpp : fix a warning + * * #6540: yaml parser : remove std::string typedefs + * * #6540: Simplify yaml unserialization + * * #6540 : add a Conf::ScalarNode constructor for booleans + * setAccountDetails(): simplify + * * #6408: store authentication username in daemon + * * #6408: Be able to set the authentication username in the GUI + * * #6507 : do not crash if the program is not sflphoned + * Fix tests + * macroify SIPAccount::unserialize() + * Move all .cpp files from sflphoned target to libsflphone.la, except + main.c + * main() : simplify, return positive error codes + * * #6507 : find codecs dir in build directory + * * #6392: Sdp: move clean functions to destructor + * AlsaLayer::adjustVolume() : simplify + * alsalayer : reduce indentation + * malloc/free -> new/delete + * malloc/free -> new[]/delete[] + * malloc/free -> new/delete + * AudioSrtpSession: simplify base64 encoding + * * #6392: Initialize std::string from pj_str_t correctly + * * #6392: AudioRtpSession: Initialize remote port + * Audio settings : Initialize _echoCancelTailLength and + _echoCancelDelay(0) + * Initialize variable + * YamlParserException : fix use of stack variable after it has been + deallocated + * * #6392: fix memory leak in history + * * #6392 AudioCodec : fix memory leak + * * #6392 : fix memory leak in sip account + * * #6408: clean up sipaccount (cosmetics mostly) + * sipaccount.cpp serialize() : reduce number of lines + * * #6392: invalid memory access + * * #6392 : fix invalid memory access + * * #6479: merged useful code from MimeParameters into Codec interface + * * #6462: fixed hangup on IP2IP call + * added run_daemon.sh script + * test: remove unused variable + * Remove functions only used by a failing test (cherry picked from + commit fcf718cb75de7f1882dc61c07bb8d300dfa10f85) + * * #6360 : make client tests build (cherry picked from commit + 028b2835f040e51ab8ab979b32732b07b8798fce) + * * #6360 : fix warnings in check_global test (cherry picked from + commit 9e2bd6a7496dd64f6f48595e385760019aab1193) + * * 6360: updated API calls in tests, but they're not building yet + (cherry picked from commit 548f6f0f919b43772a3e9c667e5e292791281795) + * Fixed include in tests (cherry picked from commit + aeadc7525c1e31f936670ac8b02f0bcf387c38a8) + * Remove unused variables and functions + * IAX: fix warnings (cherry picked from commit + fd7a113a11cac2cd9a7c36929e88ad28195c4c35) + * Remove unused DEBUG define which interferes with logger.h (cherry + picked from commit b2f72b91d0f43cb1dd94d138882a8caa9c841c24) + * * #6392: no need to check for account NULLity since it is + dereferenced above + * * #6392: fix a memory leak, replace by stack allocation + * * #6392: remove a variable assignement which confuses cppcheck + * process_conference_participant_from_serialized() : remove unused + function + * * #6392: s/free/g_free/ + * * #6392: fix a memory leak in abookfactory_load_module() + * * #6392: remove generate_call_id() used only once + * * #6392: fix memory leak (opendir() without closedir()) + * * #6392: AudioRecorder(): ensures mbuffer is set + * Remove SFLPHONED_VERSION from global.h, use autoconf PACKAGE_VERSION + * #6298: Cleanup + * #6331: Fix deleting ringtone file after call have been answered + * * #6330: merged user_cfg into headers + * #6298: Fix conference recording file update at conference end + * #6298: Fix record file name serialization for conference + * * #6295: cleanup of codec hierarchy + * #6298: Fix gtk warnings + * * #6300: added script to run tests + * #6109: Add recording playback for conference + * * #6300: tests do not require an installed sflphone + * * #6295: re-removed clone methods + * #6109: Fix gtk_critical warnings for incoming calls + * #6109: Fix GTK_CRITICAL warning + * #6109: Fix icons when history is not activated + * #6109: Fix warnings + * #6109: Implement stop recorded file playback signal + * Revert "* #6295: removed unused clone method" + * * #6295: removed unused clone method + * * #6296: removed non existant file from Makefile.am + * #6109: Stop fileplayback for outgoing call + * #6109: Implement stop recording playback button + * Fix binding names errors in dbus introspection file + * #6109: Implement playback recorded file callback in client + * #6109: Store recorded file path on client side + * #6109: Add dbus methods for call recording playback + * * #6290: remove unused classes from utilspp + * * #6288: cleanup sdp + * * #6288: fix exception usage + * * #6288: simplify SdpException + * * #6288: cleanup in sdp.cpp/h + * #6109: Only display playback button if record file is set and valid + * * 6290: updated configure.ac to remove functor Makefile + * * #6290, #6289: removed unused classes from utilspp, fixed make + check + * #6109: Add button for history playback of recorded file + * * #6289: removed unused observer class + * * #6282: forward declare sdpMedia in sdp.h + * * #6281: renamed setCallAudioLocal->setCallMediaLocal + * #6183: Handle conference with more tahn two calls + * #6183: Fix history icons when calling back a conference from history + * #6183: Fix icons inconsistencies in history for conference hang up + * #6183: Fix toolbar actions when selecting a conference in history + * #6183: Fix conference serialization + * #6268: Serialize only calls + * * #6269: removed useless type testing + * ignore some files in test/ + * * #6268: Remove dead class AudioSymmetricRtpSession + * #6251: Do not had history calls in calllist when loading history + file + * #6251: Fix insertion in history map in before saving history file in + daemon + * #6251: Fix history unit tests + * #6251: Order the list before serailization, get rid of the hashtable + in history + * #6251: Implement history serialization using a list wether than a + map + * * #6253: remove external audioport from header, make all members + private + * * #6253: don't store external local audio port (used for NAT) in + Call + * #6251: Add start_time timestamp in history serialization + * #6251: Fix call insertion in conference items + * #6233: Fix serialized account list terminated with a ";" character + * #6238: Fix draggable history calls into current calls + * #6233: Fix toolbar updates + * #6233: Fix history + * * #6235: remove pyc files from git tree + * #6233: Handle cases when one or manuy calls are unreachable in + createConfFomrParticipantList + * #6233: Handle wrong numbers in createConferenceFromParticipantList + * #6231: Fix drag-n-drop issue + * * #6173 : move sippxml in tools + * #6231: Fix merging issue + * #6183: Implement conference unserialize + * * #6212: remove extraneous flags from globals.mak + * #6183: Unserialize conference data in conference + * #6183: Add account information in request for conference call from + history + * #5755: Add -ldl to liker in sflphone-client-gnome + * #5755: Fix fedora 15 compilation issue + * #6183: Serialize conference participant phone number and account + * #6183: Add conference timestamp in serialization + * * #6186: don't include global.h, just logger.h + * #6183: Fix saving history to file + * #6183: Fix removing call from calllist + * * #6184: remove pointers to Manager from AudioRtpSessions + * #6183: Calling calltree_add_call explicitely for history + * #6183: Ability to store conference inside history tab queue + * * 6181: remove unused API from sipcall + * #6171: Implment nreCallCreated callback + * #6167: Fix participant list NULL ending + * #6149: First draft of conference creation from history + * #6149: Fix multiple call/conf selection callbacks ... + * #6129: Fix place_call function called twice for pressing enter + action + * #6129: Fix double click action for history + * #6149: Add dbus call for creating conference from history + * #6129: Fix placing call from history and addressbook (still need to + fix icon) + * * #6148: removed unused AudioRtpFactory constructor + * * #6145: remove unused isAudioStarted + * * #6145: remove unused isAudioStarted + * #6129: Add conference into history, fix call/conference selection + * * #6143: don't use getType outside of serialization methods + * * #6132: forward declarations instead of includes + * * #6132: add constness, remove redundant "inline" keywords + * #6129: Add timestamp to conference object to order history entries + * * #6128: remove unused forward declarations from header + * * #6127: make noncopyable class actually noncopyable + * * #6125: don't include AudioRtpFactory in sipcall.h + * #6123: Fix alsa ringback audio file + * #6123: Fix raw audio file loading problem + * #6109: Fix daemon plugin manager unit test + * #6109: Fix history manager unit tests + * #6109: Recording filename in daemon and client for history items + + serialization + * #6109: Refactor AudioFile to play recorded call + * * #6104: AudioCodec moved to sfl namespace + * * #6099: remove active flags from codec classes + * #6095: Add notification-daemon as a runtime dependencies for rpm + packages + * #6095: Fix fedora 15 compilation in MineParameters.h + * #6095: Declare static variable explicitely for client + * #6095: Add logs to build OSC build machine + * * #6098: global variables should have file-scope to avoid name + conflicts + * #6095: Fix compilation error for Fedora 15 + * #6095: Update SFLphone version to 0.9.14 + * #6095: Add specification file in opensusse build service for + sflphone-plugins + * #6073: Fix sflphone-plugins build on launchpad + * #6093: Rename CodecDescriptor for AudioCodecFactory + * * #6089: fix warnings in make check + * * #6086: renamed codecs methods to audio_codecs + * * #6085: renamed codec related dbus calls to audio_codec + * #6065: Remove g_print from client, use DEBUG instead + * #6065: Add actions name for addressbook + * * #6085: renamed codecs* widgets/functions audiocodecs* + * #6065: Fix Addressbook runtime warnings + * #6065: Replace Codecs tab for Audio in account preference dialog + * #6065: Fix "transfert" typo + * #6065: Fix addressbook action runtime warning in uimanager + * * #6082: fixes make check by adding libcrypto libs to test + dependencies + * #6073: Rename plugin/addressbook folders for addressbook/evolution + in sflphone-plugins + * #6074: Removed AC_SUBST from configure.ac when using + PKG_CHECK_MODULE + * #6073: Fix sflphone-plugins package build + * #6073: Fix sflphone-common build + * #6065: Fix runtime gtk warning when initializing searchbar without + addressbook + * #6063: Fix mozilla-tellify gitignore + * #6063: Remove stream copy file using ifdef macro + * * #6012: fix make dist for sflphone-common + * #6063: Update .gitignore file + * #6058: Fix base64 encoding related warnings + * #6056: Fix SdpException handling + * #6055: Fix unknown pargma warning for gcc <= 4.5 + * * #5949: test gcc version before disabling unused-but-set warning + * #6054: Fix addressbook plugin compilation warning + * #6048: Fix uimanager static initialization + * #6046: Fix addressbook factory static initialization of member + addrbook + * #5979: Fix implicit function declaration warning + * #6042: Fixed discarding qualifier warnings in client + * #6041: Fix instant messaging unhandled case warning + * #5994: Implement set current addressbook name and search type in + addressbook plugin + * #5994: add rules for launchpad packaging of addressbook plugin + * #5994: Fix addressbook plugin configuration loading + * #6027: Fix addressbook enabled test from configuration + * #6027: No need of gnomedoc related macros in addressbook plugin + * #6027: Add NEWS file required for build + * #6027: Add addressbook plugin autogen.sh script + * #6027: Remove plugins from client + * #6027: Add sflphone-plugins folder at project's root level + * #5994: Move addressbook folder from contacts to plugin folder + * * #6011: removed unused Makefiles + * * #6010: remove unused headers + * * #5952: fix "string constant to char*" warnings + * * #6009 fixed warnings + * * #6003: finished cleanup of account classes + * * #6003, #6004: cleanup of account classes, defaultAccount no longer + global + * * #6000: fix memory leak of args object + * * #5998: removed using namespace std from networkmanager + * * #5998: removed "using namespace std" from ZrtpSessionCallback + * * #5998: removed using namespacestd from AudioZrtpSession.h + * * #5998: remove "using namespace std" from auriorecord.h and + MimeParameters.h + * * #5998: remove using namespace std in main + * * #5998: removed "using namespace std" from logger + * * #5949: test gcc version before disabling unused-but-set warning + * #5994: Installation of addressbook plugin + * #5979: Implement codec full addressbook search from plugin + * #5979: Implement addressbook factory and plugin + * * #5981: unused webwidget removed + * #5966: Account config synchronization fix (for stun) + * #5954: Handle media name exception + * #5954: Fix audio codec name display in client + * #5954: Clean up getSessionMedia methods + * * #5957: getRecordingSmplRate returns a value + * #5954: Clean up getCurrentCodec methods + * * #5950: remove "converting to non-pointer type 'int' from NULL" + warnings + * #5915: Full gain control version + * * #5949: remove more unused variable warnings + * * #5949: remove unused/unused-but-set variable warnings + * * #5949: show_preferences_dialog returns a success value + * * #5946: cleanup of include directives, undefined function + * * #5515: comment out SSLv2 calls in pjsip + * #5915: Implement different slope for attack tme and release time for + gain control + * #5915: use only one input signal for gain control (removed output + buffer) + * #5921: Fix no audio after holding a conference + * #5916: Add gaincontrol files + * #5916: Implement FFMPEG/CCRTP video streaming prototype + * #5903: Fix call transfer during a conference + * #5915: implement rms detector, first order averager, limiter for + gain control + * #5914: Fix call transfer when no notification request is required + * #5899: Fix conference right-click segfault + * #5884: temporary fix segfault in pjsip memory pool + * #5883: Fix compilation issues on maverick and lucid + * #5755: Fix fedora 15 compilation without patching ccrtp + * [#5855] Make echo canceller optional + * #5855: Fix echo suppression activation/deactivation + * #5855: Implement pjsip echo canceller + * #5814: Speex initialization function uses samples, not bytes + * #5814: Test using more unbalanced signals + * #5814: Fix buffer size for long echo length or long echo delay + * #5814: Adjust level for echo cancellation at runtime + * #5814: Process noise reduction before echo cancelling + * #5814: Implement speex post echo canceller processing + * #5814: Dump echo cancel file to disk + * #5814: Add parameters for echo cancel + * #5809: Add configuration parameters + * #5809: Implement speex echo canceller in audio rtp session + * #5814: Code cleanup + * #5814: Fix conf creation with several incomming ringing calls + * #5814: Fix conf creation segfault when dragging a call on hold on a + ringing call + * #5809: Added unit test for echo cancellation and implemented + "process" virtual method + * #5709: Add always recording option in configuration + * #5709: Add always recording option in audio conference panel + * #5709: Add core functionnality for always recording (missing config + options) + * #5769: Fix conference participant handling (detach/attach) and hold + actions + * #5747: Fix recording icons and state for conference when adding new + participant + * #5769: Code cleanup + * #5769: Fix hangup unsent calls + * #5769: Fix remove/add additional participant to conference + * 5769: Several fixes concerning confererence handling + * #5769: Fix compilation error + * [#5769] Fix audio streams binding in main buffer + * #5769: Removed access to audio mixer from audio layer + * #5765: Fix audio crash for illformated wavefiles + * #5765: Add maximum iteration for finding fmt and data "chunck" + * #5589: Fix compilation of libnotify under + * #5757: Fix abort signal when receiving INFO + * #5747: Add usersDetached.svg + * #5747: Handle offhold action for recording conference + * #5747: Fix off hold action for conferences + * #5747: Implement update conference in record action in calltree + * #5747: Add new icons for recording conferences + * #5747: Add recording state for conferences + * [#5738] Remove getAudioDriver call from manager (replace by + _audiodriver var) + * [#5738] Refactor mutex protecting audiolayer + * [#5737] Fix HD conference recording + * [#5730] Fix start audio session after changing sampling rate + * [#5714] Fix enter keyboard event for addressbbok and history + * [5695] Fix addressbook combo box update when no addressbook selected + * [#5695] Fix addressbook initialization and search bar update + * [#5695] Add mutex for books_data in addressbook to protect async + calls + * [#5695] Get back addressbook open from uri + * [#5695] Fix absolute addressbook URI for local addressbooks + * [#5695] Implement libebook 3.0 interface + * [#5571] Better logic for hangup (for case where call have not been + sent yet) + * [#5571] Update error handling in voip links + * [#5571] Fix compile time warnings + * [#5696] Fix installation dependencies for Natty + * [#5669] Add mention that sflphone.org is for testing only + * [#5693] Add natty in teh dput.conf file + * [#5690] Remove not useful logs + * [#5670] Use dynamic payload type for rtp dtmf + * [#5668] Clean up sflphone configuration logging + * [#5668] Fix hook checkbox configuration update + * [#5666] Fix unit tests + * [#5666] Manage event subscription + * [#5666] Emit bye request when subscription is terminated + * [#5666] Bye request should be sent after event subscription + notification is done on transfer + * [#5666] Make reinvite method static (to be called in pjsip + callbacks) + * [#5666] Hangup Call in manager for AccountNULL and IP2IP + * [#5589] Use PKG_CHECK_MODULE for every client's dependencies + * [#5623] Enlarge initial size of pjsip memory pool for calls (16k) + * [#5564] Fix audio recording resampling for g722 + * [#5571] Move attribute handling for onhold/offhold actions in SDP + session + * [#5571] Codec negotiation refactored and unittested + * [#5571] Implement tests + * [#5571] Implement pjsip negociator + * [#5571] Fix unit tests + * [#5571] Add Fmtp.h to repository + * [#5571] Integrate mime types and codec factory + * [#5571] Handle exception when SDP negotiation fails + * [#5570] Add sflphoned-sample.yml in repository + * [#5564]: Implement stereo to mono mixing for rigntone + * [#5342] Update audio stream initialization + * [#5514] Restore test ni historytest suite + * [#5514] Fix + * [#5514] Disable test_create_history_path + * [#5514] use pulseaudio in sample config file + * [#5514] Fix test: load history from file + * [#5514] Do not use X + * [#5513] Make unit tests compile successfully + * [#3947] Enable unit tests in Jenkins + * [#5454] Fix build system to handle new version number + * [#5454] Update languages from launchpad + * [#5454] Add --without-celt in OpenSuse build service + * [#5454] Change version number + * [#5331] Added first SDP session tests + * [#5273] Update nightly build version tags to conform dpkg rules + * [#5211] Refactor send register method for iaxvoiplink and + sipvoiplink + * [#3950] Remove call being transfered from calltree + * [#5211] Use appropriate memory pool for transport selector + * [#5211] Fix strict aliasing rules warning in pjsip + * [#5211] Bring back pjsip shutting down sleep to 1000 ms + * [#5211] Fix registration callback segfault when closing the + application + * [#5211] Use the dialog memory pool for Route header in INVITE + request + * [#5211] Add temporary memory pool for findLocalAddressFromUri and + findLocalPortFromUri + * [#5211] Use individual memory pool for dtmfs + * [#5211] SipVoipLink refactoring + * [#3950] Attended transfer for conference calls + * [#5284] Fix DNS resolution for Route with specified port number + * [#5284] Some code cleanup + * [#3947] Fix typo in hudson script + * [#5284] Added sip route to REGISTER, INVITE, BYE request, plus DNS + resolution + * [#5266] Use RTP dtmf as default + * [#5284] Added pjsip_process_route_set after setting routes in regc + structure + * [#5286] Fix parsing error due to long configuration file (removed + max event) + * [#5286] Fix false test in configuration emmiter + * [#5286] Code cleanup + * [#5286] Updated exception handling in configuration system + * [#4969] Fix put SRTP call on hold + * [#3950] Add debug messages + * [#3950] Ability to perform an attended transfer + * [#5276] Fix initialization problem in g722 + * [#3950] Add replace header in SIPVoIPLink::transferWithReplaces + method + * [#3950] Implemented attended method in SIPVoIPLink + * [#3950] Cleanup transaction request received callback + * [#3950] Implement dummy attended transfer in gnome-client + * [#5249] Fix audio samplerate update algorithm for g722 + * [#5249] Fix uninitialized variable used in conditional jumps + * [#5249] Fix conditional jump error in audiolayer (uninitialized + value) + * [#5267] Use autoconf 2.65 as a requirement (instead of 2.67) + * [#5267] Restore manual pjsip configuration and compilation + * [#5267] Autodetect celt version (0.9.1, 0.7.1) + * [#5267] Fix deprecated macros in gnome client configure.ac + * [#5267] Update configuration for libcelt-dev + * [#5267] Fix build autoconf and automake + * [#5227] Deactivate automatic call to astyle after compilation + * [#5242] Hangup every calls before leaving + * [#5237] Will now nightly-build for natty, Karmic deprecated + * [#5229] Use inner class for rtp thread instead of inheritance + * [#5211] Move mainbuffer unbind call in rtp final method + * [#5211] Initialize sip call memory pool using 16 kb + * [#5211] Use call memory pool in session reinvite + * [#5211] Add debug messages + * [#5211] Use and internal pool for calls + * [#5211] Reduce pjsip memory pool usage for stateless error messages + * [#5211] Refactor call deletion + * [#5212] + * [#5208] Refactor codec management for accounts + * [#5168] Remove printf from codec's encode & decode method + * [#5168] Fix celt compilation on launchpad + * [#5168] Fix sflphoned compilation warnings in audiocodec.h + * [#[#5168] Must keep the g722 specific RTP rate to avoid incoming + packet timeout + * [#5168] Fix static/dynamic payload rtp session update + * [#5168] Throw SIPVoipLink Error if codec not instantiated in new + outgoing call + * [#5168] Fix dynamic/static codec payload type ambiguity + * [#5169] Fix doubled IP2IP profile when no config file + * [#4867] Add gtkinfobar in configuration panel + * [#4867] Disable input/output/ringtone selection when using default + alsa plugin + * [#4952] Patches for possible buffer overflows + * [$4885] Fix schemas problem + * [#4885] sflphone-client-gnome.schemas not present during build + * [#4885] Add gconf shemas directories in opensuse build system + * [#4885] Add file/folder ownership for opensuse-factory build system + * [#4906] Fix opensuse-factory build + * [#4885] Update name dependency for libedataserver + * [#4885] Fix non-void function without return in dbus-c++ + * [#4895] Update language translation + * [#4896] Update session timestamp when updating media + * [#4896] Reapply RTP hack for G722 payload type + * [#4896] Update recording sampling rate when updating codec + * [#4897] Save codecs in config for each configuration changes + * [#4895] Do not save config when sflphone quit + * [#4885] Update date for copyright + * [#4885] Deactivate siptest that require more than one sipp instance + * [#4879] Remove inmcoming call notification from IAX + * [#4885] Some cleanup + * [#4874] Add setCancel immediate/deffered for ost::Thread + * [#4879] Fix incoming call notification + * [#4878] Set keyboard focus on searchbar when selecting addressbook + * [#4874] Fixed compilation warning + * [#4874] Fixed compilation warning in sipvoiplink + * [#4874] Fix compile time warning in RTP record handler + * [#4874] Fix conditional jump in SDP + * [#4874] Fix conditional jump based on uninitialized value + * [#4874] Store call id within rtp thread context + * [#4874] Fixed conditional jump based on uninitialised value in + conference + * [#4871] Fix default account fetching + * [#4870] Delete RTP session when Refusing an incoming call + * Restore IP to IP call + * [#4857] Fix audio codec negotiation problem + * [#3947] Adjust ressources allocated to compilation + * [#3947] Disable unit tests in Hudson + * [#4305] Free mutex only when really quiting SFLphone + * [#4859] Update copyright to 2011 in every source file + * [#3218] Character '.' stripped by the caller engine + * [#4854] Fix typos, desktop entry + * [#4847] Apply RTP modification to ZRTP session + * [#4852] Update Karmic and Lucid dependencies + * [#4852] Add Libedataserver and libedataserverui as gnome client + dependencies + * [#4852] Add authentication mechanism for EDS + * [#4851] Fix segfault when closing pulseaudio layer too rapidly + * [#4808] Some otehr cleanup + * [#4808] Made some cleanup + * [#4808] Added mutex in rtp session for codecs and noise process + * [#4847] Update audio processing when updating RTP media + * [#4842] Add support for linking with gold/ld --no-add-needed + * [#4808] Make update g722 related static/dynamic payload logic + * [#4827] Upper limit on the number of contacts to import from EDS is + hard-coded to 500 + * [#4808] Fix put call on/off hold + * [#4808] Implement early RTP start for incoming calls + * [#4808] Audio stream is no longer start within RTP session. + * [#4808] Removed coupling between audio layer and and RTP session + * [#4702] Start audio rtp session as soon as it is created + * [#4702] Init timestamp to 0 + * #4702: Send RTP packets immediately, no need of outgoing queue + * [#4784] Update dbus-c++ version from gitorious + * [#4702] Update RTP timeouts + * [#4702] Lengthen RTP timeouts + * [PATCH] Fixed compatibility with old libtool versions. + * [PATCH] Accept older libebook (Maemo 5 has 1.4.2) + * [PATCH] Fixed double-free error in preferences dialog + * [PATCH] Fixed building of sflphone-common on Maemo5 + * [PATCH] Improved Gnome client initialization error handling. 1. It + no longer segfaults when sflphoned isn't available. 2. User is + provided with GUI error dialog. + * [PATCH] Improved autogen.sh scripts 1. They do not require bash + anymore 2. Added workaround for Debian bug #565663 3. Replaced + manual autotools invocations with single autoreconf call 4. Non-zero + return status on failure + * Revert "[#4468] libtool <= 2.2 doesn't have LT_INIT macro so + AC_PROG_LIBTOOL should be used instead." + * Revert "[#4468] Libebook 1.4 is sufficient" + * Revert "[#4468] Apply big path on dbus communication system" + * [#4468] Apply big path on dbus communication system + * [#4468] Libebook 1.4 is sufficient + * [#4468] libtool <= 2.2 doesn't have LT_INIT macro so AC_PROG_LIBTOOL + should be used instead. + * [#4639] Fix determining default addressbook if this property is not + set in gconf + * [#4639] Fix memory leaks in Addressbook + * [#4637] Fix opening default addressbook at sflphone init + * [#4622] Free yaml events while parsing configuration file + * [#4623] Fix conditional jumps based on uninitialized variable + * [#4622] Fix leaks in yaml serialization engine + * [#4616] Fix addressbook warnings + * [#4514] Adjust RTP timestamp + * #4527: Rename Karmic libyaml and Celt package in debian control file + * #4495: Rework addressbook opening loop + * [#4524] Increment RTP count when sending data + * [#4524] DO NOT start RTP session twice + * [#4367] Use PKG_CHECK_MODULE for celt + * [#4367] Fedora package celt as celt (not libcelt) + * [#4367] Astyling + * [#4367] Update .po files + * [#4367] Fix segfault in gensin + * [#4354] Make celt a direct dependency on launchpad opensuse build + service + * [#4367] Make celt a required package, option --without-celt valid + * [#4367] Fix zrtp timestamping error + * [#4367] Fix audio zrtp timing + * [#4367] Dispatch ZRTP packets + * [#4367] Fix segfault when unloading account map + * [#4367] Fix zrtp session + * [#4367] Implement on packet receive + * [#4367] use symetric audio rtp session, not dual + * [#4367] Reduce packet receive/sent timeout + * [#4367] Reduce RTP timeouts + * [#4367] Move speaker data receive + * [#4367] Move speaker data receive + * [#4367] Move receive speaker data method + * [#4367] Remove debug in rtp session + * [#4367] Fix g722 codec clock rate + * [#4367] Fix noise suppression initialization + * [#4367] Fix segfault in RTP mic fadein method + * [#4367] Refactor mic data encoding in rtp session + * [#4367] Implement RTP main loop + * [#4367] Fix compilation problem + * [#4367] Fix AudioRtpclass using TRTPSessionBase + * [#4367] Fix AudioRtpSession putDtmfEvent shadowing + * [#4367] Fix AudioRtpSession putDtmfEvent shadowing + * [#4367] Refactor RTP session (phase 2) + * [#4367] Refactor RTP session (phase 1) + * [#4367] Remove Redeclaration of SymetricAudioRtpSession in + rtpfactory + * [#4265] Add continue statement in for loop for invalid addressbook + * [#4261] Makes addressbook initialization more robust + * [#4257] Add maverick in build system + * [#4233] Add sdp related unit tests + * [#4233] Add condition and signal in two incoming call test + * [#4243] Fix segfault in AudioSrtpSession + * [#4243] Fix memory leak in AudioSrtpSession + * [#4243] Make audio srtp optional in for incoming call + * [#4243] Add boolean variable to make sure remote crypto context + initialized only once + * [#4243] Add documentation to AudioSrtpSession + * [#4243] Use 80 bits authentication tags by default + * [#4243] Init audio srtp remote crypto context in + call_on_media_update + * [#4243] Move SDP negotiastion in mod_on_rx_request + * [#4243] Implement initLocalCryptoInfo to be called at different + momment + * [#4243] Init init local crypto context in when initializing audiortp + * [#4243] Change key length according to sdes negociation + * [#4243] Associate callid to accountid for incoming calls + * [#4242] Fix no SDES keys in IP2IP calls + * [#4242] Fix no SDES keys in IP2IP calls + * [#4233] Test for call on/off hold + * [#4233] Add two incoming call test + * [#4233] + * [#4233] Add 2 outgoing simultaneous call unit tests + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Fri, 30 Sep 2011 13:44:57 -0400 + +sflphone-client-gnome (0.9.7~rc1~ppa1~SYSTEM) SYSTEM; urgency=low + + ** 0.9.7~rc1~ppa1~SYSTEM ** + + * [#2462] Set explicitly the transport on incoming call too + * [#2462] fix typo + * [#2462] Use different address for SDP and call IP + * [#2462] Use published address in SIP-SDP + * [#2181] Fixed changelog files + * [#2181] Updated spec file + * [#2402] Fix pointer to int conversion warning (atoi) + * [#2402] Remove daemon warnings, make indent + * [#2459] Make sure the stream is opened when the call is answered + * [#2402] Add conference related picture in documentation + * [#2443] Not much ... + * [#2399] Fix dialing display problem + * [#2450] Fix incoming call already in conference crash + * [#2399] Display peer name on the first line and peer number on the + second + * [#2450] Handle 403 FORBIDDEN when refused + * [#2447] Bind offHold/onHold actions to button in gtk client + * [#2447] Bind hangup action to button for conference + * [#2447] Add conference action in gtk client's ToolBar + * [#2381] Disable the password hashing in config file + * [#2402] Cleanup + * [#2366] Set callback to null when deleting Pulseaudio streams + * [#1313] Fix main buffer unit test + * [#1313] Fix audio layer unit test + * [#2315] Hide pw in security tab, display when editing, sync with + basic tab + * [#1313] UnitTest change AudioRtpSession for AudioSymetricRtpSession + instance + * [#2402] Code cleanup + * [#2444] Add debug to catch occasional crash when loading client's + config + * [#2444] Add debug info to catch occasional crash when loading config + dialog + * [#2402] Restore Call menu translations + * [#2403] Use the published address if checked in GUI + * [#2442] Add protection test in sdp + * [#1841] Reapply pjsip patch concerning DNS SRV resolution + * [#2384] Tags incoming call as direct SIP call, if applicable + * [#2402] Change the monkey face + * [#2315] Enable user to display password in clear text + * [#2434] Force optimization level at 2 + * [#2284] Fix dbus_get_all_ip_interface compilation warnings + * [#2431] Popup main window on incoming if applicable + * [$2402] Fix simple warnings + * [#2402] Fix implicit variable init order in LibraryManagerException + * [#2402] Fixing implicit variable initialization warnings in + AudioRtpSession + * [#2402] Revert atoi change, fixing codec list doubled entries + * [#2402] Fix gpointer to gint conversion + * [#2402] Fix pointer casting to integer different size warning in + codec list + * [#2402] Fix warning discarting qualifiers from pointer target + * [#2402] Fix gtk tree view assignement from incompatible type warning + * [#1669] Fix audio recording folder utf-8 non compatibility issue + * [#2414] Clean up debugs + * [#2414] Use transport set in iptoip Account and update it frm + preference + * [#2348] Use macro N_() to mark ui.xml strings as translatable + * [#2414] Rename getSipAddress/setSipAddress functions + * [#2407] Fix volume controls display + * [#2407] Fixes dialpad + * [#2383] Set ip to ip config when clicking apply button + * [#2404] Update call-to script - Maxime Chambreuil + * [#2405] Client handles unknown call in current state as well + * [#2383] Add DBUS signal to send IPtoIP local address and port as + string + * [#2383] Add Ip to IP config change apply call back + * Clonflict + * [#2402] Code cleanup + * [#2383] Do the same for IPtoIP (init localn ip with first in the + list) + * [#2383] Use first interface in the list if local addresss is not + defined + * [#2403] Clean up unuseful addresses/ports + * [#2403] Use the IP profile SIP port as global SIP port + * [#2383] Fix dbus_get_all_ip_interface warnings + * [#2383] Take into account sameAsLocal when loading published address + * [#2383] Tsake into account sameAsLocal option when saving published + address + * [#2383] Update local ip address in ip to ip config + * [#2383] Save ip 2 ip local port in config + * [#2406] Update toolbar at startup + * [#2284] Remove redefinition warnings + speex warnings + * [#2383] Fix security table in account config + * [#2383] Save ip 2 ip network interface parameters in config + * [#2403] Restore sip transport selector + * [#2383] Fix filling the Localt IP Address on account creation + * [#2383] Fix Gtk-Critical when checking STUN + * [#2383] Fix reopening account configuration display issue + * [#2383] Load IPtoIP local address and port in preference iptoiptab + * [#2383] Add LocalAddress and Localport in Preference IpToIp tab + * [#2403] Use the address and port associated to the account as often + as possible + * [#1753] Removed pjsip generated files + * [#1753] Removed remaining milenage lib references + * [#2383] Add _publishedSameasLocal variable in sipaccount + * [#2383] Add PUBLISHED_SAMEAS_LOCAL variable in config + * [#2383] Fix stun set active or not when opening config + * [#2181] Added RPM 64bits dbus patch + * [#2402] Code indentation + * [#2313] Force $(HOME).cache directory creation at startup + * [#2383] Separate network interface and published address in account + config + * [#2400] Change dbus service installation path to libdir + * [#2382] Move TLS related published address options in security tab + * [#2382] Indent accountconfigdialog.c + * [#2181] Install libdbus-c++ in $pkglib instead of $lib + * [#1753] Remove ILBC code and disable it by default in the configure + * [#1753] Remove milenage directory + * [#2382] Fix switching interaface instabilities + * [#2396] Save local ip in account creation wizard + * [#2284] Remove warning on hold + * [#2387] Fixes history searching and filtering + * [#1215] Add samplerate display in the GUI + * [#1663] Voicemail icon reflects voice messages + * [#2395] Fix account registration ( specifically with callcentric) + * [#2386] Strip "sip:" on incoming call, fixing history call back + * [#2181] Updated spec files + * [#1215] Display codec name in calltree instead of status bar + * [#2390] Move back nbCalls and stopStream higher in refuseCall + * [#2392] Fix ringtone during call in IAX + * [#2391] Stop audio streams when there is 0 calls only + * [#2391] Add debug when call state is not valid + * [#2390] Clear returns in IAXvoipLink::sendAudioFromMic() method + * [#2380] Fixing IncomingCallNotification not regular + * [#2339] Query conference at client startup + * [#2339] Working conference querying at startup + * [#2339] Add conference in call tree + * [#2339] Primitives to query conferences at client startup + * [#2320] Add account selection in history + * [#2355] Temporary solution: do not delete pointer when removing + account + * [#2380] Change algorithm in AudioRtp to trigger an + IncomingCallNotification + * [#2274] Comment sdebug in MainBuffer flush method + * [#2274] Add flushMain() in ManagerImpl::addStream + * [#2274] Add getBufferID() method in ring buffer + * [#2274] Fix warning, comment debug in ringbuffer's flush method + * [#2274] Use AudioLayer flushMain() and flushUrgent() in ALSA + * [#2274] Clean up unused variable warning + * [#2274] Protect minbudffer pointer on flushing + * [#2274] Fix playATone method which writing empty buffer in urgent + ringbuffer + * [#2274] Use audio layer flushUrgent and flushMain in createStreams + * [#2274] Use flush audio calls from audiolayer + * [#2274] Flush when peer answered call + * [#2375] Flush main buffer in iax when answering a call + * [#2274] Parse displayname using c++ string method + * [#2375] Flush main buffer when off holding calls + * [#2375] Flush main buffer mon RTP startup + * [#2376] Use now Pulseaudio module-cork-music-on-phone + * Updated OSC packaging + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Fri, 20 Nov 2009 13:59:02 -0500 + +sflphone-client-gnome (0.9.7~beta~ppa1~SYSTEM) SYSTEM; urgency=low + + ** 0.9.7~beta~ppa1~SYSTEM ** + + * [#1933] Cleanup debug + * [#1933] Clean up debug + * Fix mic + * [#1933] Set the IAx format earlier + * [#1933] Move IAX sendAudioFromMic outside if (call) statement + * [#1933] Fix startstream when offhold in iax and add debug concerning + codec neg. + * [#2371] sflphone_notify_voice_mail: minor gettext message formatting + cleanup + * [#2371] select_account_cb: properly gettextize status message + * [#2371] show_account_list_config_dialog: properly gettextize status + message + * INSTALL: Minor tidyup of core install guide + * Add /sflphone-client-gnome/src/icons/Makefile to .gitignore + * [#2181] Updated OpenSUSE files (tmp) + * [#1933] Add debug for codec negociation for iax + * [#1933] Get rid of getMicAvail and getMicData in audiolayer (not + used anymore) + * [#1933] Add "audio codec not determined" error in IAX + * [#1933] Test flush data + * [#1933] Do not need to start audio stream in iax anymore + * [#1933] Protecting pointer + * [#2284] Remove more compilation/execution warnings + * [#2284] Cleanup debug in client, use DEBUG instead of g_print + * [#2284] Clean up uimanager + * [#2370] Remove warnings + * [#2366] Clean up other debug + * [#2366] Clean up debug + * [#2366] Call pa_xfree explicitely in writeToSpeaker + * [#2284] Remove address book warnings + * [#2365] Fixes bad cast + * [#2352] Fix continuous ringing when peer hangup and call not yet + answered + * [#2181] Added version support + * [#2181] Fixed some minor issues + * [#2360] Moved MainBuffer from AudioLayer to ManagerImpl + * [#2352] Makes getMainBuffer() everywhere + * [#2352] Use 50 sec latency on pulseaudio stream creation + * [#2352] Add alsa debug + * [#2359] Update repository documentation + * [#2354] Move pulseaudio disconnectAudioStream after stopping main + loop + * [#2352] Adjust nb byte copied in pulseaudio according to + writeableSize + * [#2352] Specify pulseaudio tlength parameters using pa_usec_to_bytes + * [#2322] Convert italian translation to UTF-8 + * [#2357] Fixes window size + * [#2357] Display only actionnable tool item + * [#2333] Update streams parameters + * [#2347] Use GNOME user settings for Menu and Toolbar appareance + * [#2349] Load/Save properly audio params + * [#2322] Update translations from Launchpad + * [#2181] Added Francois Marier script + * [#2350] Remove non-valid test + * [#2181] Updated launchpad packaging + * [#2333] Fix Pulseaudio Capture + * [#2333] Use pulseaudio ADJUST_LATENCY flag and ALSA RT-SCHEDULING + * [#2333] Pulseaudio Interpolate timing + * [#2333] Change (again) Pulseaudio settings to fit logiteck usb hdw + requirement + * [#2333] Adjust pulseaudio fragment size to 4096 (max sflphone's + frames per buffer) + * [#2284] Remove recurrent compilation warning (g++ linker problem) + * [#2333] Safer Audiostream parameters + * [#2333] Fix alsa playback to reduce underrun + * [#2333] Better audiostream parameters + * [#2181] Updated version management + * [#2333] Exclusive test in playback loop + * [#2181] Updated build system + * [#2333] Less underrun with these value + * [#2333] Update playback audiostream parameters + * [#2333] Lengthen the audio buffer reduce number of underrun in + pulseaudio + * [#2333] Add ALSA recovery functions for underrun (begin) + * [#2333] Add pa_stream_trigger in pulse audio underrun callabck + * [#2048] Reduce prebuffering in pulseaudio (which affect incomming + calls' plbck) + * [#2316] Do not display any icons to the right on the history tab + * [#2333] Comment pa_stream_trigger in pulseaudio underrun + * [#2333] Modify pulseaudio streams parameters + * [#2318] Fix transfer tool button double signal + * [#2181] Updated + * [#2333] Fix ALSA ringtone + * [#2333] Flush all main buffer before starting audio + * [#2333] Open/Close Alsa thread between calls while there is no audio + * [#2333] Add debug message and test condition on starting playback + and capture + * [#2181] Fixed gnome client makefile + * [#2181] Updated + * [#2308] Remove getTelephoneTone debug + * [#2308] Change plughw for default in ALSA + * [#2308] Oups, forgot to change function name in audiolayertest.cpp + * [#2308] Cleanup in pulseaudio code (debug, function name) + * [#2308] Fix pulseaudio stream closing assertion failure + * [#2308] Moved pulseaudio mainloop locking from AudioStream + disconnect stream + * [2308] Fix latency at the beginning of a call, when playing DTMF and + wehn starting tone + * [#2181] Updated karmic + * [#2317] [#2319] Fix address book toggle button contextual behaviour + * [#2308] Stop stream when refusing a call + * [#2308] Stop pulseaudio stream when peer hungup + * [#2308] Fix tone and ringtone + * [#2312] Display the STUN entry widget when opening the tab + * [#2308] Implement two different callbacks for capture/playback in + pulseaudio + * [#2309] Open/close pulseaudio connections in startStream/stopStream + * [2308] Leave pulseaudio stream running, do not cork/uncork them + anymore + * [#2295] Set gtk file chooser to None if nothing is set in + configuration + * [#1976] Add codec and conference documentation + * [#2209] Fix recording in regard of resamling + * [#2297] Update .gitignore + * [#2297] Update translation files + * [#2297] Add reference to our coding standards + * [#2297] Remove old docbook code + * [#2296] Reinit tls account settings after modification + * [#2253] Add DcBlocker class to remove capture's dc offset + * [#2034] Fixes for TLS transport to initialize + * [#2284] Add silent build rule + client clean warnings + * [#2274] Fix unserialize history items in cilent at startup + * [#2274] Complete display name parsing and displaying + * [#2274] Parse the Display Name in sip INVITE message + * [#2050] Fix capture volume control in ALSA + * [#1970] Volume controls disable when using pulseaudio + * [#1970] Disable volume controls when using pulseaudio + * [#2277] Fix direct ip2ip ZRTP enabling/disabling in ip2ip + preferences + * [#2181] Added launchpad debian files + * [#2181] Added spec files for OSC + * [#2274] Set display name for "Contact" sip header as the hostname + * [#2181] Fixed daemon issues + * [#2181] Fixed gnome client issues + * [#1976] Remove warnings - need to fix the transfer + * [#2006] Add init is_rec variable in ManagerImpl + * [#2006] Update codec display on call selection + * [#2006] Restore double click actions in history and contact calltree + (GTK) + * [#2176] use XDG_CACHE_HOME when initializing sfl.zid file + * [#1976] Fix calltree switching from history + * [#2209] (Re)Fix cache for zid + * [#2209] Clean up debug messages + * [#2209] Clean debug messages + * [#2209] Fix trasnfering a call during a conference + * [#2209] Speex decode must return the number of bytes + * [#2209] Change frameSize speex 32kHz + * [#2209] Fix speex codec framesize + * [#2209] Reinit converterSamplingRate in RTP sessions + * [#2209] Change speex ultra wide band framesize + * [#1747] Add pixmap data + * [#2252] Fix Receiving a server error 488 crashes the callee + * [#2209] Fix iax low rate packate sending + * [#2209] Clean up debug messages + * [#2209] Add resampling changes for IAX + * [#2209] Clean up resampling code + * [#2209] Fix latency introduced by pulseaudio + * [#2209] Fix initialization of mainbuffer's internal sampling rate + * [#2176] Fix upsampling buffer size in audiolayer + * [#2209] Add dynamic converter sampling rate in audiortp sessions + * [#1747] Fixes runtime warnings + * [#1747] Remove from repo + * [#1747] register our icons to be used as stock icons + * [#2209] Fix number of byte in alsa's write to speaker + * [#2209] Fix putting non-resampled data in RTP's mainbuffer + * [#2209] Add alsa resampler + * [#2209] Add a samplerate converter in PulseLayer + * [#2209] Add mainbuffer's internal sampling rate and flushall method + * [#2176] Add mainbuffer stateInfo debug method + * [#2209] Resampling is optimal using SRC_LINEAR not SRC_FASTEST + * [#2176] Remove debug recordings + * [#2176] Fix Holding a conference participant on new calls + * [#2224] Add confID in callable object + * [#2176] Fix putting onhold a call participating to a conference when + pressing new call + * [#2176] Reset auidio buffers when adding streams (rtp, audiolayer) + * [#1976] Use xml to describe toolbars - Add a naviguation toolbar + * [#2176] Remove conference default_id in joinParticipant + * [#2176] Display error message in alsa's snd_pcm_avail_update call + * [#2176] Alsa mic avail data debug + * [#2176] Add some debug message for mic loss problem + * [#2176] Flush mic ring buffer when offholding a call + * [#2176] Reset ringbuffers' readpointer when adding main participant + * [#2176] Fix getAvailData algorithm + * [#2176] Reset ringbuffer's readpointer when adding a new participant + to a conference + * [#1744] Regex object renamed to Pattern. Previous attempt at + providing + * [#2176] Fix detach main participant problem when adding new one + * [#1976] Use right domain to translate + * [#1976] Add xml menu description + * [#2176] Store a list of confernece participant in client + * [#2176] Fix add participant, joinparticipant methods + * [#2181] Do not install dbus-c++ headers + add return value + * [#2176] Fix minor call handling instabilities + * [#2174] Fix incoming IP call contact address + * [#2211] Add test to protect NULL pointer + * [#1163] Add Advanced account configuration section + * [#2176] Add some usefull comments and debugging info + * [#2176] Add conditions to display security icons in conference + * [#2176] Fix detaching one participant while keeping communication to + others + * [#2176] Reenable userActive.svg in call tree + * [#2176] Make user active blue (not red) + * [#2176] Fix user active picture + * [#2176] Fix "hidden" merge conflict in sipvoiplink + * [#2176] Remove iax audio stream on peer hungup + * [#2174] Multiple UDP transports functional (TESTED with 2 accounts + and 3 calls) + * [#2176] Fix fix audio stream binding in iax + * [#2174] Create a default UDP transport + use tp selector for dialogs + also + * [#2176] Register iax audio stream in mainbuffer + * [#2176] Fix getAudioCodecName in IAXvoipLink + * [#2176] Fix iax account init + * [#2176] Handle multiple account using the same sip transport + * [#2165] Add .png files + * [#2176] Small fixes concerning dtmf + * [#2176] Fix make uninstall in codecs + * [#2174] remove stund makefile generation + * [#2176] Add conference lock + * [#2174] Add transport selector for multiple accounts + * [#2176] Change userActive picture from red to blue + * [#2176] Fix security pixbuff in calltree + * [#2176] Replace sfl.zid in .cache/sflphone instead of .sflphone + * [#2176] Fix add call description + * [#2176] Remove detach button from toolbar + * [#2176] Fix calltree call description state and state code in + conferences + * [#2176] Fix pulse audio double free + * [#2176] Fix conference selection + * [#2174] Clean up - remove stun settings in client network + configuration panel + * [#2174] Remove voviva stun code + * [#2174] Rsolve STUN with pjsip - DO NOT WORK + * [#2165] Add user svg + * [#2165] Debugging sip call failed + * [#929] Link against uuid if installed + * Oops + * Fixed bugs related to libsexy (with GTK < 2.16) + * [#929] Remove uuid-dev dependency in the core + * [#2165] Debugging no negociated codecs at communicatio start + * [#2165] Fix calltree bug (gtktreestore instead of gtkliststore) + * [#2165] Fix several merge problems + * Updated opensuse packaging script + * [#1163] Add missing figures + * [#1163] Update INSTALL file + * [#2165] Fix IAX + * [#2165] Add recordabe interface + * [#2165] Finish recording refactoring for call (not for conference) + * [#2165] Enable speaker recording for two different calls + simultanously + * [#2165] Implement call recording using the Recordable interface + * [#2165] Add get and set to AudioLayer's audio recorder + * [#2165] Add class recordable from which inherit call and conference + * [#2006] Fix G722 and Speex 8khz codec conferencing + * [#2006] add recording of audio buffers + * [#1163] Add general settings section + * [#1163] Fixes makefile error + * [#2006] Fix some minor issues + * [#2006] Drag a conference call on another conference call + (difference conferences) + * [#2006] Fix dragging a conference on itself + * [#1744] Integrating some of the needed regular expression patterns + in order + * COmplete call features + * [#1744] Added support for named subgroup in the Regex object. Also, + new + * [#1744] Adds thread safety features, compile() and setPattern() + methods to the Regex class. + * [#1744] Fix inconsistency in the finditer method from the last + commit. + * [#1744] Added regex pattern object built on top of libpcre. To be + used + * [#1744] Initial commit towards implementing RFC4568. Unimplemented + in the + * [#2157] Hide "security" and "advanced" tabs for IAX under account + * [#1163] Add call features section + * [#2006] Add joinConference capabilities + * [#2006] Add dbus joinConference signal + * [#2006] Drag a conference call onto a conference to add it + * [#1163] Add addressbook section + * [#2006] Drag a conference call onto a single call to create a + conference + * [#2006] Expand rows automatically + * [#2006] Add minimal multiple conference handling + * [#2006] Add atached/detached conference icons + * [#2006] Add function processRemainingParticipant + * [#2006] Deep refactoring, fix hangup bug + * [#1163] Update documentation - Accounts part + * [#1976] Integrate user doc to gnome client build system + * [#2122] Remove double inclusion in dbus-c++/src/Makefile.am + * Remove pjproject version number + * [#2006] Fix peerHungup + * [#1976] Make Yelp accessible from the GNOME client (need to install + the sflphone.xml first) + * [#2006] Fix multiconferencing hangup + * [#2006] Fix hangup calls in a conference + * [#2150] Make IAx2 reappear + * [#2006] Fix detach participant on multiple call + * [#2006] Can remove rining call from a conference + * [#2006] Reinit confID when removing a participant + * [#2006] Remove get isCurrentCAll in hangup/peerhungup (SipVoipLink) + * [#2006] Fix refuse call + * [#2006] Fix answerring incoming call + * [#2006] Refactor conference's participant list + * [#2101] Re-integrate test compilation in main build system + * [#2101] Make the test directory compile + * [#2136] Restore history functionality + * [#2006] Fix binding main participant to himself + * [#2006] Fix add current/incoming/onHold participant to an existing + conference + * [#2006] Fix add incoming calls to an already created conference + * [#2006] Fix remove stream + * [#2006] Fix detachParticipant/removeParticipant switchCall ids + * [#2006] Fix adding a call in conference having state "CURRENT" + * [#2006] Remove/add main participant from conferences + * [#2006] Hold/unHold conference + * [#2006] Detach a partcipant from drag n drop + * [#2006] Hangup a conference + * [#2006] Add hold/unhold conference dbus messages + * [#2034] gtk-ui fix under the "basic" tab. + * [#2006] Fix dragging calls on conference calls + * [#2006] Fix detach participant from a conference + * [#2034] Added default message is status bar under the account config + dialog + * [#2112] Fix a crashed caused when a non-md5 password was sent to + pjsip. + * [#2006] Detach participant by ID + * [#2006] Fix addParticipant method in managerImpl to handle + incoming/answered calls + * [#2006] Add addParticipant method in managerimpl and related dbus + messages + * [#2111] Added the ability to configure zrtp on sip.sflphone.org from + * [#2106] Fixed problem in the account assistant under gtk-ui. Also, + assistant.c + * [#2006] Fix dragging a conference call on another conference call + (same conference) + * [#1904] Small UI fix. Assistant was moved from "Call" to "Edit" + menu. + * [#1904] Fix a wrong label under gtk-ui. + * [#2034] Renaming and source code splitting. + * [#2034] Status bar added to account window to better reflect the + registration + * [#2006] Make calltree_remove_call recursive (for GtkTreeStore) + * [#1110] Small gtk-UI fix in the account window (alignment). + * [#2006] Fix remove conference, display children which are still + active + * [#2006] Recursive function call in calltree_update_call + * [#2006] Add multilayered capabilities to calltree (GtkTreeStore) + * [#2006] Implement remove conference in calltree + * [#2034] Now useless as Direct Ip calls settings moved under + Preferences. + * [#2034] Edit/add buttons were set insensitive all the time under + gtk-ui. + * [#1887] Information about the state of the current SIP call is + displayed + * [#2006] Add call tree remove callback + * [#2006] Fix create_conference function + * [#2006] Update conference_added_cb to add new conference to the list + * [#812] Added new tab under GTK-ui Preferences. Moving Direct Ip + Calls from + * [#2121] Disable temporarily test compilation + * [#2006] Fix conferencelist to handle conference_obj_t instead of + gchar + * [#2006] Add conference_obj structure + * [#2121] Update version + * [#2006] Fix conference selection + * [#2101] Use the new source tree to fetch the right object files + * [#2006] Add conference in calltree + * [#2006] Add Dbus signal conference added/removed/changed + * [#2006] Add getConferenceDetails call on dbus + * [#1904] Registration expire now appears as a spin box under gtk-ui. + * [#812] Fixing a segmentation fault caused by a non-existing account + ID + * [#2006] Add getConfList method over dbus + * [#2006] Add a conferencelist data structure in client-gnome + * [#812] Defaults value are now sent if a non-existing account is + requested + * [#2006] Add sflphone action sflphone_join_participant + * [#2006] Fix buffer read pointer problem deletion + * [pjsip] Attempt at fixing via header incompatibility with + Freeswitch. + * [#1797] forget something + * [#2006] Add call new state conferencing in deamon + * [#2006] Remove addParticipant method for conference, use + joinParticipant only + * [#1163] Update INSTALL documentation + * [#812] Msec/sec values were not taken into account. + * [#1797] Make pjproject-1.4 compile + * [#2006] Add Detach participant method + * [#2006] Dragndrop fully functional with INCOMING and HOLD call + * [#1797] Add pjproject-1.4 + * [#1797] Remove pjproject-1.0.3 + * [#2006] Get call state in conference related function + * [#2006] Add joinParticipant (conference) method in ManagerImpl + * [#2006] Add joinConference DBUS message + * [#2006] Store the previously selected call_id on dragndrop + * [#2006] Fix GValue pointer unref in selection callback + * [#2006] Store dragged call_id + * [#2006] Update drag_data_received_cb callback to manipulate CallIDs + * [#2006] Add dragndrop signals + * [#2006] Set calltree reordable + * [#812] Adds the ability to create a TLS listener in case the user + requests + * [#812] Adds the ability to configure local/published address from + * [#1883] Move switchCall in onHoldCall function + * [#812] Deals with the published address/port problem when + integrating TLS. + * [#1883] Switch call id in managerimpl when peerHungUp + * [#1883] Switch call id before hangup + * [#1883] Add usefull and permanent debug info for conference + cretion/deletion + * [#812] Fix various segmentation faults related to Direct IP kind of + calls. + * [#1883] Fix deletion of std::map elements using iterators + * [#2014] Add libzrtpcpp build dependency + * [#1883] Still some for loop test ambiguity (while loop instead) + * [#1883] Fix for loop initial test ambiguity (use while loop instead) + * [#1883] We must discard data in urgent ring buffer if data is get in + mainbuf + * [#1883] Fix availForGet same id for ringbuffer and readpointer + * [#812] Match "sips" as a Direct IP Call when the user enter a sip + uri + * [#812] Fix segmentation fault related to SIP URI creation. + * [#812] Towards integrating multiple tls listeners at the same time. + This + * [#1883] Add debug messages in conference and fix mainbufferTest + * [#812] gkt-ui fix. Private key must be fed as a filename and not as- + is. + * [#812] TLS integration within sipvoiplink and pjsip. Also, + configure.ac + * [#1883] Fix Alsa/Pulse mallocation + * [#1883] Fix data corruption in AudioRtp's micData buffer + * [#812] Full dbus integration for all the tls related options under + gtk-ui. + * [#1883] Fix memory leaks in audiortp session + * [#1883] Fix mem leaks in audio rtp + * [#812] Fix setAccountDetails where TLS_ENABLE was set to the value + * [#812] Small gtk-ui fix. + * [#811][#812] Small gtk-ui fix. + * [#812] Introduced a mechanism for configuration files that makes + possible + * [#812] New dbus bindings added. Also, configuration compliance was + enforced + * [#1881] Remove default buffer from MainBuffer (update unit-tests) + * [#1881] Add ring buffer read pointer tests + * [#1883] Fix issues in ringbuffer reader pointers + * [#2034] Implementing a new configuration dialogue for TLS transport + settings + * [#1883] Add some usefull debug and safety checks + * [#2028] Notify the client with libnotify when the zrtp negotiation + failed. + * [#811] Harmless no to throw an exception, an makes the application + less + * [#2028] A minidialog is showed to the user under sflphone-client- + gnome + * Removed useless file. + * Ignoring Makefile in src/widget + * [#2027] Fix segmentation fault when showMessage callback is called + after + * [#2026] keyExchange was set to ZRTP instead of "1" + * [#2024] Fix the wrong summary at the end of the assistant. + * [#1883] Fix mnagerimpl conference map insertion + * [#1883] Add Mutexes in MainBuffer + * [#811] Gtk ui was not presenting the right information about zrtp + for + * [#2023] security icons were not installed in sflphone-client-gnome. + * [#2021] Fix a mistake in the readme from sflphone-common that gives + wrong + * [#811] The current SRTP mode was not properly displayed for the + IP2IP + * [#1743] Re-implementation of the "automatically remove error dialogs + [...]" + * [#2017] [#2019] Fix the inability to dial a number and place a + registered + * [#811] Final re-integration of ZRTP support in the main branch from + 0.9.6 + * [#1883] Fix map insertion methods + * [#811] Combo box now is now set to the active key exchange method + * [#811] ZRTP options now configurable back again from the Gtk UI. + IP2IP + * Updated hostname for git clone + * [#1883] Add minimal functionalities to create a conference + * [#811] re-integration of all the methods and signals on dbus. + ManagerImpl + * [#811] Got out of a precarious position were nothing would compile. + * [#1976] Build documentation squeleton with docbook + * [#1883] Add sflphone-client "addParticipant" button for conference + * [#1994] Better organize the source directory structure. New + subdirectories + * [#1883] Add a simple Conference class + * [#1882] Use static audio buffer in Pulse and ALSA layer (instead of + malloc) + * [#811] First commit toward re-integration and refactoring of ZRTP + * [#1882] Flush RTP ring buffer before entering mainloop + * [#1882] Fixed MainBuffer::UnBinCallID() in case there is no + ringbuffer + * [#1882] Test (and fixe) high level conference and mixing + functionalities + * [#1772] Apply patch to compile on fedora (sent by Marcin + Zajączkowski <mszpak@wp.pl>) + * [#1882] Update Bind, unBind call_id in MainBuffer + * [#1959] This adds the ability to store password as an MD5 Hash in + the + * [#1538] Fixes rules compilation + * [#1930][#1931] Fixed a mistake (again) related to index and + credential count + * [#1753] Remove ILBC from pjproject - Hacks in pjsip + * [#1930][#1931] Credential was not selected properly using realm + * [#1882] Finilize multiple reading pointer in RingBuffer + * [#1538] Remove configure from autogen.sh to respect debian upstream + authors policy + * [#1773] Remove generated files from repo + * [#1791] Use XDG_CACHE_HOME to save pid file + * [#1791] Fixes path to save history + * [#1791] Fix debian installation scripts + * [#1930][#1931] Settings are now taken into account in the server. + * [#1882] Add ringbuffer default ring buffer pointer in methods + involving mStart + * [#1882] Add default ringbuffer pointer + * [#1882] Add RingBuffer multiple read pointer basic functionnalities + * [#1882] Fix MainBuffer flushData unit test + * [#1930][#1931] Ability to save and retreive the configuration from + * [#1882] Added Multiple CallID mapping to MainBuffer + * [#1791] Not much + * [#1791] If XDG env variables are not null but empty, use default + ones + * [#1791] Make XDG_CONFIG_HOME writable + * [#1930][#1931] Partial commit. Not working yet. Cannot delete + account + * [#1881] Fixed alsa capture latency problem + * [#1881] Fixed Alsa capture temporarily + * [#1930] [#1931] Partial unbroken commit providing the ability to + * [#1881] MainBuffer implemented in AudioLayer/AudioRTP + * [#1881] Add discard and flush unit-tests + * [#1881] Add discard and flush functionnalites to MainRingBuffer + * [#1881] Add availForGet in MainBuffer + * [#1881] Add availForPut function to MainBuffer + * [#1880] Remove AudioRTP* pointer from SipVoIP (reapered while + merging master) + * [#1881] Add a map between call id and coresponding ring buffer + * [#1855] Refresh pot file and upload on Launchpad + * [#1881] MainBuffe now robust to false ids on getData and putData + * [#1881] Fix big big big memory leak + * [#1881] Add getData and putData to mainBuffer + * [#1881] Unit-test basic ring buffer functionnaities + * [#1881] Add class MainBuffer and basic buffer creation unit-tests + * [#1880] Fix call transfer (step2) issues + * [#1880] Moved AudioRtp* pointer from SIPVoIPLink to SIPCall class + * [#1791] Add postinst script to keep user data when migrating + config/history file + * [#1797] Make pjsip compile + * [#1777] Code indentation + * [#1791] Use XDG_DATA_HOME and XDG_CONFIG_HOME for sflphonedrc and + history + unit tests + * [#1746] Useless space does not appear anymore when volume sliders + and + * [#1643] GtkCheckMenuItem is used instead of icons for elements in + the + * [#1110] [#1668] STUN parameters are now located in the preferences, + under + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Fri, 06 Nov 2009 11:20:01 -0500 + +sflphone-client-gnome (0.9.6-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6 ** + + * Documentation on echo test + * [redmine_down] codec names not displayed in total + * [redmine_down] crash when hanging up a dialing call because tries to + add it to history whereas no starttime + * [#1927] alternate every time screen changed to call history + * [#1886] clean code + * [#1886] debug messages when loading history removed + * [redmine_down] sflphone-kde icons + * [#1855] Update language files + * [#1502] Update version number + * [redmine_down] setHistory at close + * [#redmine_down] Handle PJ_DECLINE_SC as failure + * [#1923] Fix segmentation fault when adding a new account + * [#1923] Check on iterator before setting the config + * [#1904] Added mnemonic to tabs in sflphone-client-gnome. + * [#1905] The daemon was not sending the currentSelectedCodec signal + on dbus when answering a call. + * [#1922] Default values set to all account details + * [#1886] Spinbox reg expire enables apply, and address book is not + visible when disabled + * [#1905] Bug fix for segmentation fault caused by an empty string, + * [#1910] Warnings in test directory + * [#1919] Error fixed + * [#1855] Update russian translation - Hussein Abdallah + * [#1910] Remove files + * [#1919] fixed + * [#1777] Code indentation + * [#1918] fixed + * [#1917] fixed + * [#1910] Remove warnings compilation in src + * [#1886] removed AccountListModel in configskeleton + * [#1914] + * [#1911] check previous and new port + * [#1910] Remove compilation warnings in src/dbus and src/history + * [#1910] Remove compilation warnings in src/audio + * [1855] Update german translation - Sven Werlen + * [#1909] removed + * [#1906] Done + * [#1904] The registration expire value is now configurable from the + * Cleaned up debug messages. + * [#1886] separated initCallItem in two functions + * [#1886] reversed error in commit + * [#1886] clean debug + * [#1886] changed Name of classes and files + * [#1886] clean + * [#1870] In call_state_cb (dbus.c:126), _time_stop was overridden by + the actual time. + * [#1884] Added some new gpg flags to prevent tty warnings + * [#1886] Clean audio config dialog + * [#1886] No more compile warnings. + 1 comm + * [#1872] Check if the user input is smaller than PJ_MAX_HOSTNAME. + * [#1886] + * [#1785] Fixed build when no new commit + * [#1852] If chosen by the user, the hostname can now be solved and + used + * [#1871] * and # inverted back + * [#1869] Conditional compilation that checks if + * [#1309] removed test in main + * [#1425] Put actions in SFLPhone window class instead of ui view, + made a separate toolbar for screens. + + -- SFLphone Automatic Build System <team@sflphone.org> Mon, 27 Jul 2009 09:53:19 -0400 + +sflphone-client-gnome (0.9.6~rc2-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6~rc2 ** + + * [#1755] Remove generated file + * [#1753] restore ilbc ... + * [#1866] Methods getSipPort and setSipPort now have an effect on the + * [#1753] make pjsip compile without ilbc. Use ./autogen.sh --disable- + ilbc-codec + * [#1855] Fix error in russian translation + * [#1805] Remove the old flawed signal mechanism which was failing in + * [#1855] Refresh translation + * Spanish translation finished + po README files updated + echo's in + copy-in-clients + * [#1850] Yun made the chinese HK-CN translation + * [#1848] Fix transfer interface bug + * [#1862] At install, kde client installs only french translation file + * [#1841] A new fallback mechanism was added to the internal resolver + in PJSIP. + * Started AccountList model/view + * [#1855] Remove po subdir in Makefile.am + * [#1855] Fix typo error in sflphone-client-gnome + * [#1855] Do not generate Makefile in sflphone-common/po + * [#1855] Copy translation files into both clients dirs + * [#1855] Remove po dir from sflphone-common + * Comments added + * [#1860] mailbox->voicemail... + * make scripts executable + * [#1855] French translation + * [#1855] Chinese zh_HK partially filled... + * [#1859] An unnamed pipe monitored by poll() was added. When we want + to + * [#1855] Sven completed the first part of the german translation + * [#1855] Cantonese manually filled for already translated, almost + equal strings + * [#1855] Merge russian translation + * [#1855] Spanish manually filled for already translated, almost equal + strings + * [#1855] Update german translation in ./lang/de + * [#1858] This problem was fixed by removing a useless line in + * [#1855] merged existing translations in lang/ sflphone.po's + * [#1842] [#1843] An attempt at improving the expected behaviour that + can't + * [#1855] added po folder in gnome client and scripts for copying from + common lang folder to clients + * [#1853] Edit before call does nothing on call history + * Put most language entries possible in common. From 300 to 250 + entries. Stays underscores problem. Scripts for copy in clients. + * commit to merge master + * [#1825] Changed "Bad authentification" to "Authentication Failed". + * common po files + * [#1753] Remove ILBC from pjproject + + -- SFLphone Automatic Build System <team@sflphone.org> Fri, 17 Jul 2009 19:12:58 -0400 + +sflphone-client-gnome (0.9.6~rc1-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6~rc1 ** + + * Update some version number + * [#1792] Creates .sflphone directory with permission 600. Also, + "chmod 600" after + * [#1810] GUI is now notified that the call failed. Also, a segfault + was + * [#1816] Address book search disabled when disabled address book and + enabled it back plus button stays triggered + * codeclistmodel + asynchronous loading of address book + + enable/disable address book + * [#1810] Now checking SDP answer after 200 OK. Still need to + implement full + * [#1794] Can't use the interface during a call + * Updated translation files + * Russian translation integrated + * Codec list model/view started. + * [#1807] Add configure.ac in pjproject-1.0.3 + * [#1787] closeRtpSession added in some places where it should have + been + * Use Item class for contacts and accounts + * Comments + clean code + * [#1794] Improved debug messages + * [#1805] Replaced the old and unreliable mecanism that was was + waiting for + * [#1794] Can't use the interface during a call + * [#1787] For those cases where no registered SIP account is + configured + * [#1797] Make pjsip compile + * [#1787] Minor changes. Removed useless commented line. Changed order + of + * [#1777] Code indentation + * [#1797] Update package generation with new pjsip version + * [#1798] Does not hang up when the call is building up + * [#1797] Update .gitignore with new pjsip version + * [#1797] Remove generated files from repo + * [#1797] Main build system now uses pjproject-1.0.3 + * [#1797] Add pjproject-1.0.3 + * [#1797] Remove pjproject-1.0.2 + * [#1796] Computing time optimization (samplerate conversion) + * [#1787] _audiortp->start() moved away from offhold(), + SIPCallAnswered() + * [#1312] Added new states for calls initialized by other clients + * [#1795] Crashes when adding a new account, checking it and applying + * [#1782] Missing icons + * [#1793] KDE client compilation problem + * Fake ringtone files can no longer be set. + * indentation + * [#1312] Able to fetch to differentiate incoming/ringing call state + * [#1784] Use DESTDIR variable in po Makefile - fix language file + installation + * [#1785] Fixed typo + * [#1785] Fixed changelog update + * [#1759] ./autogen.sh --prefix=/usr --with-debug to use optimization + level 0 + * [#1773] Changed snapshot naming convention + * [#1773] Removed gpg agent use, added repository cache cleaning + * [#1759] Use optimization level 0 for repository, 2 for packages + * [#1777] Code indentation/formatting + * Translated new features in french + * [#1785] Added missing changelog entry + * [#1781] Window title is SFLPhone + * [#1777] Add code indentation/formatting in the buil system + * [#1774] Can't set voicemail number in KDE account creation wizard + * [#1775] Can't modify account information for account created with + the wizard + * [#1771] Add a "Default" button in context menu to disable chosen + prior account + * [#1705] + * [#1224] Remove generated file from the repo + * [#1224] Remove generated file from the repo + * [#1762] distclean target should remove kconfig generated files + (settings.h, settings.cpp). Rename them? + * [#1761] clear history button should really clear history + * Dialpad works. + * Implemented Dialpad widget instead of building it in main view. + * Removed last occurence of the old config dialog, that made the build + crash. + * [#1755] Do not consider G722 as a dynamic payload elsewhere than in + RTP layer + * [#1753] Remove ilbc Makefile generation + * [#1756] Implement a kde configuration dialog with kconfig xt and + kconfigdialog class + * [#1755] fix audiocodec folder parsing problem + * [#1450] Reinit timestamp comparison in RTP, create session in + newOutgoingCall + * [#1753] Remove milenage third party code from pjsip + * New Config Dialog integrated in GUI.(without codecs) + * [#1753] Remove ILBC codec + * kconfig started, tr2i18n -> i18n, icons folder, accountList changed + * [#1705] Fixed Audio RTP thread creation/start + * [#1714] Fix codec negociation result handling + * [#1678] Fix audiortp payload setting + * [#1678] Put bac putData method in rtp + * [#1669] gtk_file_chooser_get_filename() support UTF-8 by default + * [#1735] Add conditions to sdp update call if call declined + * [#1737] substr of recordings destination folder to remove "file://" + should be done in client rather than in daemon + * [#1731] Enlarge audio stream buffer size + * [#1714] Missing true + * [#1317] Fixed Mandriva timeout + * [#1317] Changed tag convention + * [#1317] Cleaned git-dch + + -- SFLphone Automatic Build System <team@sflphone.org> Fri, 10 Jul 2009 15:50:26 -0400 + +sflphone-client-gnome (0.9.6~beta-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6~beta ** + + * spec files for mandriva and opensuse updated with buildrequires + libqt4-dev >=4.3 + * [#1700] Cannot build on ubuntu 8.10 and a few other distribs + * [#1502] Update version number where applicable + * [#1642] Update client icons + * [#1450] Clean up useless debug and comments in sipvoiplink and + audiortp + * [#1450] Remove Semaphore object in AudioRtp thread deletion + * [#1450] Audio RTP init now synchronized with Sip/SDP + * [#1693] kde client crashes when changing codecs order/activation + * [#1450] Deep refactoring of audiortp + * [#1450] setRtpSessionRemoteIp + * [#1689] getCallList at start + * [#1224] Change path in package files + * [#1450] Audio RTP initialized only once, payload and remote ip set + at runtime + * [#1450] Add setRtpSessionMedia and setRtpSessionRemoteIp address + * [#1642] Make GNOME GUI fresher and younger ;) + * [#1686] Status bar displaying used account + * added sflphone-kde icon so that it compiles + * [#1659] Ending a call causes the daemon to crash + * corrected introspection XMLs, po files... + * [#1211] g722 media descriptor in codecDescriptor + * [#1310] Install sflphoned in $(prefix)/lib/sflphone + * [#1502] Do not install test binaries and dbus utilitaries + * [#1224] hack for pjsip build system! + * [#1224] Remove pjsip binaries from repo + * [#1224] Upgrade to pjsip 1.0.2 + * [#1658] About SFLphone (bugs) + * [#1658] About SFLphone + * [#1660] Displaying all dialed numbers in a call + * Tested status bar. + * [#790] Optimize pulse audio streams parameters + * [#1678] Some usefull debug messages for mutex/semaphore deadlock + problem + * [#1669] Add/remove some usefull/unusefull debug + * [#1665] Fix latency related to pulse audio stream openning/closing + * [#1457] Make the menus and panels accessible in french + * [#1457] Improve broken keyboard accessibility in menus and conf + panels + * [#961] Instanciate only once the searchbar icons + * [#961] Restore transfer fonction + * [#961] Filter on the history type OK + * [#961] Fix compilation problems on hardy/intrepid + * [#1157] Commit missing files + * [#790] Reduce number of start/stop streams call on pulse audio + * [#1639] kde client crashes when no account registered + * [#1620] Fix the searchbar + * [#1620] Get back caltree as it was during gtkcritical area + * [#1620] Add history filter reinit function + * [#1335] Add a missing label in address book preferences + * [#1561] Update russian translation - Hussein Abdallah + * [#1605] Fix edit menu french translation + * [#961] Enable to search in the history according to the call type + * [#1449] Searchbar does not work anymore + * [#961] Add popup menu on the entry primary icon for history + * [#1317] Fixed KDE client package dependency + * [#936] speex 32 khz integration completed + * [#936] Use 320 frame size + * [#936] Test using a frame size at 320 smpls + * [#1214] Enable / Disable history + * [#1607] Fix compilation problem for ubuntu 8.10 (libsexy) + * [#1313] Implement processDataEncode processDataDecode in audiortp + * [#1613] codec list order can't be set + * Better handling of localisation + added languages + corrected + warnings + begginning of new config dialog with kconfig + 14px + account leds + * [#1214] Save and load history according to the limit timestamp + + unit tests + * [1609] Fix call number copy/paste feature + * [1607] Restore clear action icon in searchbar + * [#936] Try to decode using 1280 samples + * [#936] Add some debug + * [#936] Add .cpp file + * [#936] Oops Forgot speex 32 khz + * [#1214] Add configuration panel for history + D-Bus calls + * [#1313] Test rtp thread function, frame size, nbbytes, resampling + * [#790] Flush audio data before closing audio streams + * [#1214] History displays local time + * [#1214] Skip empty field on display + * [#1214] Associate an account to an history entry + * [#1342] Get addressbook options sensitive/non-sensitive + * [#1211] Clean up and comments + * [#1211] Get back to 20 ms framesize + * [#1211] Use sendImmediate instead of putData in RTP + * [#1211] Fix nb byte available in RTP + * [#1211] Clear condition on maxNbSamples in RTP + * [#1211] Fix max byte available in RTP session + * [#1211] G722: Use 160 samples per frame instead of 320 + * [#1211] Test using a dynamic payload + * [#1211] Test using a dynamic payload type + * [#1211] Rename size variable (nb_samples, nb_bytes) + * [#1211] Test g722 ip-to-ip sending twice the data lenth + * [#1211] Test g722 ip-to-ip + * [#1214] Do not select an history item by default at startup + * [#1214] Remove some compilation warnings + * [#1214] Handle empty field - remove g_print + * [#1214] Add each history item only once + * [#1214] Handle call timestamps properlier + * [#1214] Do not need timestamp files anymore + * [#1214] Use the saved date for history entry + * Clean up + * [#1214] Client doesn't crash if the D-Bus call fails + * [#1214] Client is able to save its history - still some glitches + * [#1211] Forgot 16000 for g722 + * [#1211] G722 initialization + * [#1214] Save name/number, successfully load the history if no fields + are empty + * [#1499] Fixed destination directory bug + * [#1214] Restore all the functionalities; peer name/number way more + easy to handle !! + * [#1214] Add callable_object instead of call_t, refactoring + * [#1211] Test with polycom soundstation 16000 + * [#1211] Remove C like inline function in g722 codec + * [#1342] Finalize gnome client preference window formating + * [#1214] Retrieve the history when the gnome client startsup + * [#1306] Implement localization for KDE client + * [#1593] enable accounts apply button when account checked/unchecked + * [#1214] Implement the dbus calls on server side + * [#1214] Add serialized/unserialized functions to pass data on DBUS + * [#1342] Formating gnome client configuration windows + * [#1214] Save sucessfully a map of history items + * [#1499] Removed multiple jobs compilation for KDE client (2) + * [#1214] Load history from file into memory, add unit tests + * [#1534] Throws a length_error exception in case URL exceeds + std::string max_size + * [#1499] Removed multiple jobs compilation for KDE client + * [#1565] make account leds smaller + * [1430] Fix dbus debug + * [#1562] crashes when trying to change item of a call of state "OVER" + * [#1116] Fix compilation bug + * [#1317] Added mandriva and opensuse-11 64 bits + * [#1108] Add messges in main window concerning transfer success + failure + * [#1116] Fix compilation problems + * [#1211] g722 Makefile + * [#1108] Client side transferFailed/trasferSucceded signals handling + * [#1211] G722 mostly completed, + * [#1555] make bigger toolbar (24x24) + * [#1551] remove default mailbox number in wizard and disable mailbox + button when first account doesn't have mailbox number + * [#1342] Re-add sflphone manpages + * [#1116] Fix compilation on non-jaunty distros + * [#1317] Fixed opensuse startup sleep + * [#1108] Add a signal in the client to notify successful or failed + transfer + * [#1108] Dbus signals concerning call transfer success/failure + * [#1317] Added opensuse to automatic build system + * [#1223] Fix manpages bug + * [#1060] german translation glitch + * Clean up some gnome client warnings + * [#1547] replace ugly account leds by beautiful icons + * [#1548] add close button that hides windowand just hide on clicking + the cross + * [#1549] put introspec XMLs in the client's source + * [#1312] Implement getCallList D-BUS method + * [#1116] Clear text in history and contacts + * [#1499] KDE integration + * [#1469] Modify header linkers in dbus-c++'s Makefile.am's + * [#1469] Remove examples folder from dbus-c++ + * [#1214] History integration in build system; unit test squeleton + * [#1317] Cleaning + * [#1469] Remove configure stuff in dbus-c++ + * [#1469] Add unofficial mainline dbus-c++ + * [#1469] Remove dbus-c++ from freedesktop + * [#1430] Bring account changed signal/callback back to normal + * [#1060] Update german translation - Sven Werlen + * [#1430] Add marshaller one string define + * [#1430] Send account change signal broadcast using account id + * [#1430] Remove condition on setRegistrationState, cause stun to + crash + * [#1317] Centralized version handling + * [#1317] Fixed version number on sfl-git-dch + * [#1317] Refactoring for new distributions + * [#1215] Fix account order at startup if latency + * [#1088] Restore sip dns srv + * [#1214] Add squeleton for history manager + * [#1430] Add accout id to accout changed method + * [#1430] No connectionStatusNotification (account changed) if no + changes + * [#1538] Add COPYING file + * [#1430] Add audio rtp thread tests + * [#1317] Changed version detection + * [#1538] Document license in libs/stund + * [#1317] Added version files + * [#1538] Apply François patches - debian packages + * [#1317] Updated spec files + * add files + * [#1538] Apply François patches - debian packages + * [#1535] Change program file structure (directory src...) + * [#1317] Updated build system scripts + * [#1317] Cleaning + * [#1317] Copied introspect files to gnome client + * [#1317] Added opensuse to build-system : first-shot + * [#1317] Remove spec files from configure + * [#1317] Added missing prefix + * removed debug for daemon account fix + * [#1430] Add a connection reference which most likely belong to + libdbus + * [#1430] Use shared connection instead of private + * make daemon find the account, added userMatch + * Clean code, add comments... + * [#1317] Fixed packaging rules + * [#1317] Updated autogen + * Updated autogen.sh for pjsip + * [#1526] Set accounts order + * [#1317] Fixed pjsip lib dirs + * [#1317] Updated debian packaging for new pjsip configuration script + * [#1317] Switch to autogenerated guess and sub files + * [#1317] Updated pjsip inclusion in build system + * [#1317] Replaced pjsip guess and sub files + * [#1317] Fixed compilation issues on opensuse 11 + * [#1505] account list seem to crash the application when clicking + Apply very fast... + * [#1456] Add a flag to be replaced in the control files + * [#1456] Added version dependancy handling + * put account alias in AccountWidgetItem rather than in the item with + " " before. + * [#1034] The KDE client should start sflphoned if it is not started + * [#1500] Handle options for notifications and display on incoming + call. + * [#1443] Client should not crash when receive an unexpected + stateChanged signal + * [#1403] Do not stop the notification anymore + * [#1456] Added version dependancy handling + * [#1426] Daemon crashes when get alsa plugin + * [#1422] Improved error messages + * commit for merge + * [#1424] Change logo in tray icon and put a different one when + incoming call + * [#1425] first part done, window title... + * [#1413] add manpages creating and installing in build system + * [#1417] The client should start the account creation wizard if + started for the first time (if config file doesn't exist) + * [#1421] Make volume bars horizontal when dialpad is hidden. + * Changed main window title and fixed a mistake in sflphone_const.h + * [#1412] make debian package building work + * changelog changed. + * Changed addAccount method in gnome client. + * Debian and man folders added. + * [#1388] Change project name from sflphone_kde to sflphone-client-kde + * Better handle of kabc check. + * [#1351] Automatic generation of dbus interfaces in makefile + generated by cmake + * [#1307] Implement "edit before call" in history and address book. + * [#1344] change action_call label in call history from "call" to + "call back". + * [#1308] Implement Hook feature in kde client + * Improved build system. + * #1219 : Add address book configuration page + * Better handling of registration to the daemon. + * #1039 : Add tray icon in kde. + * Issue no 1216 : Double click on item in history or address book + causes call. + * display peer name in call list and call history when called from + address book. + * Address book functionnal with photo displayed. + * Help menu kde available but actions disappeared. All fonctions in + view. + * Address book functionnal but ugly and making its own sort in the + complete address book. + * Account choice on right click, clean out includes, page address + book, fixed bugs... + * Wizard, double click, context menu... + * Removed sflphone_kde.kdevelop.filelist + * Added account creation wizard and translated interface in english. + * Transfer functionnal but ugly. + * transfer not functionnal + * Bug fixed : unholding (UNHOLD_CURRENT, UNHOLD_RECORD) + * Commit functional for push. With install.sh + * Before merge. + * Problem with enable accounts. Account display increased. + * Functional with codec order working , playDTMF. + * Commit functional. + * sflphone_kde/build added in .gitignore. + * complete commit for checkout previous. + * Commit before checkout previous version to check the display + bug(little font everywhere...) + * Functionnal client. Rest : history icons, config icons and + functionalities + * commit before merge asavard for isRecording. + * Call and Automate fusion done and seems to work. + * Commiting before putting Automate class in Call class. + * Functionnal main window without recording, history, voicemail, kio + widgets. + * client kde avec kdevelop. + * Config Dialog almost finished. + * Base of QT client + + -- SFLphone Automatic Build System <team@sflphone.org> Tue, 23 Jun 2009 11:13:42 -0400 + +sflphone-client-gnome (0.9.5-SYSTEM) SYSTEM; urgency=low + + ** 0.9.5 release ** + + * [#1060] FIx bug in chinese translation + * [#1313] git add rtpTest.cpp rtpTest.h + * [#1313] Add init/close rtp tests + * [#1313] Basic instanciation of the rtp layer + * [#1449] Gtk-Critical concerning history filters and new calls + * [#1400] Make the match with the hostname instead of username + * [#1324] Change status bar label for "Using %s (%s)" + * [#1403] Icon size: 60x60 px + * [#1403] Do not remove notification, improve icon quality + * [#1403] Add smaller icon for gnome notifications + * [#1403] Prevent crash when hangup && no notification + * [#1403] Remove all actions on notifications; code refactoring + * [#1451] Use stun.sflphone.org as default STUN server + * [#1060] New po files - need to be translated + * [#1060] Update french translation - Rebuild template file + * [#1456] Add a flag to be replaced in the control files + * [#1454] Make cppunit optional; remove from build deps in control + files + * [#1401] Add libexpat1-dev dependency in control files + * [#1448] Take off these ugly debug messages + * [#1448] fixed getTelephoneTone and getTelephoneFile() called + repeatedly + * [#1406] add liblog4c-dev in build-depends + * [#1409] Restore .desktop icon + + -- SFLphone Automatic Build System <team@sflphone.org> Mon, 25 May 2009 11:34:48 -0400 + +sflphone-client-gnome (0.9.5-SYSTEM~rc2) SYSTEM; urgency=low + + ** 0.9.5 rc2 ** + + * [#1422] Improved error message + * [#1402] Fix pjsip build + * [#1404] Clear GTK-Critical Bug at client startup + * [#1422] Added automatic VM shutdown when building on more than one + VM + * [#1422] Fixed some issues with new changelog generation script + * [#1422] Moved distribution update to specific file + * [#1422] Dropped git-dch, replace by home made implementation + * [#1402] Fix pjsip build + * [#1404] Clear GTK-Critical Bug at client startup + * Changes for name based dbus connection + * Clean changelogs + * [#1343] Gnome: Implement a callback system to handle focus on + different widgets + * Debus Session + * Refactoring Python code, PEP8 + * [#1430] Get back dbus_g_proxy_new_for_name + * [#1430] Get back DBUS_BUS_SESSION type + * [#1430] Dbus fixed owner message binding + * Second test with DBUS owner + * [#1404] Gnome -> Preferences -> Hooks + * [#1404] Gnome -> Preferences -> Recordings + * [#1404] Call History + * [#1404] Gnome -> Preferences -> Address Book + * [#1404] IF the first notification option disable the second + notification + * Dbus with fixed owner does not automatically start the deamon + * Add codec debug tests in pysflphone + * [#1407] Some print info + * [#1407] Add a scenario to pick_up action + * Test client dbus connection to a fixed owner + * Add python dbus test suite + * [#1161] Modified version handling in build system + * [#1314] Test pulse audio and audio streams connect and disconnect + * [#1402] Add info message after configure + * [#1402] Build the daemon with the local pjsip library (vs the + installed one) + * [#1009] Fix Codec Sampling Rate set to zeros + * [#1314] Add mutex to pulse layer audio streams + * [#1314] Refactoring pulseaudio stream to test connect disconnect + * [#1314] Refactoring of pulselayer to test conect/disconnect + * Add debug messages in debus calls concerning account + * [#1314] Add some return values to audio init functions + * [#1406] add liblog4c-dev in build-depends + * [#1409] Restore .desktop icon + * Bug #1405: Fix strings as requested. + * Bug #1404: Fix strings in preferences panel. + + -- SFLphone Automatic Build System <team@sflphone.org> Tue, 19 May 2009 12:08:18 -0400 + +sflphone-client-gnome (0.9.5-0ubuntu1~rc1) SYSTEM; urgency=low + + [ SFLphone Project ] + * [#1262] Updated changelogs for version 0.9.5-0ubuntu1 Snapshot 2009- + 05-05 + + [ Emmanuel Milou ] + * Add some python CLI client code; not really functional + * [#1108] Fix peerHungup method for IP to IP call + + [ Alexandre Savard ] + * [#1108] Correct setting of SIP contact for direct IP call + * [#1108] SIP user agent handles incoming REFER + + [ Emmanuel Milou ] + * Remove website from repository + * Update translation + + [ Alexandre Savard ] + * Sflphone icon's tooltip changed for "configured" instead of + "registered" + + [ Emmanuel Milou ] + * Update translation + + [ Sflphone Project ] + + -- Sflphone Project <sflphone@mtl.savoirfairelinux.net> Tue, 05 May 2009 19:16:13 -0400 + +sflphone-client-gnome (0.9.5-0ubuntu1~beta) SYSTEM; urgency=low + + [ Julien Bonjean ] + * Updated Eclipse stuff + * Improved addressbook config window + * Added sflphone Eclipse stuff + * Implemented addressbook list server side + * Moved dbus stuff in dbus directory + * Updated addressbook configuration + + [ Emmanuel Milou ] + * Remove unuseful installation scripts. Use apt-get build-dep sflphone + instead + * fix bug #1090 + + [ Alexandre Savard ] + * defining speex 16khz + + [ Emmanuel Milou ] + * Remove unuseful file from build system + * Start dns srv resolver + + [ Alexandre Savard ] + * Basic ogg/vorbis initialization + + [ Emmanuel Milou ] + * Handle incoming IP-to-IP invite correctly + + [ Alexandre Savard ] + * speex wideband 16000 + + [ Emmanuel Milou ] + * Better handling of incoming IP to IP call + * DNS SRV resolution functional + * Implement IAX2 incoming URL + * Allow user to make IP call without any accounts configured + * Add a contextual menu to edit a number from the contacts tab + * Add comments, tooltip and new button to the contextual menu + * add delete event, migrate to GTK 2.16 for sexy icons + * Resolve ticket #1118 + * Update suse spec file + * Add phone number cleanup functions, unit tests and panel + configuration + * Add pertinent test that fails + * fix dependencies for suse package + * Add contextual edit menu in history - #1120 + + [ Alexandre Savard ] + * Temporary comit: make speex wideband (16 khz) + * Temporary: shared object for speex narrow band + * Temporary: speex narrowband and wideband coexist + + [ Julien Bonjean ] + * Fixed bug when no book selected + * Fixed addressbook related compilation warnings + * Fixed GTK client remaining compilation warnings + * Fixed segfault when book removed since last sflphone run + * Fixed bug when book is unreachable (ldap error) + + [ Alexandre Savard ] + * Fix codec list in audio config window + * Active/inactive speex codec by payload + + [ Julien Bonjean ] + * Updated gitignore + * Added some comments + + [ Emmanuel Milou ] + * Add callto: handler script for browsers and al. + * Integrate test compilation in the daemon build-system + + [ Julien Bonjean ] + * Fixed g_object_unref warning for pixbuf + * Cleaned too verbose output + * Fixed toolbar update warning + * Added support for asynchornous books open (first shot) + + [ Emmanuel Milou ] + * Add a DBus call to fetch the call details from a call ID - Ticket + #928 + + [ Julien Bonjean ] + * Improved async open books + * Fixed bug #1139 + + [ Emmanuel Milou ] + * Add a way to save account order + * commit missing files + + [ Julien Bonjean ] + * Introduced log4c (ticket #1162) + + [ Emmanuel Milou ] + * Load/save account order functionnal - ticket #813 + + [ Alexandre Savard ] + * Add CELT codec (#1143) + * Make celt frame size 256 (*1143) + + [ Julien Bonjean ] + * Switched everything to log4c (ticket #1162) + * Updated eclipse settings + + [ Emmanuel Milou ] + * Restore adding account - ticket #1172 + * Add liblog4c dependecy - ticket #1179 + + [ Alexandre Savard ] + * Double maxAvailByte for frame size in rtp (#1143) + + [ Emmanuel Milou ] + * Add User-Agent SIP header - Ticket #1173 + + [ Julien Bonjean ] + * Fixed autoresize issue (#708) + + [ Emmanuel Milou ] + * Remove libcppuint dependency for the debian packages + * Look for libsexy only if gtk version < 2.16 - Ticket #1116 + * Remove libsexy dependency for jaunty. ticket #1116 + + [ Julien Bonjean ] + * Introduced unit tests (#1146) + * Updated gitignore + * Fixed Makefile (#1146) + + [ Emmanuel Milou ] + * [TICKET #1112] Add a test on the voice buffer to send through iax + packets + * Remove doublon in dependencies + * Remove warnings from the client test framework + * Update version number to 0.9.5~beta + * Update build-package script + * Add check dependency in build-deps control file field + * Create debian files for the new sflphone-client-gnome + * [TICKET #1212] Add Replaces field in control files + * [TICKET #1212] Fix manpages installation path + * [TICKET #1212] Add maintainer scripts to create alternatives + * [#1212] Update the manpages generation - edit preinst maintainer + script + * [#1212] Fix reference error in manpage + * [#1212] Add missing files on the client side + * [#1212] Fix debian docs files - no TODO file + * [1212] Fix manpage creation problem + * [#1220] Generate client-side glue files and marshaller at + compilation time + * [#1220] Generate server-side glue files at compilation time + * [#1212] Change binary name to sflphone-client-gnome + * [#1212] Update .gitignore to fit the new working tree + * [#1220] Explicitly generate glue files before building the library + * [#1220] Compile dbus directory before audio + * [#1212] Create sflphone-common at the root of the repository + * [#1212] Re-add pjproject + * [#1212] Remove Makefile from repo + * [#1220] Fix Makefile.am + * [#1212] New working directory functional + * [#1212] Update .gitignore + * [#1212] Hack to make pjsip compile.. + * [#1220] Use non-installed binary for dbusxx-xml2cpp + * [#1212] Add descriptive files, remove unuseful scripts from tools/ + + [ Alexandre Savard ] + * Restore speex codecs + * add frame size for celt (#1143) + * add framesize to codec, independant from audiolayer (#1143) + * use codec frame size in rtp (#1143) + * compute fixed_codec_framesize (#1143) + * do not resample if not required (#1143) + * add condition on resampling for decoder (#1143) + * add a condition on bytesAvail == 0 from mic data + * no maximum in rtp decode (#1143) + * compute maximum for decoding (#1143) + + [ Emmanuel Milou ] + * [#1146] Implement unitary tests on the client-side + + [ Alexandre Savard ] + * use float instead of int to compute max nb of sample (#1143) + * add nbSampleMax for unresampled data (#1143) + * make thread sleep during 5 ms insead of 20 (#1143) + * use unix usleep (#1143) + * 50 usecond thread!!!!! (#1143) + * try with the smallest compression (#1143) + * use timer set at framesize (#1143) + + [ Emmanuel Milou ] + * [#1161] Restore changelog version + + [ Alexandre Savard ] + * Remove celt stuff + + [ Emmanuel Milou ] + * [#1161] Update changelog + * [#1220] Add Conflicts: sflphone in debian control files + * [#1179] Add liblog4c3 runtime dependency + * [#1212] FIx typo error in dependency list for itnrepid + * [#1212] FIx .desktop file to point on the right exec + * [#1212] Modify changelog replacing tag + + [ Sflphone Project ] + * "[#1262] Updated changelogs for version 0.9.5-0ubuntu1~beta" + + [ Emmanuel Milou ] + * [#1212] restore changelogs + + [ Sflphone Project ] + * [#1262] Updated changelogs for version 0.9.5-0ubuntu1 Snapshot 2009- + 04-27 + + [ Emmanuel Milou ] + * [#1212] restore changelogs + + [ Sflphone Project ] + * [#1262] Updated changelogs for version 0.9.5-0ubuntu1~beta + + [ Emmanuel Milou ] + * [#1212] restore changelogs + + [ Sflphone Project ] + + -- Sflphone Project <sflphone@mtl.savoirfairelinux.net> Mon, 27 Apr 2009 17:00:03 -0400 + +sflphone-client-gnome (0.9.4-0ubuntu2) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Restore speex and GSM detection + + [ Emmanuel Milou ] + * Fix bug #1090 + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 8 Apr 2009 11:29:15 -0500 + +sflphone (0.9.4-0ubuntu1) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Integrate DBus-c++ and libiax2 in the main build system + * Clean up in the working repository + * Reorder hooks configuration panel + * Protect case when no codecs are active + * Fix some return values + * Add unitary tests for the hook manager (premisces) + + [Yun Liu] + * Update chinese translation + + [Sven Werlen] + * Update german translation + + [Hussein Abdallah] + * Update russian translation + + [Maxime Chambreuil] + * Update spanish translation + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 3 Apr 2009 18:29:15 -0500 + + +sflphone (0.9.4-rc1) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Fix bug while trying to hold/unhold several simultaneous call + * Improve address book build system + * Implement SIP url popup on incoming call + * Improve GTK+ panel configuration + [ Julien Bonjean ] + * GTK+ client refactoring + * GTK+ clean up + * Address book improvment + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 27 Mar 2009 18:29:15 -0500 + +sflphone (0.9.4-0beta1) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Display codec used during conversation on the GUI + * Enable/disable STUN parameters at runtime + * Refactor search bar use + [ Emmanuel Milou ] + * Build system fixes + * Implement SIP re-invite + * Implement IP to IP call + [ Julien Bonjean ] + * Integrate GNOME address book based on evolution data server + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 20 Mar 2009 18:29:15 -0500 + + +sflphone (0.9.3-0ubuntu3) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Both playback and record streams in PA_STREAM_CORKED (pulseaudio) + * Use PLUGHW device for ALSA capture + * Functional IAX and SIP recording for voicemail + * Use the less CPU-consuming interpolator algorithm for resampling + * Display in GTK GUI the codec used in conversation + * GTK GUI use ASCII instread of utf-8 + * Add record menus in GTK GUI + * Put on hold when dialing a new number + * AccountID's are saved in the history + + [ Emmanuel Milou ] + * Integrate DBUS C++, libiax2 in the git repository + * Update website + * Use libspeexdsp only if available on the system + * Updated .gitignore file + + [Cyrille Béraud] + * Account assistant manager improvment + * Add an email request when creating a new account to receive voicemails + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Sat, 14 Feb 2009 13:29:15 -0500 + +sflphone (0.9.3-0ubuntu2) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Add compilation note in README + * Use default ALSA plugin for capture + * Fix the ALSA capture problem one more time + * Clean up debug messages in dbus.c + * Add libspeexdsp dependency + * Remove implicit declaration compilation warnings + * Fix links in the website, add release note + * Change capture for the website front page + * Add alsa devel dependency in build-depends control file field + * Clean up, indentation, try to handle latency problems in iax/pulseaudio + * Remove pjsip generated files from the repo + * Use the previous declared curAlias function in accountwindow + * Fix bug in history call duration when the call fails + * Remove runtime warning in the GTK+ client + * Add librsvg2-common dependency to load SVG under KDE + * Refresh .gitignore + * Update locales files + french translation + * Add configuration panel for future noise reduction + * Add configuration panel for audio record module + * Daemon less verbose; accounts don't try to access STUn options anymore + * Fix typo in configwindow + * Add content in the official website + * use a GTK_STOCK icon for the record button + * Complete description text in the assistant manager + * Add libtool flags in client configure.ac + * Remove unuseful dependency (snd) + * Fix SIP transfer problems + * Remove previous version of PJSIP from the repo + * Upgrade PJSIP to version 1.0.1 + * Add the new website source in the repository + * Use libspeexdsp for silence detection only if available + + [ Loïc Faure-Lacroix ] + * Ajout du logo gpl3 + * Ajout des images + * Ajout de la section screenshot pour le site + * Ajout du favicon dans le header + * Modification des cartes + + [ Alexandre Savard ] + * Clean up <speex/libspeexdsp> + * Small cleanup + * Save Wave fixed + * Fix new call button when recording + * libspeexdsp added + * Recording: default home folder at startup + * Minor changes to config window + * IAX recording fixed + * Set / get recording path, still need some GTK for client + * AudioRecord file name format + * Now recording in HOME folder + + [ Cyrille Béraud ] + * Fix bug in reqaccount.c + + [ Maxime Chambreuil ] + * Update spanish translation + + [Yun Liu ] + * Update chinese translation + + [ Hussein Abdallah ] + * Update russian translation + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Sat, 14 Feb 2009 13:29:15 -0500 + +sflphone (0.9.3-0ubuntu1) SYSTEM; urgency=low + + * Remove debug + * Join thread before leaving + * Fix implicit declaration in reqaccount + * Add REST code to build the request to server + * Fix GValue initialization warnings + * Update version number, fix implicit declaration, fix GTK markup + warnings + * Apply patch to create custom SIP account from our own server + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 06 Feb 2009 19:17:32 -0500 + +sflphone (0.9.2-2ubuntu9) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Speex audio codec preprocessing initialization + * peer hung up segmentation fault solved + * Stop recording when transfering + * Terminate only one call + * Add isRecording() function + * Fix call_icon GTK client + * Fix SIPCallClose() function, recorded file now close properly + * Function terminateSIPCall added in sipvoiplink and managerimpl + * Fix thread destructor + * setRecordingOption function implement in audiorecord + * Record now implemented in Call class + * Record interface complete (on hold erase previous recording) + * Added recButton in client + * Added: record button related icons + * Record button added + * Overload AudioRecord::recData to get mic and speaker data mixed + * Recording now in audiortp::run() method + * Audio recording working in AudioRTP: receiveSessionForSpeaker + * Open/close a wave file when pulse audio stream start/stop + + [ Emmanuel Milou ] + * Fix path for GTK+ icons; clean up + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Thu, 05 Feb 2009 18:27:53 -0500 + +sflphone (0.9.2-2ubuntu8) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Update changelogs + * Fix bug in merge and in Makefile.am + * Terminate only one call + * Disable PJsip shutdown when changing STUN parameters + * Function terminateSIPCall added in sipvoiplink and managerimpl + * Add a timer to the alsa thread to not jam the CPU load + * Fix bug in sipvoiplink.cpp + * Clean shutdown of pulseaudio on quiting + * Fix DTMF at first start with Pulseaudio + * Remove zeroconf from the build system + * Add a library manager + exception handling + * Clean up in the working directory + * Better handling of capture XRUNs + * Restore mic adjust volume on ALSA layer + * Protect device ALSA operation if not opened + * Fix the switching layer bug + * Use dynamic_cast<> to use audiolayer-specific methods + * Open the audio devices only once at startup + * Refactoring of the ALSA part + * Functional plug-in manager + * Use a C++ thread to handle tones and DTMF in ALSA + * Restore IAXVoIPLink, restore Mutex + * Make the plugins registering against the plugin manager + * Migrate to 1->N relationship between voiplink and accounts + * API plugin for registration + * Use C++ thread in SIP, move everything in sipvoiplink + * Complete singleton pattern for the plugin manager + * Add -Wno-return-type compilation flag to remove warnings; Update + version number in configure.ac + * Add the dynamic loading for the plugin framework; integate unittest + + [ Yun Liu ] + * Update rpm spec file + * modify build package script and spec file for suse + + [ Alexandre Savard ] + * Add audiorecorder plugin and testaudiorecorder + * Add audio Recording class, edit global.h + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 04 Feb 2009 14:00:30 -0500 + +sflphone (0.9.2-2ubuntu7) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Update changelog to 0.9.2-6 + * Fix some dbus-glib implementation details on the client side + * Init history after dbus initialization + * Add error checking in useragent; Clean sipvoiplink + * Prevent crash when trying to call an empty number + * Set the volume of the playback stream to PA_VOLUME_NORM at startup + * Fix GTK+ generic value double initialization + * Fix jaunty control file dependency problems + * Fix jaunty control file dependency problems + + [ Yun Liu ] + * Fix bug ticket # 137 + * Tolerant to gsm library of OpenSuse 11 + + [ Sven Werlen ] + * Update german translation + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 23 Jan 2009 17:48:13 -0500 + +sflphone (0.9.2-2ubuntu6) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Migrate STUN configuration to the main config window + * Update french translation + * Other tiny memory leaks + * Fix memory leak in sampleconverter.cpp + * Generate packages from the release branch + * update the build package script + * modify the control files with architecture=any + * Remove valgring uninitialized value + * IAX and SIP use the same global variables to set account + configuration ; fix broken code + + [ Maxime Chambreuil ] + * Update spanish translation + + [ Hussein Abdallah ] + * Update russian translation + + [ Yun Liu ] + * Update translation files + * Fix the bug when user uncheck the account which fails in the + previous registration + * Add stun error status + * Fix bug ticket #143 + * Script for auto-install dependencies + * Fix bug ticket #140 + * Fix bug ticket 141 + * Fix the reregister process when user change the details of an + account + + -- Emmanuel Milou <manu@sulfur.inside.savoirfairelinux.net> Fri, 16 Jan 2009 18:19:05 -0500 + +sflphone (0.9.2-2ubuntu5) SYSTEM; urgency=low + + * Fix memory leak in the pulseaudio callback + * Update debian package generation script + * Warnings removal in GTK+ client + * Clean adjust volume method in alsalayer + * Plug the sflphone playback volume control to the pulseaudio volume + manager + * Display the date in history according to the current locale + * Generate the changelog according to the git commit messages + * Complete header in chinese translation file + * Use the right gpg key to sign the packages + * add debian jaunty jackalope support + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 14 Jan 2009 21:17:20 -0500 + +sflphone (0.9.2-2ubuntu4) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * add german translation + + [ Yun Liu ] + * Fix GUI crash in Ubuntu8.10 64bit system + + -- Yun Liu <yun.liu@savoirfairelinux.com> Thu, 08 Jan 2009 13:08:51 -0500 + +sflphone (0.9.2-2ubuntu3) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * The main thread synchronizes the ringtone thread + * disable custom ringtone for the ALSA layer + * Fix the Makefile.am in man directory, add a SEE ALSO section + + [ Yun Liu ] + * Fix daemon crash caused by the previous patch ( for bug ticket #129) + + -- Yun Liu <yun.liu@savoirfairelinux.com> Tue, 06 Jan 2009 16:18:38 -0500 + +sflphone (0.9.2-2ubuntu2) SYSTEM; urgency=low + + * Fix bug ticket #129 + + -- Yun Liu <yun.liu@savoirfairelinux.com> Wed, 5 Jan 2009 15:54:53 -0500 + +sflphone (0.9.2-2ubuntu1) SYSTEM; urgency=low + + * Migrate from eXosip library to pjsip + * Add multiple SIP accounts support + * Fix ringtones problems + * Add a pulseaudio support + * Improve audio quality with ALSA + * Add chinese translation + * Improve spanish translation + * Migrate to a maintained C++ DBus bindings + * Clean and improve the build system + * Add build-dependency on Perl because we need pod2man to generate manpages + + -- Yun Liu <yun.liu@savoirfairelinux.com> Wed, 26 Nov 2008 09:47:53 -0500 + +sflphone (0.9.1) unstable; urgency=low + * Add a search tool in the history + * Migrate some gtk_entry_new to sexy_icon_entry_new + * Bug fix (Ticket #78): The voicemail password isn't displayed anymore in + the history tab + * Add the SIP registration expire value in the user file. + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Thu, 22 May 2008 11:14:25 -0500 + +sflphone (0.9.0) unstable; urgency=low + * Add history features + * Call date + * Call duration + * Mouse events in the history tab + * Smooth switch from the history tab to the calls tab + * Remove most of GTK-Critical warnings + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 13 May 2008 16:58:25 -0500 + +sflphone (0.9-2008-06-06) unstable; urgency=low + * Audio bug correction: capture stopped after a few minutes of conversation + with USB Plantronics sound card + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Tue, 06 May 2008 16:58:25 -0500 + +sflphone (0.9-2008-05-06) unstable; urgency=low + * Bug correction: account creation with the assistant + * GTK+ warnings removal + * libnotify warnings removal + * Remove aliasing on the SFLphone logo + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Mon, 05 May 2008 16:58:25 -0500 + +sflphone (0.9) unstable; urgency=low + * Clean dependencies ( removal of libboost ) + * Several GTK improvement and updates + -account window + -configuration window + * Migrate from GtkCheckMenuItem to GtkImageMenuItem + * ALSA standard I/O transfers: MMAP instead of R/W + * Fix speex audio quality + * IAX2 protocol + -Fix hold/unhold situation + -Add on hold music + * SIP protocol + -Ringtone on incoming call + -Fix transfer situation + * Add desktop notification ( libnotify ) + * Improve the system tray icon behaviour + * Improve registration error handling + * Register/unregister from the account window takes effect without starting back SFLphone + * Compilation warnings removal + * Call history + * Add an account configuration wizard + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 30 Apr 2008 16:58:25 -0500 + +sflphone (0.8.2) unstable; urgency=low + * Internationalization of the GTK GUI + * English / French + * STUN support + * Slight modifications of the graphical interface ( tooltips, dialpad, ...) + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 21 Mar 2008 11:37:53 -0500 diff --git a/tools/build-system/launchpad/sflphone-gnome-video/debian/compat b/tools/build-system/launchpad/sflphone-gnome-video/debian/compat new file mode 100644 index 0000000000..7ed6ff82de --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome-video/debian/compat @@ -0,0 +1 @@ +5 diff --git a/tools/build-system/launchpad/sflphone-gnome-video/debian/control b/tools/build-system/launchpad/sflphone-gnome-video/debian/control new file mode 100644 index 0000000000..9699203f53 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome-video/debian/control @@ -0,0 +1,21 @@ +Source: sflphone-gnome-video +Maintainer: SavoirFaireLinux Inc <julien.bonjean@savoirfairelinux.com> +Section: gnome +Priority: optional +Build-Depends: debhelper, libgcc1, intltool, autopoint, autoconf, automake, libtool, libgtk-3-dev, libdbus-glib-1-dev, libnotify4-dev | libnotify-dev (>= 0.7), check, gnome-doc-utils, rarian-compat, librsvg2-common, gnome-common, yelp-tools, libclutter-1.0-dev, libclutter-gtk-1.0-dev +Standards-Version: 3.7.3 + +Package: sflphone-gnome-video +Priority: optional +Architecture: any +Depends: sflphone-daemon-video (=${source:Version}), ${shlibs:Depends}, ${misc:Depends} +Provides: sflphone-client-gnome-video +Replaces: sflphone-client-gnome-video, sflphone +Conflicts: sflphone-client-gnome, sflphone-gnome, sflphone-data +Homepage: http://www.sflphone.org +Description: GNOME client for SFLphone, with video support + Provide a GNOME client for SFLphone. + SFLphone is meant to be a robust enterprise-class desktop phone. + SFLphone is released under the GNU General Public License. + SFLphone is being developed by the global community, and maintained by + Savoir-faire Linux, a Montreal, Quebec, Canada-based Linux consulting company. diff --git a/tools/build-system/launchpad/sflphone-gnome-video/debian/copyright b/tools/build-system/launchpad/sflphone-gnome-video/debian/copyright new file mode 100644 index 0000000000..90f090efff --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome-video/debian/copyright @@ -0,0 +1,28 @@ +This package was debianized by Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> on +Fri, 3 Apr 2009 09:47:53 -0500. + +It was downloaded from the git repository of SFLphone: git://sflphone.org/git/sflphone.git + +Upstream Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + +Copyright: + +Savoir-Faire Linux Inc. + +License: + +This software is copyright (c) 2004-2011 Savoir-Faire Linux inc. + +You are free to distribute this software under the terms of +the GNU General Public License version 3. +On Debian systems, the complete text of the GNU General Public +License can be found in the file `/usr/share/common-licenses/GPL'. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklyn St, Fifth Floor, Boston, MA 02110-1301, USA. diff --git a/tools/build-system/launchpad/sflphone-gnome-video/debian/cron.d b/tools/build-system/launchpad/sflphone-gnome-video/debian/cron.d new file mode 100644 index 0000000000..d11e611777 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome-video/debian/cron.d @@ -0,0 +1,4 @@ +# +# Regular cron jobs for the sflphone package +# +0 4 * * * root sflphone_maintenance diff --git a/tools/build-system/launchpad/sflphone-gnome-video/debian/dirs b/tools/build-system/launchpad/sflphone-gnome-video/debian/dirs new file mode 100644 index 0000000000..e2dc98dcb2 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome-video/debian/dirs @@ -0,0 +1,7 @@ +usr/bin +usr/share/applications +usr/share/pixmaps +usr/share/sflphone +usr/share/locale +usr/share/doc +usr/share/man diff --git a/tools/build-system/launchpad/sflphone-gnome-video/debian/docs b/tools/build-system/launchpad/sflphone-gnome-video/debian/docs new file mode 100644 index 0000000000..f757754f07 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome-video/debian/docs @@ -0,0 +1,4 @@ +NEWS +README +ChangeLog +AUTHORS diff --git a/tools/build-system/launchpad/sflphone-gnome-video/debian/manpages b/tools/build-system/launchpad/sflphone-gnome-video/debian/manpages new file mode 100644 index 0000000000..91e6d06b26 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome-video/debian/manpages @@ -0,0 +1,2 @@ +debian/sflphone-gnome-video/usr/share/man/man1/sflphone-client-gnome.1 +debian/sflphone-gnome-video/usr/share/man/man1/sflphone.1 diff --git a/tools/build-system/launchpad/sflphone-gnome-video/debian/postinst b/tools/build-system/launchpad/sflphone-gnome-video/debian/postinst new file mode 100644 index 0000000000..ebee7fa2bb --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome-video/debian/postinst @@ -0,0 +1,7 @@ +#!/bin/sh -e + +update-alternatives --install /usr/bin/sflphone sflphone /usr/bin/sflphone-client-gnome 100 \ + --slave /usr/share/man/man1/sflphone.1.gz sflphone.1.gz \ + /usr/share/man/man1/sflphone-client-gnome.1.gz + +exit 0 diff --git a/tools/build-system/launchpad/sflphone-gnome-video/debian/postrm b/tools/build-system/launchpad/sflphone-gnome-video/debian/postrm new file mode 100644 index 0000000000..e6107444fa --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome-video/debian/postrm @@ -0,0 +1,34 @@ +#!/bin/sh +# postrm script for sflphone +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * <postrm> `remove' +# * <postrm> `purge' +# * <old-postrm> `upgrade' <new-version> +# * <new-postrm> `failed-upgrade' <old-version> +# * <new-postrm> `abort-install' +# * <new-postrm> `abort-install' <old-version> +# * <new-postrm> `abort-upgrade' <old-version> +# * <disappearer's-postrm> `disappear' <overwriter> +# <overwriter-version> +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +if [ "$1" = "purge" ] +then + + # remove the user config file + rm -f $HOME/.sflphone/sflphonedrc + +fi + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/tools/build-system/launchpad/sflphone-gnome-video/debian/preinst b/tools/build-system/launchpad/sflphone-gnome-video/debian/preinst new file mode 100644 index 0000000000..d5e20248d9 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome-video/debian/preinst @@ -0,0 +1,19 @@ +#!/bin/sh +# postrm script for sflphone +# +# see: dh_installdeb(1) + +set -e + +package=sflphone-gnome + +case "$1" in + install|upgrade) + ## Clean up the previous manpage + if [ -f /usr/share/man/man1/sflphone-gtk.1 ]; then + rm /usr/share/man/man1/sflphone-gtk.1 + fi + ;; +esac + +exit 0 diff --git a/tools/build-system/launchpad/sflphone-gnome-video/debian/prerm b/tools/build-system/launchpad/sflphone-gnome-video/debian/prerm new file mode 100644 index 0000000000..5e90217068 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome-video/debian/prerm @@ -0,0 +1,7 @@ +#!/bin/sh -e + + +if [ "$1" = "remove" ]; then + # Remove alternatives symlink set in postinst + update-alternatives --remove sflphone /usr/bin/sflphone +fi diff --git a/tools/build-system/launchpad/sflphone-gnome-video/debian/rules b/tools/build-system/launchpad/sflphone-gnome-video/debian/rules new file mode 100755 index 0000000000..bdb37e4f60 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome-video/debian/rules @@ -0,0 +1,117 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 +export DH_OPTIONS + +package=sflphone-gnome-video + +CXX = g++-4.0 +CFLAGS = -Wall -g +DEB_INSTALL_MANPAGES_sflphone_gnome_video = sflphone.1 sflphone-client-gnome.1 + +configure: configure-stamp +configure-stamp: + dh_testdir + # Add here commands to configure the package. + NOCONFIGURE=1 ./autogen.sh + ./configure --prefix=/usr --enable-video + touch configure-stamp + + +#Architecture +build: build-arch build-indep + +build-arch: build-arch-stamp +build-arch-stamp: configure-stamp + + # Add here commands to compile the arch part of the package. + $(MAKE) + touch $@ + +build-indep: build-indep-stamp +build-indep-stamp: configure-stamp + + # Add here commands to compile the indep part of the package. + #$(MAKE) doc + touch $@ +clean: + dh_testdir + dh_testroot + rm -f build-arch-stamp build-indep-stamp configure-stamp + # Add here commands to clean up after the build process. + [ ! -f Makefile ] || $(MAKE) distclean + +ifneq "$(wildcard /usr/share/misc/config.sub)" "" + cp -f /usr/share/misc/config.sub config.sub +endif +ifneq "$(wildcard /usr/share/misc/config.guess)" "" + cp -f /usr/share/misc/config.guess config.guess +endif + dh_clean + +install: install-indep install-arch +install-indep: + dh_testdir + dh_testroot + dh_clean -k -i + dh_installdirs -i + # Add here commands to install the package into debian/sflphone. + +install-arch: + dh_testdir + dh_testroot + dh_clean -k -s + dh_installdirs -s + # Add here commands to install the arch part of the package into + # debian/tmp. + $(MAKE) DESTDIR=$(CURDIR)/debian/$(package) install + dh_install -s +# Must not depend on anything. This is to be called by +# binary-arch/binary-indep +# in another 'make' thread. + +binary-common: + dh_testdir + dh_testroot + dh_installchangelogs ChangeLog + dh_installdocs + dh_installexamples +# dh_installmenu +# dh_installdebconf +# dh_installlogrotate +# dh_installemacsen +# dh_installpam +# dh_installmime +# dh_python +# dh_installinit +# dh_installcron +# dh_installinfo +# dh_installman + dh_link + dh_strip + dh_compress + dh_fixperms +# dh_perl + dh_makeshlibs + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb +# Build architecture independant packages using the common target. +binary-indep: build-indep install-indep + $(MAKE) -f debian/rules DH_OPTIONS=-i binary-common + +# Build architecture dependant packages using the common target. +binary-arch: build-arch install-arch + $(MAKE) -f debian/rules DH_OPTIONS=-s binary-common + +binary: binary-arch binary-indep +.PHONY: build clean binary-indep binary-arch binary install install-indep install-arch configure diff --git a/tools/build-system/launchpad/sflphone-gnome-video/debian/substvars b/tools/build-system/launchpad/sflphone-gnome-video/debian/substvars new file mode 100644 index 0000000000..566a162f0d --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome-video/debian/substvars @@ -0,0 +1 @@ +plop=0.9.6 diff --git a/tools/build-system/launchpad/sflphone-gnome/debian/changelog b/tools/build-system/launchpad/sflphone-gnome/debian/changelog new file mode 100644 index 0000000000..c26c397938 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome/debian/changelog @@ -0,0 +1,3138 @@ +sflphone-gnome (1.1.0-rc20120607~ppa1~SYSTEM) SYSTEM; urgency=low + + ** SNAPSHOT 1.1.0-rc20120607~ppa1~SYSTEM ** + + * gnome: cleanup logging + * * #12061: gnome: fix video codec list display + * * #12050: gnome: fixed regression in active codec handling + * #11732: Do not align menu with other interface component + * * #11818: gnome: stop daemon on SIGTERM, SIGINT or SIGHUP + * #10304: updatePlaybackScale dbus method uses int 32 bit for size and + position (allow for 24 days long recording playback) + * #10304: Add time lable for seekslider + * * #10725: gnome: seekslider: fixed compiler warning due to missing + header + * * #11732: gnome:calltree: fix warning when searchbar is not created + * * #11252: daemon: removed deprecated zrtp code + * #10725: Consolidate test to determine if there is a recording, + remove log on NULL pointer + * #10725: Remove logging from stop callback in seekslider + * #10725: No need of playback related callbacks in uimanager + * #10725: Remove logging in seekslider when no selected call + * #11732: Align Icons with dialpad in gnome client + * #10304: Make sure that the playback won't be updated after reset + * #10304: Prevent playback widget reset from stoping dtmf + * #10304: Move Playback widget at the bottom of the main window + * #10304: Fix logic to determine outgoing/incoming calls for recording + icon + * * #11685: gnome: fixed memory leaks in config menus + * #10304: Fix reset seekslider that prevent scale to be updated at + first read + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Thu, 07 Jun 2012 16:04:59 -0400 + +sflphone-gnome (1.0.0-rc20110930~ppa1~SYSTEM) SYSTEM; urgency=low + + ** SNAPSHOT 1.0.0-rc20110930~ppa1~SYSTEM ** + + * update kde .gitignore + * Fix bug in volume widget + * More polishing for release + * Bump version to 1.0.0 + * [#7023] Add the ability to load an abstract contact backend in the + library to resolve more data, polish code + * [#7021] More cleanup for release + * Cleanup + * [#7021] Refactor KDE client dbus handling, add a missing call in + daemon and port the DataEngine to the new API + * Remove some annoying debug + * merge language scripts + * remove obsolete 'VERSION' files + * update install instructions + * Add missing translations to gnome + * language update + * Revert "Don't reference count DBus clients, exit core immediately + when one of them request it" + * Don't reference count DBus clients, exit core immediately when one + of them request it + * [7021] Add contact abstraction support + * [#7121] Polishing library (over). Indentation, spacing and naming + are now consistent + * codecs: link to libccrtp, don't use logger + * Fix a daemon bug + * [#7038] Fix adding contact + * * #7037 : stop audio stream after all calls have been hanged up + * [#7025] Add full support for bookmark + * SFLPhone KDE do not destroy history anymore + * Fix config skeleton + * Close the daemon once and for all, no more automatic respawning + * Fix "unregistered account" bug (I hope so) + * Close SFLPhone at the right place, it still respawn, I don't know + why + * Remove dead code + * Fix regressions introduced in the last commit + * Dead code elimination 1/3 + * Fix bug, add "add contact" option, fix warning + * * #7019: Fix IAX codec negociation + * Remove or comment unnecessary/unhelpful debug output + * Fix "same as local" account setting, fix IP2IP LED color + * Add support for some more advanced config options and add missing + config dialog icons + * Fix crash with noise suppressor + * Alternative can now be selected from the call view context menu + * Add drag and drop support, initial context menu and fix 3 bugs in + the account dialog + * Add basic history drag and drop support + * Complete contact support is back + * * #6991 : fix IAX problems + * Fix IAX accounts being disabled by default + * Revert "deb: forge -g flags for pjsip" + * * #5884: Disable debug code in pjsip + * echo suppressor : more assertions + * Don't let the daemon think crypto is enabled when it's not + * Simplify ToneList + * Some progress on contact support + * Remove unused getRegistrationCount() + * remove annoying debug + * revert SIP bit of e27e5c39bad27bae28f574eb2cba7717e8956229 + * Simplify CallManager::placeCallFirstAccount + * Fix crash on hold + * * #6905 : SIP refactor + * gnome client: be sure key exchange is set correctly + * Move code into createSipTransport + * Fix account registration on start + * ManagerImpl::registerAccounts(): simplify + * * #5884: don't mess with pjsip threads in echo suppressor + * * #6905 : simplify udp/stun/tls pjsip transport creation + * Restore and improve support for Call history + * fix launchpad build + * SIPVoIPLink: simplify / refactor + * Fix libwidget linking + * SIP: simplify + * IM : simplify + * gnome: remove some debug + * AudioRtpFactory::stop() cannot fail + * * #6905: simplify SIP code + * pjlib: fix build without SSLv2, fix warnings + * Port history to the new syntax + * Test a dock widget based implementation for contact and history + * Disable SSLv2 support from pjsip and sflphone + * deb: forge -g flags for pjsip + * Fix deb packaging to get debug symbols + * remove debug + * pjproject: update to last stable release (1.10) + * Require gtk >= 2.20 and glib >= 2.24 + * tlsadvanceddialog: simplify + * * #6902 : fix errors spotted by -DGSEAL_ENABLE + * Update daemon dbus XML and port KDE config backend from dbus to + local + * Remove unused but set variables + * * #6929 : fix IM widget, cleanup + * Unconditionally enable debug symbols + * Should fix many KDE issues + * * #6886 : hitting backspace on empty number have no side effects + * * #6905 : fix AudioCodecFactory access in optimized builds (-O > 0) + * Remove unsupported and broken jaunty/karmic packages + * * #6902 : avoid using some gtk deprecated functions + * Update dbus introspection files + * * #6904: removed unused contactmanager + * * #6903 : use correct dbus-cxx package name + * * #6902: don't use individual gtk headers + * Fix a segfault when config is not present + * Merge latest (0.9.13) KDE code. This version is not yet ready for + git master, but better than the previous one + * addressbook : simplify + * * #5659 : sflphone-plugins doesn't depend on libedataserverui + * * #5659 : addressbook doesn't use libedataserverui + * gnome client doesn't depend on evolution + * * #5695: addressbook: simplify + * * #5695: addressbook : remove AddrBookHandle from plugin + * * #5695 : addressbook : remove unused stuff in the client + * * #5695 : addressbook : remove unused stuff, use static mutex + * gnome client doesn't use evolution + * gnome: use proper API to set GTK_CAN_FOCUS + * * #6897: removed unused focus state vars/callbacks + * gnome: fix calls to sflphone_fill_codec_list_per_account + * * #6623: gnome: don't leak in mainwindow + * gnome: mainwindow whitespace cleanup + * gnome: actions.c parameter doesn't have to be a double pointer + * * #6895: fix memleaks, cleanup in accountconfigdialog + * * #6893: fixes segfault in client on clean history + * * #6894: fix leaks, cleanup in sflnotify + * daemon: fixed prints in main + * * #6892: simplify, fix leaks in dialpad + * * #6887: audiopreference creates audio layer + * * #6660: use const char * const, not std::string for globally + visible constants + * * #6852: Preferences now solely responsible for audiolayer creation. + * * #6860: refactor uimanager, also fixes #6865 + * * #6853: hangup as soon as all digits have been deleted + * * #6852: alsa: retry if device is busy + * * #6852: audiolayer creation depends only on preference.audioApi + * * #6850: gnome: fix build for gtk < 2.22.0 + * cleanup in iax + * alsa: typo + * pulse: if we can't peek in audio input, we can't drop samples + * * #6849: show error window if codecs are missing, instead of dying + * EchoCancel: unused, remove + * * #6629 : use number of samples as arguments for audio filters + * * #6629 : remove unused Algorithm interface + * * #6629 : use helper to call alsa functions and display error msgs + * Remove unused type + * * #6841: fix some error handling + * * #6629: simplify AlsaLayer::alsa_set_params() + * Get gdk key definition from header + * * #6828: Replace raw key codes by gdk defines + * remove some debug, enhance some other + * mainbuffer: simplify + * * #6561 : fix phantom call after transfer + * Conference Participant set : simplify + * SIPCall: remove unused functions, make invite session public + * * #6229 : remove malloc/free from pulse audio loop + * * #6629 : simplify pulse callbacks + * * #6629 + * Simplify widgets + * * #6629 : keep the correct audio module when frequency changes + * * #6751: fixed erroneous debug msgs + * callable_obj.h: removed unneeded pthread header + * alsalayer: cleanup + * * #6629: Always restart audio driver when changing parameters (ALSA + only) + * gnome GUI: don't block in DBus signal errorAlert() + * * #6629 : simplify AudioLayer creation + * * #6629 : remove unused and unconfigurable frameSize from audiolayer + * * #6629 : remove unused error message from audio layer + * Fix logic error when switching audio API + * Remove unused AudioProcessing class + * AudioRtpRecordHandler::initNoiseSuppress() : use noiseSuppress + directly + * * #6629 : use DC blocker directly in audio layers + * * #6629 : clean AudioLayer + * * #6629 : don't store mainbuffer inside audiolayer + * * #6629 : correct AudioLayer::notifyincomingCall() + * * #6554: cleanup, refactoring in sipvoiplink + * * #6554: cleanup in iaxvoiplink + * * #6554: throw exception in getSIPCall if pointer is NULL + * * #6554: make some methods of sipvoiplink static + * * #6655: cleanup in managerimpl + * * #6554: refactoring, fix memleaks in sipvoiplink + * * #6478: remove throw specs, cleanup in voiplink + * * #6629 : remove unused AudioDevice + * * #6655: removed more dependencies from managerimpl + * * #6744: simplified numbercleaner + * conference : remove one prototype + * * #6743: fix ip2ip + * Don't give glib warnings if icons are not found + * gnome: fixed includes + * Codec.h: removed unused function + * * #6742 : clean dbus & icons + * * #6699: refactor/cleanup accounts + * icons: cleanup + * timer : use second precision, not millisecond + * calltree_update_clock : use correct type, returns something + * * #6737: fixed typo in dbus call + * * #6737: removed tests for removed API + * * #6737: dbus: fixed bug from merge + * * #6737: cleanup in accountlist + * * #6737: cleanup in dbus + * * #6740 : fix history double free + * * #6740 : remove time updating thread from calls + * * #6737 : use c99 for client + * * #6738 : make history loading faster + * sipvoiplink : don't crash on transfers + * fixed typo + * Remove unused file + * Don't build networkmanager.cpp at all if NM is disabled + * _debug* -> _debug + * * #6554 : simplify sipvoiplink + * hudson: added -x to git clean command + * added git clean to hudson script + * audiocodecfactory: cleanup + * * #6718: refactored setTlsSettings into SIPAccount + * * #6718: removed more unused methods + * * #6718: refactored confmanager code into sipaccount + * remove unused functions + * * #6718: confmanager: removed more unused methods + * AudioCodecFactory : cleanup + * #6697 : Turn callableElement struct into union + * * #6718: confmanager: removed more unused methods + * * #6718: confmanager: removed more unused methods + * * #6718: removed unused dbus methods, refactoring + * * #6699: accounts: cleanup/refactoring + * * #6699: refactoring, cleanup in accounts + * * #6699: more account cleanup + * remove unused autoconf variable + * * #6714: fixed hudson script + * make distclean in hudson + * added || exit 1 to run_tests.sh call + * * #6714: fixed make distcheck for sflphone-plugins + * * #6714: fixed make distcheck for gnome client + * * #6714: fixed make distcheck for daemon + * git: #6698 split the main .gitignore file + * gnome: gpointer is already a pointer + * gnome: calltab_init: use calloc instead of malloc + * * #6699: more account cleanup + * * #6699: cleanup account + * * #6554 : more *voiplink cleanup + * * #6558 : more sipvoiplink simplification + * * #6558: saner loadSIPLocalIP prototype + * gnome: #6623 clean calllists + * * #6692: more audiolayer cleanup + * * #6692: cleanup/refactoring in audiolayers + * * #6692: more forward declarations, AudioThread->AlsaThread + * * #6692: audiolayer cleanup + * * #6692: alsalayer cleanup + * * #6558 : remove account creator + * * #6558 : clean sipvoiplink + * * #6554 : cleanup sipvoiplink + * audiortp: cleanup + * * #6657 : fix launchpad builds for good + * * #6675 : send RTP dtmf events only once + * * #6655: more cleanup + * AudioRtpSession::updateSessionMedia() : simplify + * * #6655: more cleanup in managerimpl + * * #6655: removed more code, cleanup + * * #6655: more cleanup, fixed infinite loop + * * #6655: removed more unused files + * * #6655: removed unused mutex + * * #6655 removed more unused code + * * #6655: removed unused methods + * * #6655: cleanup in main + * * #6663: fixed segfault when off hold from transfer + * * #6658: user's active codec selection is respected + * * #6660: static global string should be static const char* const + class member + * * #6659: use g_strcmp0, not strcmp for vals that may be null + * callable_obj: fix double free + * calltree_display_call_info() : simplify + * * #6657: Fix launchpad builds + * Logger::log() : simplify + * AudioRtpSession : privatize members + * * #6655: more constness, cleaned up/simplified methods + * * #6654: call DBus::_init_threading so that dbus-c++ to make it + threadaware + * set default credentials on account creation + * AudioCodecFactory::scanCodecDirectory() : simplify and correct + * * #6623: fixed typos + * * #6623: fixed more leaks + * * #6623: fixed more leaks + * * #6623: fixed more leaks, don't print codec name if null + * * #6623: more leaks fixed in client + * * #6623: fix more leaks, fixed some warnings + * * #6623: fixed leak in history + * updated gitignore + * initialize dbus dispatcher correctly + * Fix tests, hudson doesn't have a dbus daemon running + * remove unused code + * removeCall() : simplify , fix leak + * stopRtpThread() : simplify + * *CurrentCall : simplify + * Fix memleak + * fix serialization of audio api (pulse / alsa) + * account map : simplify + * remove call from callmap before terminating it, avoid use after free + * * #6630 : don't make DBusManager a singleton + * call: return confID by value + * add back history code deleted by error + * history : reverse logic + * simplify history serialization and remove some debug + * remove annoying debug + * * #6464 : replace cerr with _error + * * #6464: replace cout with logger macros + * replace printf() with logger macros + * update .gitignore + * remove unused function + * update eclipse projects + * uimanager_new() : simplify + * rename directories + * celt: simplify a bit + * Fix CELT configure.ac test + * * #6612 : template speex codecs + * * #6623: refactored conference obj + * * #6623: refactored callable object, removed leaks + * * #6623: more cleanup, fix leaks, make global vars static and rename + them + * * #6623: calltree: fixed memleaks, simplified code. + * audiolayer: init pointer members + * manager: catch exception on invalid hangup + * * #6623: don't leak on calls to create_new_call + * * #6611 : clarify codecs prototypes + * ringtones : .au and .ul files are both ulaw + * * #6611 : make sure samplerate converters are called correctly + * ManagerImpl::switchAudioManager() : simplify + * * #6623: fixed more leaks + * * #6623: fixed more leaks + * * #6623: fixed more leaks + * * #6623: fixed leak, line-endings in imwidget + * * #6627: zero-initialize pointers if they're going to be deleted + * * #6628: don't leak calls on exceptions + * Revert "audiortp: call join after calling stop on RtpThread" + * sflphone-client: more constness + * audiortp: call join after calling stop on RtpThread + * * #6625: return 0 on successful completion + * * #6624: fix segfault on servercallfailure + * * #6621: Fixed double free, unlock mutex in ManagerImpl::terminate + * * #6220: remove audio stream when peer hangs up + * * #6596: AudioSymmetricSession shouldn't self-delete + * resampler: grow internal buffers dynamically + * merge up and down sampling => resampling + * Leave test directory unchanged when running make check + * audio algorithms : remove unused prototype + * ringtone: detect codec from file extension + * *AudioFile : simplify + * * #6596: create local SDP on the stack, not the heap + * * #6596: don't call Ost::Thread::terminate from dtor + * audiofile: cleanup (samplerate -> unsigned) + * remove unused func + * samplerateconverter: cleanup + * RingBuffer::Put() : remove unused return value + * MainBuffer::putData() : remove unused return argument + * audiolayer::putMain() : remove unused func + * AudioLayer::putUrgent() : remove unused return value + * * #6618: delete any remaining ringbuffers in destructor + * RingBuffer::availForPut() : remove + * * #6617: return from main rather than calling exit + * MainBuffer::availForPut(): remove + * RingBuffer: simplify + * alsa : remove write only variable + * fix memcpy declaration + * bcopy(src, dst) -> memcpy(dst, src) + * RingBuffer::Get() : remove constant volume argument + * return a copy of the call ID, not just a reference. + * MainBuffer::getDataById() : remove volume argument (always 100) + * MainBuffer::getData() : remove constant volume argument + * RingBuffer::Put() : remove constant volume argument + * MainBuffer::putData() : remove constant (=100) volume argument + * audiolayer: remove constant _defaultvolume + * AudioRtpRecordHandler / AudioRtpSession : simplify + * mainbuffer: fix test + * iaxvoiplink : simplify + * sip registration callback: fix a dbus crash + * MainBuffer: simplify + * AudioRtpFactory: return cached type of rtp session. The rtp session + can have disappeared if the call was put on hold + * AudioRtpFactory: remove unused setters + * Fix launchpad builds + * * #6611 : remove unused bandwidth codec information + * * #6611: AudioCodec: remove useless/unused setters + * make sure buffer string is initialized correctly + * * #6596: declare certain destructors virtual + * audiolayer : cleanup + * Simplify doc build rules + * * #6270: don't build dbus-api doc with make, should require make all + * configure.ac: cleanup + * Remove copy of dbus-c++ from libs/ + * * #6596: stop clock thread when peer hangs up + * removed unused Fmtp.h + * * #6595: more logical initialization order + * * #6600 : fix account creation + * * #6601 : fix configure.ac tests + * remove unused variable + * Don't mix stack and heap based allocations + * Fix copyright (2009, 2008, 2009 -> 2008, 2009) + * Fix warnings found by clang + * * #6595: fix initialization order for AudioRTP + * * #6592: removed typedef std::string CallID + * * #6586: implement local g_slist_free_full for older glib versions + * * #6579: fix memory leaks in client (there's a lot left) + * ShortcutPreferences::setShortcuts() : simplify + * Fix merge + * * #6548: remove call to non thread-safe strerror() + * AudioRtpFactory: each instance is associated to exactly one SipCall + * create_audiocodecs_configuration() : make static + * * #6269 : refactor AudioRtpSession + * Fix AudioSymmetricRtpSession.h inclusion guard (cherry picked from + commit c3081dce1cc1370d6d3558a4c4ef5cfac0d21caf) + * * #6269: Rename AudioRtpSession to AudioSymmetricRtpSession + * * #6574: Don't exit when connection to pulseaudio server fails + * accountconfigdialog.h : remove some stuff from header + * * #6560: fix configuration test + * Fix warning in test + * * #6560: don't hide password entry in security tab + * * #6560: set initial password for SIP accounts + * * #6506: remove useless pointer indirection + * * 6560: password is now specific to IAX accounts + * * #6560 : actually use, store, restore, transmit SIP credentials + * * #6560: YamlEmitter: serialize sequences + * YamlEmitterException: typo + * ManagerImpl::computeMd5HashFromCredential() : simplify, fix memleak + * * #6561: invite_session_state_changed_cb() : simplify + * * #6561: More useful debug in VoIPLink::removeCall + * * #6561 : fix ghost call reappearing in GUI after transfer + * while -> for (make the code smaller) + * * #6558 : Account::loadConfig() : move IAX code to IAXAccount + * IAXVoIPLink::getAccountPtr : simplify + * * #6554 : access the SIPVoIPLink directly, not per account + * SIPVoIPLink is instanciated only once and is not associated to a + single account + * yamlnode: use const references when possible (still some left to do) + * Account::_accountID: constify + * VoIPLink: simplify, remove unused method + * hudson test : no need to call run_tests.sh anymore + * Remove AccountID type and AccountNULL define + * Make check runs the test (no need to call run_tests.sh manually + anymore) + * gnome GUI: Fix tests + * Revert "Move registration information from SIPAccount to + SIPVoIPLink" + * * #6392: pluginmanagertest: fix warnings reported by valgrind + * * #6547 : remove unused exceptions + * * #6547: CallManagerException: use runtime exceptions + * * #6547: InstantMessageException: use runtime exceptions + * * #6547: do not throw exceptions if some settings are not present in + config file + * * #6547: YamlParserException: use runtime exceptions + * * #6547: VoipLinkException: use runtime exceptions + * * #6547: YamlEmitterException: use runtime exceptions + * * #6547: DTMFException: use runtime exceptions + * * #6547: AudioFile: use runtime exceptions + * * 6547: AudioZRtpSession: remove impossible error case + * * #6547 : AudioRtpSession: remove impossible error case + * * #6547: AudioZrtp: use runtime exceptions + * * #6408 : send authenticationUsername to GUI + * * #6408 : store/restore authenticationUsername from config file + * SIPAccount: simplify + * Move registration information from SIPAccount to SIPVoIPLink + * SIPAccount::getAccountDetails : simplify + * * #6540: yaml parser: simplify + * sdp.cpp : fix a warning + * * #6540: yaml parser : remove std::string typedefs + * * #6540: Simplify yaml unserialization + * * #6540 : add a Conf::ScalarNode constructor for booleans + * setAccountDetails(): simplify + * * #6408: store authentication username in daemon + * * #6408: Be able to set the authentication username in the GUI + * * #6507 : do not crash if the program is not sflphoned + * Fix tests + * macroify SIPAccount::unserialize() + * Move all .cpp files from sflphoned target to libsflphone.la, except + main.c + * main() : simplify, return positive error codes + * * #6507 : find codecs dir in build directory + * * #6392: Sdp: move clean functions to destructor + * AlsaLayer::adjustVolume() : simplify + * alsalayer : reduce indentation + * malloc/free -> new/delete + * malloc/free -> new[]/delete[] + * malloc/free -> new/delete + * AudioSrtpSession: simplify base64 encoding + * * #6392: Initialize std::string from pj_str_t correctly + * * #6392: AudioRtpSession: Initialize remote port + * Audio settings : Initialize _echoCancelTailLength and + _echoCancelDelay(0) + * Initialize variable + * YamlParserException : fix use of stack variable after it has been + deallocated + * * #6392: fix memory leak in history + * * #6392 AudioCodec : fix memory leak + * * #6392 : fix memory leak in sip account + * * #6408: clean up sipaccount (cosmetics mostly) + * sipaccount.cpp serialize() : reduce number of lines + * * #6392: invalid memory access + * * #6392 : fix invalid memory access + * * #6479: merged useful code from MimeParameters into Codec interface + * * #6462: fixed hangup on IP2IP call + * added run_daemon.sh script + * test: remove unused variable + * Remove functions only used by a failing test (cherry picked from + commit fcf718cb75de7f1882dc61c07bb8d300dfa10f85) + * * #6360 : make client tests build (cherry picked from commit + 028b2835f040e51ab8ab979b32732b07b8798fce) + * * #6360 : fix warnings in check_global test (cherry picked from + commit 9e2bd6a7496dd64f6f48595e385760019aab1193) + * * 6360: updated API calls in tests, but they're not building yet + (cherry picked from commit 548f6f0f919b43772a3e9c667e5e292791281795) + * Fixed include in tests (cherry picked from commit + aeadc7525c1e31f936670ac8b02f0bcf387c38a8) + * Remove unused variables and functions + * IAX: fix warnings (cherry picked from commit + fd7a113a11cac2cd9a7c36929e88ad28195c4c35) + * Remove unused DEBUG define which interferes with logger.h (cherry + picked from commit b2f72b91d0f43cb1dd94d138882a8caa9c841c24) + * * #6392: no need to check for account NULLity since it is + dereferenced above + * * #6392: fix a memory leak, replace by stack allocation + * * #6392: remove a variable assignement which confuses cppcheck + * process_conference_participant_from_serialized() : remove unused + function + * * #6392: s/free/g_free/ + * * #6392: fix a memory leak in abookfactory_load_module() + * * #6392: remove generate_call_id() used only once + * * #6392: fix memory leak (opendir() without closedir()) + * * #6392: AudioRecorder(): ensures mbuffer is set + * Remove SFLPHONED_VERSION from global.h, use autoconf PACKAGE_VERSION + * #6298: Cleanup + * #6331: Fix deleting ringtone file after call have been answered + * * #6330: merged user_cfg into headers + * #6298: Fix conference recording file update at conference end + * #6298: Fix record file name serialization for conference + * * #6295: cleanup of codec hierarchy + * #6298: Fix gtk warnings + * * #6300: added script to run tests + * #6109: Add recording playback for conference + * * #6300: tests do not require an installed sflphone + * * #6295: re-removed clone methods + * #6109: Fix gtk_critical warnings for incoming calls + * #6109: Fix GTK_CRITICAL warning + * #6109: Fix icons when history is not activated + * #6109: Fix warnings + * #6109: Implement stop recorded file playback signal + * Revert "* #6295: removed unused clone method" + * * #6295: removed unused clone method + * * #6296: removed non existant file from Makefile.am + * #6109: Stop fileplayback for outgoing call + * #6109: Implement stop recording playback button + * Fix binding names errors in dbus introspection file + * #6109: Implement playback recorded file callback in client + * #6109: Store recorded file path on client side + * #6109: Add dbus methods for call recording playback + * * #6290: remove unused classes from utilspp + * * #6288: cleanup sdp + * * #6288: fix exception usage + * * #6288: simplify SdpException + * * #6288: cleanup in sdp.cpp/h + * #6109: Only display playback button if record file is set and valid + * * 6290: updated configure.ac to remove functor Makefile + * * #6290, #6289: removed unused classes from utilspp, fixed make + check + * #6109: Add button for history playback of recorded file + * * #6289: removed unused observer class + * * #6282: forward declare sdpMedia in sdp.h + * * #6281: renamed setCallAudioLocal->setCallMediaLocal + * #6183: Handle conference with more tahn two calls + * #6183: Fix history icons when calling back a conference from history + * #6183: Fix icons inconsistencies in history for conference hang up + * #6183: Fix toolbar actions when selecting a conference in history + * #6183: Fix conference serialization + * #6268: Serialize only calls + * * #6269: removed useless type testing + * ignore some files in test/ + * * #6268: Remove dead class AudioSymmetricRtpSession + * #6251: Do not had history calls in calllist when loading history + file + * #6251: Fix insertion in history map in before saving history file in + daemon + * #6251: Fix history unit tests + * #6251: Order the list before serailization, get rid of the hashtable + in history + * #6251: Implement history serialization using a list wether than a + map + * * #6253: remove external audioport from header, make all members + private + * * #6253: don't store external local audio port (used for NAT) in + Call + * #6251: Add start_time timestamp in history serialization + * #6251: Fix call insertion in conference items + * #6233: Fix serialized account list terminated with a ";" character + * #6238: Fix draggable history calls into current calls + * #6233: Fix toolbar updates + * #6233: Fix history + * * #6235: remove pyc files from git tree + * #6233: Handle cases when one or manuy calls are unreachable in + createConfFomrParticipantList + * #6233: Handle wrong numbers in createConferenceFromParticipantList + * #6231: Fix drag-n-drop issue + * * #6173 : move sippxml in tools + * #6231: Fix merging issue + * #6183: Implement conference unserialize + * * #6212: remove extraneous flags from globals.mak + * #6183: Unserialize conference data in conference + * #6183: Add account information in request for conference call from + history + * #5755: Add -ldl to liker in sflphone-gnome + * #5755: Fix fedora 15 compilation issue + * #6183: Serialize conference participant phone number and account + * #6183: Add conference timestamp in serialization + * * #6186: don't include global.h, just logger.h + * #6183: Fix saving history to file + * #6183: Fix removing call from calllist + * * #6184: remove pointers to Manager from AudioRtpSessions + * #6183: Calling calltree_add_call explicitely for history + * #6183: Ability to store conference inside history tab queue + * * 6181: remove unused API from sipcall + * #6171: Implment nreCallCreated callback + * #6167: Fix participant list NULL ending + * #6149: First draft of conference creation from history + * #6149: Fix multiple call/conf selection callbacks ... + * #6129: Fix place_call function called twice for pressing enter + action + * #6129: Fix double click action for history + * #6149: Add dbus call for creating conference from history + * #6129: Fix placing call from history and addressbook (still need to + fix icon) + * * #6148: removed unused AudioRtpFactory constructor + * * #6145: remove unused isAudioStarted + * * #6145: remove unused isAudioStarted + * #6129: Add conference into history, fix call/conference selection + * * #6143: don't use getType outside of serialization methods + * * #6132: forward declarations instead of includes + * * #6132: add constness, remove redundant "inline" keywords + * #6129: Add timestamp to conference object to order history entries + * * #6128: remove unused forward declarations from header + * * #6127: make noncopyable class actually noncopyable + * * #6125: don't include AudioRtpFactory in sipcall.h + * #6123: Fix alsa ringback audio file + * #6123: Fix raw audio file loading problem + * #6109: Fix daemon plugin manager unit test + * #6109: Fix history manager unit tests + * #6109: Recording filename in daemon and client for history items + + serialization + * #6109: Refactor AudioFile to play recorded call + * * #6104: AudioCodec moved to sfl namespace + * * #6099: remove active flags from codec classes + * #6095: Add notification-daemon as a runtime dependencies for rpm + packages + * #6095: Fix fedora 15 compilation in MineParameters.h + * #6095: Declare static variable explicitely for client + * #6095: Add logs to build OSC build machine + * * #6098: global variables should have file-scope to avoid name + conflicts + * #6095: Fix compilation error for Fedora 15 + * #6095: Update SFLphone version to 0.9.14 + * #6095: Add specification file in opensusse build service for + sflphone-plugins + * #6073: Fix sflphone-plugins build on launchpad + * #6093: Rename CodecDescriptor for AudioCodecFactory + * * #6089: fix warnings in make check + * * #6086: renamed codecs methods to audio_codecs + * * #6085: renamed codec related dbus calls to audio_codec + * #6065: Remove g_print from client, use DEBUG instead + * #6065: Add actions name for addressbook + * * #6085: renamed codecs* widgets/functions audiocodecs* + * #6065: Fix Addressbook runtime warnings + * #6065: Replace Codecs tab for Audio in account preference dialog + * #6065: Fix "transfert" typo + * #6065: Fix addressbook action runtime warning in uimanager + * * #6082: fixes make check by adding libcrypto libs to test + dependencies + * #6073: Rename plugin/addressbook folders for addressbook/evolution + in sflphone-plugins + * #6074: Removed AC_SUBST from configure.ac when using + PKG_CHECK_MODULE + * #6073: Fix sflphone-plugins package build + * #6073: Fix sflphone-common build + * #6065: Fix runtime gtk warning when initializing searchbar without + addressbook + * #6063: Fix mozilla-tellify gitignore + * #6063: Remove stream copy file using ifdef macro + * * #6012: fix make dist for sflphone-common + * #6063: Update .gitignore file + * #6058: Fix base64 encoding related warnings + * #6056: Fix SdpException handling + * #6055: Fix unknown pargma warning for gcc <= 4.5 + * * #5949: test gcc version before disabling unused-but-set warning + * #6054: Fix addressbook plugin compilation warning + * #6048: Fix uimanager static initialization + * #6046: Fix addressbook factory static initialization of member + addrbook + * #5979: Fix implicit function declaration warning + * #6042: Fixed discarding qualifier warnings in client + * #6041: Fix instant messaging unhandled case warning + * #5994: Implement set current addressbook name and search type in + addressbook plugin + * #5994: add rules for launchpad packaging of addressbook plugin + * #5994: Fix addressbook plugin configuration loading + * #6027: Fix addressbook enabled test from configuration + * #6027: No need of gnomedoc related macros in addressbook plugin + * #6027: Add NEWS file required for build + * #6027: Add addressbook plugin autogen.sh script + * #6027: Remove plugins from client + * #6027: Add sflphone-plugins folder at project's root level + * #5994: Move addressbook folder from contacts to plugin folder + * * #6011: removed unused Makefiles + * * #6010: remove unused headers + * * #5952: fix "string constant to char*" warnings + * * #6009 fixed warnings + * * #6003: finished cleanup of account classes + * * #6003, #6004: cleanup of account classes, defaultAccount no longer + global + * * #6000: fix memory leak of args object + * * #5998: removed using namespace std from networkmanager + * * #5998: removed "using namespace std" from ZrtpSessionCallback + * * #5998: removed using namespacestd from AudioZrtpSession.h + * * #5998: remove "using namespace std" from auriorecord.h and + MimeParameters.h + * * #5998: remove using namespace std in main + * * #5998: removed "using namespace std" from logger + * * #5949: test gcc version before disabling unused-but-set warning + * #5994: Installation of addressbook plugin + * #5979: Implement codec full addressbook search from plugin + * #5979: Implement addressbook factory and plugin + * * #5981: unused webwidget removed + * #5966: Account config synchronization fix (for stun) + * #5954: Handle media name exception + * #5954: Fix audio codec name display in client + * #5954: Clean up getSessionMedia methods + * * #5957: getRecordingSmplRate returns a value + * #5954: Clean up getCurrentCodec methods + * * #5950: remove "converting to non-pointer type 'int' from NULL" + warnings + * #5915: Full gain control version + * * #5949: remove more unused variable warnings + * * #5949: remove unused/unused-but-set variable warnings + * * #5949: show_preferences_dialog returns a success value + * * #5946: cleanup of include directives, undefined function + * * #5515: comment out SSLv2 calls in pjsip + * #5915: Implement different slope for attack tme and release time for + gain control + * #5915: use only one input signal for gain control (removed output + buffer) + * #5921: Fix no audio after holding a conference + * #5916: Add gaincontrol files + * #5916: Implement FFMPEG/CCRTP video streaming prototype + * #5903: Fix call transfer during a conference + * #5915: implement rms detector, first order averager, limiter for + gain control + * #5914: Fix call transfer when no notification request is required + * #5899: Fix conference right-click segfault + * #5884: temporary fix segfault in pjsip memory pool + * #5883: Fix compilation issues on maverick and lucid + * #5755: Fix fedora 15 compilation without patching ccrtp + * [#5855] Make echo canceller optional + * #5855: Fix echo suppression activation/deactivation + * #5855: Implement pjsip echo canceller + * #5814: Speex initialization function uses samples, not bytes + * #5814: Test using more unbalanced signals + * #5814: Fix buffer size for long echo length or long echo delay + * #5814: Adjust level for echo cancellation at runtime + * #5814: Process noise reduction before echo cancelling + * #5814: Implement speex post echo canceller processing + * #5814: Dump echo cancel file to disk + * #5814: Add parameters for echo cancel + * #5809: Add configuration parameters + * #5809: Implement speex echo canceller in audio rtp session + * #5814: Code cleanup + * #5814: Fix conf creation with several incomming ringing calls + * #5814: Fix conf creation segfault when dragging a call on hold on a + ringing call + * #5809: Added unit test for echo cancellation and implemented + "process" virtual method + * #5709: Add always recording option in configuration + * #5709: Add always recording option in audio conference panel + * #5709: Add core functionnality for always recording (missing config + options) + * #5769: Fix conference participant handling (detach/attach) and hold + actions + * #5747: Fix recording icons and state for conference when adding new + participant + * #5769: Code cleanup + * #5769: Fix hangup unsent calls + * #5769: Fix remove/add additional participant to conference + * 5769: Several fixes concerning confererence handling + * #5769: Fix compilation error + * [#5769] Fix audio streams binding in main buffer + * #5769: Removed access to audio mixer from audio layer + * #5765: Fix audio crash for illformated wavefiles + * #5765: Add maximum iteration for finding fmt and data "chunck" + * #5589: Fix compilation of libnotify under + * #5757: Fix abort signal when receiving INFO + * #5747: Add usersDetached.svg + * #5747: Handle offhold action for recording conference + * #5747: Fix off hold action for conferences + * #5747: Implement update conference in record action in calltree + * #5747: Add new icons for recording conferences + * #5747: Add recording state for conferences + * [#5738] Remove getAudioDriver call from manager (replace by + _audiodriver var) + * [#5738] Refactor mutex protecting audiolayer + * [#5737] Fix HD conference recording + * [#5730] Fix start audio session after changing sampling rate + * [#5714] Fix enter keyboard event for addressbbok and history + * [5695] Fix addressbook combo box update when no addressbook selected + * [#5695] Fix addressbook initialization and search bar update + * [#5695] Add mutex for books_data in addressbook to protect async + calls + * [#5695] Get back addressbook open from uri + * [#5695] Fix absolute addressbook URI for local addressbooks + * [#5695] Implement libebook 3.0 interface + * [#5571] Better logic for hangup (for case where call have not been + sent yet) + * [#5571] Update error handling in voip links + * [#5571] Fix compile time warnings + * [#5696] Fix installation dependencies for Natty + * [#5669] Add mention that sflphone.org is for testing only + * [#5693] Add natty in teh dput.conf file + * [#5690] Remove not useful logs + * [#5670] Use dynamic payload type for rtp dtmf + * [#5668] Clean up sflphone configuration logging + * [#5668] Fix hook checkbox configuration update + * [#5666] Fix unit tests + * [#5666] Manage event subscription + * [#5666] Emit bye request when subscription is terminated + * [#5666] Bye request should be sent after event subscription + notification is done on transfer + * [#5666] Make reinvite method static (to be called in pjsip + callbacks) + * [#5666] Hangup Call in manager for AccountNULL and IP2IP + * [#5589] Use PKG_CHECK_MODULE for every client's dependencies + * [#5623] Enlarge initial size of pjsip memory pool for calls (16k) + * [#5564] Fix audio recording resampling for g722 + * [#5571] Move attribute handling for onhold/offhold actions in SDP + session + * [#5571] Codec negotiation refactored and unittested + * [#5571] Implement tests + * [#5571] Implement pjsip negociator + * [#5571] Fix unit tests + * [#5571] Add Fmtp.h to repository + * [#5571] Integrate mime types and codec factory + * [#5571] Handle exception when SDP negotiation fails + * [#5570] Add sflphoned-sample.yml in repository + * [#5564]: Implement stereo to mono mixing for rigntone + * [#5342] Update audio stream initialization + * [#5514] Restore test ni historytest suite + * [#5514] Fix + * [#5514] Disable test_create_history_path + * [#5514] use pulseaudio in sample config file + * [#5514] Fix test: load history from file + * [#5514] Do not use X + * [#5513] Make unit tests compile successfully + * [#3947] Enable unit tests in Jenkins + * [#5454] Fix build system to handle new version number + * [#5454] Update languages from launchpad + * [#5454] Add --without-celt in OpenSuse build service + * [#5454] Change version number + * [#5331] Added first SDP session tests + * [#5273] Update nightly build version tags to conform dpkg rules + * [#5211] Refactor send register method for iaxvoiplink and + sipvoiplink + * [#3950] Remove call being transfered from calltree + * [#5211] Use appropriate memory pool for transport selector + * [#5211] Fix strict aliasing rules warning in pjsip + * [#5211] Bring back pjsip shutting down sleep to 1000 ms + * [#5211] Fix registration callback segfault when closing the + application + * [#5211] Use the dialog memory pool for Route header in INVITE + request + * [#5211] Add temporary memory pool for findLocalAddressFromUri and + findLocalPortFromUri + * [#5211] Use individual memory pool for dtmfs + * [#5211] SipVoipLink refactoring + * [#3950] Attended transfer for conference calls + * [#5284] Fix DNS resolution for Route with specified port number + * [#5284] Some code cleanup + * [#3947] Fix typo in hudson script + * [#5284] Added sip route to REGISTER, INVITE, BYE request, plus DNS + resolution + * [#5266] Use RTP dtmf as default + * [#5284] Added pjsip_process_route_set after setting routes in regc + structure + * [#5286] Fix parsing error due to long configuration file (removed + max event) + * [#5286] Fix false test in configuration emmiter + * [#5286] Code cleanup + * [#5286] Updated exception handling in configuration system + * [#4969] Fix put SRTP call on hold + * [#3950] Add debug messages + * [#3950] Ability to perform an attended transfer + * [#5276] Fix initialization problem in g722 + * [#3950] Add replace header in SIPVoIPLink::transferWithReplaces + method + * [#3950] Implemented attended method in SIPVoIPLink + * [#3950] Cleanup transaction request received callback + * [#3950] Implement dummy attended transfer in gnome-client + * [#5249] Fix audio samplerate update algorithm for g722 + * [#5249] Fix uninitialized variable used in conditional jumps + * [#5249] Fix conditional jump error in audiolayer (uninitialized + value) + * [#5267] Use autoconf 2.65 as a requirement (instead of 2.67) + * [#5267] Restore manual pjsip configuration and compilation + * [#5267] Autodetect celt version (0.9.1, 0.7.1) + * [#5267] Fix deprecated macros in gnome client configure.ac + * [#5267] Update configuration for libcelt-dev + * [#5267] Fix build autoconf and automake + * [#5227] Deactivate automatic call to astyle after compilation + * [#5242] Hangup every calls before leaving + * [#5237] Will now nightly-build for natty, Karmic deprecated + * [#5229] Use inner class for rtp thread instead of inheritance + * [#5211] Move mainbuffer unbind call in rtp final method + * [#5211] Initialize sip call memory pool using 16 kb + * [#5211] Use call memory pool in session reinvite + * [#5211] Add debug messages + * [#5211] Use and internal pool for calls + * [#5211] Reduce pjsip memory pool usage for stateless error messages + * [#5211] Refactor call deletion + * [#5212] + * [#5208] Refactor codec management for accounts + * [#5168] Remove printf from codec's encode & decode method + * [#5168] Fix celt compilation on launchpad + * [#5168] Fix sflphoned compilation warnings in audiocodec.h + * [#[#5168] Must keep the g722 specific RTP rate to avoid incoming + packet timeout + * [#5168] Fix static/dynamic payload rtp session update + * [#5168] Throw SIPVoipLink Error if codec not instantiated in new + outgoing call + * [#5168] Fix dynamic/static codec payload type ambiguity + * [#5169] Fix doubled IP2IP profile when no config file + * [#4867] Add gtkinfobar in configuration panel + * [#4867] Disable input/output/ringtone selection when using default + alsa plugin + * [#4952] Patches for possible buffer overflows + * [$4885] Fix schemas problem + * [#4885] sflphone-gnome.schemas not present during build + * [#4885] Add gconf shemas directories in opensuse build system + * [#4885] Add file/folder ownership for opensuse-factory build system + * [#4906] Fix opensuse-factory build + * [#4885] Update name dependency for libedataserver + * [#4885] Fix non-void function without return in dbus-c++ + * [#4895] Update language translation + * [#4896] Update session timestamp when updating media + * [#4896] Reapply RTP hack for G722 payload type + * [#4896] Update recording sampling rate when updating codec + * [#4897] Save codecs in config for each configuration changes + * [#4895] Do not save config when sflphone quit + * [#4885] Update date for copyright + * [#4885] Deactivate siptest that require more than one sipp instance + * [#4879] Remove inmcoming call notification from IAX + * [#4885] Some cleanup + * [#4874] Add setCancel immediate/deffered for ost::Thread + * [#4879] Fix incoming call notification + * [#4878] Set keyboard focus on searchbar when selecting addressbook + * [#4874] Fixed compilation warning + * [#4874] Fixed compilation warning in sipvoiplink + * [#4874] Fix compile time warning in RTP record handler + * [#4874] Fix conditional jump in SDP + * [#4874] Fix conditional jump based on uninitialized value + * [#4874] Store call id within rtp thread context + * [#4874] Fixed conditional jump based on uninitialised value in + conference + * [#4871] Fix default account fetching + * [#4870] Delete RTP session when Refusing an incoming call + * Restore IP to IP call + * [#4857] Fix audio codec negotiation problem + * [#3947] Adjust ressources allocated to compilation + * [#3947] Disable unit tests in Hudson + * [#4305] Free mutex only when really quiting SFLphone + * [#4859] Update copyright to 2011 in every source file + * [#3218] Character '.' stripped by the caller engine + * [#4854] Fix typos, desktop entry + * [#4847] Apply RTP modification to ZRTP session + * [#4852] Update Karmic and Lucid dependencies + * [#4852] Add Libedataserver and libedataserverui as gnome client + dependencies + * [#4852] Add authentication mechanism for EDS + * [#4851] Fix segfault when closing pulseaudio layer too rapidly + * [#4808] Some otehr cleanup + * [#4808] Made some cleanup + * [#4808] Added mutex in rtp session for codecs and noise process + * [#4847] Update audio processing when updating RTP media + * [#4842] Add support for linking with gold/ld --no-add-needed + * [#4808] Make update g722 related static/dynamic payload logic + * [#4827] Upper limit on the number of contacts to import from EDS is + hard-coded to 500 + * [#4808] Fix put call on/off hold + * [#4808] Implement early RTP start for incoming calls + * [#4808] Audio stream is no longer start within RTP session. + * [#4808] Removed coupling between audio layer and and RTP session + * [#4702] Start audio rtp session as soon as it is created + * [#4702] Init timestamp to 0 + * #4702: Send RTP packets immediately, no need of outgoing queue + * [#4784] Update dbus-c++ version from gitorious + * [#4702] Update RTP timeouts + * [#4702] Lengthen RTP timeouts + * [PATCH] Fixed compatibility with old libtool versions. + * [PATCH] Accept older libebook (Maemo 5 has 1.4.2) + * [PATCH] Fixed double-free error in preferences dialog + * [PATCH] Fixed building of sflphone-common on Maemo5 + * [PATCH] Improved Gnome client initialization error handling. 1. It + no longer segfaults when sflphoned isn't available. 2. User is + provided with GUI error dialog. + * [PATCH] Improved autogen.sh scripts 1. They do not require bash + anymore 2. Added workaround for Debian bug #565663 3. Replaced + manual autotools invocations with single autoreconf call 4. Non-zero + return status on failure + * Revert "[#4468] libtool <= 2.2 doesn't have LT_INIT macro so + AC_PROG_LIBTOOL should be used instead." + * Revert "[#4468] Libebook 1.4 is sufficient" + * Revert "[#4468] Apply big path on dbus communication system" + * [#4468] Apply big path on dbus communication system + * [#4468] Libebook 1.4 is sufficient + * [#4468] libtool <= 2.2 doesn't have LT_INIT macro so AC_PROG_LIBTOOL + should be used instead. + * [#4639] Fix determining default addressbook if this property is not + set in gconf + * [#4639] Fix memory leaks in Addressbook + * [#4637] Fix opening default addressbook at sflphone init + * [#4622] Free yaml events while parsing configuration file + * [#4623] Fix conditional jumps based on uninitialized variable + * [#4622] Fix leaks in yaml serialization engine + * [#4616] Fix addressbook warnings + * [#4514] Adjust RTP timestamp + * #4527: Rename Karmic libyaml and Celt package in debian control file + * #4495: Rework addressbook opening loop + * [#4524] Increment RTP count when sending data + * [#4524] DO NOT start RTP session twice + * [#4367] Use PKG_CHECK_MODULE for celt + * [#4367] Fedora package celt as celt (not libcelt) + * [#4367] Astyling + * [#4367] Update .po files + * [#4367] Fix segfault in gensin + * [#4354] Make celt a direct dependency on launchpad opensuse build + service + * [#4367] Make celt a required package, option --without-celt valid + * [#4367] Fix zrtp timestamping error + * [#4367] Fix audio zrtp timing + * [#4367] Dispatch ZRTP packets + * [#4367] Fix segfault when unloading account map + * [#4367] Fix zrtp session + * [#4367] Implement on packet receive + * [#4367] use symetric audio rtp session, not dual + * [#4367] Reduce packet receive/sent timeout + * [#4367] Reduce RTP timeouts + * [#4367] Move speaker data receive + * [#4367] Move speaker data receive + * [#4367] Move receive speaker data method + * [#4367] Remove debug in rtp session + * [#4367] Fix g722 codec clock rate + * [#4367] Fix noise suppression initialization + * [#4367] Fix segfault in RTP mic fadein method + * [#4367] Refactor mic data encoding in rtp session + * [#4367] Implement RTP main loop + * [#4367] Fix compilation problem + * [#4367] Fix AudioRtpclass using TRTPSessionBase + * [#4367] Fix AudioRtpSession putDtmfEvent shadowing + * [#4367] Fix AudioRtpSession putDtmfEvent shadowing + * [#4367] Refactor RTP session (phase 2) + * [#4367] Refactor RTP session (phase 1) + * [#4367] Remove Redeclaration of SymetricAudioRtpSession in + rtpfactory + * [#4265] Add continue statement in for loop for invalid addressbook + * [#4261] Makes addressbook initialization more robust + * [#4257] Add maverick in build system + * [#4233] Add sdp related unit tests + * [#4233] Add condition and signal in two incoming call test + * [#4243] Fix segfault in AudioSrtpSession + * [#4243] Fix memory leak in AudioSrtpSession + * [#4243] Make audio srtp optional in for incoming call + * [#4243] Add boolean variable to make sure remote crypto context + initialized only once + * [#4243] Add documentation to AudioSrtpSession + * [#4243] Use 80 bits authentication tags by default + * [#4243] Init audio srtp remote crypto context in + call_on_media_update + * [#4243] Move SDP negotiastion in mod_on_rx_request + * [#4243] Implement initLocalCryptoInfo to be called at different + momment + * [#4243] Init init local crypto context in when initializing audiortp + * [#4243] Change key length according to sdes negociation + * [#4243] Associate callid to accountid for incoming calls + * [#4242] Fix no SDES keys in IP2IP calls + * [#4242] Fix no SDES keys in IP2IP calls + * [#4233] Test for call on/off hold + * [#4233] Add two incoming call test + * [#4233] + * [#4233] Add 2 outgoing simultaneous call unit tests + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Fri, 30 Sep 2011 13:44:57 -0400 + +sflphone-gnome (0.9.7~rc1~ppa1~SYSTEM) SYSTEM; urgency=low + + ** 0.9.7~rc1~ppa1~SYSTEM ** + + * [#2462] Set explicitly the transport on incoming call too + * [#2462] fix typo + * [#2462] Use different address for SDP and call IP + * [#2462] Use published address in SIP-SDP + * [#2181] Fixed changelog files + * [#2181] Updated spec file + * [#2402] Fix pointer to int conversion warning (atoi) + * [#2402] Remove daemon warnings, make indent + * [#2459] Make sure the stream is opened when the call is answered + * [#2402] Add conference related picture in documentation + * [#2443] Not much ... + * [#2399] Fix dialing display problem + * [#2450] Fix incoming call already in conference crash + * [#2399] Display peer name on the first line and peer number on the + second + * [#2450] Handle 403 FORBIDDEN when refused + * [#2447] Bind offHold/onHold actions to button in gtk client + * [#2447] Bind hangup action to button for conference + * [#2447] Add conference action in gtk client's ToolBar + * [#2381] Disable the password hashing in config file + * [#2402] Cleanup + * [#2366] Set callback to null when deleting Pulseaudio streams + * [#1313] Fix main buffer unit test + * [#1313] Fix audio layer unit test + * [#2315] Hide pw in security tab, display when editing, sync with + basic tab + * [#1313] UnitTest change AudioRtpSession for AudioSymetricRtpSession + instance + * [#2402] Code cleanup + * [#2444] Add debug to catch occasional crash when loading client's + config + * [#2444] Add debug info to catch occasional crash when loading config + dialog + * [#2402] Restore Call menu translations + * [#2403] Use the published address if checked in GUI + * [#2442] Add protection test in sdp + * [#1841] Reapply pjsip patch concerning DNS SRV resolution + * [#2384] Tags incoming call as direct SIP call, if applicable + * [#2402] Change the monkey face + * [#2315] Enable user to display password in clear text + * [#2434] Force optimization level at 2 + * [#2284] Fix dbus_get_all_ip_interface compilation warnings + * [#2431] Popup main window on incoming if applicable + * [$2402] Fix simple warnings + * [#2402] Fix implicit variable init order in LibraryManagerException + * [#2402] Fixing implicit variable initialization warnings in + AudioRtpSession + * [#2402] Revert atoi change, fixing codec list doubled entries + * [#2402] Fix gpointer to gint conversion + * [#2402] Fix pointer casting to integer different size warning in + codec list + * [#2402] Fix warning discarting qualifiers from pointer target + * [#2402] Fix gtk tree view assignement from incompatible type warning + * [#1669] Fix audio recording folder utf-8 non compatibility issue + * [#2414] Clean up debugs + * [#2414] Use transport set in iptoip Account and update it frm + preference + * [#2348] Use macro N_() to mark ui.xml strings as translatable + * [#2414] Rename getSipAddress/setSipAddress functions + * [#2407] Fix volume controls display + * [#2407] Fixes dialpad + * [#2383] Set ip to ip config when clicking apply button + * [#2404] Update call-to script - Maxime Chambreuil + * [#2405] Client handles unknown call in current state as well + * [#2383] Add DBUS signal to send IPtoIP local address and port as + string + * [#2383] Add Ip to IP config change apply call back + * Clonflict + * [#2402] Code cleanup + * [#2383] Do the same for IPtoIP (init localn ip with first in the + list) + * [#2383] Use first interface in the list if local addresss is not + defined + * [#2403] Clean up unuseful addresses/ports + * [#2403] Use the IP profile SIP port as global SIP port + * [#2383] Fix dbus_get_all_ip_interface warnings + * [#2383] Take into account sameAsLocal when loading published address + * [#2383] Tsake into account sameAsLocal option when saving published + address + * [#2383] Update local ip address in ip to ip config + * [#2383] Save ip 2 ip local port in config + * [#2406] Update toolbar at startup + * [#2284] Remove redefinition warnings + speex warnings + * [#2383] Fix security table in account config + * [#2383] Save ip 2 ip network interface parameters in config + * [#2403] Restore sip transport selector + * [#2383] Fix filling the Localt IP Address on account creation + * [#2383] Fix Gtk-Critical when checking STUN + * [#2383] Fix reopening account configuration display issue + * [#2383] Load IPtoIP local address and port in preference iptoiptab + * [#2383] Add LocalAddress and Localport in Preference IpToIp tab + * [#2403] Use the address and port associated to the account as often + as possible + * [#1753] Removed pjsip generated files + * [#1753] Removed remaining milenage lib references + * [#2383] Add _publishedSameasLocal variable in sipaccount + * [#2383] Add PUBLISHED_SAMEAS_LOCAL variable in config + * [#2383] Fix stun set active or not when opening config + * [#2181] Added RPM 64bits dbus patch + * [#2402] Code indentation + * [#2313] Force $(HOME).cache directory creation at startup + * [#2383] Separate network interface and published address in account + config + * [#2400] Change dbus service installation path to libdir + * [#2382] Move TLS related published address options in security tab + * [#2382] Indent accountconfigdialog.c + * [#2181] Install libdbus-c++ in $pkglib instead of $lib + * [#1753] Remove ILBC code and disable it by default in the configure + * [#1753] Remove milenage directory + * [#2382] Fix switching interaface instabilities + * [#2396] Save local ip in account creation wizard + * [#2284] Remove warning on hold + * [#2387] Fixes history searching and filtering + * [#1215] Add samplerate display in the GUI + * [#1663] Voicemail icon reflects voice messages + * [#2395] Fix account registration ( specifically with callcentric) + * [#2386] Strip "sip:" on incoming call, fixing history call back + * [#2181] Updated spec files + * [#1215] Display codec name in calltree instead of status bar + * [#2390] Move back nbCalls and stopStream higher in refuseCall + * [#2392] Fix ringtone during call in IAX + * [#2391] Stop audio streams when there is 0 calls only + * [#2391] Add debug when call state is not valid + * [#2390] Clear returns in IAXvoipLink::sendAudioFromMic() method + * [#2380] Fixing IncomingCallNotification not regular + * [#2339] Query conference at client startup + * [#2339] Working conference querying at startup + * [#2339] Add conference in call tree + * [#2339] Primitives to query conferences at client startup + * [#2320] Add account selection in history + * [#2355] Temporary solution: do not delete pointer when removing + account + * [#2380] Change algorithm in AudioRtp to trigger an + IncomingCallNotification + * [#2274] Comment sdebug in MainBuffer flush method + * [#2274] Add flushMain() in ManagerImpl::addStream + * [#2274] Add getBufferID() method in ring buffer + * [#2274] Fix warning, comment debug in ringbuffer's flush method + * [#2274] Use AudioLayer flushMain() and flushUrgent() in ALSA + * [#2274] Clean up unused variable warning + * [#2274] Protect minbudffer pointer on flushing + * [#2274] Fix playATone method which writing empty buffer in urgent + ringbuffer + * [#2274] Use audio layer flushUrgent and flushMain in createStreams + * [#2274] Use flush audio calls from audiolayer + * [#2274] Flush when peer answered call + * [#2375] Flush main buffer in iax when answering a call + * [#2274] Parse displayname using c++ string method + * [#2375] Flush main buffer when off holding calls + * [#2375] Flush main buffer mon RTP startup + * [#2376] Use now Pulseaudio module-cork-music-on-phone + * Updated OSC packaging + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Fri, 20 Nov 2009 13:59:02 -0500 + +sflphone-gnome (0.9.7~beta~ppa1~SYSTEM) SYSTEM; urgency=low + + ** 0.9.7~beta~ppa1~SYSTEM ** + + * [#1933] Cleanup debug + * [#1933] Clean up debug + * Fix mic + * [#1933] Set the IAx format earlier + * [#1933] Move IAX sendAudioFromMic outside if (call) statement + * [#1933] Fix startstream when offhold in iax and add debug concerning + codec neg. + * [#2371] sflphone_notify_voice_mail: minor gettext message formatting + cleanup + * [#2371] select_account_cb: properly gettextize status message + * [#2371] show_account_list_config_dialog: properly gettextize status + message + * INSTALL: Minor tidyup of core install guide + * Add /sflphone-gnome/src/icons/Makefile to .gitignore + * [#2181] Updated OpenSUSE files (tmp) + * [#1933] Add debug for codec negociation for iax + * [#1933] Get rid of getMicAvail and getMicData in audiolayer (not + used anymore) + * [#1933] Add "audio codec not determined" error in IAX + * [#1933] Test flush data + * [#1933] Do not need to start audio stream in iax anymore + * [#1933] Protecting pointer + * [#2284] Remove more compilation/execution warnings + * [#2284] Cleanup debug in client, use DEBUG instead of g_print + * [#2284] Clean up uimanager + * [#2370] Remove warnings + * [#2366] Clean up other debug + * [#2366] Clean up debug + * [#2366] Call pa_xfree explicitely in writeToSpeaker + * [#2284] Remove address book warnings + * [#2365] Fixes bad cast + * [#2352] Fix continuous ringing when peer hangup and call not yet + answered + * [#2181] Added version support + * [#2181] Fixed some minor issues + * [#2360] Moved MainBuffer from AudioLayer to ManagerImpl + * [#2352] Makes getMainBuffer() everywhere + * [#2352] Use 50 sec latency on pulseaudio stream creation + * [#2352] Add alsa debug + * [#2359] Update repository documentation + * [#2354] Move pulseaudio disconnectAudioStream after stopping main + loop + * [#2352] Adjust nb byte copied in pulseaudio according to + writeableSize + * [#2352] Specify pulseaudio tlength parameters using pa_usec_to_bytes + * [#2322] Convert italian translation to UTF-8 + * [#2357] Fixes window size + * [#2357] Display only actionnable tool item + * [#2333] Update streams parameters + * [#2347] Use GNOME user settings for Menu and Toolbar appareance + * [#2349] Load/Save properly audio params + * [#2322] Update translations from Launchpad + * [#2181] Added Francois Marier script + * [#2350] Remove non-valid test + * [#2181] Updated launchpad packaging + * [#2333] Fix Pulseaudio Capture + * [#2333] Use pulseaudio ADJUST_LATENCY flag and ALSA RT-SCHEDULING + * [#2333] Pulseaudio Interpolate timing + * [#2333] Change (again) Pulseaudio settings to fit logiteck usb hdw + requirement + * [#2333] Adjust pulseaudio fragment size to 4096 (max sflphone's + frames per buffer) + * [#2284] Remove recurrent compilation warning (g++ linker problem) + * [#2333] Safer Audiostream parameters + * [#2333] Fix alsa playback to reduce underrun + * [#2333] Better audiostream parameters + * [#2181] Updated version management + * [#2333] Exclusive test in playback loop + * [#2181] Updated build system + * [#2333] Less underrun with these value + * [#2333] Update playback audiostream parameters + * [#2333] Lengthen the audio buffer reduce number of underrun in + pulseaudio + * [#2333] Add ALSA recovery functions for underrun (begin) + * [#2333] Add pa_stream_trigger in pulse audio underrun callabck + * [#2048] Reduce prebuffering in pulseaudio (which affect incomming + calls' plbck) + * [#2316] Do not display any icons to the right on the history tab + * [#2333] Comment pa_stream_trigger in pulseaudio underrun + * [#2333] Modify pulseaudio streams parameters + * [#2318] Fix transfer tool button double signal + * [#2181] Updated + * [#2333] Fix ALSA ringtone + * [#2333] Flush all main buffer before starting audio + * [#2333] Open/Close Alsa thread between calls while there is no audio + * [#2333] Add debug message and test condition on starting playback + and capture + * [#2181] Fixed gnome client makefile + * [#2181] Updated + * [#2308] Remove getTelephoneTone debug + * [#2308] Change plughw for default in ALSA + * [#2308] Oups, forgot to change function name in audiolayertest.cpp + * [#2308] Cleanup in pulseaudio code (debug, function name) + * [#2308] Fix pulseaudio stream closing assertion failure + * [#2308] Moved pulseaudio mainloop locking from AudioStream + disconnect stream + * [2308] Fix latency at the beginning of a call, when playing DTMF and + wehn starting tone + * [#2181] Updated karmic + * [#2317] [#2319] Fix address book toggle button contextual behaviour + * [#2308] Stop stream when refusing a call + * [#2308] Stop pulseaudio stream when peer hungup + * [#2308] Fix tone and ringtone + * [#2312] Display the STUN entry widget when opening the tab + * [#2308] Implement two different callbacks for capture/playback in + pulseaudio + * [#2309] Open/close pulseaudio connections in startStream/stopStream + * [2308] Leave pulseaudio stream running, do not cork/uncork them + anymore + * [#2295] Set gtk file chooser to None if nothing is set in + configuration + * [#1976] Add codec and conference documentation + * [#2209] Fix recording in regard of resamling + * [#2297] Update .gitignore + * [#2297] Update translation files + * [#2297] Add reference to our coding standards + * [#2297] Remove old docbook code + * [#2296] Reinit tls account settings after modification + * [#2253] Add DcBlocker class to remove capture's dc offset + * [#2034] Fixes for TLS transport to initialize + * [#2284] Add silent build rule + client clean warnings + * [#2274] Fix unserialize history items in cilent at startup + * [#2274] Complete display name parsing and displaying + * [#2274] Parse the Display Name in sip INVITE message + * [#2050] Fix capture volume control in ALSA + * [#1970] Volume controls disable when using pulseaudio + * [#1970] Disable volume controls when using pulseaudio + * [#2277] Fix direct ip2ip ZRTP enabling/disabling in ip2ip + preferences + * [#2181] Added launchpad debian files + * [#2181] Added spec files for OSC + * [#2274] Set display name for "Contact" sip header as the hostname + * [#2181] Fixed daemon issues + * [#2181] Fixed gnome client issues + * [#1976] Remove warnings - need to fix the transfer + * [#2006] Add init is_rec variable in ManagerImpl + * [#2006] Update codec display on call selection + * [#2006] Restore double click actions in history and contact calltree + (GTK) + * [#2176] use XDG_CACHE_HOME when initializing sfl.zid file + * [#1976] Fix calltree switching from history + * [#2209] (Re)Fix cache for zid + * [#2209] Clean up debug messages + * [#2209] Clean debug messages + * [#2209] Fix trasnfering a call during a conference + * [#2209] Speex decode must return the number of bytes + * [#2209] Change frameSize speex 32kHz + * [#2209] Fix speex codec framesize + * [#2209] Reinit converterSamplingRate in RTP sessions + * [#2209] Change speex ultra wide band framesize + * [#1747] Add pixmap data + * [#2252] Fix Receiving a server error 488 crashes the callee + * [#2209] Fix iax low rate packate sending + * [#2209] Clean up debug messages + * [#2209] Add resampling changes for IAX + * [#2209] Clean up resampling code + * [#2209] Fix latency introduced by pulseaudio + * [#2209] Fix initialization of mainbuffer's internal sampling rate + * [#2176] Fix upsampling buffer size in audiolayer + * [#2209] Add dynamic converter sampling rate in audiortp sessions + * [#1747] Fixes runtime warnings + * [#1747] Remove from repo + * [#1747] register our icons to be used as stock icons + * [#2209] Fix number of byte in alsa's write to speaker + * [#2209] Fix putting non-resampled data in RTP's mainbuffer + * [#2209] Add alsa resampler + * [#2209] Add a samplerate converter in PulseLayer + * [#2209] Add mainbuffer's internal sampling rate and flushall method + * [#2176] Add mainbuffer stateInfo debug method + * [#2209] Resampling is optimal using SRC_LINEAR not SRC_FASTEST + * [#2176] Remove debug recordings + * [#2176] Fix Holding a conference participant on new calls + * [#2224] Add confID in callable object + * [#2176] Fix putting onhold a call participating to a conference when + pressing new call + * [#2176] Reset auidio buffers when adding streams (rtp, audiolayer) + * [#1976] Use xml to describe toolbars - Add a naviguation toolbar + * [#2176] Remove conference default_id in joinParticipant + * [#2176] Display error message in alsa's snd_pcm_avail_update call + * [#2176] Alsa mic avail data debug + * [#2176] Add some debug message for mic loss problem + * [#2176] Flush mic ring buffer when offholding a call + * [#2176] Reset ringbuffers' readpointer when adding main participant + * [#2176] Fix getAvailData algorithm + * [#2176] Reset ringbuffer's readpointer when adding a new participant + to a conference + * [#1744] Regex object renamed to Pattern. Previous attempt at + providing + * [#2176] Fix detach main participant problem when adding new one + * [#1976] Use right domain to translate + * [#1976] Add xml menu description + * [#2176] Store a list of confernece participant in client + * [#2176] Fix add participant, joinparticipant methods + * [#2181] Do not install dbus-c++ headers + add return value + * [#2176] Fix minor call handling instabilities + * [#2174] Fix incoming IP call contact address + * [#2211] Add test to protect NULL pointer + * [#1163] Add Advanced account configuration section + * [#2176] Add some usefull comments and debugging info + * [#2176] Add conditions to display security icons in conference + * [#2176] Fix detaching one participant while keeping communication to + others + * [#2176] Reenable userActive.svg in call tree + * [#2176] Make user active blue (not red) + * [#2176] Fix user active picture + * [#2176] Fix "hidden" merge conflict in sipvoiplink + * [#2176] Remove iax audio stream on peer hungup + * [#2174] Multiple UDP transports functional (TESTED with 2 accounts + and 3 calls) + * [#2176] Fix fix audio stream binding in iax + * [#2174] Create a default UDP transport + use tp selector for dialogs + also + * [#2176] Register iax audio stream in mainbuffer + * [#2176] Fix getAudioCodecName in IAXvoipLink + * [#2176] Fix iax account init + * [#2176] Handle multiple account using the same sip transport + * [#2165] Add .png files + * [#2176] Small fixes concerning dtmf + * [#2176] Fix make uninstall in codecs + * [#2174] remove stund makefile generation + * [#2176] Add conference lock + * [#2174] Add transport selector for multiple accounts + * [#2176] Change userActive picture from red to blue + * [#2176] Fix security pixbuff in calltree + * [#2176] Replace sfl.zid in .cache/sflphone instead of .sflphone + * [#2176] Fix add call description + * [#2176] Remove detach button from toolbar + * [#2176] Fix calltree call description state and state code in + conferences + * [#2176] Fix pulse audio double free + * [#2176] Fix conference selection + * [#2174] Clean up - remove stun settings in client network + configuration panel + * [#2174] Remove voviva stun code + * [#2174] Rsolve STUN with pjsip - DO NOT WORK + * [#2165] Add user svg + * [#2165] Debugging sip call failed + * [#929] Link against uuid if installed + * Oops + * Fixed bugs related to libsexy (with GTK < 2.16) + * [#929] Remove uuid-dev dependency in the core + * [#2165] Debugging no negociated codecs at communicatio start + * [#2165] Fix calltree bug (gtktreestore instead of gtkliststore) + * [#2165] Fix several merge problems + * Updated opensuse packaging script + * [#1163] Add missing figures + * [#1163] Update INSTALL file + * [#2165] Fix IAX + * [#2165] Add recordabe interface + * [#2165] Finish recording refactoring for call (not for conference) + * [#2165] Enable speaker recording for two different calls + simultanously + * [#2165] Implement call recording using the Recordable interface + * [#2165] Add get and set to AudioLayer's audio recorder + * [#2165] Add class recordable from which inherit call and conference + * [#2006] Fix G722 and Speex 8khz codec conferencing + * [#2006] add recording of audio buffers + * [#1163] Add general settings section + * [#1163] Fixes makefile error + * [#2006] Fix some minor issues + * [#2006] Drag a conference call on another conference call + (difference conferences) + * [#2006] Fix dragging a conference on itself + * [#1744] Integrating some of the needed regular expression patterns + in order + * COmplete call features + * [#1744] Added support for named subgroup in the Regex object. Also, + new + * [#1744] Adds thread safety features, compile() and setPattern() + methods to the Regex class. + * [#1744] Fix inconsistency in the finditer method from the last + commit. + * [#1744] Added regex pattern object built on top of libpcre. To be + used + * [#1744] Initial commit towards implementing RFC4568. Unimplemented + in the + * [#2157] Hide "security" and "advanced" tabs for IAX under account + * [#1163] Add call features section + * [#2006] Add joinConference capabilities + * [#2006] Add dbus joinConference signal + * [#2006] Drag a conference call onto a conference to add it + * [#1163] Add addressbook section + * [#2006] Drag a conference call onto a single call to create a + conference + * [#2006] Expand rows automatically + * [#2006] Add minimal multiple conference handling + * [#2006] Add atached/detached conference icons + * [#2006] Add function processRemainingParticipant + * [#2006] Deep refactoring, fix hangup bug + * [#1163] Update documentation - Accounts part + * [#1976] Integrate user doc to gnome client build system + * [#2122] Remove double inclusion in dbus-c++/src/Makefile.am + * Remove pjproject version number + * [#2006] Fix peerHungup + * [#1976] Make Yelp accessible from the GNOME client (need to install + the sflphone.xml first) + * [#2006] Fix multiconferencing hangup + * [#2006] Fix hangup calls in a conference + * [#2150] Make IAx2 reappear + * [#2006] Fix detach participant on multiple call + * [#2006] Can remove rining call from a conference + * [#2006] Reinit confID when removing a participant + * [#2006] Remove get isCurrentCAll in hangup/peerhungup (SipVoipLink) + * [#2006] Fix refuse call + * [#2006] Fix answerring incoming call + * [#2006] Refactor conference's participant list + * [#2101] Re-integrate test compilation in main build system + * [#2101] Make the test directory compile + * [#2136] Restore history functionality + * [#2006] Fix binding main participant to himself + * [#2006] Fix add current/incoming/onHold participant to an existing + conference + * [#2006] Fix add incoming calls to an already created conference + * [#2006] Fix remove stream + * [#2006] Fix detachParticipant/removeParticipant switchCall ids + * [#2006] Fix adding a call in conference having state "CURRENT" + * [#2006] Remove/add main participant from conferences + * [#2006] Hold/unHold conference + * [#2006] Detach a partcipant from drag n drop + * [#2006] Hangup a conference + * [#2006] Add hold/unhold conference dbus messages + * [#2034] gtk-ui fix under the "basic" tab. + * [#2006] Fix dragging calls on conference calls + * [#2006] Fix detach participant from a conference + * [#2034] Added default message is status bar under the account config + dialog + * [#2112] Fix a crashed caused when a non-md5 password was sent to + pjsip. + * [#2006] Detach participant by ID + * [#2006] Fix addParticipant method in managerImpl to handle + incoming/answered calls + * [#2006] Add addParticipant method in managerimpl and related dbus + messages + * [#2111] Added the ability to configure zrtp on sip.sflphone.org from + * [#2106] Fixed problem in the account assistant under gtk-ui. Also, + assistant.c + * [#2006] Fix dragging a conference call on another conference call + (same conference) + * [#1904] Small UI fix. Assistant was moved from "Call" to "Edit" + menu. + * [#1904] Fix a wrong label under gtk-ui. + * [#2034] Renaming and source code splitting. + * [#2034] Status bar added to account window to better reflect the + registration + * [#2006] Make calltree_remove_call recursive (for GtkTreeStore) + * [#1110] Small gtk-UI fix in the account window (alignment). + * [#2006] Fix remove conference, display children which are still + active + * [#2006] Recursive function call in calltree_update_call + * [#2006] Add multilayered capabilities to calltree (GtkTreeStore) + * [#2006] Implement remove conference in calltree + * [#2034] Now useless as Direct Ip calls settings moved under + Preferences. + * [#2034] Edit/add buttons were set insensitive all the time under + gtk-ui. + * [#1887] Information about the state of the current SIP call is + displayed + * [#2006] Add call tree remove callback + * [#2006] Fix create_conference function + * [#2006] Update conference_added_cb to add new conference to the list + * [#812] Added new tab under GTK-ui Preferences. Moving Direct Ip + Calls from + * [#2121] Disable temporarily test compilation + * [#2006] Fix conferencelist to handle conference_obj_t instead of + gchar + * [#2006] Add conference_obj structure + * [#2121] Update version + * [#2006] Fix conference selection + * [#2101] Use the new source tree to fetch the right object files + * [#2006] Add conference in calltree + * [#2006] Add Dbus signal conference added/removed/changed + * [#2006] Add getConferenceDetails call on dbus + * [#1904] Registration expire now appears as a spin box under gtk-ui. + * [#812] Fixing a segmentation fault caused by a non-existing account + ID + * [#2006] Add getConfList method over dbus + * [#2006] Add a conferencelist data structure in client-gnome + * [#812] Defaults value are now sent if a non-existing account is + requested + * [#2006] Add sflphone action sflphone_join_participant + * [#2006] Fix buffer read pointer problem deletion + * [pjsip] Attempt at fixing via header incompatibility with + Freeswitch. + * [#1797] forget something + * [#2006] Add call new state conferencing in deamon + * [#2006] Remove addParticipant method for conference, use + joinParticipant only + * [#1163] Update INSTALL documentation + * [#812] Msec/sec values were not taken into account. + * [#1797] Make pjproject-1.4 compile + * [#2006] Add Detach participant method + * [#2006] Dragndrop fully functional with INCOMING and HOLD call + * [#1797] Add pjproject-1.4 + * [#1797] Remove pjproject-1.0.3 + * [#2006] Get call state in conference related function + * [#2006] Add joinParticipant (conference) method in ManagerImpl + * [#2006] Add joinConference DBUS message + * [#2006] Store the previously selected call_id on dragndrop + * [#2006] Fix GValue pointer unref in selection callback + * [#2006] Store dragged call_id + * [#2006] Update drag_data_received_cb callback to manipulate CallIDs + * [#2006] Add dragndrop signals + * [#2006] Set calltree reordable + * [#812] Adds the ability to create a TLS listener in case the user + requests + * [#812] Adds the ability to configure local/published address from + * [#1883] Move switchCall in onHoldCall function + * [#812] Deals with the published address/port problem when + integrating TLS. + * [#1883] Switch call id in managerimpl when peerHungUp + * [#1883] Switch call id before hangup + * [#1883] Add usefull and permanent debug info for conference + cretion/deletion + * [#812] Fix various segmentation faults related to Direct IP kind of + calls. + * [#1883] Fix deletion of std::map elements using iterators + * [#2014] Add libzrtpcpp build dependency + * [#1883] Still some for loop test ambiguity (while loop instead) + * [#1883] Fix for loop initial test ambiguity (use while loop instead) + * [#1883] We must discard data in urgent ring buffer if data is get in + mainbuf + * [#1883] Fix availForGet same id for ringbuffer and readpointer + * [#812] Match "sips" as a Direct IP Call when the user enter a sip + uri + * [#812] Fix segmentation fault related to SIP URI creation. + * [#812] Towards integrating multiple tls listeners at the same time. + This + * [#1883] Add debug messages in conference and fix mainbufferTest + * [#812] gkt-ui fix. Private key must be fed as a filename and not as- + is. + * [#812] TLS integration within sipvoiplink and pjsip. Also, + configure.ac + * [#1883] Fix Alsa/Pulse mallocation + * [#1883] Fix data corruption in AudioRtp's micData buffer + * [#812] Full dbus integration for all the tls related options under + gtk-ui. + * [#1883] Fix memory leaks in audiortp session + * [#1883] Fix mem leaks in audio rtp + * [#812] Fix setAccountDetails where TLS_ENABLE was set to the value + * [#812] Small gtk-ui fix. + * [#811][#812] Small gtk-ui fix. + * [#812] Introduced a mechanism for configuration files that makes + possible + * [#812] New dbus bindings added. Also, configuration compliance was + enforced + * [#1881] Remove default buffer from MainBuffer (update unit-tests) + * [#1881] Add ring buffer read pointer tests + * [#1883] Fix issues in ringbuffer reader pointers + * [#2034] Implementing a new configuration dialogue for TLS transport + settings + * [#1883] Add some usefull debug and safety checks + * [#2028] Notify the client with libnotify when the zrtp negotiation + failed. + * [#811] Harmless no to throw an exception, an makes the application + less + * [#2028] A minidialog is showed to the user under sflphone-client- + gnome + * Removed useless file. + * Ignoring Makefile in src/widget + * [#2027] Fix segmentation fault when showMessage callback is called + after + * [#2026] keyExchange was set to ZRTP instead of "1" + * [#2024] Fix the wrong summary at the end of the assistant. + * [#1883] Fix mnagerimpl conference map insertion + * [#1883] Add Mutexes in MainBuffer + * [#811] Gtk ui was not presenting the right information about zrtp + for + * [#2023] security icons were not installed in sflphone-gnome. + * [#2021] Fix a mistake in the readme from sflphone-common that gives + wrong + * [#811] The current SRTP mode was not properly displayed for the + IP2IP + * [#1743] Re-implementation of the "automatically remove error dialogs + [...]" + * [#2017] [#2019] Fix the inability to dial a number and place a + registered + * [#811] Final re-integration of ZRTP support in the main branch from + 0.9.6 + * [#1883] Fix map insertion methods + * [#811] Combo box now is now set to the active key exchange method + * [#811] ZRTP options now configurable back again from the Gtk UI. + IP2IP + * Updated hostname for git clone + * [#1883] Add minimal functionalities to create a conference + * [#811] re-integration of all the methods and signals on dbus. + ManagerImpl + * [#811] Got out of a precarious position were nothing would compile. + * [#1976] Build documentation squeleton with docbook + * [#1883] Add sflphone-client "addParticipant" button for conference + * [#1994] Better organize the source directory structure. New + subdirectories + * [#1883] Add a simple Conference class + * [#1882] Use static audio buffer in Pulse and ALSA layer (instead of + malloc) + * [#811] First commit toward re-integration and refactoring of ZRTP + * [#1882] Flush RTP ring buffer before entering mainloop + * [#1882] Fixed MainBuffer::UnBinCallID() in case there is no + ringbuffer + * [#1882] Test (and fixe) high level conference and mixing + functionalities + * [#1772] Apply patch to compile on fedora (sent by Marcin + Zajączkowski <mszpak@wp.pl>) + * [#1882] Update Bind, unBind call_id in MainBuffer + * [#1959] This adds the ability to store password as an MD5 Hash in + the + * [#1538] Fixes rules compilation + * [#1930][#1931] Fixed a mistake (again) related to index and + credential count + * [#1753] Remove ILBC from pjproject - Hacks in pjsip + * [#1930][#1931] Credential was not selected properly using realm + * [#1882] Finilize multiple reading pointer in RingBuffer + * [#1538] Remove configure from autogen.sh to respect debian upstream + authors policy + * [#1773] Remove generated files from repo + * [#1791] Use XDG_CACHE_HOME to save pid file + * [#1791] Fixes path to save history + * [#1791] Fix debian installation scripts + * [#1930][#1931] Settings are now taken into account in the server. + * [#1882] Add ringbuffer default ring buffer pointer in methods + involving mStart + * [#1882] Add default ringbuffer pointer + * [#1882] Add RingBuffer multiple read pointer basic functionnalities + * [#1882] Fix MainBuffer flushData unit test + * [#1930][#1931] Ability to save and retreive the configuration from + * [#1882] Added Multiple CallID mapping to MainBuffer + * [#1791] Not much + * [#1791] If XDG env variables are not null but empty, use default + ones + * [#1791] Make XDG_CONFIG_HOME writable + * [#1930][#1931] Partial commit. Not working yet. Cannot delete + account + * [#1881] Fixed alsa capture latency problem + * [#1881] Fixed Alsa capture temporarily + * [#1930] [#1931] Partial unbroken commit providing the ability to + * [#1881] MainBuffer implemented in AudioLayer/AudioRTP + * [#1881] Add discard and flush unit-tests + * [#1881] Add discard and flush functionnalites to MainRingBuffer + * [#1881] Add availForGet in MainBuffer + * [#1881] Add availForPut function to MainBuffer + * [#1880] Remove AudioRTP* pointer from SipVoIP (reapered while + merging master) + * [#1881] Add a map between call id and coresponding ring buffer + * [#1855] Refresh pot file and upload on Launchpad + * [#1881] MainBuffe now robust to false ids on getData and putData + * [#1881] Fix big big big memory leak + * [#1881] Add getData and putData to mainBuffer + * [#1881] Unit-test basic ring buffer functionnaities + * [#1881] Add class MainBuffer and basic buffer creation unit-tests + * [#1880] Fix call transfer (step2) issues + * [#1880] Moved AudioRtp* pointer from SIPVoIPLink to SIPCall class + * [#1791] Add postinst script to keep user data when migrating + config/history file + * [#1797] Make pjsip compile + * [#1777] Code indentation + * [#1791] Use XDG_DATA_HOME and XDG_CONFIG_HOME for sflphonedrc and + history + unit tests + * [#1746] Useless space does not appear anymore when volume sliders + and + * [#1643] GtkCheckMenuItem is used instead of icons for elements in + the + * [#1110] [#1668] STUN parameters are now located in the preferences, + under + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Fri, 06 Nov 2009 11:20:01 -0500 + +sflphone-gnome (0.9.6-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6 ** + + * Documentation on echo test + * [redmine_down] codec names not displayed in total + * [redmine_down] crash when hanging up a dialing call because tries to + add it to history whereas no starttime + * [#1927] alternate every time screen changed to call history + * [#1886] clean code + * [#1886] debug messages when loading history removed + * [redmine_down] sflphone-kde icons + * [#1855] Update language files + * [#1502] Update version number + * [redmine_down] setHistory at close + * [#redmine_down] Handle PJ_DECLINE_SC as failure + * [#1923] Fix segmentation fault when adding a new account + * [#1923] Check on iterator before setting the config + * [#1904] Added mnemonic to tabs in sflphone-gnome. + * [#1905] The daemon was not sending the currentSelectedCodec signal + on dbus when answering a call. + * [#1922] Default values set to all account details + * [#1886] Spinbox reg expire enables apply, and address book is not + visible when disabled + * [#1905] Bug fix for segmentation fault caused by an empty string, + * [#1910] Warnings in test directory + * [#1919] Error fixed + * [#1855] Update russian translation - Hussein Abdallah + * [#1910] Remove files + * [#1919] fixed + * [#1777] Code indentation + * [#1918] fixed + * [#1917] fixed + * [#1910] Remove warnings compilation in src + * [#1886] removed AccountListModel in configskeleton + * [#1914] + * [#1911] check previous and new port + * [#1910] Remove compilation warnings in src/dbus and src/history + * [#1910] Remove compilation warnings in src/audio + * [1855] Update german translation - Sven Werlen + * [#1909] removed + * [#1906] Done + * [#1904] The registration expire value is now configurable from the + * Cleaned up debug messages. + * [#1886] separated initCallItem in two functions + * [#1886] reversed error in commit + * [#1886] clean debug + * [#1886] changed Name of classes and files + * [#1886] clean + * [#1870] In call_state_cb (dbus.c:126), _time_stop was overridden by + the actual time. + * [#1884] Added some new gpg flags to prevent tty warnings + * [#1886] Clean audio config dialog + * [#1886] No more compile warnings. + 1 comm + * [#1872] Check if the user input is smaller than PJ_MAX_HOSTNAME. + * [#1886] + * [#1785] Fixed build when no new commit + * [#1852] If chosen by the user, the hostname can now be solved and + used + * [#1871] * and # inverted back + * [#1869] Conditional compilation that checks if + * [#1309] removed test in main + * [#1425] Put actions in SFLPhone window class instead of ui view, + made a separate toolbar for screens. + + -- SFLphone Automatic Build System <team@sflphone.org> Mon, 27 Jul 2009 09:53:19 -0400 + +sflphone-gnome (0.9.6~rc2-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6~rc2 ** + + * [#1755] Remove generated file + * [#1753] restore ilbc ... + * [#1866] Methods getSipPort and setSipPort now have an effect on the + * [#1753] make pjsip compile without ilbc. Use ./autogen.sh --disable- + ilbc-codec + * [#1855] Fix error in russian translation + * [#1805] Remove the old flawed signal mechanism which was failing in + * [#1855] Refresh translation + * Spanish translation finished + po README files updated + echo's in + copy-in-clients + * [#1850] Yun made the chinese HK-CN translation + * [#1848] Fix transfer interface bug + * [#1862] At install, kde client installs only french translation file + * [#1841] A new fallback mechanism was added to the internal resolver + in PJSIP. + * Started AccountList model/view + * [#1855] Remove po subdir in Makefile.am + * [#1855] Fix typo error in sflphone-gnome + * [#1855] Do not generate Makefile in sflphone-common/po + * [#1855] Copy translation files into both clients dirs + * [#1855] Remove po dir from sflphone-common + * Comments added + * [#1860] mailbox->voicemail... + * make scripts executable + * [#1855] French translation + * [#1855] Chinese zh_HK partially filled... + * [#1859] An unnamed pipe monitored by poll() was added. When we want + to + * [#1855] Sven completed the first part of the german translation + * [#1855] Cantonese manually filled for already translated, almost + equal strings + * [#1855] Merge russian translation + * [#1855] Spanish manually filled for already translated, almost equal + strings + * [#1855] Update german translation in ./lang/de + * [#1858] This problem was fixed by removing a useless line in + * [#1855] merged existing translations in lang/ sflphone.po's + * [#1842] [#1843] An attempt at improving the expected behaviour that + can't + * [#1855] added po folder in gnome client and scripts for copying from + common lang folder to clients + * [#1853] Edit before call does nothing on call history + * Put most language entries possible in common. From 300 to 250 + entries. Stays underscores problem. Scripts for copy in clients. + * commit to merge master + * [#1825] Changed "Bad authentification" to "Authentication Failed". + * common po files + * [#1753] Remove ILBC from pjproject + + -- SFLphone Automatic Build System <team@sflphone.org> Fri, 17 Jul 2009 19:12:58 -0400 + +sflphone-gnome (0.9.6~rc1-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6~rc1 ** + + * Update some version number + * [#1792] Creates .sflphone directory with permission 600. Also, + "chmod 600" after + * [#1810] GUI is now notified that the call failed. Also, a segfault + was + * [#1816] Address book search disabled when disabled address book and + enabled it back plus button stays triggered + * codeclistmodel + asynchronous loading of address book + + enable/disable address book + * [#1810] Now checking SDP answer after 200 OK. Still need to + implement full + * [#1794] Can't use the interface during a call + * Updated translation files + * Russian translation integrated + * Codec list model/view started. + * [#1807] Add configure.ac in pjproject-1.0.3 + * [#1787] closeRtpSession added in some places where it should have + been + * Use Item class for contacts and accounts + * Comments + clean code + * [#1794] Improved debug messages + * [#1805] Replaced the old and unreliable mecanism that was was + waiting for + * [#1794] Can't use the interface during a call + * [#1787] For those cases where no registered SIP account is + configured + * [#1797] Make pjsip compile + * [#1787] Minor changes. Removed useless commented line. Changed order + of + * [#1777] Code indentation + * [#1797] Update package generation with new pjsip version + * [#1798] Does not hang up when the call is building up + * [#1797] Update .gitignore with new pjsip version + * [#1797] Remove generated files from repo + * [#1797] Main build system now uses pjproject-1.0.3 + * [#1797] Add pjproject-1.0.3 + * [#1797] Remove pjproject-1.0.2 + * [#1796] Computing time optimization (samplerate conversion) + * [#1787] _audiortp->start() moved away from offhold(), + SIPCallAnswered() + * [#1312] Added new states for calls initialized by other clients + * [#1795] Crashes when adding a new account, checking it and applying + * [#1782] Missing icons + * [#1793] KDE client compilation problem + * Fake ringtone files can no longer be set. + * indentation + * [#1312] Able to fetch to differentiate incoming/ringing call state + * [#1784] Use DESTDIR variable in po Makefile - fix language file + installation + * [#1785] Fixed typo + * [#1785] Fixed changelog update + * [#1759] ./autogen.sh --prefix=/usr --with-debug to use optimization + level 0 + * [#1773] Changed snapshot naming convention + * [#1773] Removed gpg agent use, added repository cache cleaning + * [#1759] Use optimization level 0 for repository, 2 for packages + * [#1777] Code indentation/formatting + * Translated new features in french + * [#1785] Added missing changelog entry + * [#1781] Window title is SFLPhone + * [#1777] Add code indentation/formatting in the buil system + * [#1774] Can't set voicemail number in KDE account creation wizard + * [#1775] Can't modify account information for account created with + the wizard + * [#1771] Add a "Default" button in context menu to disable chosen + prior account + * [#1705] + * [#1224] Remove generated file from the repo + * [#1224] Remove generated file from the repo + * [#1762] distclean target should remove kconfig generated files + (settings.h, settings.cpp). Rename them? + * [#1761] clear history button should really clear history + * Dialpad works. + * Implemented Dialpad widget instead of building it in main view. + * Removed last occurence of the old config dialog, that made the build + crash. + * [#1755] Do not consider G722 as a dynamic payload elsewhere than in + RTP layer + * [#1753] Remove ilbc Makefile generation + * [#1756] Implement a kde configuration dialog with kconfig xt and + kconfigdialog class + * [#1755] fix audiocodec folder parsing problem + * [#1450] Reinit timestamp comparison in RTP, create session in + newOutgoingCall + * [#1753] Remove milenage third party code from pjsip + * New Config Dialog integrated in GUI.(without codecs) + * [#1753] Remove ILBC codec + * kconfig started, tr2i18n -> i18n, icons folder, accountList changed + * [#1705] Fixed Audio RTP thread creation/start + * [#1714] Fix codec negociation result handling + * [#1678] Fix audiortp payload setting + * [#1678] Put bac putData method in rtp + * [#1669] gtk_file_chooser_get_filename() support UTF-8 by default + * [#1735] Add conditions to sdp update call if call declined + * [#1737] substr of recordings destination folder to remove "file://" + should be done in client rather than in daemon + * [#1731] Enlarge audio stream buffer size + * [#1714] Missing true + * [#1317] Fixed Mandriva timeout + * [#1317] Changed tag convention + * [#1317] Cleaned git-dch + + -- SFLphone Automatic Build System <team@sflphone.org> Fri, 10 Jul 2009 15:50:26 -0400 + +sflphone-gnome (0.9.6~beta-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6~beta ** + + * spec files for mandriva and opensuse updated with buildrequires + libqt4-dev >=4.3 + * [#1700] Cannot build on ubuntu 8.10 and a few other distribs + * [#1502] Update version number where applicable + * [#1642] Update client icons + * [#1450] Clean up useless debug and comments in sipvoiplink and + audiortp + * [#1450] Remove Semaphore object in AudioRtp thread deletion + * [#1450] Audio RTP init now synchronized with Sip/SDP + * [#1693] kde client crashes when changing codecs order/activation + * [#1450] Deep refactoring of audiortp + * [#1450] setRtpSessionRemoteIp + * [#1689] getCallList at start + * [#1224] Change path in package files + * [#1450] Audio RTP initialized only once, payload and remote ip set + at runtime + * [#1450] Add setRtpSessionMedia and setRtpSessionRemoteIp address + * [#1642] Make GNOME GUI fresher and younger ;) + * [#1686] Status bar displaying used account + * added sflphone-kde icon so that it compiles + * [#1659] Ending a call causes the daemon to crash + * corrected introspection XMLs, po files... + * [#1211] g722 media descriptor in codecDescriptor + * [#1310] Install sflphoned in $(prefix)/lib/sflphone + * [#1502] Do not install test binaries and dbus utilitaries + * [#1224] hack for pjsip build system! + * [#1224] Remove pjsip binaries from repo + * [#1224] Upgrade to pjsip 1.0.2 + * [#1658] About SFLphone (bugs) + * [#1658] About SFLphone + * [#1660] Displaying all dialed numbers in a call + * Tested status bar. + * [#790] Optimize pulse audio streams parameters + * [#1678] Some usefull debug messages for mutex/semaphore deadlock + problem + * [#1669] Add/remove some usefull/unusefull debug + * [#1665] Fix latency related to pulse audio stream openning/closing + * [#1457] Make the menus and panels accessible in french + * [#1457] Improve broken keyboard accessibility in menus and conf + panels + * [#961] Instanciate only once the searchbar icons + * [#961] Restore transfer fonction + * [#961] Filter on the history type OK + * [#961] Fix compilation problems on hardy/intrepid + * [#1157] Commit missing files + * [#790] Reduce number of start/stop streams call on pulse audio + * [#1639] kde client crashes when no account registered + * [#1620] Fix the searchbar + * [#1620] Get back caltree as it was during gtkcritical area + * [#1620] Add history filter reinit function + * [#1335] Add a missing label in address book preferences + * [#1561] Update russian translation - Hussein Abdallah + * [#1605] Fix edit menu french translation + * [#961] Enable to search in the history according to the call type + * [#1449] Searchbar does not work anymore + * [#961] Add popup menu on the entry primary icon for history + * [#1317] Fixed KDE client package dependency + * [#936] speex 32 khz integration completed + * [#936] Use 320 frame size + * [#936] Test using a frame size at 320 smpls + * [#1214] Enable / Disable history + * [#1607] Fix compilation problem for ubuntu 8.10 (libsexy) + * [#1313] Implement processDataEncode processDataDecode in audiortp + * [#1613] codec list order can't be set + * Better handling of localisation + added languages + corrected + warnings + begginning of new config dialog with kconfig + 14px + account leds + * [#1214] Save and load history according to the limit timestamp + + unit tests + * [1609] Fix call number copy/paste feature + * [1607] Restore clear action icon in searchbar + * [#936] Try to decode using 1280 samples + * [#936] Add some debug + * [#936] Add .cpp file + * [#936] Oops Forgot speex 32 khz + * [#1214] Add configuration panel for history + D-Bus calls + * [#1313] Test rtp thread function, frame size, nbbytes, resampling + * [#790] Flush audio data before closing audio streams + * [#1214] History displays local time + * [#1214] Skip empty field on display + * [#1214] Associate an account to an history entry + * [#1342] Get addressbook options sensitive/non-sensitive + * [#1211] Clean up and comments + * [#1211] Get back to 20 ms framesize + * [#1211] Use sendImmediate instead of putData in RTP + * [#1211] Fix nb byte available in RTP + * [#1211] Clear condition on maxNbSamples in RTP + * [#1211] Fix max byte available in RTP session + * [#1211] G722: Use 160 samples per frame instead of 320 + * [#1211] Test using a dynamic payload + * [#1211] Test using a dynamic payload type + * [#1211] Rename size variable (nb_samples, nb_bytes) + * [#1211] Test g722 ip-to-ip sending twice the data lenth + * [#1211] Test g722 ip-to-ip + * [#1214] Do not select an history item by default at startup + * [#1214] Remove some compilation warnings + * [#1214] Handle empty field - remove g_print + * [#1214] Add each history item only once + * [#1214] Handle call timestamps properlier + * [#1214] Do not need timestamp files anymore + * [#1214] Use the saved date for history entry + * Clean up + * [#1214] Client doesn't crash if the D-Bus call fails + * [#1214] Client is able to save its history - still some glitches + * [#1211] Forgot 16000 for g722 + * [#1211] G722 initialization + * [#1214] Save name/number, successfully load the history if no fields + are empty + * [#1499] Fixed destination directory bug + * [#1214] Restore all the functionalities; peer name/number way more + easy to handle !! + * [#1214] Add callable_object instead of call_t, refactoring + * [#1211] Test with polycom soundstation 16000 + * [#1211] Remove C like inline function in g722 codec + * [#1342] Finalize gnome client preference window formating + * [#1214] Retrieve the history when the gnome client startsup + * [#1306] Implement localization for KDE client + * [#1593] enable accounts apply button when account checked/unchecked + * [#1214] Implement the dbus calls on server side + * [#1214] Add serialized/unserialized functions to pass data on DBUS + * [#1342] Formating gnome client configuration windows + * [#1214] Save sucessfully a map of history items + * [#1499] Removed multiple jobs compilation for KDE client (2) + * [#1214] Load history from file into memory, add unit tests + * [#1534] Throws a length_error exception in case URL exceeds + std::string max_size + * [#1499] Removed multiple jobs compilation for KDE client + * [#1565] make account leds smaller + * [1430] Fix dbus debug + * [#1562] crashes when trying to change item of a call of state "OVER" + * [#1116] Fix compilation bug + * [#1317] Added mandriva and opensuse-11 64 bits + * [#1108] Add messges in main window concerning transfer success + failure + * [#1116] Fix compilation problems + * [#1211] g722 Makefile + * [#1108] Client side transferFailed/trasferSucceded signals handling + * [#1211] G722 mostly completed, + * [#1555] make bigger toolbar (24x24) + * [#1551] remove default mailbox number in wizard and disable mailbox + button when first account doesn't have mailbox number + * [#1342] Re-add sflphone manpages + * [#1116] Fix compilation on non-jaunty distros + * [#1317] Fixed opensuse startup sleep + * [#1108] Add a signal in the client to notify successful or failed + transfer + * [#1108] Dbus signals concerning call transfer success/failure + * [#1317] Added opensuse to automatic build system + * [#1223] Fix manpages bug + * [#1060] german translation glitch + * Clean up some gnome client warnings + * [#1547] replace ugly account leds by beautiful icons + * [#1548] add close button that hides windowand just hide on clicking + the cross + * [#1549] put introspec XMLs in the client's source + * [#1312] Implement getCallList D-BUS method + * [#1116] Clear text in history and contacts + * [#1499] KDE integration + * [#1469] Modify header linkers in dbus-c++'s Makefile.am's + * [#1469] Remove examples folder from dbus-c++ + * [#1214] History integration in build system; unit test squeleton + * [#1317] Cleaning + * [#1469] Remove configure stuff in dbus-c++ + * [#1469] Add unofficial mainline dbus-c++ + * [#1469] Remove dbus-c++ from freedesktop + * [#1430] Bring account changed signal/callback back to normal + * [#1060] Update german translation - Sven Werlen + * [#1430] Add marshaller one string define + * [#1430] Send account change signal broadcast using account id + * [#1430] Remove condition on setRegistrationState, cause stun to + crash + * [#1317] Centralized version handling + * [#1317] Fixed version number on sfl-git-dch + * [#1317] Refactoring for new distributions + * [#1215] Fix account order at startup if latency + * [#1088] Restore sip dns srv + * [#1214] Add squeleton for history manager + * [#1430] Add accout id to accout changed method + * [#1430] No connectionStatusNotification (account changed) if no + changes + * [#1538] Add COPYING file + * [#1430] Add audio rtp thread tests + * [#1317] Changed version detection + * [#1538] Document license in libs/stund + * [#1317] Added version files + * [#1538] Apply François patches - debian packages + * [#1317] Updated spec files + * add files + * [#1538] Apply François patches - debian packages + * [#1535] Change program file structure (directory src...) + * [#1317] Updated build system scripts + * [#1317] Cleaning + * [#1317] Copied introspect files to gnome client + * [#1317] Added opensuse to build-system : first-shot + * [#1317] Remove spec files from configure + * [#1317] Added missing prefix + * removed debug for daemon account fix + * [#1430] Add a connection reference which most likely belong to + libdbus + * [#1430] Use shared connection instead of private + * make daemon find the account, added userMatch + * Clean code, add comments... + * [#1317] Fixed packaging rules + * [#1317] Updated autogen + * Updated autogen.sh for pjsip + * [#1526] Set accounts order + * [#1317] Fixed pjsip lib dirs + * [#1317] Updated debian packaging for new pjsip configuration script + * [#1317] Switch to autogenerated guess and sub files + * [#1317] Updated pjsip inclusion in build system + * [#1317] Replaced pjsip guess and sub files + * [#1317] Fixed compilation issues on opensuse 11 + * [#1505] account list seem to crash the application when clicking + Apply very fast... + * [#1456] Add a flag to be replaced in the control files + * [#1456] Added version dependancy handling + * put account alias in AccountWidgetItem rather than in the item with + " " before. + * [#1034] The KDE client should start sflphoned if it is not started + * [#1500] Handle options for notifications and display on incoming + call. + * [#1443] Client should not crash when receive an unexpected + stateChanged signal + * [#1403] Do not stop the notification anymore + * [#1456] Added version dependancy handling + * [#1426] Daemon crashes when get alsa plugin + * [#1422] Improved error messages + * commit for merge + * [#1424] Change logo in tray icon and put a different one when + incoming call + * [#1425] first part done, window title... + * [#1413] add manpages creating and installing in build system + * [#1417] The client should start the account creation wizard if + started for the first time (if config file doesn't exist) + * [#1421] Make volume bars horizontal when dialpad is hidden. + * Changed main window title and fixed a mistake in sflphone_const.h + * [#1412] make debian package building work + * changelog changed. + * Changed addAccount method in gnome client. + * Debian and man folders added. + * [#1388] Change project name from sflphone_kde to sflphone-client-kde + * Better handle of kabc check. + * [#1351] Automatic generation of dbus interfaces in makefile + generated by cmake + * [#1307] Implement "edit before call" in history and address book. + * [#1344] change action_call label in call history from "call" to + "call back". + * [#1308] Implement Hook feature in kde client + * Improved build system. + * #1219 : Add address book configuration page + * Better handling of registration to the daemon. + * #1039 : Add tray icon in kde. + * Issue no 1216 : Double click on item in history or address book + causes call. + * display peer name in call list and call history when called from + address book. + * Address book functionnal with photo displayed. + * Help menu kde available but actions disappeared. All fonctions in + view. + * Address book functionnal but ugly and making its own sort in the + complete address book. + * Account choice on right click, clean out includes, page address + book, fixed bugs... + * Wizard, double click, context menu... + * Removed sflphone_kde.kdevelop.filelist + * Added account creation wizard and translated interface in english. + * Transfer functionnal but ugly. + * transfer not functionnal + * Bug fixed : unholding (UNHOLD_CURRENT, UNHOLD_RECORD) + * Commit functional for push. With install.sh + * Before merge. + * Problem with enable accounts. Account display increased. + * Functional with codec order working , playDTMF. + * Commit functional. + * sflphone_kde/build added in .gitignore. + * complete commit for checkout previous. + * Commit before checkout previous version to check the display + bug(little font everywhere...) + * Functionnal client. Rest : history icons, config icons and + functionalities + * commit before merge asavard for isRecording. + * Call and Automate fusion done and seems to work. + * Commiting before putting Automate class in Call class. + * Functionnal main window without recording, history, voicemail, kio + widgets. + * client kde avec kdevelop. + * Config Dialog almost finished. + * Base of QT client + + -- SFLphone Automatic Build System <team@sflphone.org> Tue, 23 Jun 2009 11:13:42 -0400 + +sflphone-gnome (0.9.5-SYSTEM) SYSTEM; urgency=low + + ** 0.9.5 release ** + + * [#1060] FIx bug in chinese translation + * [#1313] git add rtpTest.cpp rtpTest.h + * [#1313] Add init/close rtp tests + * [#1313] Basic instanciation of the rtp layer + * [#1449] Gtk-Critical concerning history filters and new calls + * [#1400] Make the match with the hostname instead of username + * [#1324] Change status bar label for "Using %s (%s)" + * [#1403] Icon size: 60x60 px + * [#1403] Do not remove notification, improve icon quality + * [#1403] Add smaller icon for gnome notifications + * [#1403] Prevent crash when hangup && no notification + * [#1403] Remove all actions on notifications; code refactoring + * [#1451] Use stun.sflphone.org as default STUN server + * [#1060] New po files - need to be translated + * [#1060] Update french translation - Rebuild template file + * [#1456] Add a flag to be replaced in the control files + * [#1454] Make cppunit optional; remove from build deps in control + files + * [#1401] Add libexpat1-dev dependency in control files + * [#1448] Take off these ugly debug messages + * [#1448] fixed getTelephoneTone and getTelephoneFile() called + repeatedly + * [#1406] add liblog4c-dev in build-depends + * [#1409] Restore .desktop icon + + -- SFLphone Automatic Build System <team@sflphone.org> Mon, 25 May 2009 11:34:48 -0400 + +sflphone-gnome (0.9.5-SYSTEM~rc2) SYSTEM; urgency=low + + ** 0.9.5 rc2 ** + + * [#1422] Improved error message + * [#1402] Fix pjsip build + * [#1404] Clear GTK-Critical Bug at client startup + * [#1422] Added automatic VM shutdown when building on more than one + VM + * [#1422] Fixed some issues with new changelog generation script + * [#1422] Moved distribution update to specific file + * [#1422] Dropped git-dch, replace by home made implementation + * [#1402] Fix pjsip build + * [#1404] Clear GTK-Critical Bug at client startup + * Changes for name based dbus connection + * Clean changelogs + * [#1343] Gnome: Implement a callback system to handle focus on + different widgets + * Debus Session + * Refactoring Python code, PEP8 + * [#1430] Get back dbus_g_proxy_new_for_name + * [#1430] Get back DBUS_BUS_SESSION type + * [#1430] Dbus fixed owner message binding + * Second test with DBUS owner + * [#1404] Gnome -> Preferences -> Hooks + * [#1404] Gnome -> Preferences -> Recordings + * [#1404] Call History + * [#1404] Gnome -> Preferences -> Address Book + * [#1404] IF the first notification option disable the second + notification + * Dbus with fixed owner does not automatically start the deamon + * Add codec debug tests in pysflphone + * [#1407] Some print info + * [#1407] Add a scenario to pick_up action + * Test client dbus connection to a fixed owner + * Add python dbus test suite + * [#1161] Modified version handling in build system + * [#1314] Test pulse audio and audio streams connect and disconnect + * [#1402] Add info message after configure + * [#1402] Build the daemon with the local pjsip library (vs the + installed one) + * [#1009] Fix Codec Sampling Rate set to zeros + * [#1314] Add mutex to pulse layer audio streams + * [#1314] Refactoring pulseaudio stream to test connect disconnect + * [#1314] Refactoring of pulselayer to test conect/disconnect + * Add debug messages in debus calls concerning account + * [#1314] Add some return values to audio init functions + * [#1406] add liblog4c-dev in build-depends + * [#1409] Restore .desktop icon + * Bug #1405: Fix strings as requested. + * Bug #1404: Fix strings in preferences panel. + + -- SFLphone Automatic Build System <team@sflphone.org> Tue, 19 May 2009 12:08:18 -0400 + +sflphone-gnome (0.9.5-0ubuntu1~rc1) SYSTEM; urgency=low + + [ SFLphone Project ] + * [#1262] Updated changelogs for version 0.9.5-0ubuntu1 Snapshot 2009- + 05-05 + + [ Emmanuel Milou ] + * Add some python CLI client code; not really functional + * [#1108] Fix peerHungup method for IP to IP call + + [ Alexandre Savard ] + * [#1108] Correct setting of SIP contact for direct IP call + * [#1108] SIP user agent handles incoming REFER + + [ Emmanuel Milou ] + * Remove website from repository + * Update translation + + [ Alexandre Savard ] + * Sflphone icon's tooltip changed for "configured" instead of + "registered" + + [ Emmanuel Milou ] + * Update translation + + [ Sflphone Project ] + + -- Sflphone Project <sflphone@mtl.savoirfairelinux.net> Tue, 05 May 2009 19:16:13 -0400 + +sflphone-gnome (0.9.5-0ubuntu1~beta) SYSTEM; urgency=low + + [ Julien Bonjean ] + * Updated Eclipse stuff + * Improved addressbook config window + * Added sflphone Eclipse stuff + * Implemented addressbook list server side + * Moved dbus stuff in dbus directory + * Updated addressbook configuration + + [ Emmanuel Milou ] + * Remove unuseful installation scripts. Use apt-get build-dep sflphone + instead + * fix bug #1090 + + [ Alexandre Savard ] + * defining speex 16khz + + [ Emmanuel Milou ] + * Remove unuseful file from build system + * Start dns srv resolver + + [ Alexandre Savard ] + * Basic ogg/vorbis initialization + + [ Emmanuel Milou ] + * Handle incoming IP-to-IP invite correctly + + [ Alexandre Savard ] + * speex wideband 16000 + + [ Emmanuel Milou ] + * Better handling of incoming IP to IP call + * DNS SRV resolution functional + * Implement IAX2 incoming URL + * Allow user to make IP call without any accounts configured + * Add a contextual menu to edit a number from the contacts tab + * Add comments, tooltip and new button to the contextual menu + * add delete event, migrate to GTK 2.16 for sexy icons + * Resolve ticket #1118 + * Update suse spec file + * Add phone number cleanup functions, unit tests and panel + configuration + * Add pertinent test that fails + * fix dependencies for suse package + * Add contextual edit menu in history - #1120 + + [ Alexandre Savard ] + * Temporary comit: make speex wideband (16 khz) + * Temporary: shared object for speex narrow band + * Temporary: speex narrowband and wideband coexist + + [ Julien Bonjean ] + * Fixed bug when no book selected + * Fixed addressbook related compilation warnings + * Fixed GTK client remaining compilation warnings + * Fixed segfault when book removed since last sflphone run + * Fixed bug when book is unreachable (ldap error) + + [ Alexandre Savard ] + * Fix codec list in audio config window + * Active/inactive speex codec by payload + + [ Julien Bonjean ] + * Updated gitignore + * Added some comments + + [ Emmanuel Milou ] + * Add callto: handler script for browsers and al. + * Integrate test compilation in the daemon build-system + + [ Julien Bonjean ] + * Fixed g_object_unref warning for pixbuf + * Cleaned too verbose output + * Fixed toolbar update warning + * Added support for asynchornous books open (first shot) + + [ Emmanuel Milou ] + * Add a DBus call to fetch the call details from a call ID - Ticket + #928 + + [ Julien Bonjean ] + * Improved async open books + * Fixed bug #1139 + + [ Emmanuel Milou ] + * Add a way to save account order + * commit missing files + + [ Julien Bonjean ] + * Introduced log4c (ticket #1162) + + [ Emmanuel Milou ] + * Load/save account order functionnal - ticket #813 + + [ Alexandre Savard ] + * Add CELT codec (#1143) + * Make celt frame size 256 (*1143) + + [ Julien Bonjean ] + * Switched everything to log4c (ticket #1162) + * Updated eclipse settings + + [ Emmanuel Milou ] + * Restore adding account - ticket #1172 + * Add liblog4c dependecy - ticket #1179 + + [ Alexandre Savard ] + * Double maxAvailByte for frame size in rtp (#1143) + + [ Emmanuel Milou ] + * Add User-Agent SIP header - Ticket #1173 + + [ Julien Bonjean ] + * Fixed autoresize issue (#708) + + [ Emmanuel Milou ] + * Remove libcppuint dependency for the debian packages + * Look for libsexy only if gtk version < 2.16 - Ticket #1116 + * Remove libsexy dependency for jaunty. ticket #1116 + + [ Julien Bonjean ] + * Introduced unit tests (#1146) + * Updated gitignore + * Fixed Makefile (#1146) + + [ Emmanuel Milou ] + * [TICKET #1112] Add a test on the voice buffer to send through iax + packets + * Remove doublon in dependencies + * Remove warnings from the client test framework + * Update version number to 0.9.5~beta + * Update build-package script + * Add check dependency in build-deps control file field + * Create debian files for the new sflphone-gnome + * [TICKET #1212] Add Replaces field in control files + * [TICKET #1212] Fix manpages installation path + * [TICKET #1212] Add maintainer scripts to create alternatives + * [#1212] Update the manpages generation - edit preinst maintainer + script + * [#1212] Fix reference error in manpage + * [#1212] Add missing files on the client side + * [#1212] Fix debian docs files - no TODO file + * [1212] Fix manpage creation problem + * [#1220] Generate client-side glue files and marshaller at + compilation time + * [#1220] Generate server-side glue files at compilation time + * [#1212] Change binary name to sflphone-gnome + * [#1212] Update .gitignore to fit the new working tree + * [#1220] Explicitly generate glue files before building the library + * [#1220] Compile dbus directory before audio + * [#1212] Create sflphone-common at the root of the repository + * [#1212] Re-add pjproject + * [#1212] Remove Makefile from repo + * [#1220] Fix Makefile.am + * [#1212] New working directory functional + * [#1212] Update .gitignore + * [#1212] Hack to make pjsip compile.. + * [#1220] Use non-installed binary for dbusxx-xml2cpp + * [#1212] Add descriptive files, remove unuseful scripts from tools/ + + [ Alexandre Savard ] + * Restore speex codecs + * add frame size for celt (#1143) + * add framesize to codec, independant from audiolayer (#1143) + * use codec frame size in rtp (#1143) + * compute fixed_codec_framesize (#1143) + * do not resample if not required (#1143) + * add condition on resampling for decoder (#1143) + * add a condition on bytesAvail == 0 from mic data + * no maximum in rtp decode (#1143) + * compute maximum for decoding (#1143) + + [ Emmanuel Milou ] + * [#1146] Implement unitary tests on the client-side + + [ Alexandre Savard ] + * use float instead of int to compute max nb of sample (#1143) + * add nbSampleMax for unresampled data (#1143) + * make thread sleep during 5 ms insead of 20 (#1143) + * use unix usleep (#1143) + * 50 usecond thread!!!!! (#1143) + * try with the smallest compression (#1143) + * use timer set at framesize (#1143) + + [ Emmanuel Milou ] + * [#1161] Restore changelog version + + [ Alexandre Savard ] + * Remove celt stuff + + [ Emmanuel Milou ] + * [#1161] Update changelog + * [#1220] Add Conflicts: sflphone in debian control files + * [#1179] Add liblog4c3 runtime dependency + * [#1212] FIx typo error in dependency list for itnrepid + * [#1212] FIx .desktop file to point on the right exec + * [#1212] Modify changelog replacing tag + + [ Sflphone Project ] + * "[#1262] Updated changelogs for version 0.9.5-0ubuntu1~beta" + + [ Emmanuel Milou ] + * [#1212] restore changelogs + + [ Sflphone Project ] + * [#1262] Updated changelogs for version 0.9.5-0ubuntu1 Snapshot 2009- + 04-27 + + [ Emmanuel Milou ] + * [#1212] restore changelogs + + [ Sflphone Project ] + * [#1262] Updated changelogs for version 0.9.5-0ubuntu1~beta + + [ Emmanuel Milou ] + * [#1212] restore changelogs + + [ Sflphone Project ] + + -- Sflphone Project <sflphone@mtl.savoirfairelinux.net> Mon, 27 Apr 2009 17:00:03 -0400 + +sflphone-gnome (0.9.4-0ubuntu2) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Restore speex and GSM detection + + [ Emmanuel Milou ] + * Fix bug #1090 + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 8 Apr 2009 11:29:15 -0500 + +sflphone (0.9.4-0ubuntu1) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Integrate DBus-c++ and libiax2 in the main build system + * Clean up in the working repository + * Reorder hooks configuration panel + * Protect case when no codecs are active + * Fix some return values + * Add unitary tests for the hook manager (premisces) + + [Yun Liu] + * Update chinese translation + + [Sven Werlen] + * Update german translation + + [Hussein Abdallah] + * Update russian translation + + [Maxime Chambreuil] + * Update spanish translation + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 3 Apr 2009 18:29:15 -0500 + + +sflphone (0.9.4-rc1) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Fix bug while trying to hold/unhold several simultaneous call + * Improve address book build system + * Implement SIP url popup on incoming call + * Improve GTK+ panel configuration + [ Julien Bonjean ] + * GTK+ client refactoring + * GTK+ clean up + * Address book improvment + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 27 Mar 2009 18:29:15 -0500 + +sflphone (0.9.4-0beta1) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Display codec used during conversation on the GUI + * Enable/disable STUN parameters at runtime + * Refactor search bar use + [ Emmanuel Milou ] + * Build system fixes + * Implement SIP re-invite + * Implement IP to IP call + [ Julien Bonjean ] + * Integrate GNOME address book based on evolution data server + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 20 Mar 2009 18:29:15 -0500 + + +sflphone (0.9.3-0ubuntu3) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Both playback and record streams in PA_STREAM_CORKED (pulseaudio) + * Use PLUGHW device for ALSA capture + * Functional IAX and SIP recording for voicemail + * Use the less CPU-consuming interpolator algorithm for resampling + * Display in GTK GUI the codec used in conversation + * GTK GUI use ASCII instread of utf-8 + * Add record menus in GTK GUI + * Put on hold when dialing a new number + * AccountID's are saved in the history + + [ Emmanuel Milou ] + * Integrate DBUS C++, libiax2 in the git repository + * Update website + * Use libspeexdsp only if available on the system + * Updated .gitignore file + + [Cyrille Béraud] + * Account assistant manager improvment + * Add an email request when creating a new account to receive voicemails + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Sat, 14 Feb 2009 13:29:15 -0500 + +sflphone (0.9.3-0ubuntu2) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Add compilation note in README + * Use default ALSA plugin for capture + * Fix the ALSA capture problem one more time + * Clean up debug messages in dbus.c + * Add libspeexdsp dependency + * Remove implicit declaration compilation warnings + * Fix links in the website, add release note + * Change capture for the website front page + * Add alsa devel dependency in build-depends control file field + * Clean up, indentation, try to handle latency problems in iax/pulseaudio + * Remove pjsip generated files from the repo + * Use the previous declared curAlias function in accountwindow + * Fix bug in history call duration when the call fails + * Remove runtime warning in the GTK+ client + * Add librsvg2-common dependency to load SVG under KDE + * Refresh .gitignore + * Update locales files + french translation + * Add configuration panel for future noise reduction + * Add configuration panel for audio record module + * Daemon less verbose; accounts don't try to access STUn options anymore + * Fix typo in configwindow + * Add content in the official website + * use a GTK_STOCK icon for the record button + * Complete description text in the assistant manager + * Add libtool flags in client configure.ac + * Remove unuseful dependency (snd) + * Fix SIP transfer problems + * Remove previous version of PJSIP from the repo + * Upgrade PJSIP to version 1.0.1 + * Add the new website source in the repository + * Use libspeexdsp for silence detection only if available + + [ Loïc Faure-Lacroix ] + * Ajout du logo gpl3 + * Ajout des images + * Ajout de la section screenshot pour le site + * Ajout du favicon dans le header + * Modification des cartes + + [ Alexandre Savard ] + * Clean up <speex/libspeexdsp> + * Small cleanup + * Save Wave fixed + * Fix new call button when recording + * libspeexdsp added + * Recording: default home folder at startup + * Minor changes to config window + * IAX recording fixed + * Set / get recording path, still need some GTK for client + * AudioRecord file name format + * Now recording in HOME folder + + [ Cyrille Béraud ] + * Fix bug in reqaccount.c + + [ Maxime Chambreuil ] + * Update spanish translation + + [Yun Liu ] + * Update chinese translation + + [ Hussein Abdallah ] + * Update russian translation + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Sat, 14 Feb 2009 13:29:15 -0500 + +sflphone (0.9.3-0ubuntu1) SYSTEM; urgency=low + + * Remove debug + * Join thread before leaving + * Fix implicit declaration in reqaccount + * Add REST code to build the request to server + * Fix GValue initialization warnings + * Update version number, fix implicit declaration, fix GTK markup + warnings + * Apply patch to create custom SIP account from our own server + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 06 Feb 2009 19:17:32 -0500 + +sflphone (0.9.2-2ubuntu9) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Speex audio codec preprocessing initialization + * peer hung up segmentation fault solved + * Stop recording when transfering + * Terminate only one call + * Add isRecording() function + * Fix call_icon GTK client + * Fix SIPCallClose() function, recorded file now close properly + * Function terminateSIPCall added in sipvoiplink and managerimpl + * Fix thread destructor + * setRecordingOption function implement in audiorecord + * Record now implemented in Call class + * Record interface complete (on hold erase previous recording) + * Added recButton in client + * Added: record button related icons + * Record button added + * Overload AudioRecord::recData to get mic and speaker data mixed + * Recording now in audiortp::run() method + * Audio recording working in AudioRTP: receiveSessionForSpeaker + * Open/close a wave file when pulse audio stream start/stop + + [ Emmanuel Milou ] + * Fix path for GTK+ icons; clean up + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Thu, 05 Feb 2009 18:27:53 -0500 + +sflphone (0.9.2-2ubuntu8) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Update changelogs + * Fix bug in merge and in Makefile.am + * Terminate only one call + * Disable PJsip shutdown when changing STUN parameters + * Function terminateSIPCall added in sipvoiplink and managerimpl + * Add a timer to the alsa thread to not jam the CPU load + * Fix bug in sipvoiplink.cpp + * Clean shutdown of pulseaudio on quiting + * Fix DTMF at first start with Pulseaudio + * Remove zeroconf from the build system + * Add a library manager + exception handling + * Clean up in the working directory + * Better handling of capture XRUNs + * Restore mic adjust volume on ALSA layer + * Protect device ALSA operation if not opened + * Fix the switching layer bug + * Use dynamic_cast<> to use audiolayer-specific methods + * Open the audio devices only once at startup + * Refactoring of the ALSA part + * Functional plug-in manager + * Use a C++ thread to handle tones and DTMF in ALSA + * Restore IAXVoIPLink, restore Mutex + * Make the plugins registering against the plugin manager + * Migrate to 1->N relationship between voiplink and accounts + * API plugin for registration + * Use C++ thread in SIP, move everything in sipvoiplink + * Complete singleton pattern for the plugin manager + * Add -Wno-return-type compilation flag to remove warnings; Update + version number in configure.ac + * Add the dynamic loading for the plugin framework; integate unittest + + [ Yun Liu ] + * Update rpm spec file + * modify build package script and spec file for suse + + [ Alexandre Savard ] + * Add audiorecorder plugin and testaudiorecorder + * Add audio Recording class, edit global.h + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 04 Feb 2009 14:00:30 -0500 + +sflphone (0.9.2-2ubuntu7) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Update changelog to 0.9.2-6 + * Fix some dbus-glib implementation details on the client side + * Init history after dbus initialization + * Add error checking in useragent; Clean sipvoiplink + * Prevent crash when trying to call an empty number + * Set the volume of the playback stream to PA_VOLUME_NORM at startup + * Fix GTK+ generic value double initialization + * Fix jaunty control file dependency problems + * Fix jaunty control file dependency problems + + [ Yun Liu ] + * Fix bug ticket # 137 + * Tolerant to gsm library of OpenSuse 11 + + [ Sven Werlen ] + * Update german translation + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 23 Jan 2009 17:48:13 -0500 + +sflphone (0.9.2-2ubuntu6) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Migrate STUN configuration to the main config window + * Update french translation + * Other tiny memory leaks + * Fix memory leak in sampleconverter.cpp + * Generate packages from the release branch + * update the build package script + * modify the control files with architecture=any + * Remove valgring uninitialized value + * IAX and SIP use the same global variables to set account + configuration ; fix broken code + + [ Maxime Chambreuil ] + * Update spanish translation + + [ Hussein Abdallah ] + * Update russian translation + + [ Yun Liu ] + * Update translation files + * Fix the bug when user uncheck the account which fails in the + previous registration + * Add stun error status + * Fix bug ticket #143 + * Script for auto-install dependencies + * Fix bug ticket #140 + * Fix bug ticket 141 + * Fix the reregister process when user change the details of an + account + + -- Emmanuel Milou <manu@sulfur.inside.savoirfairelinux.net> Fri, 16 Jan 2009 18:19:05 -0500 + +sflphone (0.9.2-2ubuntu5) SYSTEM; urgency=low + + * Fix memory leak in the pulseaudio callback + * Update debian package generation script + * Warnings removal in GTK+ client + * Clean adjust volume method in alsalayer + * Plug the sflphone playback volume control to the pulseaudio volume + manager + * Display the date in history according to the current locale + * Generate the changelog according to the git commit messages + * Complete header in chinese translation file + * Use the right gpg key to sign the packages + * add debian jaunty jackalope support + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 14 Jan 2009 21:17:20 -0500 + +sflphone (0.9.2-2ubuntu4) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * add german translation + + [ Yun Liu ] + * Fix GUI crash in Ubuntu8.10 64bit system + + -- Yun Liu <yun.liu@savoirfairelinux.com> Thu, 08 Jan 2009 13:08:51 -0500 + +sflphone (0.9.2-2ubuntu3) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * The main thread synchronizes the ringtone thread + * disable custom ringtone for the ALSA layer + * Fix the Makefile.am in man directory, add a SEE ALSO section + + [ Yun Liu ] + * Fix daemon crash caused by the previous patch ( for bug ticket #129) + + -- Yun Liu <yun.liu@savoirfairelinux.com> Tue, 06 Jan 2009 16:18:38 -0500 + +sflphone (0.9.2-2ubuntu2) SYSTEM; urgency=low + + * Fix bug ticket #129 + + -- Yun Liu <yun.liu@savoirfairelinux.com> Wed, 5 Jan 2009 15:54:53 -0500 + +sflphone (0.9.2-2ubuntu1) SYSTEM; urgency=low + + * Migrate from eXosip library to pjsip + * Add multiple SIP accounts support + * Fix ringtones problems + * Add a pulseaudio support + * Improve audio quality with ALSA + * Add chinese translation + * Improve spanish translation + * Migrate to a maintained C++ DBus bindings + * Clean and improve the build system + * Add build-dependency on Perl because we need pod2man to generate manpages + + -- Yun Liu <yun.liu@savoirfairelinux.com> Wed, 26 Nov 2008 09:47:53 -0500 + +sflphone (0.9.1) unstable; urgency=low + * Add a search tool in the history + * Migrate some gtk_entry_new to sexy_icon_entry_new + * Bug fix (Ticket #78): The voicemail password isn't displayed anymore in + the history tab + * Add the SIP registration expire value in the user file. + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Thu, 22 May 2008 11:14:25 -0500 + +sflphone (0.9.0) unstable; urgency=low + * Add history features + * Call date + * Call duration + * Mouse events in the history tab + * Smooth switch from the history tab to the calls tab + * Remove most of GTK-Critical warnings + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 13 May 2008 16:58:25 -0500 + +sflphone (0.9-2008-06-06) unstable; urgency=low + * Audio bug correction: capture stopped after a few minutes of conversation + with USB Plantronics sound card + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Tue, 06 May 2008 16:58:25 -0500 + +sflphone (0.9-2008-05-06) unstable; urgency=low + * Bug correction: account creation with the assistant + * GTK+ warnings removal + * libnotify warnings removal + * Remove aliasing on the SFLphone logo + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Mon, 05 May 2008 16:58:25 -0500 + +sflphone (0.9) unstable; urgency=low + * Clean dependencies ( removal of libboost ) + * Several GTK improvement and updates + -account window + -configuration window + * Migrate from GtkCheckMenuItem to GtkImageMenuItem + * ALSA standard I/O transfers: MMAP instead of R/W + * Fix speex audio quality + * IAX2 protocol + -Fix hold/unhold situation + -Add on hold music + * SIP protocol + -Ringtone on incoming call + -Fix transfer situation + * Add desktop notification ( libnotify ) + * Improve the system tray icon behaviour + * Improve registration error handling + * Register/unregister from the account window takes effect without starting back SFLphone + * Compilation warnings removal + * Call history + * Add an account configuration wizard + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 30 Apr 2008 16:58:25 -0500 + +sflphone (0.8.2) unstable; urgency=low + * Internationalization of the GTK GUI + * English / French + * STUN support + * Slight modifications of the graphical interface ( tooltips, dialpad, ...) + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 21 Mar 2008 11:37:53 -0500 diff --git a/tools/build-system/launchpad/sflphone-gnome/debian/compat b/tools/build-system/launchpad/sflphone-gnome/debian/compat new file mode 100644 index 0000000000..7ed6ff82de --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome/debian/compat @@ -0,0 +1 @@ +5 diff --git a/tools/build-system/launchpad/sflphone-gnome/debian/control b/tools/build-system/launchpad/sflphone-gnome/debian/control new file mode 100644 index 0000000000..656bc4f249 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome/debian/control @@ -0,0 +1,21 @@ +Source: sflphone-gnome +Maintainer: SavoirFaireLinux Inc <julien.bonjean@savoirfairelinux.com> +Section: gnome +Priority: optional +Build-Depends: debhelper, libgcc1, intltool, autopoint, autoconf, automake, libtool, libgtk-3-dev, libdbus-glib-1-dev, libnotify4-dev | libnotify-dev (>= 0.7), check, gnome-doc-utils, rarian-compat, librsvg2-common, yelp-tools, gnome-common +Standards-Version: 3.7.3 + +Package: sflphone-gnome +Priority: optional +Architecture: any +Depends: sflphone-daemon (=${source:Version}), ${shlibs:Depends}, ${misc:Depends} +Provides: sflphone-client-gnome +Replaces: sflphone-client-gnome, sflphone +Conflicts: sflphone-client-gnome-video, sflphone-gnome-video, sflphone-data +Homepage: http://www.sflphone.org +Description: GNOME client for SFLphone + Provide a GNOME client for SFLphone. + SFLphone is meant to be a robust enterprise-class desktop phone. + SFLphone is released under the GNU General Public License. + SFLphone is being developed by the global community, and maintained by + Savoir-faire Linux, a Montreal, Quebec, Canada-based Linux consulting company. diff --git a/tools/build-system/launchpad/sflphone-gnome/debian/copyright b/tools/build-system/launchpad/sflphone-gnome/debian/copyright new file mode 100644 index 0000000000..90f090efff --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome/debian/copyright @@ -0,0 +1,28 @@ +This package was debianized by Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> on +Fri, 3 Apr 2009 09:47:53 -0500. + +It was downloaded from the git repository of SFLphone: git://sflphone.org/git/sflphone.git + +Upstream Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + +Copyright: + +Savoir-Faire Linux Inc. + +License: + +This software is copyright (c) 2004-2011 Savoir-Faire Linux inc. + +You are free to distribute this software under the terms of +the GNU General Public License version 3. +On Debian systems, the complete text of the GNU General Public +License can be found in the file `/usr/share/common-licenses/GPL'. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklyn St, Fifth Floor, Boston, MA 02110-1301, USA. diff --git a/tools/build-system/launchpad/sflphone-gnome/debian/cron.d b/tools/build-system/launchpad/sflphone-gnome/debian/cron.d new file mode 100644 index 0000000000..d11e611777 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome/debian/cron.d @@ -0,0 +1,4 @@ +# +# Regular cron jobs for the sflphone package +# +0 4 * * * root sflphone_maintenance diff --git a/tools/build-system/launchpad/sflphone-gnome/debian/dirs b/tools/build-system/launchpad/sflphone-gnome/debian/dirs new file mode 100644 index 0000000000..e2dc98dcb2 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome/debian/dirs @@ -0,0 +1,7 @@ +usr/bin +usr/share/applications +usr/share/pixmaps +usr/share/sflphone +usr/share/locale +usr/share/doc +usr/share/man diff --git a/tools/build-system/launchpad/sflphone-gnome/debian/docs b/tools/build-system/launchpad/sflphone-gnome/debian/docs new file mode 100644 index 0000000000..9830da213f --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome/debian/docs @@ -0,0 +1,5 @@ +NEWS +README +ChangeLog +AUTHORS + diff --git a/tools/build-system/launchpad/sflphone-gnome/debian/manpages b/tools/build-system/launchpad/sflphone-gnome/debian/manpages new file mode 100644 index 0000000000..27631d29ed --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome/debian/manpages @@ -0,0 +1,2 @@ +debian/sflphone-gnome/usr/share/man/man1/sflphone-client-gnome.1 +debian/sflphone-gnome/usr/share/man/man1/sflphone.1 diff --git a/tools/build-system/launchpad/sflphone-gnome/debian/postinst b/tools/build-system/launchpad/sflphone-gnome/debian/postinst new file mode 100644 index 0000000000..ebee7fa2bb --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome/debian/postinst @@ -0,0 +1,7 @@ +#!/bin/sh -e + +update-alternatives --install /usr/bin/sflphone sflphone /usr/bin/sflphone-client-gnome 100 \ + --slave /usr/share/man/man1/sflphone.1.gz sflphone.1.gz \ + /usr/share/man/man1/sflphone-client-gnome.1.gz + +exit 0 diff --git a/tools/build-system/launchpad/sflphone-gnome/debian/postrm b/tools/build-system/launchpad/sflphone-gnome/debian/postrm new file mode 100644 index 0000000000..70be710bd1 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome/debian/postrm @@ -0,0 +1,36 @@ +#!/bin/sh +# postrm script for sflphone +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * <postrm> `remove' +# * <postrm> `purge' +# * <old-postrm> `upgrade' <new-version> +# * <new-postrm> `failed-upgrade' <old-version> +# * <new-postrm> `abort-install' +# * <new-postrm> `abort-install' <old-version> +# * <new-postrm> `abort-upgrade' <old-version> +# * <disappearer's-postrm> `disappear' <overwriter> +# <overwriter-version> +# for details, see http://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +if [ "$1" = "purge" ] +then + + # remove the user config file + rm -f $HOME/.sflphone/sflphonedrc + +fi + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 + + diff --git a/tools/build-system/launchpad/sflphone-gnome/debian/preinst b/tools/build-system/launchpad/sflphone-gnome/debian/preinst new file mode 100644 index 0000000000..d5e20248d9 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome/debian/preinst @@ -0,0 +1,19 @@ +#!/bin/sh +# postrm script for sflphone +# +# see: dh_installdeb(1) + +set -e + +package=sflphone-gnome + +case "$1" in + install|upgrade) + ## Clean up the previous manpage + if [ -f /usr/share/man/man1/sflphone-gtk.1 ]; then + rm /usr/share/man/man1/sflphone-gtk.1 + fi + ;; +esac + +exit 0 diff --git a/tools/build-system/launchpad/sflphone-gnome/debian/prerm b/tools/build-system/launchpad/sflphone-gnome/debian/prerm new file mode 100644 index 0000000000..5e90217068 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome/debian/prerm @@ -0,0 +1,7 @@ +#!/bin/sh -e + + +if [ "$1" = "remove" ]; then + # Remove alternatives symlink set in postinst + update-alternatives --remove sflphone /usr/bin/sflphone +fi diff --git a/tools/build-system/launchpad/sflphone-gnome/debian/rules b/tools/build-system/launchpad/sflphone-gnome/debian/rules new file mode 100755 index 0000000000..3a3522cccd --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome/debian/rules @@ -0,0 +1,117 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 +export DH_OPTIONS + +package=sflphone-gnome + +CXX = g++-4.0 +CFLAGS = -Wall -g +DEB_INSTALL_MANPAGES_sflphone_gnome = sflphone.1 sflphone-client-gnome.1 + +configure: configure-stamp +configure-stamp: + dh_testdir + # Add here commands to configure the package. + NOCONFIGURE=1 ./autogen.sh + ./configure --prefix=/usr --disable-video + touch configure-stamp + + +#Architecture +build: build-arch build-indep + +build-arch: build-arch-stamp +build-arch-stamp: configure-stamp + + # Add here commands to compile the arch part of the package. + $(MAKE) + touch $@ + +build-indep: build-indep-stamp +build-indep-stamp: configure-stamp + + # Add here commands to compile the indep part of the package. + #$(MAKE) doc + touch $@ +clean: + dh_testdir + dh_testroot + rm -f build-arch-stamp build-indep-stamp configure-stamp + # Add here commands to clean up after the build process. + [ ! -f Makefile ] || $(MAKE) distclean + +ifneq "$(wildcard /usr/share/misc/config.sub)" "" + cp -f /usr/share/misc/config.sub config.sub +endif +ifneq "$(wildcard /usr/share/misc/config.guess)" "" + cp -f /usr/share/misc/config.guess config.guess +endif + dh_clean + +install: install-indep install-arch +install-indep: + dh_testdir + dh_testroot + dh_clean -k -i + dh_installdirs -i + # Add here commands to install the package into debian/sflphone. + +install-arch: + dh_testdir + dh_testroot + dh_clean -k -s + dh_installdirs -s + # Add here commands to install the arch part of the package into + # debian/tmp. + $(MAKE) DESTDIR=$(CURDIR)/debian/$(package) install + dh_install -s +# Must not depend on anything. This is to be called by +# binary-arch/binary-indep +# in another 'make' thread. + +binary-common: + dh_testdir + dh_testroot + dh_installchangelogs ChangeLog + dh_installdocs + dh_installexamples +# dh_installmenu +# dh_installdebconf +# dh_installlogrotate +# dh_installemacsen +# dh_installpam +# dh_installmime +# dh_python +# dh_installinit +# dh_installcron +# dh_installinfo + dh_installman + dh_link + dh_strip + dh_compress + dh_fixperms +# dh_perl + dh_makeshlibs + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb +# Build architecture independant packages using the common target. +binary-indep: build-indep install-indep + $(MAKE) -f debian/rules DH_OPTIONS=-i binary-common + +# Build architecture dependant packages using the common target. +binary-arch: build-arch install-arch + $(MAKE) -f debian/rules DH_OPTIONS=-s binary-common + +binary: binary-arch binary-indep +.PHONY: build clean binary-indep binary-arch binary install install-indep install-arch configure diff --git a/tools/build-system/launchpad/sflphone-gnome/debian/substvars b/tools/build-system/launchpad/sflphone-gnome/debian/substvars new file mode 100644 index 0000000000..566a162f0d --- /dev/null +++ b/tools/build-system/launchpad/sflphone-gnome/debian/substvars @@ -0,0 +1 @@ +plop=0.9.6 diff --git a/tools/build-system/launchpad/sflphone-kde/debian/changelog b/tools/build-system/launchpad/sflphone-kde/debian/changelog new file mode 100644 index 0000000000..7bc6d8ca8c --- /dev/null +++ b/tools/build-system/launchpad/sflphone-kde/debian/changelog @@ -0,0 +1,180 @@ +sflphone-kde (1.4.0) SYSTEM; urgency=low + * See website at www.sflphone.org for more details + + -- Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> Tue, 7 Jun 2012 11:38:30 -0500 + +sflphone-kde (1.1.0-rc20120607~ppa1~SYSTEM) SYSTEM; urgency=low + + ** SNAPSHOT 1.1.0-rc20120607~ppa1~SYSTEM ** + + * [ #12096 ] Implement more calls details, make it more scalable + * [ #11290 ] Update french translation + * [ #11859 ] Fix a regression in name conversion + * [ #11850 ] Fix a little regression + * [ #11859 ] Fix sorting by popularity + * [ #11569 ] Fix filter + * [ #12003 ] Add icon overlay for history item + * [ #11886 ] Add basic reverse peer naming support + * kde/callview: removed trailing whitespace + * [ #12008 ] Implement GUI part + * [ #11988 ] Move ringtone to a new tab + * [ #11990 ] Fix history delegate width + * [ #11990 ] Make call item height configurable + * [ #7007 ] Fix many history bugs and add 'copy' contextual menu + * [ #7007 ] Add more contextual options + * [ #11963 ] Move ringtone to account + * [ #7003 ] Implement more drag and drop + * [ #11888 ] Add support for previous timestamp + * [ #11887 ] Drop phonon dependency + * [ #11887 ] Use daemon player instead of phonon + * [ #11889 ] Fix a regression + * [ #11889 ] Fix a regression + * [ #11861 ] Fix memory leak + * [ #11861 ] Little profiling + * [ #11850 ] Reimplement most destructor + * [ #11847 ] Move unregister back into SFLPhoneApplication + * [ #11847 ] Fix the unregister signal + * [ #11846 ] Fix the text message box behavior + * [ #11845 ] Refactor menus, duplicate display dock options + * [ #11731 ] Fix visual glitch when hang up, harden against 0 + participant conference + * [ #11798 ] Implement basic signal handling, it does not seem to be + enough, but it help + * [ #11815 ] Make double click call instead of queing items + * [ #11822 ] Add configuration options + * [ #11814 ] Add selection again + * [ #3912 ] Finally make double clicking work, it was the last client + bug, the other one is in the daemon + * Fix plasmoid + * [ #11733 ] Update the state machine to handle conferences correctly, + not just ignore them + * * #11252: daemon: removed deprecated zrtp code + * [ #11385 ] Removing account work again + * [ #11435 ] Fix trasfer, it apparently never worked, there was a bug + in the state machine path + * [ #11621 ] Support conferences in init + * [ #3905 ] Fix the oldest KDE open bug, edit the conference pixmap + * [ #11573 ] Add paste option + * [ #11574 ] Add a account status label in status bar + * [ #11577 ] Add a timer label + * [ #11572 ] Implement accent independent filter for history and + bookmark + * [ #11572 ] Implement accent independent filter for contact + * [ #11576 ] Implement optional contact details + * [ #10222 ] Change default caller name from Unknown to his/her phone + number + * [ #11337 ] Add/Restore keyboard call selection + * [ #11337 ] Add more accessibility options + * [ #11337 ] Add basic text to speech status for calls + * [ #11290 ] Fix dynamic translation + * [ #11290 ] Update french translation + * [ #11290 ] Add missing i18n() call, it should be translatable again + * Make dependencies check more enforced for submodules + * * #11269: merged master into video + * [ #11255 ] Code polishing + * [ #10222 ] Call again on double click + * [ #11219 ] Change license to LGPLv2 for libraries + * [ #7022 ] Make the new plasmoid the default, remove the old one, end + of an era + * [ #7022 ] Merge the new plasmoid branch, the older one is still the + default + * [ v1.1 ] Update version number + * [ #10724 ] Add more dataengine services + * [ #10724 ] Add bookmarks to the dataengine + * [ #10724 ] Add bookmarks to the dataengine + * [ #10724 ] Make contact sorting work for some sorting type + * [ #10724 ] Refactor sorting in the KDE library, implement contact in + the dataengine + * [ #10724 ] Move the dataengine to the new KDE lib + * [ #10724 ] Refactor data engine and split akonadi support out of the + client to make the dataengine more future proof + * Fix missing phonon + * Fix compilation warning on old GCC + * [ #10222 ] Fix warnings + * [ #10380 ] Can't send message without a call + * [ #10222 ] Test configuration dialog, fix it, implement basic + messaging + * [ #10222 ] Fix saving default history sorting + * [ #10222 ] Use categorized Tree for history + * [ #10222 ] Fix contact sorting + * [ #10222 ] Async update for bookmark dock + * [ #10222 ] Fix issues with account config dialog + * [ #10222 ] Use categorized views, fix bookmark + * [ #10222 ] Fix minor itches and bugs + * [ #10121 ] Sync the KDE with daemon, fix a few issues and implement + a recorded call player + * * #10018: renamed registration related keys in dbus + * * #8542: removed trailing whitespace from tree + * * #8357: gnome client now works with new dbus video API + * * 8487: cleanup in dbus + * * #8450: fixed confusion between expire and enable values + * * #8435: Remove typos + * cleanup in xml files + * Revert "Merge branch 'master' of + git+ssh://git.sflphone.org/var/repos/sflphone/git/sflphone" + * * #7264: gnome client now saves/loads history as List of Dicts. + * [ #7901 ] Cleanup the changes + * [ #7901 ] Fix compiler warnings + * [ #7901 ] Do not show the conf button when in the same conference + * [ #7929 ] Add some client side work around until it is fixed + * [ #7901 ] Cosmetic and bug fixes + * [ #7901 ] It is far from beautiful, but it work in most cases + * [ #7901 ] Partial rewrite of the drag and drop event, does not + really work + * [ #7899 #7900 ] Fix toolbar icons + * Remove useless icon in toolbar + * [ #7876 ] qDebug -> kDebug (to be able to disable them using KDE + gui) + * [ #7876 ] Remove unneeded comments + * [ #7887 ] 7% faster load time + * [ #7887 ] Fix warning + * [ #7887 ] Prevent most useless object copy + * [ #7874 ] Twice less lines, same result + * [ #7876 ] Remove unused class attribute, rename all attribute use + use m_ for private member, m_p for private pointer, m_s for private + static and m_sp for private static pointer + * [ #7876 ] Update copyright from 2010 to 2012 (next version wont be + released until then, so why not doing it now) + * [ #7876 ] Add some doxygen + * [ #7876 ] Sort include by owner, fix license issues + * [ #7876 ] Clean includes + * [ #7876 ] Add box comment for file sections (getter, setter, + mutator, slots) + * [ #7863 ] It work, here we go again + * [ #7876 ] Spring cleanup + * * #7264: added getHistorySimple, which return a dict of history + entries + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Thu, 07 Jun 2012 16:10:36 -0400 + +sflphone-kde (1.1.0-rc20120607~ppa1~SYSTEM) SYSTEM; urgency=low + + ** SNAPSHOT 1.1.0-rc20120607~ppa1~SYSTEM ** + + * [ #12096 ] Implement more calls details, make it more scalable + * [ #11290 ] Update french translation + * [ #11859 ] Fix a regression in name conversion + * [ #11850 ] Fix a little regression + * [ #11859 ] Fix sorting by popularity + * [ #11569 ] Fix filter + * [ #12003 ] Add icon overlay for history item + * [ #11886 ] Add basic reverse peer naming support + * kde/callview: removed trailing whitespace + * [ #12008 ] Implement GUI part + * [ #11988 ] Move ringtone to a new tab + * [ #11990 ] Fix history delegate width + * [ #11990 ] Make call item height configurable + * [ #7007 ] Fix many history bugs and add 'copy' contextual menu + * [ #7007 ] Add more contextual options + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Thu, 07 Jun 2012 15:52:52 -0400 + +sflphone-kde (1.1.0) SYSTEM; urgency=low + + ** SNAPSHOT 1.0.0-rc20110930~ppa1~SYSTEM ** + + * Improve accessibility + * Improve usability/UX + * Fix all know bugs + + -- Emmanuel Lepage Vallee <emmanuel.lepage@savoirfairelinux.com> Tue, 7 Jun 2012 11:38:30 -0500 diff --git a/tools/build-system/launchpad/sflphone-kde/debian/compat b/tools/build-system/launchpad/sflphone-kde/debian/compat new file mode 100644 index 0000000000..7ed6ff82de --- /dev/null +++ b/tools/build-system/launchpad/sflphone-kde/debian/compat @@ -0,0 +1 @@ +5 diff --git a/tools/build-system/launchpad/sflphone-kde/debian/control b/tools/build-system/launchpad/sflphone-kde/debian/control new file mode 100644 index 0000000000..d07015dffd --- /dev/null +++ b/tools/build-system/launchpad/sflphone-kde/debian/control @@ -0,0 +1,13 @@ +Source: sflphone-kde +Section: kde +Priority: optional +Maintainer: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> +Build-Depends: debhelper (>= 5.0), cdbs, kdelibs5-dev, cmake, kdepimlibs5-dev, libboost-dev, libx11-dev, libqt4-opengl-dev, +Homepage: http://www.sfphone.org/ + +Package: sflphone-kde +Architecture: any +Depends: sflphone-daemon-video (=${source:Version}), ${shlibs:Depends}, ${misc:Depends}, libqt4-dbus, libqt4-opengl +Provides: sflphone-client-kde +Replaces: sflphone-client-kde +Description:KDE client for the sflphone-daemon SIP/IAX softphone diff --git a/tools/build-system/launchpad/sflphone-kde/debian/copyright b/tools/build-system/launchpad/sflphone-kde/debian/copyright new file mode 100644 index 0000000000..9a2517742f --- /dev/null +++ b/tools/build-system/launchpad/sflphone-kde/debian/copyright @@ -0,0 +1,8 @@ +SFLPhone: + + (C) 2004-2012 Savoir-Faire Linux <contact@savoirfairelinux.com> + +SFLPhone KDE: + + Copyright (C) 2008-2009 Savoir-Faire Linux <jeremy.quentin@savoirfairelinux.com> + Copyright (C) 2009-2012 Savoir-Faire Linux <emmanuel.lepage@savoirfairelinux.com> diff --git a/tools/build-system/launchpad/sflphone-kde/debian/menu b/tools/build-system/launchpad/sflphone-kde/debian/menu new file mode 100644 index 0000000000..293ff7265f --- /dev/null +++ b/tools/build-system/launchpad/sflphone-kde/debian/menu @@ -0,0 +1,6 @@ +?package(filelight):needs="X11" \ + section="Applications/Multimedia" \ + hints="KDE, Phone, Sip,Call" \ + command="sflphone-kde" \ + title="SFLPhone-KDE" \ + longtitle="SFLPhone Client KDE: Enterprise class softphone for KDE" diff --git a/tools/build-system/launchpad/sflphone-kde/debian/rules b/tools/build-system/launchpad/sflphone-kde/debian/rules new file mode 100755 index 0000000000..1c0258f9c2 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-kde/debian/rules @@ -0,0 +1,5 @@ +#!/usr/bin/make -f + +include /usr/share/cdbs/1/rules/debhelper.mk +include /usr/share/cdbs/1/class/cmake.mk + diff --git a/tools/build-system/launchpad/sflphone-kde/debian/source.backup/format b/tools/build-system/launchpad/sflphone-kde/debian/source.backup/format new file mode 100644 index 0000000000..163aaf8d82 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-kde/debian/source.backup/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/tools/build-system/launchpad/sflphone-plugins/debian/changelog b/tools/build-system/launchpad/sflphone-plugins/debian/changelog new file mode 100644 index 0000000000..2cccb5d474 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-plugins/debian/changelog @@ -0,0 +1,3117 @@ +sflphone-plugins (1.1.0-rc20120607~ppa1~SYSTEM) SYSTEM; urgency=low + + ** SNAPSHOT 1.1.0-rc20120607~ppa1~SYSTEM ** + + * * #11208: bump version numbers for release 1.1.0 + * * #9381: use autoconf macros and AC_SEARCH_LIBS + * * #9144: Fixes "Only <glib.h> can be included directly" error + * #8449: Update version 1.0.2 + * * #8542: removed trailing whitespace from tree + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Thu, 07 Jun 2012 16:10:55 -0400 + +sflphone-plugins (1.0.0-rc20110930~ppa1~SYSTEM) SYSTEM; urgency=low + + ** SNAPSHOT 1.0.0-rc20110930~ppa1~SYSTEM ** + + * update kde .gitignore + * Fix bug in volume widget + * More polishing for release + * Bump version to 1.0.0 + * [#7023] Add the ability to load an abstract contact backend in the + library to resolve more data, polish code + * [#7021] More cleanup for release + * Cleanup + * [#7021] Refactor KDE client dbus handling, add a missing call in + daemon and port the DataEngine to the new API + * Remove some annoying debug + * merge language scripts + * remove obsolete 'VERSION' files + * update install instructions + * Add missing translations to gnome + * language update + * Revert "Don't reference count DBus clients, exit core immediately + when one of them request it" + * Don't reference count DBus clients, exit core immediately when one + of them request it + * [7021] Add contact abstraction support + * [#7121] Polishing library (over). Indentation, spacing and naming + are now consistent + * codecs: link to libccrtp, don't use logger + * Fix a daemon bug + * [#7038] Fix adding contact + * * #7037 : stop audio stream after all calls have been hanged up + * [#7025] Add full support for bookmark + * SFLPhone KDE do not destroy history anymore + * Fix config skeleton + * Close the daemon once and for all, no more automatic respawning + * Fix "unregistered account" bug (I hope so) + * Close SFLPhone at the right place, it still respawn, I don't know + why + * Remove dead code + * Fix regressions introduced in the last commit + * Dead code elimination 1/3 + * Fix bug, add "add contact" option, fix warning + * * #7019: Fix IAX codec negociation + * Remove or comment unnecessary/unhelpful debug output + * Fix "same as local" account setting, fix IP2IP LED color + * Add support for some more advanced config options and add missing + config dialog icons + * Fix crash with noise suppressor + * Alternative can now be selected from the call view context menu + * Add drag and drop support, initial context menu and fix 3 bugs in + the account dialog + * Add basic history drag and drop support + * Complete contact support is back + * * #6991 : fix IAX problems + * Fix IAX accounts being disabled by default + * Revert "deb: forge -g flags for pjsip" + * * #5884: Disable debug code in pjsip + * echo suppressor : more assertions + * Don't let the daemon think crypto is enabled when it's not + * Simplify ToneList + * Some progress on contact support + * Remove unused getRegistrationCount() + * remove annoying debug + * revert SIP bit of e27e5c39bad27bae28f574eb2cba7717e8956229 + * Simplify CallManager::placeCallFirstAccount + * Fix crash on hold + * * #6905 : SIP refactor + * gnome client: be sure key exchange is set correctly + * Move code into createSipTransport + * Fix account registration on start + * ManagerImpl::registerAccounts(): simplify + * * #5884: don't mess with pjsip threads in echo suppressor + * * #6905 : simplify udp/stun/tls pjsip transport creation + * Restore and improve support for Call history + * fix launchpad build + * SIPVoIPLink: simplify / refactor + * Fix libwidget linking + * SIP: simplify + * IM : simplify + * gnome: remove some debug + * AudioRtpFactory::stop() cannot fail + * * #6905: simplify SIP code + * pjlib: fix build without SSLv2, fix warnings + * Port history to the new syntax + * Test a dock widget based implementation for contact and history + * Disable SSLv2 support from pjsip and sflphone + * deb: forge -g flags for pjsip + * Fix deb packaging to get debug symbols + * remove debug + * pjproject: update to last stable release (1.10) + * Require gtk >= 2.20 and glib >= 2.24 + * tlsadvanceddialog: simplify + * * #6902 : fix errors spotted by -DGSEAL_ENABLE + * Update daemon dbus XML and port KDE config backend from dbus to + local + * Remove unused but set variables + * * #6929 : fix IM widget, cleanup + * Unconditionally enable debug symbols + * Should fix many KDE issues + * * #6886 : hitting backspace on empty number have no side effects + * * #6905 : fix AudioCodecFactory access in optimized builds (-O > 0) + * Remove unsupported and broken jaunty/karmic packages + * * #6902 : avoid using some gtk deprecated functions + * Update dbus introspection files + * * #6904: removed unused contactmanager + * * #6903 : use correct dbus-cxx package name + * * #6902: don't use individual gtk headers + * Fix a segfault when config is not present + * Merge latest (0.9.13) KDE code. This version is not yet ready for + git master, but better than the previous one + * addressbook : simplify + * * #5659 : sflphone-plugins doesn't depend on libedataserverui + * * #5659 : addressbook doesn't use libedataserverui + * gnome client doesn't depend on evolution + * * #5695: addressbook: simplify + * * #5695: addressbook : remove AddrBookHandle from plugin + * * #5695 : addressbook : remove unused stuff in the client + * * #5695 : addressbook : remove unused stuff, use static mutex + * gnome client doesn't use evolution + * gnome: use proper API to set GTK_CAN_FOCUS + * * #6897: removed unused focus state vars/callbacks + * gnome: fix calls to sflphone_fill_codec_list_per_account + * * #6623: gnome: don't leak in mainwindow + * gnome: mainwindow whitespace cleanup + * gnome: actions.c parameter doesn't have to be a double pointer + * * #6895: fix memleaks, cleanup in accountconfigdialog + * * #6893: fixes segfault in client on clean history + * * #6894: fix leaks, cleanup in sflnotify + * daemon: fixed prints in main + * * #6892: simplify, fix leaks in dialpad + * * #6887: audiopreference creates audio layer + * * #6660: use const char * const, not std::string for globally + visible constants + * * #6852: Preferences now solely responsible for audiolayer creation. + * * #6860: refactor uimanager, also fixes #6865 + * * #6853: hangup as soon as all digits have been deleted + * * #6852: alsa: retry if device is busy + * * #6852: audiolayer creation depends only on preference.audioApi + * * #6850: gnome: fix build for gtk < 2.22.0 + * cleanup in iax + * alsa: typo + * pulse: if we can't peek in audio input, we can't drop samples + * * #6849: show error window if codecs are missing, instead of dying + * EchoCancel: unused, remove + * * #6629 : use number of samples as arguments for audio filters + * * #6629 : remove unused Algorithm interface + * * #6629 : use helper to call alsa functions and display error msgs + * Remove unused type + * * #6841: fix some error handling + * * #6629: simplify AlsaLayer::alsa_set_params() + * Get gdk key definition from header + * * #6828: Replace raw key codes by gdk defines + * remove some debug, enhance some other + * mainbuffer: simplify + * * #6561 : fix phantom call after transfer + * Conference Participant set : simplify + * SIPCall: remove unused functions, make invite session public + * * #6229 : remove malloc/free from pulse audio loop + * * #6629 : simplify pulse callbacks + * * #6629 + * Simplify widgets + * * #6629 : keep the correct audio module when frequency changes + * * #6751: fixed erroneous debug msgs + * callable_obj.h: removed unneeded pthread header + * alsalayer: cleanup + * * #6629: Always restart audio driver when changing parameters (ALSA + only) + * gnome GUI: don't block in DBus signal errorAlert() + * * #6629 : simplify AudioLayer creation + * * #6629 : remove unused and unconfigurable frameSize from audiolayer + * * #6629 : remove unused error message from audio layer + * Fix logic error when switching audio API + * Remove unused AudioProcessing class + * AudioRtpRecordHandler::initNoiseSuppress() : use noiseSuppress + directly + * * #6629 : use DC blocker directly in audio layers + * * #6629 : clean AudioLayer + * * #6629 : don't store mainbuffer inside audiolayer + * * #6629 : correct AudioLayer::notifyincomingCall() + * * #6554: cleanup, refactoring in sipvoiplink + * * #6554: cleanup in iaxvoiplink + * * #6554: throw exception in getSIPCall if pointer is NULL + * * #6554: make some methods of sipvoiplink static + * * #6655: cleanup in managerimpl + * * #6554: refactoring, fix memleaks in sipvoiplink + * * #6478: remove throw specs, cleanup in voiplink + * * #6629 : remove unused AudioDevice + * * #6655: removed more dependencies from managerimpl + * * #6744: simplified numbercleaner + * conference : remove one prototype + * * #6743: fix ip2ip + * Don't give glib warnings if icons are not found + * gnome: fixed includes + * Codec.h: removed unused function + * * #6742 : clean dbus & icons + * * #6699: refactor/cleanup accounts + * icons: cleanup + * timer : use second precision, not millisecond + * calltree_update_clock : use correct type, returns something + * * #6737: fixed typo in dbus call + * * #6737: removed tests for removed API + * * #6737: dbus: fixed bug from merge + * * #6737: cleanup in accountlist + * * #6737: cleanup in dbus + * * #6740 : fix history double free + * * #6740 : remove time updating thread from calls + * * #6737 : use c99 for client + * * #6738 : make history loading faster + * sipvoiplink : don't crash on transfers + * fixed typo + * Remove unused file + * Don't build networkmanager.cpp at all if NM is disabled + * _debug* -> _debug + * * #6554 : simplify sipvoiplink + * hudson: added -x to git clean command + * added git clean to hudson script + * audiocodecfactory: cleanup + * * #6718: refactored setTlsSettings into SIPAccount + * * #6718: removed more unused methods + * * #6718: refactored confmanager code into sipaccount + * remove unused functions + * * #6718: confmanager: removed more unused methods + * AudioCodecFactory : cleanup + * #6697 : Turn callableElement struct into union + * * #6718: confmanager: removed more unused methods + * * #6718: confmanager: removed more unused methods + * * #6718: removed unused dbus methods, refactoring + * * #6699: accounts: cleanup/refactoring + * * #6699: refactoring, cleanup in accounts + * * #6699: more account cleanup + * remove unused autoconf variable + * * #6714: fixed hudson script + * make distclean in hudson + * added || exit 1 to run_tests.sh call + * * #6714: fixed make distcheck for sflphone-plugins + * * #6714: fixed make distcheck for gnome client + * * #6714: fixed make distcheck for daemon + * git: #6698 split the main .gitignore file + * gnome: gpointer is already a pointer + * gnome: calltab_init: use calloc instead of malloc + * * #6699: more account cleanup + * * #6699: cleanup account + * * #6554 : more *voiplink cleanup + * * #6558 : more sipvoiplink simplification + * * #6558: saner loadSIPLocalIP prototype + * gnome: #6623 clean calllists + * * #6692: more audiolayer cleanup + * * #6692: cleanup/refactoring in audiolayers + * * #6692: more forward declarations, AudioThread->AlsaThread + * * #6692: audiolayer cleanup + * * #6692: alsalayer cleanup + * * #6558 : remove account creator + * * #6558 : clean sipvoiplink + * * #6554 : cleanup sipvoiplink + * audiortp: cleanup + * * #6657 : fix launchpad builds for good + * * #6675 : send RTP dtmf events only once + * * #6655: more cleanup + * AudioRtpSession::updateSessionMedia() : simplify + * * #6655: more cleanup in managerimpl + * * #6655: removed more code, cleanup + * * #6655: more cleanup, fixed infinite loop + * * #6655: removed more unused files + * * #6655: removed unused mutex + * * #6655 removed more unused code + * * #6655: removed unused methods + * * #6655: cleanup in main + * * #6663: fixed segfault when off hold from transfer + * * #6658: user's active codec selection is respected + * * #6660: static global string should be static const char* const + class member + * * #6659: use g_strcmp0, not strcmp for vals that may be null + * callable_obj: fix double free + * calltree_display_call_info() : simplify + * * #6657: Fix launchpad builds + * Logger::log() : simplify + * AudioRtpSession : privatize members + * * #6655: more constness, cleaned up/simplified methods + * * #6654: call DBus::_init_threading so that dbus-c++ to make it + threadaware + * set default credentials on account creation + * AudioCodecFactory::scanCodecDirectory() : simplify and correct + * * #6623: fixed typos + * * #6623: fixed more leaks + * * #6623: fixed more leaks + * * #6623: fixed more leaks, don't print codec name if null + * * #6623: more leaks fixed in client + * * #6623: fix more leaks, fixed some warnings + * * #6623: fixed leak in history + * updated gitignore + * initialize dbus dispatcher correctly + * Fix tests, hudson doesn't have a dbus daemon running + * remove unused code + * removeCall() : simplify , fix leak + * stopRtpThread() : simplify + * *CurrentCall : simplify + * Fix memleak + * fix serialization of audio api (pulse / alsa) + * account map : simplify + * remove call from callmap before terminating it, avoid use after free + * * #6630 : don't make DBusManager a singleton + * call: return confID by value + * add back history code deleted by error + * history : reverse logic + * simplify history serialization and remove some debug + * remove annoying debug + * * #6464 : replace cerr with _error + * * #6464: replace cout with logger macros + * replace printf() with logger macros + * update .gitignore + * remove unused function + * update eclipse projects + * uimanager_new() : simplify + * rename directories + * celt: simplify a bit + * Fix CELT configure.ac test + * * #6612 : template speex codecs + * * #6623: refactored conference obj + * * #6623: refactored callable object, removed leaks + * * #6623: more cleanup, fix leaks, make global vars static and rename + them + * * #6623: calltree: fixed memleaks, simplified code. + * audiolayer: init pointer members + * manager: catch exception on invalid hangup + * * #6623: don't leak on calls to create_new_call + * * #6611 : clarify codecs prototypes + * ringtones : .au and .ul files are both ulaw + * * #6611 : make sure samplerate converters are called correctly + * ManagerImpl::switchAudioManager() : simplify + * * #6623: fixed more leaks + * * #6623: fixed more leaks + * * #6623: fixed more leaks + * * #6623: fixed leak, line-endings in imwidget + * * #6627: zero-initialize pointers if they're going to be deleted + * * #6628: don't leak calls on exceptions + * Revert "audiortp: call join after calling stop on RtpThread" + * sflphone-client: more constness + * audiortp: call join after calling stop on RtpThread + * * #6625: return 0 on successful completion + * * #6624: fix segfault on servercallfailure + * * #6621: Fixed double free, unlock mutex in ManagerImpl::terminate + * * #6220: remove audio stream when peer hangs up + * * #6596: AudioSymmetricSession shouldn't self-delete + * resampler: grow internal buffers dynamically + * merge up and down sampling => resampling + * Leave test directory unchanged when running make check + * audio algorithms : remove unused prototype + * ringtone: detect codec from file extension + * *AudioFile : simplify + * * #6596: create local SDP on the stack, not the heap + * * #6596: don't call Ost::Thread::terminate from dtor + * audiofile: cleanup (samplerate -> unsigned) + * remove unused func + * samplerateconverter: cleanup + * RingBuffer::Put() : remove unused return value + * MainBuffer::putData() : remove unused return argument + * audiolayer::putMain() : remove unused func + * AudioLayer::putUrgent() : remove unused return value + * * #6618: delete any remaining ringbuffers in destructor + * RingBuffer::availForPut() : remove + * * #6617: return from main rather than calling exit + * MainBuffer::availForPut(): remove + * RingBuffer: simplify + * alsa : remove write only variable + * fix memcpy declaration + * bcopy(src, dst) -> memcpy(dst, src) + * RingBuffer::Get() : remove constant volume argument + * return a copy of the call ID, not just a reference. + * MainBuffer::getDataById() : remove volume argument (always 100) + * MainBuffer::getData() : remove constant volume argument + * RingBuffer::Put() : remove constant volume argument + * MainBuffer::putData() : remove constant (=100) volume argument + * audiolayer: remove constant _defaultvolume + * AudioRtpRecordHandler / AudioRtpSession : simplify + * mainbuffer: fix test + * iaxvoiplink : simplify + * sip registration callback: fix a dbus crash + * MainBuffer: simplify + * AudioRtpFactory: return cached type of rtp session. The rtp session + can have disappeared if the call was put on hold + * AudioRtpFactory: remove unused setters + * Fix launchpad builds + * * #6611 : remove unused bandwidth codec information + * * #6611: AudioCodec: remove useless/unused setters + * make sure buffer string is initialized correctly + * * #6596: declare certain destructors virtual + * audiolayer : cleanup + * Simplify doc build rules + * * #6270: don't build dbus-api doc with make, should require make all + * configure.ac: cleanup + * Remove copy of dbus-c++ from libs/ + * * #6596: stop clock thread when peer hangs up + * removed unused Fmtp.h + * * #6595: more logical initialization order + * * #6600 : fix account creation + * * #6601 : fix configure.ac tests + * remove unused variable + * Don't mix stack and heap based allocations + * Fix copyright (2009, 2008, 2009 -> 2008, 2009) + * Fix warnings found by clang + * * #6595: fix initialization order for AudioRTP + * * #6592: removed typedef std::string CallID + * * #6586: implement local g_slist_free_full for older glib versions + * * #6579: fix memory leaks in client (there's a lot left) + * ShortcutPreferences::setShortcuts() : simplify + * Fix merge + * * #6548: remove call to non thread-safe strerror() + * AudioRtpFactory: each instance is associated to exactly one SipCall + * create_audiocodecs_configuration() : make static + * * #6269 : refactor AudioRtpSession + * Fix AudioSymmetricRtpSession.h inclusion guard (cherry picked from + commit c3081dce1cc1370d6d3558a4c4ef5cfac0d21caf) + * * #6269: Rename AudioRtpSession to AudioSymmetricRtpSession + * * #6574: Don't exit when connection to pulseaudio server fails + * accountconfigdialog.h : remove some stuff from header + * * #6560: fix configuration test + * Fix warning in test + * * #6560: don't hide password entry in security tab + * * #6560: set initial password for SIP accounts + * * #6506: remove useless pointer indirection + * * 6560: password is now specific to IAX accounts + * * #6560 : actually use, store, restore, transmit SIP credentials + * * #6560: YamlEmitter: serialize sequences + * YamlEmitterException: typo + * ManagerImpl::computeMd5HashFromCredential() : simplify, fix memleak + * * #6561: invite_session_state_changed_cb() : simplify + * * #6561: More useful debug in VoIPLink::removeCall + * * #6561 : fix ghost call reappearing in GUI after transfer + * while -> for (make the code smaller) + * * #6558 : Account::loadConfig() : move IAX code to IAXAccount + * IAXVoIPLink::getAccountPtr : simplify + * * #6554 : access the SIPVoIPLink directly, not per account + * SIPVoIPLink is instanciated only once and is not associated to a + single account + * yamlnode: use const references when possible (still some left to do) + * Account::_accountID: constify + * VoIPLink: simplify, remove unused method + * hudson test : no need to call run_tests.sh anymore + * Remove AccountID type and AccountNULL define + * Make check runs the test (no need to call run_tests.sh manually + anymore) + * gnome GUI: Fix tests + * Revert "Move registration information from SIPAccount to + SIPVoIPLink" + * * #6392: pluginmanagertest: fix warnings reported by valgrind + * * #6547 : remove unused exceptions + * * #6547: CallManagerException: use runtime exceptions + * * #6547: InstantMessageException: use runtime exceptions + * * #6547: do not throw exceptions if some settings are not present in + config file + * * #6547: YamlParserException: use runtime exceptions + * * #6547: VoipLinkException: use runtime exceptions + * * #6547: YamlEmitterException: use runtime exceptions + * * #6547: DTMFException: use runtime exceptions + * * #6547: AudioFile: use runtime exceptions + * * 6547: AudioZRtpSession: remove impossible error case + * * #6547 : AudioRtpSession: remove impossible error case + * * #6547: AudioZrtp: use runtime exceptions + * * #6408 : send authenticationUsername to GUI + * * #6408 : store/restore authenticationUsername from config file + * SIPAccount: simplify + * Move registration information from SIPAccount to SIPVoIPLink + * SIPAccount::getAccountDetails : simplify + * * #6540: yaml parser: simplify + * sdp.cpp : fix a warning + * * #6540: yaml parser : remove std::string typedefs + * * #6540: Simplify yaml unserialization + * * #6540 : add a Conf::ScalarNode constructor for booleans + * setAccountDetails(): simplify + * * #6408: store authentication username in daemon + * * #6408: Be able to set the authentication username in the GUI + * * #6507 : do not crash if the program is not sflphoned + * Fix tests + * macroify SIPAccount::unserialize() + * Move all .cpp files from sflphoned target to libsflphone.la, except + main.c + * main() : simplify, return positive error codes + * * #6507 : find codecs dir in build directory + * * #6392: Sdp: move clean functions to destructor + * AlsaLayer::adjustVolume() : simplify + * alsalayer : reduce indentation + * malloc/free -> new/delete + * malloc/free -> new[]/delete[] + * malloc/free -> new/delete + * AudioSrtpSession: simplify base64 encoding + * * #6392: Initialize std::string from pj_str_t correctly + * * #6392: AudioRtpSession: Initialize remote port + * Audio settings : Initialize _echoCancelTailLength and + _echoCancelDelay(0) + * Initialize variable + * YamlParserException : fix use of stack variable after it has been + deallocated + * * #6392: fix memory leak in history + * * #6392 AudioCodec : fix memory leak + * * #6392 : fix memory leak in sip account + * * #6408: clean up sipaccount (cosmetics mostly) + * sipaccount.cpp serialize() : reduce number of lines + * * #6392: invalid memory access + * * #6392 : fix invalid memory access + * * #6479: merged useful code from MimeParameters into Codec interface + * * #6462: fixed hangup on IP2IP call + * added run_daemon.sh script + * test: remove unused variable + * Remove functions only used by a failing test (cherry picked from + commit fcf718cb75de7f1882dc61c07bb8d300dfa10f85) + * * #6360 : make client tests build (cherry picked from commit + 028b2835f040e51ab8ab979b32732b07b8798fce) + * * #6360 : fix warnings in check_global test (cherry picked from + commit 9e2bd6a7496dd64f6f48595e385760019aab1193) + * * 6360: updated API calls in tests, but they're not building yet + (cherry picked from commit 548f6f0f919b43772a3e9c667e5e292791281795) + * Fixed include in tests (cherry picked from commit + aeadc7525c1e31f936670ac8b02f0bcf387c38a8) + * Remove unused variables and functions + * IAX: fix warnings (cherry picked from commit + fd7a113a11cac2cd9a7c36929e88ad28195c4c35) + * Remove unused DEBUG define which interferes with logger.h (cherry + picked from commit b2f72b91d0f43cb1dd94d138882a8caa9c841c24) + * * #6392: no need to check for account NULLity since it is + dereferenced above + * * #6392: fix a memory leak, replace by stack allocation + * * #6392: remove a variable assignement which confuses cppcheck + * process_conference_participant_from_serialized() : remove unused + function + * * #6392: s/free/g_free/ + * * #6392: fix a memory leak in abookfactory_load_module() + * * #6392: remove generate_call_id() used only once + * * #6392: fix memory leak (opendir() without closedir()) + * * #6392: AudioRecorder(): ensures mbuffer is set + * Remove SFLPHONED_VERSION from global.h, use autoconf PACKAGE_VERSION + * #6298: Cleanup + * #6331: Fix deleting ringtone file after call have been answered + * * #6330: merged user_cfg into headers + * #6298: Fix conference recording file update at conference end + * #6298: Fix record file name serialization for conference + * * #6295: cleanup of codec hierarchy + * #6298: Fix gtk warnings + * * #6300: added script to run tests + * #6109: Add recording playback for conference + * * #6300: tests do not require an installed sflphone + * * #6295: re-removed clone methods + * #6109: Fix gtk_critical warnings for incoming calls + * #6109: Fix GTK_CRITICAL warning + * #6109: Fix icons when history is not activated + * #6109: Fix warnings + * #6109: Implement stop recorded file playback signal + * Revert "* #6295: removed unused clone method" + * * #6295: removed unused clone method + * * #6296: removed non existant file from Makefile.am + * #6109: Stop fileplayback for outgoing call + * #6109: Implement stop recording playback button + * Fix binding names errors in dbus introspection file + * #6109: Implement playback recorded file callback in client + * #6109: Store recorded file path on client side + * #6109: Add dbus methods for call recording playback + * * #6290: remove unused classes from utilspp + * * #6288: cleanup sdp + * * #6288: fix exception usage + * * #6288: simplify SdpException + * * #6288: cleanup in sdp.cpp/h + * #6109: Only display playback button if record file is set and valid + * * 6290: updated configure.ac to remove functor Makefile + * * #6290, #6289: removed unused classes from utilspp, fixed make + check + * #6109: Add button for history playback of recorded file + * * #6289: removed unused observer class + * * #6282: forward declare sdpMedia in sdp.h + * * #6281: renamed setCallAudioLocal->setCallMediaLocal + * #6183: Handle conference with more tahn two calls + * #6183: Fix history icons when calling back a conference from history + * #6183: Fix icons inconsistencies in history for conference hang up + * #6183: Fix toolbar actions when selecting a conference in history + * #6183: Fix conference serialization + * #6268: Serialize only calls + * * #6269: removed useless type testing + * ignore some files in test/ + * * #6268: Remove dead class AudioSymmetricRtpSession + * #6251: Do not had history calls in calllist when loading history + file + * #6251: Fix insertion in history map in before saving history file in + daemon + * #6251: Fix history unit tests + * #6251: Order the list before serailization, get rid of the hashtable + in history + * #6251: Implement history serialization using a list wether than a + map + * * #6253: remove external audioport from header, make all members + private + * * #6253: don't store external local audio port (used for NAT) in + Call + * #6251: Add start_time timestamp in history serialization + * #6251: Fix call insertion in conference items + * #6233: Fix serialized account list terminated with a ";" character + * #6238: Fix draggable history calls into current calls + * #6233: Fix toolbar updates + * #6233: Fix history + * * #6235: remove pyc files from git tree + * #6233: Handle cases when one or manuy calls are unreachable in + createConfFomrParticipantList + * #6233: Handle wrong numbers in createConferenceFromParticipantList + * #6231: Fix drag-n-drop issue + * * #6173 : move sippxml in tools + * #6231: Fix merging issue + * #6183: Implement conference unserialize + * * #6212: remove extraneous flags from globals.mak + * #6183: Unserialize conference data in conference + * #6183: Add account information in request for conference call from + history + * #5755: Add -ldl to liker in sflphone-client-gnome + * #5755: Fix fedora 15 compilation issue + * #6183: Serialize conference participant phone number and account + * #6183: Add conference timestamp in serialization + * * #6186: don't include global.h, just logger.h + * #6183: Fix saving history to file + * #6183: Fix removing call from calllist + * * #6184: remove pointers to Manager from AudioRtpSessions + * #6183: Calling calltree_add_call explicitely for history + * #6183: Ability to store conference inside history tab queue + * * 6181: remove unused API from sipcall + * #6171: Implment nreCallCreated callback + * #6167: Fix participant list NULL ending + * #6149: First draft of conference creation from history + * #6149: Fix multiple call/conf selection callbacks ... + * #6129: Fix place_call function called twice for pressing enter + action + * #6129: Fix double click action for history + * #6149: Add dbus call for creating conference from history + * #6129: Fix placing call from history and addressbook (still need to + fix icon) + * * #6148: removed unused AudioRtpFactory constructor + * * #6145: remove unused isAudioStarted + * * #6145: remove unused isAudioStarted + * #6129: Add conference into history, fix call/conference selection + * * #6143: don't use getType outside of serialization methods + * * #6132: forward declarations instead of includes + * * #6132: add constness, remove redundant "inline" keywords + * #6129: Add timestamp to conference object to order history entries + * * #6128: remove unused forward declarations from header + * * #6127: make noncopyable class actually noncopyable + * * #6125: don't include AudioRtpFactory in sipcall.h + * #6123: Fix alsa ringback audio file + * #6123: Fix raw audio file loading problem + * #6109: Fix daemon plugin manager unit test + * #6109: Fix history manager unit tests + * #6109: Recording filename in daemon and client for history items + + serialization + * #6109: Refactor AudioFile to play recorded call + * * #6104: AudioCodec moved to sfl namespace + * * #6099: remove active flags from codec classes + * #6095: Add notification-daemon as a runtime dependencies for rpm + packages + * #6095: Fix fedora 15 compilation in MineParameters.h + * #6095: Declare static variable explicitely for client + * #6095: Add logs to build OSC build machine + * * #6098: global variables should have file-scope to avoid name + conflicts + * #6095: Fix compilation error for Fedora 15 + * #6095: Update SFLphone version to 0.9.14 + * #6095: Add specification file in opensusse build service for + sflphone-plugins + * #6073: Fix sflphone-plugins build on launchpad + * #6093: Rename CodecDescriptor for AudioCodecFactory + * * #6089: fix warnings in make check + * * #6086: renamed codecs methods to audio_codecs + * * #6085: renamed codec related dbus calls to audio_codec + * #6065: Remove g_print from client, use DEBUG instead + * #6065: Add actions name for addressbook + * * #6085: renamed codecs* widgets/functions audiocodecs* + * #6065: Fix Addressbook runtime warnings + * #6065: Replace Codecs tab for Audio in account preference dialog + * #6065: Fix "transfert" typo + * #6065: Fix addressbook action runtime warning in uimanager + * * #6082: fixes make check by adding libcrypto libs to test + dependencies + * #6073: Rename plugin/addressbook folders for addressbook/evolution + in sflphone-plugins + * #6074: Removed AC_SUBST from configure.ac when using + PKG_CHECK_MODULE + * #6073: Fix sflphone-plugins package build + * #6073: Fix sflphone-common build + * #6065: Fix runtime gtk warning when initializing searchbar without + addressbook + * #6063: Fix mozilla-tellify gitignore + * #6063: Remove stream copy file using ifdef macro + * * #6012: fix make dist for sflphone-common + * #6063: Update .gitignore file + * #6058: Fix base64 encoding related warnings + * #6056: Fix SdpException handling + * #6055: Fix unknown pargma warning for gcc <= 4.5 + * * #5949: test gcc version before disabling unused-but-set warning + * #6054: Fix addressbook plugin compilation warning + * #6048: Fix uimanager static initialization + * #6046: Fix addressbook factory static initialization of member + addrbook + * #5979: Fix implicit function declaration warning + * #6042: Fixed discarding qualifier warnings in client + * #6041: Fix instant messaging unhandled case warning + * #5994: Implement set current addressbook name and search type in + addressbook plugin + * #5994: add rules for launchpad packaging of addressbook plugin + * #5994: Fix addressbook plugin configuration loading + * #6027: Fix addressbook enabled test from configuration + * #6027: No need of gnomedoc related macros in addressbook plugin + * #6027: Add NEWS file required for build + * #6027: Add addressbook plugin autogen.sh script + * #6027: Remove plugins from client + * #6027: Add sflphone-plugins folder at project's root level + * #5994: Move addressbook folder from contacts to plugin folder + * * #6011: removed unused Makefiles + * * #6010: remove unused headers + * * #5952: fix "string constant to char*" warnings + * * #6009 fixed warnings + * * #6003: finished cleanup of account classes + * * #6003, #6004: cleanup of account classes, defaultAccount no longer + global + * * #6000: fix memory leak of args object + * * #5998: removed using namespace std from networkmanager + * * #5998: removed "using namespace std" from ZrtpSessionCallback + * * #5998: removed using namespacestd from AudioZrtpSession.h + * * #5998: remove "using namespace std" from auriorecord.h and + MimeParameters.h + * * #5998: remove using namespace std in main + * * #5998: removed "using namespace std" from logger + * * #5949: test gcc version before disabling unused-but-set warning + * #5994: Installation of addressbook plugin + * #5979: Implement codec full addressbook search from plugin + * #5979: Implement addressbook factory and plugin + * * #5981: unused webwidget removed + * #5966: Account config synchronization fix (for stun) + * #5954: Handle media name exception + * #5954: Fix audio codec name display in client + * #5954: Clean up getSessionMedia methods + * * #5957: getRecordingSmplRate returns a value + * #5954: Clean up getCurrentCodec methods + * * #5950: remove "converting to non-pointer type 'int' from NULL" + warnings + * #5915: Full gain control version + * * #5949: remove more unused variable warnings + * * #5949: remove unused/unused-but-set variable warnings + * * #5949: show_preferences_dialog returns a success value + * * #5946: cleanup of include directives, undefined function + * * #5515: comment out SSLv2 calls in pjsip + * #5915: Implement different slope for attack tme and release time for + gain control + * #5915: use only one input signal for gain control (removed output + buffer) + * #5921: Fix no audio after holding a conference + * #5916: Add gaincontrol files + * #5916: Implement FFMPEG/CCRTP video streaming prototype + * #5903: Fix call transfer during a conference + * #5915: implement rms detector, first order averager, limiter for + gain control + * #5914: Fix call transfer when no notification request is required + * #5899: Fix conference right-click segfault + * #5884: temporary fix segfault in pjsip memory pool + * #5883: Fix compilation issues on maverick and lucid + * #5755: Fix fedora 15 compilation without patching ccrtp + * [#5855] Make echo canceller optional + * #5855: Fix echo suppression activation/deactivation + * #5855: Implement pjsip echo canceller + * #5814: Speex initialization function uses samples, not bytes + * #5814: Test using more unbalanced signals + * #5814: Fix buffer size for long echo length or long echo delay + * #5814: Adjust level for echo cancellation at runtime + * #5814: Process noise reduction before echo cancelling + * #5814: Implement speex post echo canceller processing + * #5814: Dump echo cancel file to disk + * #5814: Add parameters for echo cancel + * #5809: Add configuration parameters + * #5809: Implement speex echo canceller in audio rtp session + * #5814: Code cleanup + * #5814: Fix conf creation with several incomming ringing calls + * #5814: Fix conf creation segfault when dragging a call on hold on a + ringing call + * #5809: Added unit test for echo cancellation and implemented + "process" virtual method + * #5709: Add always recording option in configuration + * #5709: Add always recording option in audio conference panel + * #5709: Add core functionnality for always recording (missing config + options) + * #5769: Fix conference participant handling (detach/attach) and hold + actions + * #5747: Fix recording icons and state for conference when adding new + participant + * #5769: Code cleanup + * #5769: Fix hangup unsent calls + * #5769: Fix remove/add additional participant to conference + * 5769: Several fixes concerning confererence handling + * #5769: Fix compilation error + * [#5769] Fix audio streams binding in main buffer + * #5769: Removed access to audio mixer from audio layer + * #5765: Fix audio crash for illformated wavefiles + * #5765: Add maximum iteration for finding fmt and data "chunck" + * #5589: Fix compilation of libnotify under + * #5757: Fix abort signal when receiving INFO + * #5747: Add usersDetached.svg + * #5747: Handle offhold action for recording conference + * #5747: Fix off hold action for conferences + * #5747: Implement update conference in record action in calltree + * #5747: Add new icons for recording conferences + * #5747: Add recording state for conferences + * [#5738] Remove getAudioDriver call from manager (replace by + _audiodriver var) + * [#5738] Refactor mutex protecting audiolayer + * [#5737] Fix HD conference recording + * [#5730] Fix start audio session after changing sampling rate + * [#5714] Fix enter keyboard event for addressbbok and history + * [5695] Fix addressbook combo box update when no addressbook selected + * [#5695] Fix addressbook initialization and search bar update + * [#5695] Add mutex for books_data in addressbook to protect async + calls + * [#5695] Get back addressbook open from uri + * [#5695] Fix absolute addressbook URI for local addressbooks + * [#5695] Implement libebook 3.0 interface + * [#5571] Better logic for hangup (for case where call have not been + sent yet) + * [#5571] Update error handling in voip links + * [#5571] Fix compile time warnings + * [#5696] Fix installation dependencies for Natty + * [#5669] Add mention that sflphone.org is for testing only + * [#5693] Add natty in teh dput.conf file + * [#5690] Remove not useful logs + * [#5670] Use dynamic payload type for rtp dtmf + * [#5668] Clean up sflphone configuration logging + * [#5668] Fix hook checkbox configuration update + * [#5666] Fix unit tests + * [#5666] Manage event subscription + * [#5666] Emit bye request when subscription is terminated + * [#5666] Bye request should be sent after event subscription + notification is done on transfer + * [#5666] Make reinvite method static (to be called in pjsip + callbacks) + * [#5666] Hangup Call in manager for AccountNULL and IP2IP + * [#5589] Use PKG_CHECK_MODULE for every client's dependencies + * [#5623] Enlarge initial size of pjsip memory pool for calls (16k) + * [#5564] Fix audio recording resampling for g722 + * [#5571] Move attribute handling for onhold/offhold actions in SDP + session + * [#5571] Codec negotiation refactored and unittested + * [#5571] Implement tests + * [#5571] Implement pjsip negociator + * [#5571] Fix unit tests + * [#5571] Add Fmtp.h to repository + * [#5571] Integrate mime types and codec factory + * [#5571] Handle exception when SDP negotiation fails + * [#5570] Add sflphoned-sample.yml in repository + * [#5564]: Implement stereo to mono mixing for rigntone + * [#5342] Update audio stream initialization + * [#5514] Restore test ni historytest suite + * [#5514] Fix + * [#5514] Disable test_create_history_path + * [#5514] use pulseaudio in sample config file + * [#5514] Fix test: load history from file + * [#5514] Do not use X + * [#5513] Make unit tests compile successfully + * [#3947] Enable unit tests in Jenkins + * [#5454] Fix build system to handle new version number + * [#5454] Update languages from launchpad + * [#5454] Add --without-celt in OpenSuse build service + * [#5454] Change version number + * [#5331] Added first SDP session tests + * [#5273] Update nightly build version tags to conform dpkg rules + * [#5211] Refactor send register method for iaxvoiplink and + sipvoiplink + * [#3950] Remove call being transfered from calltree + * [#5211] Use appropriate memory pool for transport selector + * [#5211] Fix strict aliasing rules warning in pjsip + * [#5211] Bring back pjsip shutting down sleep to 1000 ms + * [#5211] Fix registration callback segfault when closing the + application + * [#5211] Use the dialog memory pool for Route header in INVITE + request + * [#5211] Add temporary memory pool for findLocalAddressFromUri and + findLocalPortFromUri + * [#5211] Use individual memory pool for dtmfs + * [#5211] SipVoipLink refactoring + * [#3950] Attended transfer for conference calls + * [#5284] Fix DNS resolution for Route with specified port number + * [#5284] Some code cleanup + * [#3947] Fix typo in hudson script + * [#5284] Added sip route to REGISTER, INVITE, BYE request, plus DNS + resolution + * [#5266] Use RTP dtmf as default + * [#5284] Added pjsip_process_route_set after setting routes in regc + structure + * [#5286] Fix parsing error due to long configuration file (removed + max event) + * [#5286] Fix false test in configuration emmiter + * [#5286] Code cleanup + * [#5286] Updated exception handling in configuration system + * [#4969] Fix put SRTP call on hold + * [#3950] Add debug messages + * [#3950] Ability to perform an attended transfer + * [#5276] Fix initialization problem in g722 + * [#3950] Add replace header in SIPVoIPLink::transferWithReplaces + method + * [#3950] Implemented attended method in SIPVoIPLink + * [#3950] Cleanup transaction request received callback + * [#3950] Implement dummy attended transfer in gnome-client + * [#5249] Fix audio samplerate update algorithm for g722 + * [#5249] Fix uninitialized variable used in conditional jumps + * [#5249] Fix conditional jump error in audiolayer (uninitialized + value) + * [#5267] Use autoconf 2.65 as a requirement (instead of 2.67) + * [#5267] Restore manual pjsip configuration and compilation + * [#5267] Autodetect celt version (0.9.1, 0.7.1) + * [#5267] Fix deprecated macros in gnome client configure.ac + * [#5267] Update configuration for libcelt-dev + * [#5267] Fix build autoconf and automake + * [#5227] Deactivate automatic call to astyle after compilation + * [#5242] Hangup every calls before leaving + * [#5237] Will now nightly-build for natty, Karmic deprecated + * [#5229] Use inner class for rtp thread instead of inheritance + * [#5211] Move mainbuffer unbind call in rtp final method + * [#5211] Initialize sip call memory pool using 16 kb + * [#5211] Use call memory pool in session reinvite + * [#5211] Add debug messages + * [#5211] Use and internal pool for calls + * [#5211] Reduce pjsip memory pool usage for stateless error messages + * [#5211] Refactor call deletion + * [#5212] + * [#5208] Refactor codec management for accounts + * [#5168] Remove printf from codec's encode & decode method + * [#5168] Fix celt compilation on launchpad + * [#5168] Fix sflphoned compilation warnings in audiocodec.h + * [#[#5168] Must keep the g722 specific RTP rate to avoid incoming + packet timeout + * [#5168] Fix static/dynamic payload rtp session update + * [#5168] Throw SIPVoipLink Error if codec not instantiated in new + outgoing call + * [#5168] Fix dynamic/static codec payload type ambiguity + * [#5169] Fix doubled IP2IP profile when no config file + * [#4867] Add gtkinfobar in configuration panel + * [#4867] Disable input/output/ringtone selection when using default + alsa plugin + * [#4952] Patches for possible buffer overflows + * [$4885] Fix schemas problem + * [#4885] sflphone-client-gnome.schemas not present during build + * [#4885] Add gconf shemas directories in opensuse build system + * [#4885] Add file/folder ownership for opensuse-factory build system + * [#4906] Fix opensuse-factory build + * [#4885] Update name dependency for libedataserver + * [#4885] Fix non-void function without return in dbus-c++ + * [#4895] Update language translation + * [#4896] Update session timestamp when updating media + * [#4896] Reapply RTP hack for G722 payload type + * [#4896] Update recording sampling rate when updating codec + * [#4897] Save codecs in config for each configuration changes + * [#4895] Do not save config when sflphone quit + * [#4885] Update date for copyright + * [#4885] Deactivate siptest that require more than one sipp instance + * [#4879] Remove inmcoming call notification from IAX + * [#4885] Some cleanup + * [#4874] Add setCancel immediate/deffered for ost::Thread + * [#4879] Fix incoming call notification + * [#4878] Set keyboard focus on searchbar when selecting addressbook + * [#4874] Fixed compilation warning + * [#4874] Fixed compilation warning in sipvoiplink + * [#4874] Fix compile time warning in RTP record handler + * [#4874] Fix conditional jump in SDP + * [#4874] Fix conditional jump based on uninitialized value + * [#4874] Store call id within rtp thread context + * [#4874] Fixed conditional jump based on uninitialised value in + conference + * [#4871] Fix default account fetching + * [#4870] Delete RTP session when Refusing an incoming call + * Restore IP to IP call + * [#4857] Fix audio codec negotiation problem + * [#3947] Adjust ressources allocated to compilation + * [#3947] Disable unit tests in Hudson + * [#4305] Free mutex only when really quiting SFLphone + * [#4859] Update copyright to 2011 in every source file + * [#3218] Character '.' stripped by the caller engine + * [#4854] Fix typos, desktop entry + * [#4847] Apply RTP modification to ZRTP session + * [#4852] Update Karmic and Lucid dependencies + * [#4852] Add Libedataserver and libedataserverui as gnome client + dependencies + * [#4852] Add authentication mechanism for EDS + * [#4851] Fix segfault when closing pulseaudio layer too rapidly + * [#4808] Some otehr cleanup + * [#4808] Made some cleanup + * [#4808] Added mutex in rtp session for codecs and noise process + * [#4847] Update audio processing when updating RTP media + * [#4842] Add support for linking with gold/ld --no-add-needed + * [#4808] Make update g722 related static/dynamic payload logic + * [#4827] Upper limit on the number of contacts to import from EDS is + hard-coded to 500 + * [#4808] Fix put call on/off hold + * [#4808] Implement early RTP start for incoming calls + * [#4808] Audio stream is no longer start within RTP session. + * [#4808] Removed coupling between audio layer and and RTP session + * [#4702] Start audio rtp session as soon as it is created + * [#4702] Init timestamp to 0 + * #4702: Send RTP packets immediately, no need of outgoing queue + * [#4784] Update dbus-c++ version from gitorious + * [#4702] Update RTP timeouts + * [#4702] Lengthen RTP timeouts + * [PATCH] Fixed compatibility with old libtool versions. + * [PATCH] Accept older libebook (Maemo 5 has 1.4.2) + * [PATCH] Fixed double-free error in preferences dialog + * [PATCH] Fixed building of sflphone-common on Maemo5 + * [PATCH] Improved Gnome client initialization error handling. 1. It + no longer segfaults when sflphoned isn't available. 2. User is + provided with GUI error dialog. + * [PATCH] Improved autogen.sh scripts 1. They do not require bash + anymore 2. Added workaround for Debian bug #565663 3. Replaced + manual autotools invocations with single autoreconf call 4. Non-zero + return status on failure + * Revert "[#4468] libtool <= 2.2 doesn't have LT_INIT macro so + AC_PROG_LIBTOOL should be used instead." + * Revert "[#4468] Libebook 1.4 is sufficient" + * Revert "[#4468] Apply big path on dbus communication system" + * [#4468] Apply big path on dbus communication system + * [#4468] Libebook 1.4 is sufficient + * [#4468] libtool <= 2.2 doesn't have LT_INIT macro so AC_PROG_LIBTOOL + should be used instead. + * [#4639] Fix determining default addressbook if this property is not + set in gconf + * [#4639] Fix memory leaks in Addressbook + * [#4637] Fix opening default addressbook at sflphone init + * [#4622] Free yaml events while parsing configuration file + * [#4623] Fix conditional jumps based on uninitialized variable + * [#4622] Fix leaks in yaml serialization engine + * [#4616] Fix addressbook warnings + * [#4514] Adjust RTP timestamp + * #4527: Rename Karmic libyaml and Celt package in debian control file + * #4495: Rework addressbook opening loop + * [#4524] Increment RTP count when sending data + * [#4524] DO NOT start RTP session twice + * [#4367] Use PKG_CHECK_MODULE for celt + * [#4367] Fedora package celt as celt (not libcelt) + * [#4367] Astyling + * [#4367] Update .po files + * [#4367] Fix segfault in gensin + * [#4354] Make celt a direct dependency on launchpad opensuse build + service + * [#4367] Make celt a required package, option --without-celt valid + * [#4367] Fix zrtp timestamping error + * [#4367] Fix audio zrtp timing + * [#4367] Dispatch ZRTP packets + * [#4367] Fix segfault when unloading account map + * [#4367] Fix zrtp session + * [#4367] Implement on packet receive + * [#4367] use symetric audio rtp session, not dual + * [#4367] Reduce packet receive/sent timeout + * [#4367] Reduce RTP timeouts + * [#4367] Move speaker data receive + * [#4367] Move speaker data receive + * [#4367] Move receive speaker data method + * [#4367] Remove debug in rtp session + * [#4367] Fix g722 codec clock rate + * [#4367] Fix noise suppression initialization + * [#4367] Fix segfault in RTP mic fadein method + * [#4367] Refactor mic data encoding in rtp session + * [#4367] Implement RTP main loop + * [#4367] Fix compilation problem + * [#4367] Fix AudioRtpclass using TRTPSessionBase + * [#4367] Fix AudioRtpSession putDtmfEvent shadowing + * [#4367] Fix AudioRtpSession putDtmfEvent shadowing + * [#4367] Refactor RTP session (phase 2) + * [#4367] Refactor RTP session (phase 1) + * [#4367] Remove Redeclaration of SymetricAudioRtpSession in + rtpfactory + * [#4265] Add continue statement in for loop for invalid addressbook + * [#4261] Makes addressbook initialization more robust + * [#4257] Add maverick in build system + * [#4233] Add sdp related unit tests + * [#4233] Add condition and signal in two incoming call test + * [#4243] Fix segfault in AudioSrtpSession + * [#4243] Fix memory leak in AudioSrtpSession + * [#4243] Make audio srtp optional in for incoming call + * [#4243] Add boolean variable to make sure remote crypto context + initialized only once + * [#4243] Add documentation to AudioSrtpSession + * [#4243] Use 80 bits authentication tags by default + * [#4243] Init audio srtp remote crypto context in + call_on_media_update + * [#4243] Move SDP negotiastion in mod_on_rx_request + * [#4243] Implement initLocalCryptoInfo to be called at different + momment + * [#4243] Init init local crypto context in when initializing audiortp + * [#4243] Change key length according to sdes negociation + * [#4243] Associate callid to accountid for incoming calls + * [#4242] Fix no SDES keys in IP2IP calls + * [#4242] Fix no SDES keys in IP2IP calls + * [#4233] Test for call on/off hold + * [#4233] Add two incoming call test + * [#4233] + * [#4233] Add 2 outgoing simultaneous call unit tests + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Fri, 30 Sep 2011 13:57:10 -0400 + +sflphone-plugins (0.9.7~rc1~ppa1~SYSTEM) SYSTEM; urgency=low + + ** 0.9.7~rc1~ppa1~SYSTEM ** + + * [#2462] Set explicitly the transport on incoming call too + * [#2462] fix typo + * [#2462] Use different address for SDP and call IP + * [#2462] Use published address in SIP-SDP + * [#2181] Fixed changelog files + * [#2181] Updated spec file + * [#2402] Fix pointer to int conversion warning (atoi) + * [#2402] Remove daemon warnings, make indent + * [#2459] Make sure the stream is opened when the call is answered + * [#2402] Add conference related picture in documentation + * [#2443] Not much ... + * [#2399] Fix dialing display problem + * [#2450] Fix incoming call already in conference crash + * [#2399] Display peer name on the first line and peer number on the + second + * [#2450] Handle 403 FORBIDDEN when refused + * [#2447] Bind offHold/onHold actions to button in gtk client + * [#2447] Bind hangup action to button for conference + * [#2447] Add conference action in gtk client's ToolBar + * [#2381] Disable the password hashing in config file + * [#2402] Cleanup + * [#2366] Set callback to null when deleting Pulseaudio streams + * [#1313] Fix main buffer unit test + * [#1313] Fix audio layer unit test + * [#2315] Hide pw in security tab, display when editing, sync with + basic tab + * [#1313] UnitTest change AudioRtpSession for AudioSymetricRtpSession + instance + * [#2402] Code cleanup + * [#2444] Add debug to catch occasional crash when loading client's + config + * [#2444] Add debug info to catch occasional crash when loading config + dialog + * [#2402] Restore Call menu translations + * [#2403] Use the published address if checked in GUI + * [#2442] Add protection test in sdp + * [#1841] Reapply pjsip patch concerning DNS SRV resolution + * [#2384] Tags incoming call as direct SIP call, if applicable + * [#2402] Change the monkey face + * [#2315] Enable user to display password in clear text + * [#2434] Force optimization level at 2 + * [#2284] Fix dbus_get_all_ip_interface compilation warnings + * [#2431] Popup main window on incoming if applicable + * [$2402] Fix simple warnings + * [#2402] Fix implicit variable init order in LibraryManagerException + * [#2402] Fixing implicit variable initialization warnings in + AudioRtpSession + * [#2402] Revert atoi change, fixing codec list doubled entries + * [#2402] Fix gpointer to gint conversion + * [#2402] Fix pointer casting to integer different size warning in + codec list + * [#2402] Fix warning discarting qualifiers from pointer target + * [#2402] Fix gtk tree view assignement from incompatible type warning + * [#1669] Fix audio recording folder utf-8 non compatibility issue + * [#2414] Clean up debugs + * [#2414] Use transport set in iptoip Account and update it frm + preference + * [#2348] Use macro N_() to mark ui.xml strings as translatable + * [#2414] Rename getSipAddress/setSipAddress functions + * [#2407] Fix volume controls display + * [#2407] Fixes dialpad + * [#2383] Set ip to ip config when clicking apply button + * [#2404] Update call-to script - Maxime Chambreuil + * [#2405] Client handles unknown call in current state as well + * [#2383] Add DBUS signal to send IPtoIP local address and port as + string + * [#2383] Add Ip to IP config change apply call back + * Clonflict + * [#2402] Code cleanup + * [#2383] Do the same for IPtoIP (init localn ip with first in the + list) + * [#2383] Use first interface in the list if local addresss is not + defined + * [#2403] Clean up unuseful addresses/ports + * [#2403] Use the IP profile SIP port as global SIP port + * [#2383] Fix dbus_get_all_ip_interface warnings + * [#2383] Take into account sameAsLocal when loading published address + * [#2383] Tsake into account sameAsLocal option when saving published + address + * [#2383] Update local ip address in ip to ip config + * [#2383] Save ip 2 ip local port in config + * [#2406] Update toolbar at startup + * [#2284] Remove redefinition warnings + speex warnings + * [#2383] Fix security table in account config + * [#2383] Save ip 2 ip network interface parameters in config + * [#2403] Restore sip transport selector + * [#2383] Fix filling the Localt IP Address on account creation + * [#2383] Fix Gtk-Critical when checking STUN + * [#2383] Fix reopening account configuration display issue + * [#2383] Load IPtoIP local address and port in preference iptoiptab + * [#2383] Add LocalAddress and Localport in Preference IpToIp tab + * [#2403] Use the address and port associated to the account as often + as possible + * [#1753] Removed pjsip generated files + * [#1753] Removed remaining milenage lib references + * [#2383] Add _publishedSameasLocal variable in sipaccount + * [#2383] Add PUBLISHED_SAMEAS_LOCAL variable in config + * [#2383] Fix stun set active or not when opening config + * [#2181] Added RPM 64bits dbus patch + * [#2402] Code indentation + * [#2313] Force $(HOME).cache directory creation at startup + * [#2383] Separate network interface and published address in account + config + * [#2400] Change dbus service installation path to libdir + * [#2382] Move TLS related published address options in security tab + * [#2382] Indent accountconfigdialog.c + * [#2181] Install libdbus-c++ in $pkglib instead of $lib + * [#1753] Remove ILBC code and disable it by default in the configure + * [#1753] Remove milenage directory + * [#2382] Fix switching interaface instabilities + * [#2396] Save local ip in account creation wizard + * [#2284] Remove warning on hold + * [#2387] Fixes history searching and filtering + * [#1215] Add samplerate display in the GUI + * [#1663] Voicemail icon reflects voice messages + * [#2395] Fix account registration ( specifically with callcentric) + * [#2386] Strip "sip:" on incoming call, fixing history call back + * [#2181] Updated spec files + * [#1215] Display codec name in calltree instead of status bar + * [#2390] Move back nbCalls and stopStream higher in refuseCall + * [#2392] Fix ringtone during call in IAX + * [#2391] Stop audio streams when there is 0 calls only + * [#2391] Add debug when call state is not valid + * [#2390] Clear returns in IAXvoipLink::sendAudioFromMic() method + * [#2380] Fixing IncomingCallNotification not regular + * [#2339] Query conference at client startup + * [#2339] Working conference querying at startup + * [#2339] Add conference in call tree + * [#2339] Primitives to query conferences at client startup + * [#2320] Add account selection in history + * [#2355] Temporary solution: do not delete pointer when removing + account + * [#2380] Change algorithm in AudioRtp to trigger an + IncomingCallNotification + * [#2274] Comment sdebug in MainBuffer flush method + * [#2274] Add flushMain() in ManagerImpl::addStream + * [#2274] Add getBufferID() method in ring buffer + * [#2274] Fix warning, comment debug in ringbuffer's flush method + * [#2274] Use AudioLayer flushMain() and flushUrgent() in ALSA + * [#2274] Clean up unused variable warning + * [#2274] Protect minbudffer pointer on flushing + * [#2274] Fix playATone method which writing empty buffer in urgent + ringbuffer + * [#2274] Use audio layer flushUrgent and flushMain in createStreams + * [#2274] Use flush audio calls from audiolayer + * [#2274] Flush when peer answered call + * [#2375] Flush main buffer in iax when answering a call + * [#2274] Parse displayname using c++ string method + * [#2375] Flush main buffer when off holding calls + * [#2375] Flush main buffer mon RTP startup + * [#2376] Use now Pulseaudio module-cork-music-on-phone + * Updated OSC packaging + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Fri, 20 Nov 2009 13:59:02 -0500 + +sflphone-plugins (0.9.7~beta~ppa1~SYSTEM) SYSTEM; urgency=low + + ** 0.9.7~beta~ppa1~SYSTEM ** + + * [#1933] Cleanup debug + * [#1933] Clean up debug + * Fix mic + * [#1933] Set the IAx format earlier + * [#1933] Move IAX sendAudioFromMic outside if (call) statement + * [#1933] Fix startstream when offhold in iax and add debug concerning + codec neg. + * [#2371] sflphone_notify_voice_mail: minor gettext message formatting + cleanup + * [#2371] select_account_cb: properly gettextize status message + * [#2371] show_account_list_config_dialog: properly gettextize status + message + * INSTALL: Minor tidyup of core install guide + * Add /sflphone-client-gnome/src/icons/Makefile to .gitignore + * [#2181] Updated OpenSUSE files (tmp) + * [#1933] Add debug for codec negociation for iax + * [#1933] Get rid of getMicAvail and getMicData in audiolayer (not + used anymore) + * [#1933] Add "audio codec not determined" error in IAX + * [#1933] Test flush data + * [#1933] Do not need to start audio stream in iax anymore + * [#1933] Protecting pointer + * [#2284] Remove more compilation/execution warnings + * [#2284] Cleanup debug in client, use DEBUG instead of g_print + * [#2284] Clean up uimanager + * [#2370] Remove warnings + * [#2366] Clean up other debug + * [#2366] Clean up debug + * [#2366] Call pa_xfree explicitely in writeToSpeaker + * [#2284] Remove address book warnings + * [#2365] Fixes bad cast + * [#2352] Fix continuous ringing when peer hangup and call not yet + answered + * [#2181] Added version support + * [#2181] Fixed some minor issues + * [#2360] Moved MainBuffer from AudioLayer to ManagerImpl + * [#2352] Makes getMainBuffer() everywhere + * [#2352] Use 50 sec latency on pulseaudio stream creation + * [#2352] Add alsa debug + * [#2359] Update repository documentation + * [#2354] Move pulseaudio disconnectAudioStream after stopping main + loop + * [#2352] Adjust nb byte copied in pulseaudio according to + writeableSize + * [#2352] Specify pulseaudio tlength parameters using pa_usec_to_bytes + * [#2322] Convert italian translation to UTF-8 + * [#2357] Fixes window size + * [#2357] Display only actionnable tool item + * [#2333] Update streams parameters + * [#2347] Use GNOME user settings for Menu and Toolbar appareance + * [#2349] Load/Save properly audio params + * [#2322] Update translations from Launchpad + * [#2181] Added Francois Marier script + * [#2350] Remove non-valid test + * [#2181] Updated launchpad packaging + * [#2333] Fix Pulseaudio Capture + * [#2333] Use pulseaudio ADJUST_LATENCY flag and ALSA RT-SCHEDULING + * [#2333] Pulseaudio Interpolate timing + * [#2333] Change (again) Pulseaudio settings to fit logiteck usb hdw + requirement + * [#2333] Adjust pulseaudio fragment size to 4096 (max sflphone's + frames per buffer) + * [#2284] Remove recurrent compilation warning (g++ linker problem) + * [#2333] Safer Audiostream parameters + * [#2333] Fix alsa playback to reduce underrun + * [#2333] Better audiostream parameters + * [#2181] Updated version management + * [#2333] Exclusive test in playback loop + * [#2181] Updated build system + * [#2333] Less underrun with these value + * [#2333] Update playback audiostream parameters + * [#2333] Lengthen the audio buffer reduce number of underrun in + pulseaudio + * [#2333] Add ALSA recovery functions for underrun (begin) + * [#2333] Add pa_stream_trigger in pulse audio underrun callabck + * [#2048] Reduce prebuffering in pulseaudio (which affect incomming + calls' plbck) + * [#2316] Do not display any icons to the right on the history tab + * [#2333] Comment pa_stream_trigger in pulseaudio underrun + * [#2333] Modify pulseaudio streams parameters + * [#2318] Fix transfer tool button double signal + * [#2181] Updated + * [#2333] Fix ALSA ringtone + * [#2333] Flush all main buffer before starting audio + * [#2333] Open/Close Alsa thread between calls while there is no audio + * [#2333] Add debug message and test condition on starting playback + and capture + * [#2181] Fixed gnome client makefile + * [#2181] Updated + * [#2308] Remove getTelephoneTone debug + * [#2308] Change plughw for default in ALSA + * [#2308] Oups, forgot to change function name in audiolayertest.cpp + * [#2308] Cleanup in pulseaudio code (debug, function name) + * [#2308] Fix pulseaudio stream closing assertion failure + * [#2308] Moved pulseaudio mainloop locking from AudioStream + disconnect stream + * [2308] Fix latency at the beginning of a call, when playing DTMF and + wehn starting tone + * [#2181] Updated karmic + * [#2317] [#2319] Fix address book toggle button contextual behaviour + * [#2308] Stop stream when refusing a call + * [#2308] Stop pulseaudio stream when peer hungup + * [#2308] Fix tone and ringtone + * [#2312] Display the STUN entry widget when opening the tab + * [#2308] Implement two different callbacks for capture/playback in + pulseaudio + * [#2309] Open/close pulseaudio connections in startStream/stopStream + * [2308] Leave pulseaudio stream running, do not cork/uncork them + anymore + * [#2295] Set gtk file chooser to None if nothing is set in + configuration + * [#1976] Add codec and conference documentation + * [#2209] Fix recording in regard of resamling + * [#2297] Update .gitignore + * [#2297] Update translation files + * [#2297] Add reference to our coding standards + * [#2297] Remove old docbook code + * [#2296] Reinit tls account settings after modification + * [#2253] Add DcBlocker class to remove capture's dc offset + * [#2034] Fixes for TLS transport to initialize + * [#2284] Add silent build rule + client clean warnings + * [#2274] Fix unserialize history items in cilent at startup + * [#2274] Complete display name parsing and displaying + * [#2274] Parse the Display Name in sip INVITE message + * [#2050] Fix capture volume control in ALSA + * [#1970] Volume controls disable when using pulseaudio + * [#1970] Disable volume controls when using pulseaudio + * [#2277] Fix direct ip2ip ZRTP enabling/disabling in ip2ip + preferences + * [#2181] Added launchpad debian files + * [#2181] Added spec files for OSC + * [#2274] Set display name for "Contact" sip header as the hostname + * [#2181] Fixed daemon issues + * [#2181] Fixed gnome client issues + * [#1976] Remove warnings - need to fix the transfer + * [#2006] Add init is_rec variable in ManagerImpl + * [#2006] Update codec display on call selection + * [#2006] Restore double click actions in history and contact calltree + (GTK) + * [#2176] use XDG_CACHE_HOME when initializing sfl.zid file + * [#1976] Fix calltree switching from history + * [#2209] (Re)Fix cache for zid + * [#2209] Clean up debug messages + * [#2209] Clean debug messages + * [#2209] Fix trasnfering a call during a conference + * [#2209] Speex decode must return the number of bytes + * [#2209] Change frameSize speex 32kHz + * [#2209] Fix speex codec framesize + * [#2209] Reinit converterSamplingRate in RTP sessions + * [#2209] Change speex ultra wide band framesize + * [#1747] Add pixmap data + * [#2252] Fix Receiving a server error 488 crashes the callee + * [#2209] Fix iax low rate packate sending + * [#2209] Clean up debug messages + * [#2209] Add resampling changes for IAX + * [#2209] Clean up resampling code + * [#2209] Fix latency introduced by pulseaudio + * [#2209] Fix initialization of mainbuffer's internal sampling rate + * [#2176] Fix upsampling buffer size in audiolayer + * [#2209] Add dynamic converter sampling rate in audiortp sessions + * [#1747] Fixes runtime warnings + * [#1747] Remove from repo + * [#1747] register our icons to be used as stock icons + * [#2209] Fix number of byte in alsa's write to speaker + * [#2209] Fix putting non-resampled data in RTP's mainbuffer + * [#2209] Add alsa resampler + * [#2209] Add a samplerate converter in PulseLayer + * [#2209] Add mainbuffer's internal sampling rate and flushall method + * [#2176] Add mainbuffer stateInfo debug method + * [#2209] Resampling is optimal using SRC_LINEAR not SRC_FASTEST + * [#2176] Remove debug recordings + * [#2176] Fix Holding a conference participant on new calls + * [#2224] Add confID in callable object + * [#2176] Fix putting onhold a call participating to a conference when + pressing new call + * [#2176] Reset auidio buffers when adding streams (rtp, audiolayer) + * [#1976] Use xml to describe toolbars - Add a naviguation toolbar + * [#2176] Remove conference default_id in joinParticipant + * [#2176] Display error message in alsa's snd_pcm_avail_update call + * [#2176] Alsa mic avail data debug + * [#2176] Add some debug message for mic loss problem + * [#2176] Flush mic ring buffer when offholding a call + * [#2176] Reset ringbuffers' readpointer when adding main participant + * [#2176] Fix getAvailData algorithm + * [#2176] Reset ringbuffer's readpointer when adding a new participant + to a conference + * [#1744] Regex object renamed to Pattern. Previous attempt at + providing + * [#2176] Fix detach main participant problem when adding new one + * [#1976] Use right domain to translate + * [#1976] Add xml menu description + * [#2176] Store a list of confernece participant in client + * [#2176] Fix add participant, joinparticipant methods + * [#2181] Do not install dbus-c++ headers + add return value + * [#2176] Fix minor call handling instabilities + * [#2174] Fix incoming IP call contact address + * [#2211] Add test to protect NULL pointer + * [#1163] Add Advanced account configuration section + * [#2176] Add some usefull comments and debugging info + * [#2176] Add conditions to display security icons in conference + * [#2176] Fix detaching one participant while keeping communication to + others + * [#2176] Reenable userActive.svg in call tree + * [#2176] Make user active blue (not red) + * [#2176] Fix user active picture + * [#2176] Fix "hidden" merge conflict in sipvoiplink + * [#2176] Remove iax audio stream on peer hungup + * [#2174] Multiple UDP transports functional (TESTED with 2 accounts + and 3 calls) + * [#2176] Fix fix audio stream binding in iax + * [#2174] Create a default UDP transport + use tp selector for dialogs + also + * [#2176] Register iax audio stream in mainbuffer + * [#2176] Fix getAudioCodecName in IAXvoipLink + * [#2176] Fix iax account init + * [#2176] Handle multiple account using the same sip transport + * [#2165] Add .png files + * [#2176] Small fixes concerning dtmf + * [#2176] Fix make uninstall in codecs + * [#2174] remove stund makefile generation + * [#2176] Add conference lock + * [#2174] Add transport selector for multiple accounts + * [#2176] Change userActive picture from red to blue + * [#2176] Fix security pixbuff in calltree + * [#2176] Replace sfl.zid in .cache/sflphone instead of .sflphone + * [#2176] Fix add call description + * [#2176] Remove detach button from toolbar + * [#2176] Fix calltree call description state and state code in + conferences + * [#2176] Fix pulse audio double free + * [#2176] Fix conference selection + * [#2174] Clean up - remove stun settings in client network + configuration panel + * [#2174] Remove voviva stun code + * [#2174] Rsolve STUN with pjsip - DO NOT WORK + * [#2165] Add user svg + * [#2165] Debugging sip call failed + * [#929] Link against uuid if installed + * Oops + * Fixed bugs related to libsexy (with GTK < 2.16) + * [#929] Remove uuid-dev dependency in the core + * [#2165] Debugging no negociated codecs at communicatio start + * [#2165] Fix calltree bug (gtktreestore instead of gtkliststore) + * [#2165] Fix several merge problems + * Updated opensuse packaging script + * [#1163] Add missing figures + * [#1163] Update INSTALL file + * [#2165] Fix IAX + * [#2165] Add recordabe interface + * [#2165] Finish recording refactoring for call (not for conference) + * [#2165] Enable speaker recording for two different calls + simultanously + * [#2165] Implement call recording using the Recordable interface + * [#2165] Add get and set to AudioLayer's audio recorder + * [#2165] Add class recordable from which inherit call and conference + * [#2006] Fix G722 and Speex 8khz codec conferencing + * [#2006] add recording of audio buffers + * [#1163] Add general settings section + * [#1163] Fixes makefile error + * [#2006] Fix some minor issues + * [#2006] Drag a conference call on another conference call + (difference conferences) + * [#2006] Fix dragging a conference on itself + * [#1744] Integrating some of the needed regular expression patterns + in order + * COmplete call features + * [#1744] Added support for named subgroup in the Regex object. Also, + new + * [#1744] Adds thread safety features, compile() and setPattern() + methods to the Regex class. + * [#1744] Fix inconsistency in the finditer method from the last + commit. + * [#1744] Added regex pattern object built on top of libpcre. To be + used + * [#1744] Initial commit towards implementing RFC4568. Unimplemented + in the + * [#2157] Hide "security" and "advanced" tabs for IAX under account + * [#1163] Add call features section + * [#2006] Add joinConference capabilities + * [#2006] Add dbus joinConference signal + * [#2006] Drag a conference call onto a conference to add it + * [#1163] Add addressbook section + * [#2006] Drag a conference call onto a single call to create a + conference + * [#2006] Expand rows automatically + * [#2006] Add minimal multiple conference handling + * [#2006] Add atached/detached conference icons + * [#2006] Add function processRemainingParticipant + * [#2006] Deep refactoring, fix hangup bug + * [#1163] Update documentation - Accounts part + * [#1976] Integrate user doc to gnome client build system + * [#2122] Remove double inclusion in dbus-c++/src/Makefile.am + * Remove pjproject version number + * [#2006] Fix peerHungup + * [#1976] Make Yelp accessible from the GNOME client (need to install + the sflphone.xml first) + * [#2006] Fix multiconferencing hangup + * [#2006] Fix hangup calls in a conference + * [#2150] Make IAx2 reappear + * [#2006] Fix detach participant on multiple call + * [#2006] Can remove rining call from a conference + * [#2006] Reinit confID when removing a participant + * [#2006] Remove get isCurrentCAll in hangup/peerhungup (SipVoipLink) + * [#2006] Fix refuse call + * [#2006] Fix answerring incoming call + * [#2006] Refactor conference's participant list + * [#2101] Re-integrate test compilation in main build system + * [#2101] Make the test directory compile + * [#2136] Restore history functionality + * [#2006] Fix binding main participant to himself + * [#2006] Fix add current/incoming/onHold participant to an existing + conference + * [#2006] Fix add incoming calls to an already created conference + * [#2006] Fix remove stream + * [#2006] Fix detachParticipant/removeParticipant switchCall ids + * [#2006] Fix adding a call in conference having state "CURRENT" + * [#2006] Remove/add main participant from conferences + * [#2006] Hold/unHold conference + * [#2006] Detach a partcipant from drag n drop + * [#2006] Hangup a conference + * [#2006] Add hold/unhold conference dbus messages + * [#2034] gtk-ui fix under the "basic" tab. + * [#2006] Fix dragging calls on conference calls + * [#2006] Fix detach participant from a conference + * [#2034] Added default message is status bar under the account config + dialog + * [#2112] Fix a crashed caused when a non-md5 password was sent to + pjsip. + * [#2006] Detach participant by ID + * [#2006] Fix addParticipant method in managerImpl to handle + incoming/answered calls + * [#2006] Add addParticipant method in managerimpl and related dbus + messages + * [#2111] Added the ability to configure zrtp on sip.sflphone.org from + * [#2106] Fixed problem in the account assistant under gtk-ui. Also, + assistant.c + * [#2006] Fix dragging a conference call on another conference call + (same conference) + * [#1904] Small UI fix. Assistant was moved from "Call" to "Edit" + menu. + * [#1904] Fix a wrong label under gtk-ui. + * [#2034] Renaming and source code splitting. + * [#2034] Status bar added to account window to better reflect the + registration + * [#2006] Make calltree_remove_call recursive (for GtkTreeStore) + * [#1110] Small gtk-UI fix in the account window (alignment). + * [#2006] Fix remove conference, display children which are still + active + * [#2006] Recursive function call in calltree_update_call + * [#2006] Add multilayered capabilities to calltree (GtkTreeStore) + * [#2006] Implement remove conference in calltree + * [#2034] Now useless as Direct Ip calls settings moved under + Preferences. + * [#2034] Edit/add buttons were set insensitive all the time under + gtk-ui. + * [#1887] Information about the state of the current SIP call is + displayed + * [#2006] Add call tree remove callback + * [#2006] Fix create_conference function + * [#2006] Update conference_added_cb to add new conference to the list + * [#812] Added new tab under GTK-ui Preferences. Moving Direct Ip + Calls from + * [#2121] Disable temporarily test compilation + * [#2006] Fix conferencelist to handle conference_obj_t instead of + gchar + * [#2006] Add conference_obj structure + * [#2121] Update version + * [#2006] Fix conference selection + * [#2101] Use the new source tree to fetch the right object files + * [#2006] Add conference in calltree + * [#2006] Add Dbus signal conference added/removed/changed + * [#2006] Add getConferenceDetails call on dbus + * [#1904] Registration expire now appears as a spin box under gtk-ui. + * [#812] Fixing a segmentation fault caused by a non-existing account + ID + * [#2006] Add getConfList method over dbus + * [#2006] Add a conferencelist data structure in client-gnome + * [#812] Defaults value are now sent if a non-existing account is + requested + * [#2006] Add sflphone action sflphone_join_participant + * [#2006] Fix buffer read pointer problem deletion + * [pjsip] Attempt at fixing via header incompatibility with + Freeswitch. + * [#1797] forget something + * [#2006] Add call new state conferencing in deamon + * [#2006] Remove addParticipant method for conference, use + joinParticipant only + * [#1163] Update INSTALL documentation + * [#812] Msec/sec values were not taken into account. + * [#1797] Make pjproject-1.4 compile + * [#2006] Add Detach participant method + * [#2006] Dragndrop fully functional with INCOMING and HOLD call + * [#1797] Add pjproject-1.4 + * [#1797] Remove pjproject-1.0.3 + * [#2006] Get call state in conference related function + * [#2006] Add joinParticipant (conference) method in ManagerImpl + * [#2006] Add joinConference DBUS message + * [#2006] Store the previously selected call_id on dragndrop + * [#2006] Fix GValue pointer unref in selection callback + * [#2006] Store dragged call_id + * [#2006] Update drag_data_received_cb callback to manipulate CallIDs + * [#2006] Add dragndrop signals + * [#2006] Set calltree reordable + * [#812] Adds the ability to create a TLS listener in case the user + requests + * [#812] Adds the ability to configure local/published address from + * [#1883] Move switchCall in onHoldCall function + * [#812] Deals with the published address/port problem when + integrating TLS. + * [#1883] Switch call id in managerimpl when peerHungUp + * [#1883] Switch call id before hangup + * [#1883] Add usefull and permanent debug info for conference + cretion/deletion + * [#812] Fix various segmentation faults related to Direct IP kind of + calls. + * [#1883] Fix deletion of std::map elements using iterators + * [#2014] Add libzrtpcpp build dependency + * [#1883] Still some for loop test ambiguity (while loop instead) + * [#1883] Fix for loop initial test ambiguity (use while loop instead) + * [#1883] We must discard data in urgent ring buffer if data is get in + mainbuf + * [#1883] Fix availForGet same id for ringbuffer and readpointer + * [#812] Match "sips" as a Direct IP Call when the user enter a sip + uri + * [#812] Fix segmentation fault related to SIP URI creation. + * [#812] Towards integrating multiple tls listeners at the same time. + This + * [#1883] Add debug messages in conference and fix mainbufferTest + * [#812] gkt-ui fix. Private key must be fed as a filename and not as- + is. + * [#812] TLS integration within sipvoiplink and pjsip. Also, + configure.ac + * [#1883] Fix Alsa/Pulse mallocation + * [#1883] Fix data corruption in AudioRtp's micData buffer + * [#812] Full dbus integration for all the tls related options under + gtk-ui. + * [#1883] Fix memory leaks in audiortp session + * [#1883] Fix mem leaks in audio rtp + * [#812] Fix setAccountDetails where TLS_ENABLE was set to the value + * [#812] Small gtk-ui fix. + * [#811][#812] Small gtk-ui fix. + * [#812] Introduced a mechanism for configuration files that makes + possible + * [#812] New dbus bindings added. Also, configuration compliance was + enforced + * [#1881] Remove default buffer from MainBuffer (update unit-tests) + * [#1881] Add ring buffer read pointer tests + * [#1883] Fix issues in ringbuffer reader pointers + * [#2034] Implementing a new configuration dialogue for TLS transport + settings + * [#1883] Add some usefull debug and safety checks + * [#2028] Notify the client with libnotify when the zrtp negotiation + failed. + * [#811] Harmless no to throw an exception, an makes the application + less + * [#2028] A minidialog is showed to the user under sflphone-client- + gnome + * Removed useless file. + * Ignoring Makefile in src/widget + * [#2027] Fix segmentation fault when showMessage callback is called + after + * [#2026] keyExchange was set to ZRTP instead of "1" + * [#2024] Fix the wrong summary at the end of the assistant. + * [#1883] Fix mnagerimpl conference map insertion + * [#1883] Add Mutexes in MainBuffer + * [#811] Gtk ui was not presenting the right information about zrtp + for + * [#2023] security icons were not installed in sflphone-client-gnome. + * [#2021] Fix a mistake in the readme from sflphone-common that gives + wrong + * [#811] The current SRTP mode was not properly displayed for the + IP2IP + * [#1743] Re-implementation of the "automatically remove error dialogs + [...]" + * [#2017] [#2019] Fix the inability to dial a number and place a + registered + * [#811] Final re-integration of ZRTP support in the main branch from + 0.9.6 + * [#1883] Fix map insertion methods + * [#811] Combo box now is now set to the active key exchange method + * [#811] ZRTP options now configurable back again from the Gtk UI. + IP2IP + * Updated hostname for git clone + * [#1883] Add minimal functionalities to create a conference + * [#811] re-integration of all the methods and signals on dbus. + ManagerImpl + * [#811] Got out of a precarious position were nothing would compile. + * [#1976] Build documentation squeleton with docbook + * [#1883] Add sflphone-client "addParticipant" button for conference + * [#1994] Better organize the source directory structure. New + subdirectories + * [#1883] Add a simple Conference class + * [#1882] Use static audio buffer in Pulse and ALSA layer (instead of + malloc) + * [#811] First commit toward re-integration and refactoring of ZRTP + * [#1882] Flush RTP ring buffer before entering mainloop + * [#1882] Fixed MainBuffer::UnBinCallID() in case there is no + ringbuffer + * [#1882] Test (and fixe) high level conference and mixing + functionalities + * [#1772] Apply patch to compile on fedora (sent by Marcin + Zajączkowski <mszpak@wp.pl>) + * [#1882] Update Bind, unBind call_id in MainBuffer + * [#1959] This adds the ability to store password as an MD5 Hash in + the + * [#1538] Fixes rules compilation + * [#1930][#1931] Fixed a mistake (again) related to index and + credential count + * [#1753] Remove ILBC from pjproject - Hacks in pjsip + * [#1930][#1931] Credential was not selected properly using realm + * [#1882] Finilize multiple reading pointer in RingBuffer + * [#1538] Remove configure from autogen.sh to respect debian upstream + authors policy + * [#1773] Remove generated files from repo + * [#1791] Use XDG_CACHE_HOME to save pid file + * [#1791] Fixes path to save history + * [#1791] Fix debian installation scripts + * [#1930][#1931] Settings are now taken into account in the server. + * [#1882] Add ringbuffer default ring buffer pointer in methods + involving mStart + * [#1882] Add default ringbuffer pointer + * [#1882] Add RingBuffer multiple read pointer basic functionnalities + * [#1882] Fix MainBuffer flushData unit test + * [#1930][#1931] Ability to save and retreive the configuration from + * [#1882] Added Multiple CallID mapping to MainBuffer + * [#1791] Not much + * [#1791] If XDG env variables are not null but empty, use default + ones + * [#1791] Make XDG_CONFIG_HOME writable + * [#1930][#1931] Partial commit. Not working yet. Cannot delete + account + * [#1881] Fixed alsa capture latency problem + * [#1881] Fixed Alsa capture temporarily + * [#1930] [#1931] Partial unbroken commit providing the ability to + * [#1881] MainBuffer implemented in AudioLayer/AudioRTP + * [#1881] Add discard and flush unit-tests + * [#1881] Add discard and flush functionnalites to MainRingBuffer + * [#1881] Add availForGet in MainBuffer + * [#1881] Add availForPut function to MainBuffer + * [#1880] Remove AudioRTP* pointer from SipVoIP (reapered while + merging master) + * [#1881] Add a map between call id and coresponding ring buffer + * [#1855] Refresh pot file and upload on Launchpad + * [#1881] MainBuffe now robust to false ids on getData and putData + * [#1881] Fix big big big memory leak + * [#1881] Add getData and putData to mainBuffer + * [#1881] Unit-test basic ring buffer functionnaities + * [#1881] Add class MainBuffer and basic buffer creation unit-tests + * [#1880] Fix call transfer (step2) issues + * [#1880] Moved AudioRtp* pointer from SIPVoIPLink to SIPCall class + * [#1791] Add postinst script to keep user data when migrating + config/history file + * [#1797] Make pjsip compile + * [#1777] Code indentation + * [#1791] Use XDG_DATA_HOME and XDG_CONFIG_HOME for sflphonedrc and + history + unit tests + * [#1746] Useless space does not appear anymore when volume sliders + and + * [#1643] GtkCheckMenuItem is used instead of icons for elements in + the + * [#1110] [#1668] STUN parameters are now located in the preferences, + under + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Fri, 06 Nov 2009 11:20:01 -0500 + +sflphone-plugins (0.9.6-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6 ** + + * Documentation on echo test + * [redmine_down] codec names not displayed in total + * [redmine_down] crash when hanging up a dialing call because tries to + add it to history whereas no starttime + * [#1927] alternate every time screen changed to call history + * [#1886] clean code + * [#1886] debug messages when loading history removed + * [redmine_down] sflphone-kde icons + * [#1855] Update language files + * [#1502] Update version number + * [redmine_down] setHistory at close + * [#redmine_down] Handle PJ_DECLINE_SC as failure + * [#1923] Fix segmentation fault when adding a new account + * [#1923] Check on iterator before setting the config + * [#1904] Added mnemonic to tabs in sflphone-client-gnome. + * [#1905] The daemon was not sending the currentSelectedCodec signal + on dbus when answering a call. + * [#1922] Default values set to all account details + * [#1886] Spinbox reg expire enables apply, and address book is not + visible when disabled + * [#1905] Bug fix for segmentation fault caused by an empty string, + * [#1910] Warnings in test directory + * [#1919] Error fixed + * [#1855] Update russian translation - Hussein Abdallah + * [#1910] Remove files + * [#1919] fixed + * [#1777] Code indentation + * [#1918] fixed + * [#1917] fixed + * [#1910] Remove warnings compilation in src + * [#1886] removed AccountListModel in configskeleton + * [#1914] + * [#1911] check previous and new port + * [#1910] Remove compilation warnings in src/dbus and src/history + * [#1910] Remove compilation warnings in src/audio + * [1855] Update german translation - Sven Werlen + * [#1909] removed + * [#1906] Done + * [#1904] The registration expire value is now configurable from the + * Cleaned up debug messages. + * [#1886] separated initCallItem in two functions + * [#1886] reversed error in commit + * [#1886] clean debug + * [#1886] changed Name of classes and files + * [#1886] clean + * [#1870] In call_state_cb (dbus.c:126), _time_stop was overridden by + the actual time. + * [#1884] Added some new gpg flags to prevent tty warnings + * [#1886] Clean audio config dialog + * [#1886] No more compile warnings. + 1 comm + * [#1872] Check if the user input is smaller than PJ_MAX_HOSTNAME. + * [#1886] + * [#1785] Fixed build when no new commit + * [#1852] If chosen by the user, the hostname can now be solved and + used + * [#1871] * and # inverted back + * [#1869] Conditional compilation that checks if + * [#1309] removed test in main + * [#1425] Put actions in SFLPhone window class instead of ui view, + made a separate toolbar for screens. + + -- SFLphone Automatic Build System <team@sflphone.org> Mon, 27 Jul 2009 09:53:19 -0400 + +sflphone-plugins (0.9.6~rc2-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6~rc2 ** + + * [#1755] Remove generated file + * [#1753] restore ilbc ... + * [#1866] Methods getSipPort and setSipPort now have an effect on the + * [#1753] make pjsip compile without ilbc. Use ./autogen.sh --disable- + ilbc-codec + * [#1855] Fix error in russian translation + * [#1805] Remove the old flawed signal mechanism which was failing in + * [#1855] Refresh translation + * Spanish translation finished + po README files updated + echo's in + copy-in-clients + * [#1850] Yun made the chinese HK-CN translation + * [#1848] Fix transfer interface bug + * [#1862] At install, kde client installs only french translation file + * [#1841] A new fallback mechanism was added to the internal resolver + in PJSIP. + * Started AccountList model/view + * [#1855] Remove po subdir in Makefile.am + * [#1855] Fix typo error in sflphone-client-gnome + * [#1855] Do not generate Makefile in sflphone-common/po + * [#1855] Copy translation files into both clients dirs + * [#1855] Remove po dir from sflphone-common + * Comments added + * [#1860] mailbox->voicemail... + * make scripts executable + * [#1855] French translation + * [#1855] Chinese zh_HK partially filled... + * [#1859] An unnamed pipe monitored by poll() was added. When we want + to + * [#1855] Sven completed the first part of the german translation + * [#1855] Cantonese manually filled for already translated, almost + equal strings + * [#1855] Merge russian translation + * [#1855] Spanish manually filled for already translated, almost equal + strings + * [#1855] Update german translation in ./lang/de + * [#1858] This problem was fixed by removing a useless line in + * [#1855] merged existing translations in lang/ sflphone.po's + * [#1842] [#1843] An attempt at improving the expected behaviour that + can't + * [#1855] added po folder in gnome client and scripts for copying from + common lang folder to clients + * [#1853] Edit before call does nothing on call history + * Put most language entries possible in common. From 300 to 250 + entries. Stays underscores problem. Scripts for copy in clients. + * commit to merge master + * [#1825] Changed "Bad authentification" to "Authentication Failed". + * common po files + * [#1753] Remove ILBC from pjproject + + -- SFLphone Automatic Build System <team@sflphone.org> Fri, 17 Jul 2009 19:12:58 -0400 + +sflphone-plugins (0.9.6~rc1-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6~rc1 ** + + * Update some version number + * [#1792] Creates .sflphone directory with permission 600. Also, + "chmod 600" after + * [#1810] GUI is now notified that the call failed. Also, a segfault + was + * [#1816] Address book search disabled when disabled address book and + enabled it back plus button stays triggered + * codeclistmodel + asynchronous loading of address book + + enable/disable address book + * [#1810] Now checking SDP answer after 200 OK. Still need to + implement full + * [#1794] Can't use the interface during a call + * Updated translation files + * Russian translation integrated + * Codec list model/view started. + * [#1807] Add configure.ac in pjproject-1.0.3 + * [#1787] closeRtpSession added in some places where it should have + been + * Use Item class for contacts and accounts + * Comments + clean code + * [#1794] Improved debug messages + * [#1805] Replaced the old and unreliable mecanism that was was + waiting for + * [#1794] Can't use the interface during a call + * [#1787] For those cases where no registered SIP account is + configured + * [#1797] Make pjsip compile + * [#1787] Minor changes. Removed useless commented line. Changed order + of + * [#1777] Code indentation + * [#1797] Update package generation with new pjsip version + * [#1798] Does not hang up when the call is building up + * [#1797] Update .gitignore with new pjsip version + * [#1797] Remove generated files from repo + * [#1797] Main build system now uses pjproject-1.0.3 + * [#1797] Add pjproject-1.0.3 + * [#1797] Remove pjproject-1.0.2 + * [#1796] Computing time optimization (samplerate conversion) + * [#1787] _audiortp->start() moved away from offhold(), + SIPCallAnswered() + * [#1312] Added new states for calls initialized by other clients + * [#1795] Crashes when adding a new account, checking it and applying + * [#1782] Missing icons + * [#1793] KDE client compilation problem + * Fake ringtone files can no longer be set. + * indentation + * [#1312] Able to fetch to differentiate incoming/ringing call state + * [#1784] Use DESTDIR variable in po Makefile - fix language file + installation + * [#1785] Fixed typo + * [#1785] Fixed changelog update + * [#1759] ./autogen.sh --prefix=/usr --with-debug to use optimization + level 0 + * [#1773] Changed snapshot naming convention + * [#1773] Removed gpg agent use, added repository cache cleaning + * [#1759] Use optimization level 0 for repository, 2 for packages + * [#1777] Code indentation/formatting + * Translated new features in french + * [#1785] Added missing changelog entry + * [#1781] Window title is SFLPhone + * [#1777] Add code indentation/formatting in the buil system + * [#1774] Can't set voicemail number in KDE account creation wizard + * [#1775] Can't modify account information for account created with + the wizard + * [#1771] Add a "Default" button in context menu to disable chosen + prior account + * [#1705] + * [#1224] Remove generated file from the repo + * [#1224] Remove generated file from the repo + * [#1762] distclean target should remove kconfig generated files + (settings.h, settings.cpp). Rename them? + * [#1761] clear history button should really clear history + * Dialpad works. + * Implemented Dialpad widget instead of building it in main view. + * Removed last occurence of the old config dialog, that made the build + crash. + * [#1755] Do not consider G722 as a dynamic payload elsewhere than in + RTP layer + * [#1753] Remove ilbc Makefile generation + * [#1756] Implement a kde configuration dialog with kconfig xt and + kconfigdialog class + * [#1755] fix audiocodec folder parsing problem + * [#1450] Reinit timestamp comparison in RTP, create session in + newOutgoingCall + * [#1753] Remove milenage third party code from pjsip + * New Config Dialog integrated in GUI.(without codecs) + * [#1753] Remove ILBC codec + * kconfig started, tr2i18n -> i18n, icons folder, accountList changed + * [#1705] Fixed Audio RTP thread creation/start + * [#1714] Fix codec negociation result handling + * [#1678] Fix audiortp payload setting + * [#1678] Put bac putData method in rtp + * [#1669] gtk_file_chooser_get_filename() support UTF-8 by default + * [#1735] Add conditions to sdp update call if call declined + * [#1737] substr of recordings destination folder to remove "file://" + should be done in client rather than in daemon + * [#1731] Enlarge audio stream buffer size + * [#1714] Missing true + * [#1317] Fixed Mandriva timeout + * [#1317] Changed tag convention + * [#1317] Cleaned git-dch + + -- SFLphone Automatic Build System <team@sflphone.org> Fri, 10 Jul 2009 15:50:26 -0400 + +sflphone-plugins (0.9.6~beta-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6~beta ** + + * spec files for mandriva and opensuse updated with buildrequires + libqt4-dev >=4.3 + * [#1700] Cannot build on ubuntu 8.10 and a few other distribs + * [#1502] Update version number where applicable + * [#1642] Update client icons + * [#1450] Clean up useless debug and comments in sipvoiplink and + audiortp + * [#1450] Remove Semaphore object in AudioRtp thread deletion + * [#1450] Audio RTP init now synchronized with Sip/SDP + * [#1693] kde client crashes when changing codecs order/activation + * [#1450] Deep refactoring of audiortp + * [#1450] setRtpSessionRemoteIp + * [#1689] getCallList at start + * [#1224] Change path in package files + * [#1450] Audio RTP initialized only once, payload and remote ip set + at runtime + * [#1450] Add setRtpSessionMedia and setRtpSessionRemoteIp address + * [#1642] Make GNOME GUI fresher and younger ;) + * [#1686] Status bar displaying used account + * added sflphone-kde icon so that it compiles + * [#1659] Ending a call causes the daemon to crash + * corrected introspection XMLs, po files... + * [#1211] g722 media descriptor in codecDescriptor + * [#1310] Install sflphoned in $(prefix)/lib/sflphone + * [#1502] Do not install test binaries and dbus utilitaries + * [#1224] hack for pjsip build system! + * [#1224] Remove pjsip binaries from repo + * [#1224] Upgrade to pjsip 1.0.2 + * [#1658] About SFLphone (bugs) + * [#1658] About SFLphone + * [#1660] Displaying all dialed numbers in a call + * Tested status bar. + * [#790] Optimize pulse audio streams parameters + * [#1678] Some usefull debug messages for mutex/semaphore deadlock + problem + * [#1669] Add/remove some usefull/unusefull debug + * [#1665] Fix latency related to pulse audio stream openning/closing + * [#1457] Make the menus and panels accessible in french + * [#1457] Improve broken keyboard accessibility in menus and conf + panels + * [#961] Instanciate only once the searchbar icons + * [#961] Restore transfer fonction + * [#961] Filter on the history type OK + * [#961] Fix compilation problems on hardy/intrepid + * [#1157] Commit missing files + * [#790] Reduce number of start/stop streams call on pulse audio + * [#1639] kde client crashes when no account registered + * [#1620] Fix the searchbar + * [#1620] Get back caltree as it was during gtkcritical area + * [#1620] Add history filter reinit function + * [#1335] Add a missing label in address book preferences + * [#1561] Update russian translation - Hussein Abdallah + * [#1605] Fix edit menu french translation + * [#961] Enable to search in the history according to the call type + * [#1449] Searchbar does not work anymore + * [#961] Add popup menu on the entry primary icon for history + * [#1317] Fixed KDE client package dependency + * [#936] speex 32 khz integration completed + * [#936] Use 320 frame size + * [#936] Test using a frame size at 320 smpls + * [#1214] Enable / Disable history + * [#1607] Fix compilation problem for ubuntu 8.10 (libsexy) + * [#1313] Implement processDataEncode processDataDecode in audiortp + * [#1613] codec list order can't be set + * Better handling of localisation + added languages + corrected + warnings + begginning of new config dialog with kconfig + 14px + account leds + * [#1214] Save and load history according to the limit timestamp + + unit tests + * [1609] Fix call number copy/paste feature + * [1607] Restore clear action icon in searchbar + * [#936] Try to decode using 1280 samples + * [#936] Add some debug + * [#936] Add .cpp file + * [#936] Oops Forgot speex 32 khz + * [#1214] Add configuration panel for history + D-Bus calls + * [#1313] Test rtp thread function, frame size, nbbytes, resampling + * [#790] Flush audio data before closing audio streams + * [#1214] History displays local time + * [#1214] Skip empty field on display + * [#1214] Associate an account to an history entry + * [#1342] Get addressbook options sensitive/non-sensitive + * [#1211] Clean up and comments + * [#1211] Get back to 20 ms framesize + * [#1211] Use sendImmediate instead of putData in RTP + * [#1211] Fix nb byte available in RTP + * [#1211] Clear condition on maxNbSamples in RTP + * [#1211] Fix max byte available in RTP session + * [#1211] G722: Use 160 samples per frame instead of 320 + * [#1211] Test using a dynamic payload + * [#1211] Test using a dynamic payload type + * [#1211] Rename size variable (nb_samples, nb_bytes) + * [#1211] Test g722 ip-to-ip sending twice the data lenth + * [#1211] Test g722 ip-to-ip + * [#1214] Do not select an history item by default at startup + * [#1214] Remove some compilation warnings + * [#1214] Handle empty field - remove g_print + * [#1214] Add each history item only once + * [#1214] Handle call timestamps properlier + * [#1214] Do not need timestamp files anymore + * [#1214] Use the saved date for history entry + * Clean up + * [#1214] Client doesn't crash if the D-Bus call fails + * [#1214] Client is able to save its history - still some glitches + * [#1211] Forgot 16000 for g722 + * [#1211] G722 initialization + * [#1214] Save name/number, successfully load the history if no fields + are empty + * [#1499] Fixed destination directory bug + * [#1214] Restore all the functionalities; peer name/number way more + easy to handle !! + * [#1214] Add callable_object instead of call_t, refactoring + * [#1211] Test with polycom soundstation 16000 + * [#1211] Remove C like inline function in g722 codec + * [#1342] Finalize gnome client preference window formating + * [#1214] Retrieve the history when the gnome client startsup + * [#1306] Implement localization for KDE client + * [#1593] enable accounts apply button when account checked/unchecked + * [#1214] Implement the dbus calls on server side + * [#1214] Add serialized/unserialized functions to pass data on DBUS + * [#1342] Formating gnome client configuration windows + * [#1214] Save sucessfully a map of history items + * [#1499] Removed multiple jobs compilation for KDE client (2) + * [#1214] Load history from file into memory, add unit tests + * [#1534] Throws a length_error exception in case URL exceeds + std::string max_size + * [#1499] Removed multiple jobs compilation for KDE client + * [#1565] make account leds smaller + * [1430] Fix dbus debug + * [#1562] crashes when trying to change item of a call of state "OVER" + * [#1116] Fix compilation bug + * [#1317] Added mandriva and opensuse-11 64 bits + * [#1108] Add messges in main window concerning transfer success + failure + * [#1116] Fix compilation problems + * [#1211] g722 Makefile + * [#1108] Client side transferFailed/trasferSucceded signals handling + * [#1211] G722 mostly completed, + * [#1555] make bigger toolbar (24x24) + * [#1551] remove default mailbox number in wizard and disable mailbox + button when first account doesn't have mailbox number + * [#1342] Re-add sflphone manpages + * [#1116] Fix compilation on non-jaunty distros + * [#1317] Fixed opensuse startup sleep + * [#1108] Add a signal in the client to notify successful or failed + transfer + * [#1108] Dbus signals concerning call transfer success/failure + * [#1317] Added opensuse to automatic build system + * [#1223] Fix manpages bug + * [#1060] german translation glitch + * Clean up some gnome client warnings + * [#1547] replace ugly account leds by beautiful icons + * [#1548] add close button that hides windowand just hide on clicking + the cross + * [#1549] put introspec XMLs in the client's source + * [#1312] Implement getCallList D-BUS method + * [#1116] Clear text in history and contacts + * [#1499] KDE integration + * [#1469] Modify header linkers in dbus-c++'s Makefile.am's + * [#1469] Remove examples folder from dbus-c++ + * [#1214] History integration in build system; unit test squeleton + * [#1317] Cleaning + * [#1469] Remove configure stuff in dbus-c++ + * [#1469] Add unofficial mainline dbus-c++ + * [#1469] Remove dbus-c++ from freedesktop + * [#1430] Bring account changed signal/callback back to normal + * [#1060] Update german translation - Sven Werlen + * [#1430] Add marshaller one string define + * [#1430] Send account change signal broadcast using account id + * [#1430] Remove condition on setRegistrationState, cause stun to + crash + * [#1317] Centralized version handling + * [#1317] Fixed version number on sfl-git-dch + * [#1317] Refactoring for new distributions + * [#1215] Fix account order at startup if latency + * [#1088] Restore sip dns srv + * [#1214] Add squeleton for history manager + * [#1430] Add accout id to accout changed method + * [#1430] No connectionStatusNotification (account changed) if no + changes + * [#1538] Add COPYING file + * [#1430] Add audio rtp thread tests + * [#1317] Changed version detection + * [#1538] Document license in libs/stund + * [#1317] Added version files + * [#1538] Apply François patches - debian packages + * [#1317] Updated spec files + * add files + * [#1538] Apply François patches - debian packages + * [#1535] Change program file structure (directory src...) + * [#1317] Updated build system scripts + * [#1317] Cleaning + * [#1317] Copied introspect files to gnome client + * [#1317] Added opensuse to build-system : first-shot + * [#1317] Remove spec files from configure + * [#1317] Added missing prefix + * removed debug for daemon account fix + * [#1430] Add a connection reference which most likely belong to + libdbus + * [#1430] Use shared connection instead of private + * make daemon find the account, added userMatch + * Clean code, add comments... + * [#1317] Fixed packaging rules + * [#1317] Updated autogen + * Updated autogen.sh for pjsip + * [#1526] Set accounts order + * [#1317] Fixed pjsip lib dirs + * [#1317] Updated debian packaging for new pjsip configuration script + * [#1317] Switch to autogenerated guess and sub files + * [#1317] Updated pjsip inclusion in build system + * [#1317] Replaced pjsip guess and sub files + * [#1317] Fixed compilation issues on opensuse 11 + * [#1505] account list seem to crash the application when clicking + Apply very fast... + * [#1456] Add a flag to be replaced in the control files + * [#1456] Added version dependancy handling + * put account alias in AccountWidgetItem rather than in the item with + " " before. + * [#1034] The KDE client should start sflphoned if it is not started + * [#1500] Handle options for notifications and display on incoming + call. + * [#1443] Client should not crash when receive an unexpected + stateChanged signal + * [#1403] Do not stop the notification anymore + * [#1456] Added version dependancy handling + * [#1426] Daemon crashes when get alsa plugin + * [#1422] Improved error messages + * commit for merge + * [#1424] Change logo in tray icon and put a different one when + incoming call + * [#1425] first part done, window title... + * [#1413] add manpages creating and installing in build system + * [#1417] The client should start the account creation wizard if + started for the first time (if config file doesn't exist) + * [#1421] Make volume bars horizontal when dialpad is hidden. + * Changed main window title and fixed a mistake in sflphone_const.h + * [#1412] make debian package building work + * changelog changed. + * Changed addAccount method in gnome client. + * Debian and man folders added. + * [#1388] Change project name from sflphone_kde to sflphone-client-kde + * Better handle of kabc check. + * [#1351] Automatic generation of dbus interfaces in makefile + generated by cmake + * [#1307] Implement "edit before call" in history and address book. + * [#1344] change action_call label in call history from "call" to + "call back". + * [#1308] Implement Hook feature in kde client + * Improved build system. + * #1219 : Add address book configuration page + * Better handling of registration to the daemon. + * #1039 : Add tray icon in kde. + * Issue no 1216 : Double click on item in history or address book + causes call. + * display peer name in call list and call history when called from + address book. + * Address book functionnal with photo displayed. + * Help menu kde available but actions disappeared. All fonctions in + view. + * Address book functionnal but ugly and making its own sort in the + complete address book. + * Account choice on right click, clean out includes, page address + book, fixed bugs... + * Wizard, double click, context menu... + * Removed sflphone_kde.kdevelop.filelist + * Added account creation wizard and translated interface in english. + * Transfer functionnal but ugly. + * transfer not functionnal + * Bug fixed : unholding (UNHOLD_CURRENT, UNHOLD_RECORD) + * Commit functional for push. With install.sh + * Before merge. + * Problem with enable accounts. Account display increased. + * Functional with codec order working , playDTMF. + * Commit functional. + * sflphone_kde/build added in .gitignore. + * complete commit for checkout previous. + * Commit before checkout previous version to check the display + bug(little font everywhere...) + * Functionnal client. Rest : history icons, config icons and + functionalities + * commit before merge asavard for isRecording. + * Call and Automate fusion done and seems to work. + * Commiting before putting Automate class in Call class. + * Functionnal main window without recording, history, voicemail, kio + widgets. + * client kde avec kdevelop. + * Config Dialog almost finished. + * Base of QT client + + -- SFLphone Automatic Build System <team@sflphone.org> Tue, 23 Jun 2009 11:13:42 -0400 + +sflphone-plugins (0.9.5-SYSTEM) SYSTEM; urgency=low + + ** 0.9.5 release ** + + * [#1060] FIx bug in chinese translation + * [#1313] git add rtpTest.cpp rtpTest.h + * [#1313] Add init/close rtp tests + * [#1313] Basic instanciation of the rtp layer + * [#1449] Gtk-Critical concerning history filters and new calls + * [#1400] Make the match with the hostname instead of username + * [#1324] Change status bar label for "Using %s (%s)" + * [#1403] Icon size: 60x60 px + * [#1403] Do not remove notification, improve icon quality + * [#1403] Add smaller icon for gnome notifications + * [#1403] Prevent crash when hangup && no notification + * [#1403] Remove all actions on notifications; code refactoring + * [#1451] Use stun.sflphone.org as default STUN server + * [#1060] New po files - need to be translated + * [#1060] Update french translation - Rebuild template file + * [#1456] Add a flag to be replaced in the control files + * [#1454] Make cppunit optional; remove from build deps in control + files + * [#1401] Add libexpat1-dev dependency in control files + * [#1448] Take off these ugly debug messages + * [#1448] fixed getTelephoneTone and getTelephoneFile() called + repeatedly + * [#1406] add liblog4c-dev in build-depends + * [#1409] Restore .desktop icon + + -- SFLphone Automatic Build System <team@sflphone.org> Mon, 25 May 2009 11:34:48 -0400 + +sflphone-plugins (0.9.5-SYSTEM~rc2) SYSTEM; urgency=low + + ** 0.9.5 rc2 ** + + * [#1422] Improved error message + * [#1402] Fix pjsip build + * [#1404] Clear GTK-Critical Bug at client startup + * [#1422] Added automatic VM shutdown when building on more than one + VM + * [#1422] Fixed some issues with new changelog generation script + * [#1422] Moved distribution update to specific file + * [#1422] Dropped git-dch, replace by home made implementation + * [#1402] Fix pjsip build + * [#1404] Clear GTK-Critical Bug at client startup + * Changes for name based dbus connection + * Clean changelogs + * [#1343] Gnome: Implement a callback system to handle focus on + different widgets + * Debus Session + * Refactoring Python code, PEP8 + * [#1430] Get back dbus_g_proxy_new_for_name + * [#1430] Get back DBUS_BUS_SESSION type + * [#1430] Dbus fixed owner message binding + * Second test with DBUS owner + * [#1404] Gnome -> Preferences -> Hooks + * [#1404] Gnome -> Preferences -> Recordings + * [#1404] Call History + * [#1404] Gnome -> Preferences -> Address Book + * [#1404] IF the first notification option disable the second + notification + * Dbus with fixed owner does not automatically start the deamon + * Add codec debug tests in pysflphone + * [#1407] Some print info + * [#1407] Add a scenario to pick_up action + * Test client dbus connection to a fixed owner + * Add python dbus test suite + * [#1161] Modified version handling in build system + * [#1314] Test pulse audio and audio streams connect and disconnect + * [#1402] Add info message after configure + * [#1402] Build the daemon with the local pjsip library (vs the + installed one) + * [#1009] Fix Codec Sampling Rate set to zeros + * [#1314] Add mutex to pulse layer audio streams + * [#1314] Refactoring pulseaudio stream to test connect disconnect + * [#1314] Refactoring of pulselayer to test conect/disconnect + * Add debug messages in debus calls concerning account + * [#1314] Add some return values to audio init functions + * [#1406] add liblog4c-dev in build-depends + * [#1409] Restore .desktop icon + * Bug #1405: Fix strings as requested. + * Bug #1404: Fix strings in preferences panel. + + -- SFLphone Automatic Build System <team@sflphone.org> Tue, 19 May 2009 12:08:18 -0400 + +sflphone-plugins (0.9.5-0ubuntu1~rc1) SYSTEM; urgency=low + + [ SFLphone Project ] + * [#1262] Updated changelogs for version 0.9.5-0ubuntu1 Snapshot 2009- + 05-05 + + [ Emmanuel Milou ] + * Add some python CLI client code; not really functional + * [#1108] Fix peerHungup method for IP to IP call + + [ Alexandre Savard ] + * [#1108] Correct setting of SIP contact for direct IP call + * [#1108] SIP user agent handles incoming REFER + + [ Emmanuel Milou ] + * Remove website from repository + * Update translation + + [ Alexandre Savard ] + * Sflphone icon's tooltip changed for "configured" instead of + "registered" + + [ Emmanuel Milou ] + * Update translation + + [ Sflphone Project ] + + -- Sflphone Project <sflphone@mtl.savoirfairelinux.net> Tue, 05 May 2009 19:16:13 -0400 + +sflphone-plugins (0.9.5-0ubuntu1~beta) SYSTEM; urgency=low + + [ Julien Bonjean ] + * Updated Eclipse stuff + * Improved addressbook config window + * Added sflphone Eclipse stuff + * Implemented addressbook list server side + * Moved dbus stuff in dbus directory + * Updated addressbook configuration + + [ Emmanuel Milou ] + * Remove unuseful installation scripts. Use apt-get build-dep sflphone + instead + * fix bug #1090 + + [ Alexandre Savard ] + * defining speex 16khz + + [ Emmanuel Milou ] + * Remove unuseful file from build system + * Start dns srv resolver + + [ Alexandre Savard ] + * Basic ogg/vorbis initialization + + [ Emmanuel Milou ] + * Handle incoming IP-to-IP invite correctly + + [ Alexandre Savard ] + * speex wideband 16000 + + [ Emmanuel Milou ] + * Better handling of incoming IP to IP call + * DNS SRV resolution functional + * Implement IAX2 incoming URL + * Allow user to make IP call without any accounts configured + * Add a contextual menu to edit a number from the contacts tab + * Add comments, tooltip and new button to the contextual menu + * add delete event, migrate to GTK 2.16 for sexy icons + * Resolve ticket #1118 + * Update suse spec file + * Add phone number cleanup functions, unit tests and panel + configuration + * Add pertinent test that fails + * fix dependencies for suse package + * Add contextual edit menu in history - #1120 + + [ Alexandre Savard ] + * Temporary comit: make speex wideband (16 khz) + * Temporary: shared object for speex narrow band + * Temporary: speex narrowband and wideband coexist + + [ Julien Bonjean ] + * Fixed bug when no book selected + * Fixed addressbook related compilation warnings + * Fixed GTK client remaining compilation warnings + * Fixed segfault when book removed since last sflphone run + * Fixed bug when book is unreachable (ldap error) + + [ Alexandre Savard ] + * Fix codec list in audio config window + * Active/inactive speex codec by payload + + [ Julien Bonjean ] + * Updated gitignore + * Added some comments + + [ Emmanuel Milou ] + * Add callto: handler script for browsers and al. + * Integrate test compilation in the daemon build-system + + [ Julien Bonjean ] + * Fixed g_object_unref warning for pixbuf + * Cleaned too verbose output + * Fixed toolbar update warning + * Added support for asynchornous books open (first shot) + + [ Emmanuel Milou ] + * Add a DBus call to fetch the call details from a call ID - Ticket + #928 + + [ Julien Bonjean ] + * Improved async open books + * Fixed bug #1139 + + [ Emmanuel Milou ] + * Add a way to save account order + * commit missing files + + [ Julien Bonjean ] + * Introduced log4c (ticket #1162) + + [ Emmanuel Milou ] + * Load/save account order functionnal - ticket #813 + + [ Alexandre Savard ] + * Add CELT codec (#1143) + * Make celt frame size 256 (*1143) + + [ Julien Bonjean ] + * Switched everything to log4c (ticket #1162) + * Updated eclipse settings + + [ Emmanuel Milou ] + * Restore adding account - ticket #1172 + * Add liblog4c dependecy - ticket #1179 + + [ Alexandre Savard ] + * Double maxAvailByte for frame size in rtp (#1143) + + [ Emmanuel Milou ] + * Add User-Agent SIP header - Ticket #1173 + + [ Julien Bonjean ] + * Fixed autoresize issue (#708) + + [ Emmanuel Milou ] + * Remove libcppuint dependency for the debian packages + * Look for libsexy only if gtk version < 2.16 - Ticket #1116 + * Remove libsexy dependency for jaunty. ticket #1116 + + [ Julien Bonjean ] + * Introduced unit tests (#1146) + * Updated gitignore + * Fixed Makefile (#1146) + + [ Emmanuel Milou ] + * [TICKET #1112] Add a test on the voice buffer to send through iax + packets + * Remove doublon in dependencies + * Remove warnings from the client test framework + * Update version number to 0.9.5~beta + * Update build-package script + * Add check dependency in build-deps control file field + * Create debian files for the new sflphone-client-gnome + * [TICKET #1212] Add Replaces field in control files + * [TICKET #1212] Fix manpages installation path + * [TICKET #1212] Add maintainer scripts to create alternatives + * [#1212] Update the manpages generation - edit preinst maintainer + script + * [#1212] Fix reference error in manpage + * [#1212] Add missing files on the client side + * [#1212] Fix debian docs files - no TODO file + * [1212] Fix manpage creation problem + * [#1220] Generate client-side glue files and marshaller at + compilation time + * [#1220] Generate server-side glue files at compilation time + * [#1212] Change binary name to sflphone-client-gnome + * [#1212] Update .gitignore to fit the new working tree + * [#1220] Explicitly generate glue files before building the library + * [#1220] Compile dbus directory before audio + * [#1212] Create sflphone-common at the root of the repository + * [#1212] Re-add pjproject + * [#1212] Remove Makefile from repo + * [#1220] Fix Makefile.am + * [#1212] New working directory functional + * [#1212] Update .gitignore + * [#1212] Hack to make pjsip compile.. + * [#1220] Use non-installed binary for dbusxx-xml2cpp + * [#1212] Add descriptive files, remove unuseful scripts from tools/ + + [ Alexandre Savard ] + * Restore speex codecs + * add frame size for celt (#1143) + * add framesize to codec, independant from audiolayer (#1143) + * use codec frame size in rtp (#1143) + * compute fixed_codec_framesize (#1143) + * do not resample if not required (#1143) + * add condition on resampling for decoder (#1143) + * add a condition on bytesAvail == 0 from mic data + * no maximum in rtp decode (#1143) + * compute maximum for decoding (#1143) + + [ Emmanuel Milou ] + * [#1146] Implement unitary tests on the client-side + + [ Alexandre Savard ] + * use float instead of int to compute max nb of sample (#1143) + * add nbSampleMax for unresampled data (#1143) + * make thread sleep during 5 ms insead of 20 (#1143) + * use unix usleep (#1143) + * 50 usecond thread!!!!! (#1143) + * try with the smallest compression (#1143) + * use timer set at framesize (#1143) + + [ Emmanuel Milou ] + * [#1161] Restore changelog version + + [ Alexandre Savard ] + * Remove celt stuff + + [ Emmanuel Milou ] + * [#1161] Update changelog + * [#1220] Add Conflicts: sflphone in debian control files + * [#1179] Add liblog4c3 runtime dependency + * [#1212] FIx typo error in dependency list for itnrepid + * [#1212] FIx .desktop file to point on the right exec + * [#1212] Modify changelog replacing tag + + [ Sflphone Project ] + * "[#1262] Updated changelogs for version 0.9.5-0ubuntu1~beta" + + [ Emmanuel Milou ] + * [#1212] restore changelogs + + [ Sflphone Project ] + * [#1262] Updated changelogs for version 0.9.5-0ubuntu1 Snapshot 2009- + 04-27 + + [ Emmanuel Milou ] + * [#1212] restore changelogs + + [ Sflphone Project ] + * [#1262] Updated changelogs for version 0.9.5-0ubuntu1~beta + + [ Emmanuel Milou ] + * [#1212] restore changelogs + + [ Sflphone Project ] + + -- Sflphone Project <sflphone@mtl.savoirfairelinux.net> Mon, 27 Apr 2009 17:00:03 -0400 + +sflphone-plugins (0.9.4-0ubuntu2) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Restore speex and GSM detection + + [ Emmanuel Milou ] + * Fix bug #1090 + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 8 Apr 2009 11:29:15 -0500 + +sflphone (0.9.4-0ubuntu1) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Integrate DBus-c++ and libiax2 in the main build system + * Clean up in the working repository + * Reorder hooks configuration panel + * Protect case when no codecs are active + * Fix some return values + * Add unitary tests for the hook manager (premisces) + + [Yun Liu] + * Update chinese translation + + [Sven Werlen] + * Update german translation + + [Hussein Abdallah] + * Update russian translation + + [Maxime Chambreuil] + * Update spanish translation + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 3 Apr 2009 18:29:15 -0500 + + +sflphone (0.9.4-rc1) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Fix bug while trying to hold/unhold several simultaneous call + * Improve address book build system + * Implement SIP url popup on incoming call + * Improve GTK+ panel configuration + [ Julien Bonjean ] + * GTK+ client refactoring + * GTK+ clean up + * Address book improvment + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 27 Mar 2009 18:29:15 -0500 + +sflphone (0.9.4-0beta1) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Display codec used during conversation on the GUI + * Enable/disable STUN parameters at runtime + * Refactor search bar use + [ Emmanuel Milou ] + * Build system fixes + * Implement SIP re-invite + * Implement IP to IP call + [ Julien Bonjean ] + * Integrate GNOME address book based on evolution data server + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 20 Mar 2009 18:29:15 -0500 + + +sflphone (0.9.3-0ubuntu3) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Both playback and record streams in PA_STREAM_CORKED (pulseaudio) + * Use PLUGHW device for ALSA capture + * Functional IAX and SIP recording for voicemail + * Use the less CPU-consuming interpolator algorithm for resampling + * Display in GTK GUI the codec used in conversation + * GTK GUI use ASCII instread of utf-8 + * Add record menus in GTK GUI + * Put on hold when dialing a new number + * AccountID's are saved in the history + + [ Emmanuel Milou ] + * Integrate DBUS C++, libiax2 in the git repository + * Update website + * Use libspeexdsp only if available on the system + * Updated .gitignore file + + [Cyrille Béraud] + * Account assistant manager improvment + * Add an email request when creating a new account to receive voicemails + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Sat, 14 Feb 2009 13:29:15 -0500 + +sflphone (0.9.3-0ubuntu2) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Add compilation note in README + * Use default ALSA plugin for capture + * Fix the ALSA capture problem one more time + * Clean up debug messages in dbus.c + * Add libspeexdsp dependency + * Remove implicit declaration compilation warnings + * Fix links in the website, add release note + * Change capture for the website front page + * Add alsa devel dependency in build-depends control file field + * Clean up, indentation, try to handle latency problems in iax/pulseaudio + * Remove pjsip generated files from the repo + * Use the previous declared curAlias function in accountwindow + * Fix bug in history call duration when the call fails + * Remove runtime warning in the GTK+ client + * Add librsvg2-common dependency to load SVG under KDE + * Refresh .gitignore + * Update locales files + french translation + * Add configuration panel for future noise reduction + * Add configuration panel for audio record module + * Daemon less verbose; accounts don't try to access STUn options anymore + * Fix typo in configwindow + * Add content in the official website + * use a GTK_STOCK icon for the record button + * Complete description text in the assistant manager + * Add libtool flags in client configure.ac + * Remove unuseful dependency (snd) + * Fix SIP transfer problems + * Remove previous version of PJSIP from the repo + * Upgrade PJSIP to version 1.0.1 + * Add the new website source in the repository + * Use libspeexdsp for silence detection only if available + + [ Loïc Faure-Lacroix ] + * Ajout du logo gpl3 + * Ajout des images + * Ajout de la section screenshot pour le site + * Ajout du favicon dans le header + * Modification des cartes + + [ Alexandre Savard ] + * Clean up <speex/libspeexdsp> + * Small cleanup + * Save Wave fixed + * Fix new call button when recording + * libspeexdsp added + * Recording: default home folder at startup + * Minor changes to config window + * IAX recording fixed + * Set / get recording path, still need some GTK for client + * AudioRecord file name format + * Now recording in HOME folder + + [ Cyrille Béraud ] + * Fix bug in reqaccount.c + + [ Maxime Chambreuil ] + * Update spanish translation + + [Yun Liu ] + * Update chinese translation + + [ Hussein Abdallah ] + * Update russian translation + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Sat, 14 Feb 2009 13:29:15 -0500 + +sflphone (0.9.3-0ubuntu1) SYSTEM; urgency=low + + * Remove debug + * Join thread before leaving + * Fix implicit declaration in reqaccount + * Add REST code to build the request to server + * Fix GValue initialization warnings + * Update version number, fix implicit declaration, fix GTK markup + warnings + * Apply patch to create custom SIP account from our own server + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 06 Feb 2009 19:17:32 -0500 + +sflphone (0.9.2-2ubuntu9) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Speex audio codec preprocessing initialization + * peer hung up segmentation fault solved + * Stop recording when transfering + * Terminate only one call + * Add isRecording() function + * Fix call_icon GTK client + * Fix SIPCallClose() function, recorded file now close properly + * Function terminateSIPCall added in sipvoiplink and managerimpl + * Fix thread destructor + * setRecordingOption function implement in audiorecord + * Record now implemented in Call class + * Record interface complete (on hold erase previous recording) + * Added recButton in client + * Added: record button related icons + * Record button added + * Overload AudioRecord::recData to get mic and speaker data mixed + * Recording now in audiortp::run() method + * Audio recording working in AudioRTP: receiveSessionForSpeaker + * Open/close a wave file when pulse audio stream start/stop + + [ Emmanuel Milou ] + * Fix path for GTK+ icons; clean up + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Thu, 05 Feb 2009 18:27:53 -0500 + +sflphone (0.9.2-2ubuntu8) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Update changelogs + * Fix bug in merge and in Makefile.am + * Terminate only one call + * Disable PJsip shutdown when changing STUN parameters + * Function terminateSIPCall added in sipvoiplink and managerimpl + * Add a timer to the alsa thread to not jam the CPU load + * Fix bug in sipvoiplink.cpp + * Clean shutdown of pulseaudio on quiting + * Fix DTMF at first start with Pulseaudio + * Remove zeroconf from the build system + * Add a library manager + exception handling + * Clean up in the working directory + * Better handling of capture XRUNs + * Restore mic adjust volume on ALSA layer + * Protect device ALSA operation if not opened + * Fix the switching layer bug + * Use dynamic_cast<> to use audiolayer-specific methods + * Open the audio devices only once at startup + * Refactoring of the ALSA part + * Functional plug-in manager + * Use a C++ thread to handle tones and DTMF in ALSA + * Restore IAXVoIPLink, restore Mutex + * Make the plugins registering against the plugin manager + * Migrate to 1->N relationship between voiplink and accounts + * API plugin for registration + * Use C++ thread in SIP, move everything in sipvoiplink + * Complete singleton pattern for the plugin manager + * Add -Wno-return-type compilation flag to remove warnings; Update + version number in configure.ac + * Add the dynamic loading for the plugin framework; integate unittest + + [ Yun Liu ] + * Update rpm spec file + * modify build package script and spec file for suse + + [ Alexandre Savard ] + * Add audiorecorder plugin and testaudiorecorder + * Add audio Recording class, edit global.h + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 04 Feb 2009 14:00:30 -0500 + +sflphone (0.9.2-2ubuntu7) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Update changelog to 0.9.2-6 + * Fix some dbus-glib implementation details on the client side + * Init history after dbus initialization + * Add error checking in useragent; Clean sipvoiplink + * Prevent crash when trying to call an empty number + * Set the volume of the playback stream to PA_VOLUME_NORM at startup + * Fix GTK+ generic value double initialization + * Fix jaunty control file dependency problems + * Fix jaunty control file dependency problems + + [ Yun Liu ] + * Fix bug ticket # 137 + * Tolerant to gsm library of OpenSuse 11 + + [ Sven Werlen ] + * Update german translation + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 23 Jan 2009 17:48:13 -0500 + +sflphone (0.9.2-2ubuntu6) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Migrate STUN configuration to the main config window + * Update french translation + * Other tiny memory leaks + * Fix memory leak in sampleconverter.cpp + * Generate packages from the release branch + * update the build package script + * modify the control files with architecture=any + * Remove valgring uninitialized value + * IAX and SIP use the same global variables to set account + configuration ; fix broken code + + [ Maxime Chambreuil ] + * Update spanish translation + + [ Hussein Abdallah ] + * Update russian translation + + [ Yun Liu ] + * Update translation files + * Fix the bug when user uncheck the account which fails in the + previous registration + * Add stun error status + * Fix bug ticket #143 + * Script for auto-install dependencies + * Fix bug ticket #140 + * Fix bug ticket 141 + * Fix the reregister process when user change the details of an + account + + -- Emmanuel Milou <manu@sulfur.inside.savoirfairelinux.net> Fri, 16 Jan 2009 18:19:05 -0500 + +sflphone (0.9.2-2ubuntu5) SYSTEM; urgency=low + + * Fix memory leak in the pulseaudio callback + * Update debian package generation script + * Warnings removal in GTK+ client + * Clean adjust volume method in alsalayer + * Plug the sflphone playback volume control to the pulseaudio volume + manager + * Display the date in history according to the current locale + * Generate the changelog according to the git commit messages + * Complete header in chinese translation file + * Use the right gpg key to sign the packages + * add debian jaunty jackalope support + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 14 Jan 2009 21:17:20 -0500 + +sflphone (0.9.2-2ubuntu4) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * add german translation + + [ Yun Liu ] + * Fix GUI crash in Ubuntu8.10 64bit system + + -- Yun Liu <yun.liu@savoirfairelinux.com> Thu, 08 Jan 2009 13:08:51 -0500 + +sflphone (0.9.2-2ubuntu3) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * The main thread synchronizes the ringtone thread + * disable custom ringtone for the ALSA layer + * Fix the Makefile.am in man directory, add a SEE ALSO section + + [ Yun Liu ] + * Fix daemon crash caused by the previous patch ( for bug ticket #129) + + -- Yun Liu <yun.liu@savoirfairelinux.com> Tue, 06 Jan 2009 16:18:38 -0500 + +sflphone (0.9.2-2ubuntu2) SYSTEM; urgency=low + + * Fix bug ticket #129 + + -- Yun Liu <yun.liu@savoirfairelinux.com> Wed, 5 Jan 2009 15:54:53 -0500 + +sflphone (0.9.2-2ubuntu1) SYSTEM; urgency=low + + * Migrate from eXosip library to pjsip + * Add multiple SIP accounts support + * Fix ringtones problems + * Add a pulseaudio support + * Improve audio quality with ALSA + * Add chinese translation + * Improve spanish translation + * Migrate to a maintained C++ DBus bindings + * Clean and improve the build system + * Add build-dependency on Perl because we need pod2man to generate manpages + + -- Yun Liu <yun.liu@savoirfairelinux.com> Wed, 26 Nov 2008 09:47:53 -0500 + +sflphone (0.9.1) unstable; urgency=low + * Add a search tool in the history + * Migrate some gtk_entry_new to sexy_icon_entry_new + * Bug fix (Ticket #78): The voicemail password isn't displayed anymore in + the history tab + * Add the SIP registration expire value in the user file. + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Thu, 22 May 2008 11:14:25 -0500 + +sflphone (0.9.0) unstable; urgency=low + * Add history features + * Call date + * Call duration + * Mouse events in the history tab + * Smooth switch from the history tab to the calls tab + * Remove most of GTK-Critical warnings + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 13 May 2008 16:58:25 -0500 + +sflphone (0.9-2008-06-06) unstable; urgency=low + * Audio bug correction: capture stopped after a few minutes of conversation + with USB Plantronics sound card + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Tue, 06 May 2008 16:58:25 -0500 + +sflphone (0.9-2008-05-06) unstable; urgency=low + * Bug correction: account creation with the assistant + * GTK+ warnings removal + * libnotify warnings removal + * Remove aliasing on the SFLphone logo + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Mon, 05 May 2008 16:58:25 -0500 + +sflphone (0.9) unstable; urgency=low + * Clean dependencies ( removal of libboost ) + * Several GTK improvement and updates + -account window + -configuration window + * Migrate from GtkCheckMenuItem to GtkImageMenuItem + * ALSA standard I/O transfers: MMAP instead of R/W + * Fix speex audio quality + * IAX2 protocol + -Fix hold/unhold situation + -Add on hold music + * SIP protocol + -Ringtone on incoming call + -Fix transfer situation + * Add desktop notification ( libnotify ) + * Improve the system tray icon behaviour + * Improve registration error handling + * Register/unregister from the account window takes effect without starting back SFLphone + * Compilation warnings removal + * Call history + * Add an account configuration wizard + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 30 Apr 2008 16:58:25 -0500 + +sflphone (0.8.2) unstable; urgency=low + * Internationalization of the GTK GUI + * English / French + * STUN support + * Slight modifications of the graphical interface ( tooltips, dialpad, ...) + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 21 Mar 2008 11:37:53 -0500 diff --git a/tools/build-system/launchpad/sflphone-plugins/debian/compat b/tools/build-system/launchpad/sflphone-plugins/debian/compat new file mode 100644 index 0000000000..7ed6ff82de --- /dev/null +++ b/tools/build-system/launchpad/sflphone-plugins/debian/compat @@ -0,0 +1 @@ +5 diff --git a/tools/build-system/launchpad/sflphone-plugins/debian/control b/tools/build-system/launchpad/sflphone-plugins/debian/control new file mode 100644 index 0000000000..8e52e26ae2 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-plugins/debian/control @@ -0,0 +1,20 @@ +Source: sflphone-plugins +Maintainer: SavoirFaireLinux Inc <julien.bonjean@savoirfairelinux.com> +Section: gnome +Priority: optional +Build-Depends: debhelper, libgcc1, autoconf, automake, libtool, libgconf2-dev, libgtk-3-dev, libebook1.2-dev, libedataserver1.2-dev +Standards-Version: 3.7.3 + +Package: sflphone-plugins +Priority: optional +Architecture: any +Depends: sflphone-gnome (=${source:Version}) | sflphone-gnome-video (=${source:Version}), ${shlibs:Depends}, ${misc:Depends} +Replaces: sflphone +Conflicts: sflphone +Homepage: http://www.sflphone.org +Description: Evolution addressbook plugin for SFLphone + Integrate evolution addressbook functionality to SFLphone. + SFLphone is meant to be a robust enterprise-class desktop phone. + SFLphone is released under the GNU General Public License. + SFLphone is being developed by the global community, and maintained by + Savoir-faire Linux, a Montreal, Quebec, Canada-based Linux consulting company. diff --git a/tools/build-system/launchpad/sflphone-plugins/debian/copyright b/tools/build-system/launchpad/sflphone-plugins/debian/copyright new file mode 100644 index 0000000000..14695bb9f7 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-plugins/debian/copyright @@ -0,0 +1,28 @@ +This package was debianized by Alexandre Savard <alexandre.savard@savoirfairelinux.com> on +Tue, Jun 2011 09:47:53 -0500. + +It was downloaded from the git repository of SFLphone: git://sflphone.org/git/sflphone.git + +Upstream Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> + +Copyright: + +Savoir-Faire Linux Inc. + +License: + +This software is copyright (c) 2004-2011 Savoir-Faire Linux inc. + +You are free to distribute this software under the terms of +the GNU General Public License version 3. +On Debian systems, the complete text of the GNU General Public +License can be found in the file `/usr/share/common-licenses/GPL'. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklyn St, Fifth Floor, Boston, MA 02110-1301, USA. diff --git a/tools/build-system/launchpad/sflphone-plugins/debian/cron.d b/tools/build-system/launchpad/sflphone-plugins/debian/cron.d new file mode 100644 index 0000000000..d11e611777 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-plugins/debian/cron.d @@ -0,0 +1,4 @@ +# +# Regular cron jobs for the sflphone package +# +0 4 * * * root sflphone_maintenance diff --git a/tools/build-system/launchpad/sflphone-plugins/debian/dirs b/tools/build-system/launchpad/sflphone-plugins/debian/dirs new file mode 100644 index 0000000000..e8b33c5399 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-plugins/debian/dirs @@ -0,0 +1,3 @@ +usr/lib +usr/lib/sflphone +usr/lib/sflphone/plugins diff --git a/tools/build-system/launchpad/sflphone-plugins/debian/docs b/tools/build-system/launchpad/sflphone-plugins/debian/docs new file mode 100644 index 0000000000..9830da213f --- /dev/null +++ b/tools/build-system/launchpad/sflphone-plugins/debian/docs @@ -0,0 +1,5 @@ +NEWS +README +ChangeLog +AUTHORS + diff --git a/tools/build-system/launchpad/sflphone-plugins/debian/manpages b/tools/build-system/launchpad/sflphone-plugins/debian/manpages new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tools/build-system/launchpad/sflphone-plugins/debian/rules b/tools/build-system/launchpad/sflphone-plugins/debian/rules new file mode 100755 index 0000000000..0395b7ec35 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-plugins/debian/rules @@ -0,0 +1,116 @@ +#!/usr/bin/make -f +# -*- makefile -*- +# Sample debian/rules that uses debhelper. +# This file was originally written by Joey Hess and Craig Small. +# As a special exception, when this file is copied by dh-make into a +# dh-make output file, you may use that output file without restriction. +# This special exception was added by Craig Small in version 0.37 of dh-make. + +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 +export DH_OPTIONS + +package=sflphone-plugins + +CXX = gcc-4.0 +CFLAGS = -Wall -g + +configure: configure-stamp +configure-stamp: + dh_testdir + # Add here commands to configure the package. + ./autogen.sh + ./configure --prefix=/usr + touch configure-stamp + + +#Architecture +build: build-arch build-indep + +build-arch: build-arch-stamp +build-arch-stamp: configure-stamp + + # Add here commands to compile the arch part of the package. + $(MAKE) + touch $@ + +build-indep: build-indep-stamp +build-indep-stamp: configure-stamp + + # Add here commands to compile the indep part of the package. + #$(MAKE) doc + touch $@ +clean: + dh_testdir + dh_testroot + rm -f build-arch-stamp build-indep-stamp configure-stamp + # Add here commands to clean up after the build process. + [ ! -f Makefile ] || $(MAKE) distclean + +ifneq "$(wildcard /usr/share/misc/config.sub)" "" + cp -f /usr/share/misc/config.sub config.sub +endif +ifneq "$(wildcard /usr/share/misc/config.guess)" "" + cp -f /usr/share/misc/config.guess config.guess +endif + dh_clean + +install: install-indep install-arch +install-indep: + dh_testdir + dh_testroot + dh_clean -k -i + dh_installdirs -i + # Add here commands to install the package into debian/sflphone. + +install-arch: + dh_testdir + dh_testroot + dh_clean -k -s + dh_installdirs -s + # Add here commands to install the arch part of the package into + # debian/tmp. + $(MAKE) DESTDIR=$(CURDIR)/debian/$(package) install + dh_install -s +# Must not depend on anything. This is to be called by +# binary-arch/binary-indep +# in another 'make' thread. + +binary-common: + dh_testdir + dh_testroot + dh_installchangelogs ChangeLog + dh_installdocs + dh_installexamples +# dh_installmenu +# dh_installdebconf +# dh_installlogrotate +# dh_installemacsen +# dh_installpam +# dh_installmime +# dh_python +# dh_installinit +# dh_installcron +# dh_installinfo + dh_installman + dh_link + dh_strip + dh_compress + dh_fixperms +# dh_perl + dh_makeshlibs + dh_installdeb + dh_shlibdeps + dh_gencontrol + dh_md5sums + dh_builddeb +# Build architecture independant packages using the common target. +binary-indep: build-indep install-indep + $(MAKE) -f debian/rules DH_OPTIONS=-i binary-common + +# Build architecture dependant packages using the common target. +binary-arch: build-arch install-arch + $(MAKE) -f debian/rules DH_OPTIONS=-s binary-common + +binary: binary-arch binary-indep +.PHONY: build clean binary-indep binary-arch binary install install-indep install-arch configure diff --git a/tools/build-system/launchpad/sflphone-plugins/debian/substvars b/tools/build-system/launchpad/sflphone-plugins/debian/substvars new file mode 100644 index 0000000000..566a162f0d --- /dev/null +++ b/tools/build-system/launchpad/sflphone-plugins/debian/substvars @@ -0,0 +1 @@ +plop=0.9.6 diff --git a/tools/build-system/launchpad/sflphone-video/debian/changelog b/tools/build-system/launchpad/sflphone-video/debian/changelog new file mode 100644 index 0000000000..19ebbcbbb6 --- /dev/null +++ b/tools/build-system/launchpad/sflphone-video/debian/changelog @@ -0,0 +1,3585 @@ +sflphone-video (1.1.0-rc20120607~ppa1~SYSTEM) SYSTEM; urgency=low + + ** SNAPSHOT 1.1.0-rc20120607~ppa1~SYSTEM ** + + * * #12071: audiopreferences: fix make check + * * #12071: cleanup + * #12071: Lower expat dependency version + * * #12085: alsa: fix ringtone update bug, cleanup + * #12071: Fix pulseaudio compilation error + * #12071: Make pulseaudio optional at configuration time + * * #12091: daemon: issue warning if falling back to ALSA + * video: fixed make check + * *#12085: alsa: don't segfault on snd_pcm_avail_update error + * #12070: Add --without-pulse option + * * #12055: video: fix build for older libav + * [ #11886 ] cleanup + * [ #11886 ] Add basic reverse peer naming support + * [ #12008 ] Implement GUI part + * * #12012: video: fix some regressions + * * #12002: yamlparser: don't wipe out config if going from normal + build to --enable-video + * [ #12008 ] Add ConfigurationManager::getRingtoneList() + * * #12002: video: fix config file serialization/deserialization + * * #11987: managerimpl: fix bugs in conference when removing + participants + * * #11987: manager: fixed transfer from conference + * * #11987: mainbuffer: cleanup logging + * * #11979: pulse: fixed mismatched device list + * * #11971: audiolayer: fix bugs with getDeviceList + * * #11960: manager: validate conference earlier when processing + participants + * * #11960: manager: fixed segfault on transfer from conference + * * #11966: IP2IP: make alias consistently IP2IP + * * #11965: sipvoiplink: add more error checking in SDP negotiation + * * #11964: mainbuffer/ringbuffer: cleanup API + * sdp: remove unused variable warning + * * #11941: video: fix deprecated libav_api warnings + * * #11949: pulselayer: fix bug in getDeviceList + * video: whitespace fixes + * * #11951: video: fixed threading issues for ucommon Thread + * [#11848] Properly disable testPulseConnect + * [#11848] Disable pulseConnect test + * sdp: cleanup + * sdp: cleanup + * * #11860: mainbuffer: remove dead and/or buggy code + * * #11851: sdp: fixed gcc type narrowing warnings + * * #11851: audiostream: fixed gcc type narrowing warnings + * * #11841: don't put code with side effects in assert() + * * #11840: audiortp: remove some global symbols/variables + * * #11828: audiofile: fix broken build + * * #11828: audioloop: don't shadow sampleRate variable in derived + classes + * * #11818: gnome: stop daemon on SIGTERM, SIGINT or SIGHUP + * * #11499: daemon should also quit gracefully on SIGHUP + * * #11813: daemon configure should fail if expat is not installed + * #10304: updatePlaybackScale dbus method uses int 32 bit for size and + position (allow for 24 days long recording playback) + * #10304: Add time lable for seekslider + * * #11780: sip: don't use abort or leak calls on error and don't + restrict SDP size to 1000 bytes in transaction_request_cb + * * #11735: daemon: added timestamp start to call details + * * #10304: historyitem: added operator > defined in terms of operator + < + * * #11252: historyitem: added missing unistd.h header + * * #11252: daemon: removed deprecated zrtp code + * * #11728: yaml: check that nodes are valid before using them. + * * #10797: send DTMF over RTP as per RFC2833 + * * #11706: managerimpl: added unsetCurrentCall method + * * #11698: daemon: fix build for c++11 + * * #10304: historyitem: file_exists need not be a member method + * * #11499: managerimpl: don't crash if signal and dbus try and finish + the manager at the same time + * #10304: Prevent from storing removed files in history + * * #11499: daemon: Exit cleanly on SIGINT or SIGTERM + * * #10226: audiocodecfactory: use array instead of vector for codec + name lookup + * * #10226: audiocodecfactory: make codec loading stricter + * #10304: RCecale positions and size values for playback recording + * #11530: Make sure that only appropriate configuration option are + parsed for IP2IP calls + * * 11480: video: disabled by default + * * #11459: history: protect historyitems vector with mutex + * * #11448: fix video preferences for empty camera list + * #10304: Implemented playback seek in gnome client + * * #11269: video: fix codec per account management + * #10304: Implemented playback scale in gnome client + * Fix includes for gcc 4.7 + * * #11269: make clearer distinction between codecs and audiocodecs + * * #11269: merged master into video + * * #10296: managerimpl: more usage of getCallFromCallID + * * #10296: verify that calls exists before trying to join them in a + conference + * * #11208: bump version numbers for release 1.1.0 + * managerimpl: rename ManagerImpl::serialize/unserialize -> + join_string/split_string + * Fix warnings in resampler test + * * #10732: sipvoiplink: fix code that validates IP address + * Add historyChanged signal, better than managing it client side + * #10795: fix sipaccount deserialisation broken + * #10736: implement getConferenceId dbus method given a call id + * #10736: do not use iterator in daemon when joining conferences + * #10736: Fix joining conferences in daemon + * * #10736: gnome: fix crash on restart with active conference + * managerimpl: removed unused pulselayer.h header + * Save history everytime it change, prevent the file never to be saved + in some senario (SIG, crash, ASSERT, etc) + * * #10320: manager: check that participants are unique before joining + * #10335: Add a noise suppressor for incoming rtp streams + * * #10322: sip: registration state should not be always set to + ErrorAuth on error + * #10220: Fix recording thread does not exit when hanging up while + recording + * * #9903: fix includes for new ccrtp + * #10230: Get back default mainbuffer sampling rate to 8kHz, no need + of decoding noise suppressor + * #10230: Use a different samplerate converter for rtp encoding and + decoding + * * #9903: create DynamicPayloadFormat on stack, initialize earlier + * * #9903: audiorecorder: initialize buffer to silence, not random + data + * #10230: Test for triangular and sine signals + * #10230: Add resampling unit test + * * #10230: DTMF sample rate should come from main buffer, it should + not be hardcoded + * * #10213: audiolayer: create samplerateconverter on the stack + * * #10213: audiolayer: cleanup + * * #10213: increase resample buffer size, and check output size when + resampling + * * #10095: sipvoiplink: check pointers before using them + * #9981: IP2IP calls based on ip address instead of sip: + * * #10213: speex codecs should initialize their own parameters + * * #10213: account: removed redundant cast + * * #9832: removed extra printf + * * #10172: include -sflphone in recording file name + * * #9832: cleanup logging in tests + * * #10096: srtp: use vectors to simplify key/salt manipulation + * #10096: add case for non-srtp calls + * [ #10121 ] Sync the KDE with daemon, fix a few issues and implement + a recorded call player + * #9980: make keep registration optional as there is different + behavior on different registrar + * #10096: use c++ arrays to store keys in srtp sesssion + * * #10018: renamed registration related keys in dbus + * #10096: Fix onhold/offhold srtp + * * #8586: fixed make distcheck + * * #9832: logger: don't hide logging if NDEBUG is present + * #10096: Reinit crypto context when required on INVITE request + * * #10111: Fixes segfault on empty config file + * #100096: Set in/out queue crypto context at initialization, not when + starting the thread + * #10096: Update srtp key generation when holding/unholding a call + * * #9831: logger: removed extraneous carriage-return character + * * #10095: sipvoiplink: validate pointers before using them + * * #10094: renamed config/config.{h,cpp} config/sfl_config.{h,cpp} + * * #10090: fix segfault in transaction_state_changed_cb + * * #9832: audio_rtp_record_handler: cleanup logging + * * #9832: pulse: cleanup logging + * * #9832: cleanup logging + * * #9832: cleanup logging + * * #9832: dbus: fix logging + * * #9832: config: cleanup logging + * * #9832: remove unused header + * * #9832: manager: fix logging + * * #9832: audio: fix logging + * * #9832: audio: fix logging + * * #9832: zrtp: cleanup logging + * * #9832: AudioZRTPSession: cleanup logging + * * #9832: AudioSRTPSession: fix logging + * * #9832: cleanup logging + * * #9832: AudioRtpSession: cleanup logging + * * #9832: AudioRtpFactory: cleanup logging + * * #9832: codecs: fix logging + * * #9832: alsa: fix logging + * * #9832: audio: clean up logging + * * #9832: AudioRecord: cleanup logging + * * #9832: Fix logging in Manager + * * #9832: new logging macros + * * #9979: ulaw: fixed unused var warning + * * #10039: sipvoiplink: use references to avoid unnecessary parameter + validation + * * #10039: Fixed segfault on failed registration + * * #9979: codecs: fixed unused variable warnings + * * #9979: Don't do runtime assertions on data. + * * #10016: SDP: removed verbose debuggin + * #10016: Crypto context deletion are now managed inside the library + * * #10016: srtp: cleanup + * * #10016: SDES: fix uninitialized value bug, use const char* + * * 100016: don't double free crypto contexts, and don't improperly + copy CryptoSuiteDefinitions + * * #100016: cleanup crypto contexts in audio_srtp_session + * * #9979: removed unused methods from audicodec + * * #9979: ulaw: normalize types + * * #9979: cleanup + * * #9979: Alaw: cleanup + * * #9979: removed duplicate/superfluous code and type issues from + g722 + * * #9979: AudioRtpRecord: let AudioRtpRecord handle fadeIn internally + * #9980: Fix registration timer and transport shutdown on 401, default + registration timer to 3600 + * * #9979: use std::tr1::array instead of plain array for audio + buffers + * * #9969: set loose routing param when creating route set + * #9975: Fix account registration status display + * * #9969: SIP: initialize body earlier + * * #9969: sip: get received and rport fields if present in OK + * * #9968: fixed segfault in transaction callback + * #9898: make sure account are unregistered when sflphone quit, add + timeout on pending transaction + * #9910: fix contact header in outgoing request if via parameter are + present + * * #9910: SIP: use rport from VIA header if present + * * #9910: SipTransport: pass parameters by const reference + * #9910: fix sending call with new transport + * yaml: remove verbose debug messages + * * #9911: sipvoiplink: fixed "unused variable" warning + * #9910: create new udp transport to fix registration failure with 606 + error & received parameter + * * #9910: SIP: use pjsip error codes instead of magic numbers + * * #9911: SIP Transports must be cached by IP:port + * * #9910: SIP: cleanup + * * #9905: SipTransport: address has to stay on stack to be valid + * #9910: Update parse received parameter on 606 registration error + * * #9911: simplify network manager state reporting + * #9902: Fix SIPTest for IP to IP call + * #9911: Fix network manager crashes + * #9902: Move logic for ip2ip call in SIPVoIPLink + * #9902: Move logic for ip2ip call in SIPVoIPLink + * * #9910: fix 606 error code nomenclature + * * #9905: fixed address initialization in createUdpTransport + * * #9903: cleanup + * #9902: Log failure cause when new outgoing call fail + * * #9898: properly initialize ports + * #9898: Unregister account when leaving sflphone + * iax: create iaxvoiplink on stack + * account: removed unused methods + * * #9847: don't use assertions for input coming from DBus + * * #9897: audiorecord cleanup + * * #9897: audiorecord: cleanup, removed unused methods + * #9897: Initialize and fallback recording path in home directory if + not valid + * * #9871: SipTransport: hide more implementation + * * #9871: SipTransport: refactor SIP transport creation + * * #9871: disable STUN for account if STUN setup failed + * * #9847: check pointer before using it + * #9871: Fallback on normal upd transport when stun resolution fails + * Revert "#9871: Fallback on normal upd transport when stun resolution + fails" + * #9871: Fallback on normal upd transport when stun resolution fails + * pulse: cleanup + * * #9847: removed outdated README file + * * #9847: use references instead of pointers where possible + * * #9847: pass call by reference where possible + * * #9847: audiolayer: fixed typo + * * #9847: SIPVoipLink: gracefully handle invalid pointers + * * #9847: check that transport is initialized + * * #9847: SDP: avoid buffer overflow + * * #9847: fixed segfault on bad call invite + * * #9847: SDP: don't use assertions for runtime errors + * * #9847: handle invalid remote session gracefully + * * #9851: fixed segfault on stun socket cleanup + * * #8586: fixed warnings + * * #9849: added missing sstream header + * #9623: add required TLS certificates for testing purpose + * #9623: fixed tls registration + * #9623: fix storing tls port in config for normal account + * #9623: Allow all account to change tls listener port (not only + IP2IP) + * #9623: Allow for changing interface / port for tls transport + * #9623: Open TLS listener on selected interface + * #9833: remove unused debug + * #9833: handlingEvents_ must be initialized to true when starting iax + thread + * #9830: Remove create_route_set from sipvoiplink + * #9831: Fix sip transport port number + * #9830: move sip header parsing function in sip_utils + * Revert "* #8586: don't restore and save test files" + * * #8586: fixed make distcheck + * #9623: fixed tls registration + * * #8586: don't restore and save test files + * * #8586: refactored yaml code + * #9623: fix storing tls port in config for normal account + * #9623: Allow all account to change tls listener port (not only + IP2IP) + * #9623: Allow for changing interface / port for tls transport + * #9623: Open TLS listener on selected interface + * * #8586: added missing tests + * #9833: remove unused debug + * #9833: handlingEvents_ must be initialized to true when starting iax + thread + * #9830: Remove create_route_set from sipvoiplink + * #9831: Fix sip transport port number + * #9830: move sip header parsing function in sip_utils + * * #8977: removed unnecessary AC_CANONICAL macros from configure.ac + * * #8977: use actual PJSIP linking flags from pjproject/build.mak + * * #9774: sipvoiplink's destructor should not be public + * dbus: cleanup + * * #9774: make sure sipvoiplink is destroyed before accounts are + unloaded + * * #9777: don't use deprecated auto_ptr + * * #9778: removed AC_CHECK_FUNCS calls + * * #9782: fix warnings in tests + * * #9782: sip/sdp: fix emptiness checks + * * #9782: sdes_negotiator: fix iterator usage and set dangling + pointers to 0 + * * #9782: initialize all vars in iaxvoiplink + * * #9782: use fstreams instead of fscanf + * * #9782: yamlnode: fixed iterator usage + * * #9782: yamlemitter: fix iterator usage + * * #9782: yamlnode: make some methods const + * * #9782: initialize all member vars in constructor + * * #9782: Tone::interpolate should be const + * * #9782: mainbuffer: get rid of unused vars + * * #9782: GainControl::limit should be const + * * #9782: fix ARRAYSIZE check + * * #9782: use nanosleep instead of usleep + * * #9782: fixed "inefficient emptiness test" cppcheck warning + * * #9782: initialize dcblockers vars in constructor + * * #9779: dropped CELT support + * * #9750: moved sfl_data_format.h -> sfl_types.h + * * #9750: refactored global.h + * * #9736: restored command line options to daemon + * tests: cleanup + * * #8586: make distcheck was missing a header + * tests: cleanup + * * #9731: use all caps for application-wide constants + * tests: cleanup + * * #9730: cc++: enforce better checks in headers + * * #9730: builds against libccrtp1 + * * #9572: sipvoiplink: fixed typo + * * #9572: fixed threading issues with ccrtp2 + * * #9572: manager: pass config filename by const reference + * * #9572: Replace utilspp singleton implementation + * * #9571: regenerated config.{guess,sub} file to fix FTBFS on + armel/armhf. + * * #9665: siptransport: fixed udp_transport_start calls + * #9620 Add test SIP account in configuration sample (test/sflphoned- + sample.yml) + * * #9641: audiortp: Fixed CryptoContext management + * * #9641: fixed another memory leak in audio_srtp_session + * * #9641: audiosrtpsession: fixed memory leak, simplified memory + management + * * #9641: avoid dynamic memory allocs/raw pointer usage in audio rtp + stack + * * #9641: get rid of getType/RtpMethod logic + * fixed typo + * #9572: make sflphone compile with libccrtp 2 + * * #9490: fixed registration state change callback that was crashing + client + * * #9547: fixed warnings in SipTransport header + * #9547: Add SipTransport class + * #9547: Extract all the transport layer from SIPVoIPLink to new + SipTransport Class + * #9547: Destroy the STUN resolver in Transport shutdown + * sipvoiplink: removed erroneous FIXME + * sipvoiplink: cleanup + * #9547: Destroy the STUN resolver if server name change + * sipvoiplink: fix warning about variable shadowing + * #8320: Rename declared exception to avoid parameter shadowing + * #8320: Send signal to client on stun failure + * #8320: Use the same API for all transport creation (UDP, STUN, TLS) + * * #9509: use vector for credential info + * * #9508: fixes segfault in manager by changing order in which + destructors are called + * #8320: add dbus signal for stun failure + * #8320: Use two different variables for status and return statement + in stun's on_status_cb + * * #9490: removed resolve_once parameter that was causing a segfault + * #8320: make the retransmission callback to be rescheduled on error + * HookPreference: cleanup + * daemon: hookpreference: cleanup + * iaxvoiplink: terminate() doesn't have to be virtual + * sipvoiplink: functions need not be static if they are in an + anonymous namespace + * * #9037: moved CHECK macro into separate header + * * #9037: cleanup error handling/checking in video threads + * * #9037: video: cleanup + * * #9037: only signal receiving_video_event for rtp sessions + * * #9037: shared memory moved out of video_receive_thread + * * #9381: daemon: fixed make check for video + * * #9381: YAML_LIBS must be explicitly set in AC_SEARCH_LIBS macro + * * #9381: reverted yaml check + * * #9381: fix celt plugin compilation on fedora + * * #9381: use PKG_CHECK_MODULES to test for yaml + * * #9381: use autoconf macros and AC_SEARCH_LIBS + * * #9381: use AC_SEARCH_LIBS, AC_CHECK_LIB + * ringtonetest: cleanup + * configurationtest: cleanup + * instantmessagingtest.cpp: cleanup + * mainbuffertest: cleanup + * tests: cleanup + * #8320: Make sure stun keep alive is enabled + * call: push answer logic into call classes + * sipaccount: simplify IP2IP code + * sipaccount: avoid segfault if sipaccount is NULL + * sipaccount: cleanup + * #8084: Fix get sip header segfault when stun transport selected + * * #9037: created shared_memory class + * #8084: Init stun port with default valueas defined by RFC 3489 + * #9046: Move IP2IP_PROFILE global definition inside SIPAccount class + * #9045: fix Changing the account expire is not taken applied in + daemon + * vidoe_receive_thread: cleanup + * * #8968: suppress unusedFunction warnings for functions that are + actually used + * * #8821: fixed unit tests + * #8821: Renamed account map keys for consistency + * * #8968: audiorecord: added debug, clarified wave header creation + * * #8968: added debug message to get rid of "unused struct member" + warning + * * #8968: manager: create History on the stack + * * #9026: sfl::InstantMessaging is now a namespace + * * #8698: managerimpl: removed unused method isWaitingCall + * * #9008: don't include yaml headers in serializable.h + * * #9008: cleanup account map initialization + * refactor accountmap initialization + * #9020: fix config file not generated when no account created + * * #8968: audiorecord: removed unused getSndSamplingRate + * * #8968: config: removed getConfigTreeItemIntValue + * * #8968: recordable: removed unused getRecFileId + * * #8968: removed unused Codec::getMimeType method + * * #8968: manage lifetime of IMModule with auto_ptr + * * #8968: history: removed unused method + * * #8968: managerimpl: removed unused method + * * #8968: config: removed unused methods + * * #8968: managerimpl: removed unused getConfigBool/Int methods + * * #8968: networkmanager: cleanup + * * #8968: managerimpl: removed unused getConfig + * * #8968: managerimpl: Manage telephoneTone_ with auto_ptr. + * * #8968: history: fix memory leak upon exception + * * #8968: AudioFile: initialize filepath earlier + * * #8968: audiofile: fix memory leak on exception + * * #8968: audiocodec: removed unused getChannel method + * * #8968: use auto_ptr for dtmfKey + * * #8968: use vector instead of dynamically allocated int array + * * #8968: sdp.h: pass paramter by reference + * * #8968: sipvoiplink: avoid C-style pointer casting + * * #8968: yaml: avoid C-style pointer casts + * * #8968: pulselayer: avoid C-style pointer casting + * * #8968: recordable: removed unused getRecordingSmplRate method + * * #8968: alsalayer: use preincrement for iterators + * * #8968: config: removed unused method saveConfigTree + * * #8968: mainbuffer: preincrement iterators + * * #8977: history: added #include <fstream> + * * #8968: don't leak memory on exception + * * #8968: pulselayer: avoid C-style pointer casting + * * #8968: Member variables must be initialized in AudioSrtpSession + constructor + * * #8968: fix potential memory leak in audiorecord + * * #8968: Pass function parameter 'item' by const reference. + * * #8969: fixed memory leaks in sdes_negotiator + * video: cleanup + * * #8940: removed video test source for now + * #8763 Fix doxygen generation + * #8763 Generate Doxygen with Hudson + * * #8940: videosendthread: cleanup + * sipvoiplink: cleanup + * fileutils: cleanup + * #8335 Fix default transport initialization on 5062, 5064 + * #8762: update mute for mic only, fix remove slide for pulseaudio + * #8672: Add linear to decibel conversion functions in audio layer + * #8672: Implement audio gain management in pulseaudio + * #8671: Move audio gain management in audiolayer + * * #8542: create symbolic link properly + * * #8613: make check should fail early if another sflphone is running + * #8449: Update version 1.0.2 + * * #8545: fixed error case + * * #8545: fixed broken ringtone + * * #8586: fixed make dist + * sipvoiplink: use static_cast instead of reinterpret_cast if possible + * * #8542: test for .git existence before moving pre-commit hook + * * #8542: autogen.sh should not require git + * eventthread: cleanup + * * #8542: removed trailing whitespace from tree + * * #8357: added disable video option to client + * siptest: cleanup + * * #8521: use avcodec_open2 instead of deprecated avcodec_open, if + available + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Thu, 07 Jun 2012 16:08:15 -0400 + +sflphone-common (1.0.0-rc20110930~ppa1~SYSTEM) SYSTEM; urgency=low + + ** SNAPSHOT 1.0.0-rc20110930~ppa1~SYSTEM ** + + * update kde .gitignore + * Fix bug in volume widget + * More polishing for release + * Bump version to 1.0.0 + * [#7023] Add the ability to load an abstract contact backend in the + library to resolve more data, polish code + * [#7021] More cleanup for release + * Cleanup + * [#7021] Refactor KDE client dbus handling, add a missing call in + daemon and port the DataEngine to the new API + * Remove some annoying debug + * merge language scripts + * remove obsolete 'VERSION' files + * update install instructions + * Add missing translations to gnome + * language update + * Revert "Don't reference count DBus clients, exit core immediately + when one of them request it" + * Don't reference count DBus clients, exit core immediately when one + of them request it + * [7021] Add contact abstraction support + * [#7121] Polishing library (over). Indentation, spacing and naming + are now consistent + * codecs: link to libccrtp, don't use logger + * Fix a daemon bug + * [#7038] Fix adding contact + * * #7037 : stop audio stream after all calls have been hanged up + * [#7025] Add full support for bookmark + * SFLPhone KDE do not destroy history anymore + * Fix config skeleton + * Close the daemon once and for all, no more automatic respawning + * Fix "unregistered account" bug (I hope so) + * Close SFLPhone at the right place, it still respawn, I don't know + why + * Remove dead code + * Fix regressions introduced in the last commit + * Dead code elimination 1/3 + * Fix bug, add "add contact" option, fix warning + * * #7019: Fix IAX codec negociation + * Remove or comment unnecessary/unhelpful debug output + * Fix "same as local" account setting, fix IP2IP LED color + * Add support for some more advanced config options and add missing + config dialog icons + * Fix crash with noise suppressor + * Alternative can now be selected from the call view context menu + * Add drag and drop support, initial context menu and fix 3 bugs in + the account dialog + * Add basic history drag and drop support + * Complete contact support is back + * * #6991 : fix IAX problems + * Fix IAX accounts being disabled by default + * Revert "deb: forge -g flags for pjsip" + * * #5884: Disable debug code in pjsip + * echo suppressor : more assertions + * Don't let the daemon think crypto is enabled when it's not + * Simplify ToneList + * Some progress on contact support + * Remove unused getRegistrationCount() + * remove annoying debug + * revert SIP bit of e27e5c39bad27bae28f574eb2cba7717e8956229 + * Simplify CallManager::placeCallFirstAccount + * Fix crash on hold + * * #6905 : SIP refactor + * gnome client: be sure key exchange is set correctly + * Move code into createSipTransport + * Fix account registration on start + * ManagerImpl::registerAccounts(): simplify + * * #5884: don't mess with pjsip threads in echo suppressor + * * #6905 : simplify udp/stun/tls pjsip transport creation + * Restore and improve support for Call history + * fix launchpad build + * SIPVoIPLink: simplify / refactor + * Fix libwidget linking + * SIP: simplify + * IM : simplify + * gnome: remove some debug + * AudioRtpFactory::stop() cannot fail + * * #6905: simplify SIP code + * pjlib: fix build without SSLv2, fix warnings + * Port history to the new syntax + * Test a dock widget based implementation for contact and history + * Disable SSLv2 support from pjsip and sflphone + * deb: forge -g flags for pjsip + * Fix deb packaging to get debug symbols + * remove debug + * pjproject: update to last stable release (1.10) + * Require gtk >= 2.20 and glib >= 2.24 + * tlsadvanceddialog: simplify + * * #6902 : fix errors spotted by -DGSEAL_ENABLE + * Update daemon dbus XML and port KDE config backend from dbus to + local + * Remove unused but set variables + * * #6929 : fix IM widget, cleanup + * Unconditionally enable debug symbols + * Should fix many KDE issues + * * #6886 : hitting backspace on empty number have no side effects + * * #6905 : fix AudioCodecFactory access in optimized builds (-O > 0) + * Remove unsupported and broken jaunty/karmic packages + * * #6902 : avoid using some gtk deprecated functions + * Update dbus introspection files + * * #6904: removed unused contactmanager + * * #6903 : use correct dbus-cxx package name + * * #6902: don't use individual gtk headers + * Fix a segfault when config is not present + * Merge latest (0.9.13) KDE code. This version is not yet ready for + git master, but better than the previous one + * addressbook : simplify + * * #5659 : sflphone-plugins doesn't depend on libedataserverui + * * #5659 : addressbook doesn't use libedataserverui + * gnome client doesn't depend on evolution + * * #5695: addressbook: simplify + * * #5695: addressbook : remove AddrBookHandle from plugin + * * #5695 : addressbook : remove unused stuff in the client + * * #5695 : addressbook : remove unused stuff, use static mutex + * gnome client doesn't use evolution + * gnome: use proper API to set GTK_CAN_FOCUS + * * #6897: removed unused focus state vars/callbacks + * gnome: fix calls to sflphone_fill_codec_list_per_account + * * #6623: gnome: don't leak in mainwindow + * gnome: mainwindow whitespace cleanup + * gnome: actions.c parameter doesn't have to be a double pointer + * * #6895: fix memleaks, cleanup in accountconfigdialog + * * #6893: fixes segfault in client on clean history + * * #6894: fix leaks, cleanup in sflnotify + * daemon: fixed prints in main + * * #6892: simplify, fix leaks in dialpad + * * #6887: audiopreference creates audio layer + * * #6660: use const char * const, not std::string for globally + visible constants + * * #6852: Preferences now solely responsible for audiolayer creation. + * * #6860: refactor uimanager, also fixes #6865 + * * #6853: hangup as soon as all digits have been deleted + * * #6852: alsa: retry if device is busy + * * #6852: audiolayer creation depends only on preference.audioApi + * * #6850: gnome: fix build for gtk < 2.22.0 + * cleanup in iax + * alsa: typo + * pulse: if we can't peek in audio input, we can't drop samples + * * #6849: show error window if codecs are missing, instead of dying + * EchoCancel: unused, remove + * * #6629 : use number of samples as arguments for audio filters + * * #6629 : remove unused Algorithm interface + * * #6629 : use helper to call alsa functions and display error msgs + * Remove unused type + * * #6841: fix some error handling + * * #6629: simplify AlsaLayer::alsa_set_params() + * Get gdk key definition from header + * * #6828: Replace raw key codes by gdk defines + * remove some debug, enhance some other + * mainbuffer: simplify + * * #6561 : fix phantom call after transfer + * Conference Participant set : simplify + * SIPCall: remove unused functions, make invite session public + * * #6229 : remove malloc/free from pulse audio loop + * * #6629 : simplify pulse callbacks + * * #6629 + * Simplify widgets + * * #6629 : keep the correct audio module when frequency changes + * * #6751: fixed erroneous debug msgs + * callable_obj.h: removed unneeded pthread header + * alsalayer: cleanup + * * #6629: Always restart audio driver when changing parameters (ALSA + only) + * gnome GUI: don't block in DBus signal errorAlert() + * * #6629 : simplify AudioLayer creation + * * #6629 : remove unused and unconfigurable frameSize from audiolayer + * * #6629 : remove unused error message from audio layer + * Fix logic error when switching audio API + * Remove unused AudioProcessing class + * AudioRtpRecordHandler::initNoiseSuppress() : use noiseSuppress + directly + * * #6629 : use DC blocker directly in audio layers + * * #6629 : clean AudioLayer + * * #6629 : don't store mainbuffer inside audiolayer + * * #6629 : correct AudioLayer::notifyincomingCall() + * * #6554: cleanup, refactoring in sipvoiplink + * * #6554: cleanup in iaxvoiplink + * * #6554: throw exception in getSIPCall if pointer is NULL + * * #6554: make some methods of sipvoiplink static + * * #6655: cleanup in managerimpl + * * #6554: refactoring, fix memleaks in sipvoiplink + * * #6478: remove throw specs, cleanup in voiplink + * * #6629 : remove unused AudioDevice + * * #6655: removed more dependencies from managerimpl + * * #6744: simplified numbercleaner + * conference : remove one prototype + * * #6743: fix ip2ip + * Don't give glib warnings if icons are not found + * gnome: fixed includes + * Codec.h: removed unused function + * * #6742 : clean dbus & icons + * * #6699: refactor/cleanup accounts + * icons: cleanup + * timer : use second precision, not millisecond + * calltree_update_clock : use correct type, returns something + * * #6737: fixed typo in dbus call + * * #6737: removed tests for removed API + * * #6737: dbus: fixed bug from merge + * * #6737: cleanup in accountlist + * * #6737: cleanup in dbus + * * #6740 : fix history double free + * * #6740 : remove time updating thread from calls + * * #6737 : use c99 for client + * * #6738 : make history loading faster + * sipvoiplink : don't crash on transfers + * fixed typo + * Remove unused file + * Don't build networkmanager.cpp at all if NM is disabled + * _debug* -> _debug + * * #6554 : simplify sipvoiplink + * hudson: added -x to git clean command + * added git clean to hudson script + * audiocodecfactory: cleanup + * * #6718: refactored setTlsSettings into SIPAccount + * * #6718: removed more unused methods + * * #6718: refactored confmanager code into sipaccount + * remove unused functions + * * #6718: confmanager: removed more unused methods + * AudioCodecFactory : cleanup + * #6697 : Turn callableElement struct into union + * * #6718: confmanager: removed more unused methods + * * #6718: confmanager: removed more unused methods + * * #6718: removed unused dbus methods, refactoring + * * #6699: accounts: cleanup/refactoring + * * #6699: refactoring, cleanup in accounts + * * #6699: more account cleanup + * remove unused autoconf variable + * * #6714: fixed hudson script + * make distclean in hudson + * added || exit 1 to run_tests.sh call + * * #6714: fixed make distcheck for sflphone-plugins + * * #6714: fixed make distcheck for gnome client + * * #6714: fixed make distcheck for daemon + * git: #6698 split the main .gitignore file + * gnome: gpointer is already a pointer + * gnome: calltab_init: use calloc instead of malloc + * * #6699: more account cleanup + * * #6699: cleanup account + * * #6554 : more *voiplink cleanup + * * #6558 : more sipvoiplink simplification + * * #6558: saner loadSIPLocalIP prototype + * gnome: #6623 clean calllists + * * #6692: more audiolayer cleanup + * * #6692: cleanup/refactoring in audiolayers + * * #6692: more forward declarations, AudioThread->AlsaThread + * * #6692: audiolayer cleanup + * * #6692: alsalayer cleanup + * * #6558 : remove account creator + * * #6558 : clean sipvoiplink + * * #6554 : cleanup sipvoiplink + * audiortp: cleanup + * * #6657 : fix launchpad builds for good + * * #6675 : send RTP dtmf events only once + * * #6655: more cleanup + * AudioRtpSession::updateSessionMedia() : simplify + * * #6655: more cleanup in managerimpl + * * #6655: removed more code, cleanup + * * #6655: more cleanup, fixed infinite loop + * * #6655: removed more unused files + * * #6655: removed unused mutex + * * #6655 removed more unused code + * * #6655: removed unused methods + * * #6655: cleanup in main + * * #6663: fixed segfault when off hold from transfer + * * #6658: user's active codec selection is respected + * * #6660: static global string should be static const char* const + class member + * * #6659: use g_strcmp0, not strcmp for vals that may be null + * callable_obj: fix double free + * calltree_display_call_info() : simplify + * * #6657: Fix launchpad builds + * Logger::log() : simplify + * AudioRtpSession : privatize members + * * #6655: more constness, cleaned up/simplified methods + * * #6654: call DBus::_init_threading so that dbus-c++ to make it + threadaware + * set default credentials on account creation + * AudioCodecFactory::scanCodecDirectory() : simplify and correct + * * #6623: fixed typos + * * #6623: fixed more leaks + * * #6623: fixed more leaks + * * #6623: fixed more leaks, don't print codec name if null + * * #6623: more leaks fixed in client + * * #6623: fix more leaks, fixed some warnings + * * #6623: fixed leak in history + * updated gitignore + * initialize dbus dispatcher correctly + * Fix tests, hudson doesn't have a dbus daemon running + * remove unused code + * removeCall() : simplify , fix leak + * stopRtpThread() : simplify + * *CurrentCall : simplify + * Fix memleak + * fix serialization of audio api (pulse / alsa) + * account map : simplify + * remove call from callmap before terminating it, avoid use after free + * * #6630 : don't make DBusManager a singleton + * call: return confID by value + * add back history code deleted by error + * history : reverse logic + * simplify history serialization and remove some debug + * remove annoying debug + * * #6464 : replace cerr with _error + * * #6464: replace cout with logger macros + * replace printf() with logger macros + * update .gitignore + * remove unused function + * update eclipse projects + * uimanager_new() : simplify + * rename directories + * celt: simplify a bit + * Fix CELT configure.ac test + * * #6612 : template speex codecs + * * #6623: refactored conference obj + * * #6623: refactored callable object, removed leaks + * * #6623: more cleanup, fix leaks, make global vars static and rename + them + * * #6623: calltree: fixed memleaks, simplified code. + * audiolayer: init pointer members + * manager: catch exception on invalid hangup + * * #6623: don't leak on calls to create_new_call + * * #6611 : clarify codecs prototypes + * ringtones : .au and .ul files are both ulaw + * * #6611 : make sure samplerate converters are called correctly + * ManagerImpl::switchAudioManager() : simplify + * * #6623: fixed more leaks + * * #6623: fixed more leaks + * * #6623: fixed more leaks + * * #6623: fixed leak, line-endings in imwidget + * * #6627: zero-initialize pointers if they're going to be deleted + * * #6628: don't leak calls on exceptions + * Revert "audiortp: call join after calling stop on RtpThread" + * sflphone-client: more constness + * audiortp: call join after calling stop on RtpThread + * * #6625: return 0 on successful completion + * * #6624: fix segfault on servercallfailure + * * #6621: Fixed double free, unlock mutex in ManagerImpl::terminate + * * #6220: remove audio stream when peer hangs up + * * #6596: AudioSymmetricSession shouldn't self-delete + * resampler: grow internal buffers dynamically + * merge up and down sampling => resampling + * Leave test directory unchanged when running make check + * audio algorithms : remove unused prototype + * ringtone: detect codec from file extension + * *AudioFile : simplify + * * #6596: create local SDP on the stack, not the heap + * * #6596: don't call Ost::Thread::terminate from dtor + * audiofile: cleanup (samplerate -> unsigned) + * remove unused func + * samplerateconverter: cleanup + * RingBuffer::Put() : remove unused return value + * MainBuffer::putData() : remove unused return argument + * audiolayer::putMain() : remove unused func + * AudioLayer::putUrgent() : remove unused return value + * * #6618: delete any remaining ringbuffers in destructor + * RingBuffer::availForPut() : remove + * * #6617: return from main rather than calling exit + * MainBuffer::availForPut(): remove + * RingBuffer: simplify + * alsa : remove write only variable + * fix memcpy declaration + * bcopy(src, dst) -> memcpy(dst, src) + * RingBuffer::Get() : remove constant volume argument + * return a copy of the call ID, not just a reference. + * MainBuffer::getDataById() : remove volume argument (always 100) + * MainBuffer::getData() : remove constant volume argument + * RingBuffer::Put() : remove constant volume argument + * MainBuffer::putData() : remove constant (=100) volume argument + * audiolayer: remove constant _defaultvolume + * AudioRtpRecordHandler / AudioRtpSession : simplify + * mainbuffer: fix test + * iaxvoiplink : simplify + * sip registration callback: fix a dbus crash + * MainBuffer: simplify + * AudioRtpFactory: return cached type of rtp session. The rtp session + can have disappeared if the call was put on hold + * AudioRtpFactory: remove unused setters + * Fix launchpad builds + * * #6611 : remove unused bandwidth codec information + * * #6611: AudioCodec: remove useless/unused setters + * make sure buffer string is initialized correctly + * * #6596: declare certain destructors virtual + * audiolayer : cleanup + * Simplify doc build rules + * * #6270: don't build dbus-api doc with make, should require make all + * configure.ac: cleanup + * Remove copy of dbus-c++ from libs/ + * * #6596: stop clock thread when peer hangs up + * removed unused Fmtp.h + * * #6595: more logical initialization order + * * #6600 : fix account creation + * * #6601 : fix configure.ac tests + * remove unused variable + * Don't mix stack and heap based allocations + * Fix copyright (2009, 2008, 2009 -> 2008, 2009) + * Fix warnings found by clang + * * #6595: fix initialization order for AudioRTP + * * #6592: removed typedef std::string CallID + * * #6586: implement local g_slist_free_full for older glib versions + * * #6579: fix memory leaks in client (there's a lot left) + * ShortcutPreferences::setShortcuts() : simplify + * Fix merge + * * #6548: remove call to non thread-safe strerror() + * AudioRtpFactory: each instance is associated to exactly one SipCall + * create_audiocodecs_configuration() : make static + * * #6269 : refactor AudioRtpSession + * Fix AudioSymmetricRtpSession.h inclusion guard (cherry picked from + commit c3081dce1cc1370d6d3558a4c4ef5cfac0d21caf) + * * #6269: Rename AudioRtpSession to AudioSymmetricRtpSession + * * #6574: Don't exit when connection to pulseaudio server fails + * accountconfigdialog.h : remove some stuff from header + * * #6560: fix configuration test + * Fix warning in test + * * #6560: don't hide password entry in security tab + * * #6560: set initial password for SIP accounts + * * #6506: remove useless pointer indirection + * * 6560: password is now specific to IAX accounts + * * #6560 : actually use, store, restore, transmit SIP credentials + * * #6560: YamlEmitter: serialize sequences + * YamlEmitterException: typo + * ManagerImpl::computeMd5HashFromCredential() : simplify, fix memleak + * * #6561: invite_session_state_changed_cb() : simplify + * * #6561: More useful debug in VoIPLink::removeCall + * * #6561 : fix ghost call reappearing in GUI after transfer + * while -> for (make the code smaller) + * * #6558 : Account::loadConfig() : move IAX code to IAXAccount + * IAXVoIPLink::getAccountPtr : simplify + * * #6554 : access the SIPVoIPLink directly, not per account + * SIPVoIPLink is instanciated only once and is not associated to a + single account + * yamlnode: use const references when possible (still some left to do) + * Account::_accountID: constify + * VoIPLink: simplify, remove unused method + * hudson test : no need to call run_tests.sh anymore + * Remove AccountID type and AccountNULL define + * Make check runs the test (no need to call run_tests.sh manually + anymore) + * gnome GUI: Fix tests + * Revert "Move registration information from SIPAccount to + SIPVoIPLink" + * * #6392: pluginmanagertest: fix warnings reported by valgrind + * * #6547 : remove unused exceptions + * * #6547: CallManagerException: use runtime exceptions + * * #6547: InstantMessageException: use runtime exceptions + * * #6547: do not throw exceptions if some settings are not present in + config file + * * #6547: YamlParserException: use runtime exceptions + * * #6547: VoipLinkException: use runtime exceptions + * * #6547: YamlEmitterException: use runtime exceptions + * * #6547: DTMFException: use runtime exceptions + * * #6547: AudioFile: use runtime exceptions + * * 6547: AudioZRtpSession: remove impossible error case + * * #6547 : AudioRtpSession: remove impossible error case + * * #6547: AudioZrtp: use runtime exceptions + * * #6408 : send authenticationUsername to GUI + * * #6408 : store/restore authenticationUsername from config file + * SIPAccount: simplify + * Move registration information from SIPAccount to SIPVoIPLink + * SIPAccount::getAccountDetails : simplify + * * #6540: yaml parser: simplify + * sdp.cpp : fix a warning + * * #6540: yaml parser : remove std::string typedefs + * * #6540: Simplify yaml unserialization + * * #6540 : add a Conf::ScalarNode constructor for booleans + * setAccountDetails(): simplify + * * #6408: store authentication username in daemon + * * #6408: Be able to set the authentication username in the GUI + * * #6507 : do not crash if the program is not sflphoned + * Fix tests + * macroify SIPAccount::unserialize() + * Move all .cpp files from sflphoned target to libsflphone.la, except + main.c + * main() : simplify, return positive error codes + * * #6507 : find codecs dir in build directory + * * #6392: Sdp: move clean functions to destructor + * AlsaLayer::adjustVolume() : simplify + * alsalayer : reduce indentation + * malloc/free -> new/delete + * malloc/free -> new[]/delete[] + * malloc/free -> new/delete + * AudioSrtpSession: simplify base64 encoding + * * #6392: Initialize std::string from pj_str_t correctly + * * #6392: AudioRtpSession: Initialize remote port + * Audio settings : Initialize _echoCancelTailLength and + _echoCancelDelay(0) + * Initialize variable + * YamlParserException : fix use of stack variable after it has been + deallocated + * * #6392: fix memory leak in history + * * #6392 AudioCodec : fix memory leak + * * #6392 : fix memory leak in sip account + * * #6408: clean up sipaccount (cosmetics mostly) + * sipaccount.cpp serialize() : reduce number of lines + * * #6392: invalid memory access + * * #6392 : fix invalid memory access + * * #6479: merged useful code from MimeParameters into Codec interface + * * #6462: fixed hangup on IP2IP call + * added run_daemon.sh script + * test: remove unused variable + * Remove functions only used by a failing test (cherry picked from + commit fcf718cb75de7f1882dc61c07bb8d300dfa10f85) + * * #6360 : make client tests build (cherry picked from commit + 028b2835f040e51ab8ab979b32732b07b8798fce) + * * #6360 : fix warnings in check_global test (cherry picked from + commit 9e2bd6a7496dd64f6f48595e385760019aab1193) + * * 6360: updated API calls in tests, but they're not building yet + (cherry picked from commit 548f6f0f919b43772a3e9c667e5e292791281795) + * Fixed include in tests (cherry picked from commit + aeadc7525c1e31f936670ac8b02f0bcf387c38a8) + * Remove unused variables and functions + * IAX: fix warnings (cherry picked from commit + fd7a113a11cac2cd9a7c36929e88ad28195c4c35) + * Remove unused DEBUG define which interferes with logger.h (cherry + picked from commit b2f72b91d0f43cb1dd94d138882a8caa9c841c24) + * * #6392: no need to check for account NULLity since it is + dereferenced above + * * #6392: fix a memory leak, replace by stack allocation + * * #6392: remove a variable assignement which confuses cppcheck + * process_conference_participant_from_serialized() : remove unused + function + * * #6392: s/free/g_free/ + * * #6392: fix a memory leak in abookfactory_load_module() + * * #6392: remove generate_call_id() used only once + * * #6392: fix memory leak (opendir() without closedir()) + * * #6392: AudioRecorder(): ensures mbuffer is set + * Remove SFLPHONED_VERSION from global.h, use autoconf PACKAGE_VERSION + * #6298: Cleanup + * #6331: Fix deleting ringtone file after call have been answered + * * #6330: merged user_cfg into headers + * #6298: Fix conference recording file update at conference end + * #6298: Fix record file name serialization for conference + * * #6295: cleanup of codec hierarchy + * #6298: Fix gtk warnings + * * #6300: added script to run tests + * #6109: Add recording playback for conference + * * #6300: tests do not require an installed sflphone + * * #6295: re-removed clone methods + * #6109: Fix gtk_critical warnings for incoming calls + * #6109: Fix GTK_CRITICAL warning + * #6109: Fix icons when history is not activated + * #6109: Fix warnings + * #6109: Implement stop recorded file playback signal + * Revert "* #6295: removed unused clone method" + * * #6295: removed unused clone method + * * #6296: removed non existant file from Makefile.am + * #6109: Stop fileplayback for outgoing call + * #6109: Implement stop recording playback button + * Fix binding names errors in dbus introspection file + * #6109: Implement playback recorded file callback in client + * #6109: Store recorded file path on client side + * #6109: Add dbus methods for call recording playback + * * #6290: remove unused classes from utilspp + * * #6288: cleanup sdp + * * #6288: fix exception usage + * * #6288: simplify SdpException + * * #6288: cleanup in sdp.cpp/h + * #6109: Only display playback button if record file is set and valid + * * 6290: updated configure.ac to remove functor Makefile + * * #6290, #6289: removed unused classes from utilspp, fixed make + check + * #6109: Add button for history playback of recorded file + * * #6289: removed unused observer class + * * #6282: forward declare sdpMedia in sdp.h + * * #6281: renamed setCallAudioLocal->setCallMediaLocal + * #6183: Handle conference with more tahn two calls + * #6183: Fix history icons when calling back a conference from history + * #6183: Fix icons inconsistencies in history for conference hang up + * #6183: Fix toolbar actions when selecting a conference in history + * #6183: Fix conference serialization + * #6268: Serialize only calls + * * #6269: removed useless type testing + * ignore some files in test/ + * * #6268: Remove dead class AudioSymmetricRtpSession + * #6251: Do not had history calls in calllist when loading history + file + * #6251: Fix insertion in history map in before saving history file in + daemon + * #6251: Fix history unit tests + * #6251: Order the list before serailization, get rid of the hashtable + in history + * #6251: Implement history serialization using a list wether than a + map + * * #6253: remove external audioport from header, make all members + private + * * #6253: don't store external local audio port (used for NAT) in + Call + * #6251: Add start_time timestamp in history serialization + * #6251: Fix call insertion in conference items + * #6233: Fix serialized account list terminated with a ";" character + * #6238: Fix draggable history calls into current calls + * #6233: Fix toolbar updates + * #6233: Fix history + * * #6235: remove pyc files from git tree + * #6233: Handle cases when one or manuy calls are unreachable in + createConfFomrParticipantList + * #6233: Handle wrong numbers in createConferenceFromParticipantList + * #6231: Fix drag-n-drop issue + * * #6173 : move sippxml in tools + * #6231: Fix merging issue + * #6183: Implement conference unserialize + * * #6212: remove extraneous flags from globals.mak + * #6183: Unserialize conference data in conference + * #6183: Add account information in request for conference call from + history + * #5755: Add -ldl to liker in sflphone-client-gnome + * #5755: Fix fedora 15 compilation issue + * #6183: Serialize conference participant phone number and account + * #6183: Add conference timestamp in serialization + * * #6186: don't include global.h, just logger.h + * #6183: Fix saving history to file + * #6183: Fix removing call from calllist + * * #6184: remove pointers to Manager from AudioRtpSessions + * #6183: Calling calltree_add_call explicitely for history + * #6183: Ability to store conference inside history tab queue + * * 6181: remove unused API from sipcall + * #6171: Implment nreCallCreated callback + * #6167: Fix participant list NULL ending + * #6149: First draft of conference creation from history + * #6149: Fix multiple call/conf selection callbacks ... + * #6129: Fix place_call function called twice for pressing enter + action + * #6129: Fix double click action for history + * #6149: Add dbus call for creating conference from history + * #6129: Fix placing call from history and addressbook (still need to + fix icon) + * * #6148: removed unused AudioRtpFactory constructor + * * #6145: remove unused isAudioStarted + * * #6145: remove unused isAudioStarted + * #6129: Add conference into history, fix call/conference selection + * * #6143: don't use getType outside of serialization methods + * * #6132: forward declarations instead of includes + * * #6132: add constness, remove redundant "inline" keywords + * #6129: Add timestamp to conference object to order history entries + * * #6128: remove unused forward declarations from header + * * #6127: make noncopyable class actually noncopyable + * * #6125: don't include AudioRtpFactory in sipcall.h + * #6123: Fix alsa ringback audio file + * #6123: Fix raw audio file loading problem + * #6109: Fix daemon plugin manager unit test + * #6109: Fix history manager unit tests + * #6109: Recording filename in daemon and client for history items + + serialization + * #6109: Refactor AudioFile to play recorded call + * * #6104: AudioCodec moved to sfl namespace + * * #6099: remove active flags from codec classes + * #6095: Add notification-daemon as a runtime dependencies for rpm + packages + * #6095: Fix fedora 15 compilation in MineParameters.h + * #6095: Declare static variable explicitely for client + * #6095: Add logs to build OSC build machine + * * #6098: global variables should have file-scope to avoid name + conflicts + * #6095: Fix compilation error for Fedora 15 + * #6095: Update SFLphone version to 0.9.14 + * #6095: Add specification file in opensusse build service for + sflphone-plugins + * #6073: Fix sflphone-plugins build on launchpad + * #6093: Rename CodecDescriptor for AudioCodecFactory + * * #6089: fix warnings in make check + * * #6086: renamed codecs methods to audio_codecs + * * #6085: renamed codec related dbus calls to audio_codec + * #6065: Remove g_print from client, use DEBUG instead + * #6065: Add actions name for addressbook + * * #6085: renamed codecs* widgets/functions audiocodecs* + * #6065: Fix Addressbook runtime warnings + * #6065: Replace Codecs tab for Audio in account preference dialog + * #6065: Fix "transfert" typo + * #6065: Fix addressbook action runtime warning in uimanager + * * #6082: fixes make check by adding libcrypto libs to test + dependencies + * #6073: Rename plugin/addressbook folders for addressbook/evolution + in sflphone-plugins + * #6074: Removed AC_SUBST from configure.ac when using + PKG_CHECK_MODULE + * #6073: Fix sflphone-plugins package build + * #6073: Fix sflphone-common build + * #6065: Fix runtime gtk warning when initializing searchbar without + addressbook + * #6063: Fix mozilla-tellify gitignore + * #6063: Remove stream copy file using ifdef macro + * * #6012: fix make dist for sflphone-common + * #6063: Update .gitignore file + * #6058: Fix base64 encoding related warnings + * #6056: Fix SdpException handling + * #6055: Fix unknown pargma warning for gcc <= 4.5 + * * #5949: test gcc version before disabling unused-but-set warning + * #6054: Fix addressbook plugin compilation warning + * #6048: Fix uimanager static initialization + * #6046: Fix addressbook factory static initialization of member + addrbook + * #5979: Fix implicit function declaration warning + * #6042: Fixed discarding qualifier warnings in client + * #6041: Fix instant messaging unhandled case warning + * #5994: Implement set current addressbook name and search type in + addressbook plugin + * #5994: add rules for launchpad packaging of addressbook plugin + * #5994: Fix addressbook plugin configuration loading + * #6027: Fix addressbook enabled test from configuration + * #6027: No need of gnomedoc related macros in addressbook plugin + * #6027: Add NEWS file required for build + * #6027: Add addressbook plugin autogen.sh script + * #6027: Remove plugins from client + * #6027: Add sflphone-plugins folder at project's root level + * #5994: Move addressbook folder from contacts to plugin folder + * * #6011: removed unused Makefiles + * * #6010: remove unused headers + * * #5952: fix "string constant to char*" warnings + * * #6009 fixed warnings + * * #6003: finished cleanup of account classes + * * #6003, #6004: cleanup of account classes, defaultAccount no longer + global + * * #6000: fix memory leak of args object + * * #5998: removed using namespace std from networkmanager + * * #5998: removed "using namespace std" from ZrtpSessionCallback + * * #5998: removed using namespacestd from AudioZrtpSession.h + * * #5998: remove "using namespace std" from auriorecord.h and + MimeParameters.h + * * #5998: remove using namespace std in main + * * #5998: removed "using namespace std" from logger + * * #5949: test gcc version before disabling unused-but-set warning + * #5994: Installation of addressbook plugin + * #5979: Implement codec full addressbook search from plugin + * #5979: Implement addressbook factory and plugin + * * #5981: unused webwidget removed + * #5966: Account config synchronization fix (for stun) + * #5954: Handle media name exception + * #5954: Fix audio codec name display in client + * #5954: Clean up getSessionMedia methods + * * #5957: getRecordingSmplRate returns a value + * #5954: Clean up getCurrentCodec methods + * * #5950: remove "converting to non-pointer type 'int' from NULL" + warnings + * #5915: Full gain control version + * * #5949: remove more unused variable warnings + * * #5949: remove unused/unused-but-set variable warnings + * * #5949: show_preferences_dialog returns a success value + * * #5946: cleanup of include directives, undefined function + * * #5515: comment out SSLv2 calls in pjsip + * #5915: Implement different slope for attack tme and release time for + gain control + * #5915: use only one input signal for gain control (removed output + buffer) + * #5921: Fix no audio after holding a conference + * #5916: Add gaincontrol files + * #5916: Implement FFMPEG/CCRTP video streaming prototype + * #5903: Fix call transfer during a conference + * #5915: implement rms detector, first order averager, limiter for + gain control + * #5914: Fix call transfer when no notification request is required + * #5899: Fix conference right-click segfault + * #5884: temporary fix segfault in pjsip memory pool + * #5883: Fix compilation issues on maverick and lucid + * #5755: Fix fedora 15 compilation without patching ccrtp + * [#5855] Make echo canceller optional + * #5855: Fix echo suppression activation/deactivation + * #5855: Implement pjsip echo canceller + * #5814: Speex initialization function uses samples, not bytes + * #5814: Test using more unbalanced signals + * #5814: Fix buffer size for long echo length or long echo delay + * #5814: Adjust level for echo cancellation at runtime + * #5814: Process noise reduction before echo cancelling + * #5814: Implement speex post echo canceller processing + * #5814: Dump echo cancel file to disk + * #5814: Add parameters for echo cancel + * #5809: Add configuration parameters + * #5809: Implement speex echo canceller in audio rtp session + * #5814: Code cleanup + * #5814: Fix conf creation with several incomming ringing calls + * #5814: Fix conf creation segfault when dragging a call on hold on a + ringing call + * #5809: Added unit test for echo cancellation and implemented + "process" virtual method + * #5709: Add always recording option in configuration + * #5709: Add always recording option in audio conference panel + * #5709: Add core functionnality for always recording (missing config + options) + * #5769: Fix conference participant handling (detach/attach) and hold + actions + * #5747: Fix recording icons and state for conference when adding new + participant + * #5769: Code cleanup + * #5769: Fix hangup unsent calls + * #5769: Fix remove/add additional participant to conference + * 5769: Several fixes concerning confererence handling + * #5769: Fix compilation error + * [#5769] Fix audio streams binding in main buffer + * #5769: Removed access to audio mixer from audio layer + * #5765: Fix audio crash for illformated wavefiles + * #5765: Add maximum iteration for finding fmt and data "chunck" + * #5589: Fix compilation of libnotify under + * #5757: Fix abort signal when receiving INFO + * #5747: Add usersDetached.svg + * #5747: Handle offhold action for recording conference + * #5747: Fix off hold action for conferences + * #5747: Implement update conference in record action in calltree + * #5747: Add new icons for recording conferences + * #5747: Add recording state for conferences + * [#5738] Remove getAudioDriver call from manager (replace by + _audiodriver var) + * [#5738] Refactor mutex protecting audiolayer + * [#5737] Fix HD conference recording + * [#5730] Fix start audio session after changing sampling rate + * [#5714] Fix enter keyboard event for addressbbok and history + * [5695] Fix addressbook combo box update when no addressbook selected + * [#5695] Fix addressbook initialization and search bar update + * [#5695] Add mutex for books_data in addressbook to protect async + calls + * [#5695] Get back addressbook open from uri + * [#5695] Fix absolute addressbook URI for local addressbooks + * [#5695] Implement libebook 3.0 interface + * [#5571] Better logic for hangup (for case where call have not been + sent yet) + * [#5571] Update error handling in voip links + * [#5571] Fix compile time warnings + * [#5696] Fix installation dependencies for Natty + * [#5669] Add mention that sflphone.org is for testing only + * [#5693] Add natty in teh dput.conf file + * [#5690] Remove not useful logs + * [#5670] Use dynamic payload type for rtp dtmf + * [#5668] Clean up sflphone configuration logging + * [#5668] Fix hook checkbox configuration update + * [#5666] Fix unit tests + * [#5666] Manage event subscription + * [#5666] Emit bye request when subscription is terminated + * [#5666] Bye request should be sent after event subscription + notification is done on transfer + * [#5666] Make reinvite method static (to be called in pjsip + callbacks) + * [#5666] Hangup Call in manager for AccountNULL and IP2IP + * [#5589] Use PKG_CHECK_MODULE for every client's dependencies + * [#5623] Enlarge initial size of pjsip memory pool for calls (16k) + * [#5564] Fix audio recording resampling for g722 + * [#5571] Move attribute handling for onhold/offhold actions in SDP + session + * [#5571] Codec negotiation refactored and unittested + * [#5571] Implement tests + * [#5571] Implement pjsip negociator + * [#5571] Fix unit tests + * [#5571] Add Fmtp.h to repository + * [#5571] Integrate mime types and codec factory + * [#5571] Handle exception when SDP negotiation fails + * [#5570] Add sflphoned-sample.yml in repository + * [#5564]: Implement stereo to mono mixing for rigntone + * [#5342] Update audio stream initialization + * [#5514] Restore test ni historytest suite + * [#5514] Fix + * [#5514] Disable test_create_history_path + * [#5514] use pulseaudio in sample config file + * [#5514] Fix test: load history from file + * [#5514] Do not use X + * [#5513] Make unit tests compile successfully + * [#3947] Enable unit tests in Jenkins + * [#5454] Fix build system to handle new version number + * [#5454] Update languages from launchpad + * [#5454] Add --without-celt in OpenSuse build service + * [#5454] Change version number + * [#5331] Added first SDP session tests + * [#5273] Update nightly build version tags to conform dpkg rules + * [#5211] Refactor send register method for iaxvoiplink and + sipvoiplink + * [#3950] Remove call being transfered from calltree + * [#5211] Use appropriate memory pool for transport selector + * [#5211] Fix strict aliasing rules warning in pjsip + * [#5211] Bring back pjsip shutting down sleep to 1000 ms + * [#5211] Fix registration callback segfault when closing the + application + * [#5211] Use the dialog memory pool for Route header in INVITE + request + * [#5211] Add temporary memory pool for findLocalAddressFromUri and + findLocalPortFromUri + * [#5211] Use individual memory pool for dtmfs + * [#5211] SipVoipLink refactoring + * [#3950] Attended transfer for conference calls + * [#5284] Fix DNS resolution for Route with specified port number + * [#5284] Some code cleanup + * [#3947] Fix typo in hudson script + * [#5284] Added sip route to REGISTER, INVITE, BYE request, plus DNS + resolution + * [#5266] Use RTP dtmf as default + * [#5284] Added pjsip_process_route_set after setting routes in regc + structure + * [#5286] Fix parsing error due to long configuration file (removed + max event) + * [#5286] Fix false test in configuration emmiter + * [#5286] Code cleanup + * [#5286] Updated exception handling in configuration system + * [#4969] Fix put SRTP call on hold + * [#3950] Add debug messages + * [#3950] Ability to perform an attended transfer + * [#5276] Fix initialization problem in g722 + * [#3950] Add replace header in SIPVoIPLink::transferWithReplaces + method + * [#3950] Implemented attended method in SIPVoIPLink + * [#3950] Cleanup transaction request received callback + * [#3950] Implement dummy attended transfer in gnome-client + * [#5249] Fix audio samplerate update algorithm for g722 + * [#5249] Fix uninitialized variable used in conditional jumps + * [#5249] Fix conditional jump error in audiolayer (uninitialized + value) + * [#5267] Use autoconf 2.65 as a requirement (instead of 2.67) + * [#5267] Restore manual pjsip configuration and compilation + * [#5267] Autodetect celt version (0.9.1, 0.7.1) + * [#5267] Fix deprecated macros in gnome client configure.ac + * [#5267] Update configuration for libcelt-dev + * [#5267] Fix build autoconf and automake + * [#5227] Deactivate automatic call to astyle after compilation + * [#5242] Hangup every calls before leaving + * [#5237] Will now nightly-build for natty, Karmic deprecated + * [#5229] Use inner class for rtp thread instead of inheritance + * [#5211] Move mainbuffer unbind call in rtp final method + * [#5211] Initialize sip call memory pool using 16 kb + * [#5211] Use call memory pool in session reinvite + * [#5211] Add debug messages + * [#5211] Use and internal pool for calls + * [#5211] Reduce pjsip memory pool usage for stateless error messages + * [#5211] Refactor call deletion + * [#5212] + * [#5208] Refactor codec management for accounts + * [#5168] Remove printf from codec's encode & decode method + * [#5168] Fix celt compilation on launchpad + * [#5168] Fix sflphoned compilation warnings in audiocodec.h + * [#[#5168] Must keep the g722 specific RTP rate to avoid incoming + packet timeout + * [#5168] Fix static/dynamic payload rtp session update + * [#5168] Throw SIPVoipLink Error if codec not instantiated in new + outgoing call + * [#5168] Fix dynamic/static codec payload type ambiguity + * [#5169] Fix doubled IP2IP profile when no config file + * [#4867] Add gtkinfobar in configuration panel + * [#4867] Disable input/output/ringtone selection when using default + alsa plugin + * [#4952] Patches for possible buffer overflows + * [$4885] Fix schemas problem + * [#4885] sflphone-client-gnome.schemas not present during build + * [#4885] Add gconf shemas directories in opensuse build system + * [#4885] Add file/folder ownership for opensuse-factory build system + * [#4906] Fix opensuse-factory build + * [#4885] Update name dependency for libedataserver + * [#4885] Fix non-void function without return in dbus-c++ + * [#4895] Update language translation + * [#4896] Update session timestamp when updating media + * [#4896] Reapply RTP hack for G722 payload type + * [#4896] Update recording sampling rate when updating codec + * [#4897] Save codecs in config for each configuration changes + * [#4895] Do not save config when sflphone quit + * [#4885] Update date for copyright + * [#4885] Deactivate siptest that require more than one sipp instance + * [#4879] Remove inmcoming call notification from IAX + * [#4885] Some cleanup + * [#4874] Add setCancel immediate/deffered for ost::Thread + * [#4879] Fix incoming call notification + * [#4878] Set keyboard focus on searchbar when selecting addressbook + * [#4874] Fixed compilation warning + * [#4874] Fixed compilation warning in sipvoiplink + * [#4874] Fix compile time warning in RTP record handler + * [#4874] Fix conditional jump in SDP + * [#4874] Fix conditional jump based on uninitialized value + * [#4874] Store call id within rtp thread context + * [#4874] Fixed conditional jump based on uninitialised value in + conference + * [#4871] Fix default account fetching + * [#4870] Delete RTP session when Refusing an incoming call + * Restore IP to IP call + * [#4857] Fix audio codec negotiation problem + * [#3947] Adjust ressources allocated to compilation + * [#3947] Disable unit tests in Hudson + * [#4305] Free mutex only when really quiting SFLphone + * [#4859] Update copyright to 2011 in every source file + * [#3218] Character '.' stripped by the caller engine + * [#4854] Fix typos, desktop entry + * [#4847] Apply RTP modification to ZRTP session + * [#4852] Update Karmic and Lucid dependencies + * [#4852] Add Libedataserver and libedataserverui as gnome client + dependencies + * [#4852] Add authentication mechanism for EDS + * [#4851] Fix segfault when closing pulseaudio layer too rapidly + * [#4808] Some otehr cleanup + * [#4808] Made some cleanup + * [#4808] Added mutex in rtp session for codecs and noise process + * [#4847] Update audio processing when updating RTP media + * [#4842] Add support for linking with gold/ld --no-add-needed + * [#4808] Make update g722 related static/dynamic payload logic + * [#4827] Upper limit on the number of contacts to import from EDS is + hard-coded to 500 + * [#4808] Fix put call on/off hold + * [#4808] Implement early RTP start for incoming calls + * [#4808] Audio stream is no longer start within RTP session. + * [#4808] Removed coupling between audio layer and and RTP session + * [#4702] Start audio rtp session as soon as it is created + * [#4702] Init timestamp to 0 + * #4702: Send RTP packets immediately, no need of outgoing queue + * [#4784] Update dbus-c++ version from gitorious + * [#4702] Update RTP timeouts + * [#4702] Lengthen RTP timeouts + * [PATCH] Fixed compatibility with old libtool versions. + * [PATCH] Accept older libebook (Maemo 5 has 1.4.2) + * [PATCH] Fixed double-free error in preferences dialog + * [PATCH] Fixed building of sflphone-common on Maemo5 + * [PATCH] Improved Gnome client initialization error handling. 1. It + no longer segfaults when sflphoned isn't available. 2. User is + provided with GUI error dialog. + * [PATCH] Improved autogen.sh scripts 1. They do not require bash + anymore 2. Added workaround for Debian bug #565663 3. Replaced + manual autotools invocations with single autoreconf call 4. Non-zero + return status on failure + * Revert "[#4468] libtool <= 2.2 doesn't have LT_INIT macro so + AC_PROG_LIBTOOL should be used instead." + * Revert "[#4468] Libebook 1.4 is sufficient" + * Revert "[#4468] Apply big path on dbus communication system" + * [#4468] Apply big path on dbus communication system + * [#4468] Libebook 1.4 is sufficient + * [#4468] libtool <= 2.2 doesn't have LT_INIT macro so AC_PROG_LIBTOOL + should be used instead. + * [#4639] Fix determining default addressbook if this property is not + set in gconf + * [#4639] Fix memory leaks in Addressbook + * [#4637] Fix opening default addressbook at sflphone init + * [#4622] Free yaml events while parsing configuration file + * [#4623] Fix conditional jumps based on uninitialized variable + * [#4622] Fix leaks in yaml serialization engine + * [#4616] Fix addressbook warnings + * [#4514] Adjust RTP timestamp + * #4527: Rename Karmic libyaml and Celt package in debian control file + * #4495: Rework addressbook opening loop + * [#4524] Increment RTP count when sending data + * [#4524] DO NOT start RTP session twice + * [#4367] Use PKG_CHECK_MODULE for celt + * [#4367] Fedora package celt as celt (not libcelt) + * [#4367] Astyling + * [#4367] Update .po files + * [#4367] Fix segfault in gensin + * [#4354] Make celt a direct dependency on launchpad opensuse build + service + * [#4367] Make celt a required package, option --without-celt valid + * [#4367] Fix zrtp timestamping error + * [#4367] Fix audio zrtp timing + * [#4367] Dispatch ZRTP packets + * [#4367] Fix segfault when unloading account map + * [#4367] Fix zrtp session + * [#4367] Implement on packet receive + * [#4367] use symetric audio rtp session, not dual + * [#4367] Reduce packet receive/sent timeout + * [#4367] Reduce RTP timeouts + * [#4367] Move speaker data receive + * [#4367] Move speaker data receive + * [#4367] Move receive speaker data method + * [#4367] Remove debug in rtp session + * [#4367] Fix g722 codec clock rate + * [#4367] Fix noise suppression initialization + * [#4367] Fix segfault in RTP mic fadein method + * [#4367] Refactor mic data encoding in rtp session + * [#4367] Implement RTP main loop + * [#4367] Fix compilation problem + * [#4367] Fix AudioRtpclass using TRTPSessionBase + * [#4367] Fix AudioRtpSession putDtmfEvent shadowing + * [#4367] Fix AudioRtpSession putDtmfEvent shadowing + * [#4367] Refactor RTP session (phase 2) + * [#4367] Refactor RTP session (phase 1) + * [#4367] Remove Redeclaration of SymetricAudioRtpSession in + rtpfactory + * [#4265] Add continue statement in for loop for invalid addressbook + * [#4261] Makes addressbook initialization more robust + * [#4257] Add maverick in build system + * [#4233] Add sdp related unit tests + * [#4233] Add condition and signal in two incoming call test + * [#4243] Fix segfault in AudioSrtpSession + * [#4243] Fix memory leak in AudioSrtpSession + * [#4243] Make audio srtp optional in for incoming call + * [#4243] Add boolean variable to make sure remote crypto context + initialized only once + * [#4243] Add documentation to AudioSrtpSession + * [#4243] Use 80 bits authentication tags by default + * [#4243] Init audio srtp remote crypto context in + call_on_media_update + * [#4243] Move SDP negotiastion in mod_on_rx_request + * [#4243] Implement initLocalCryptoInfo to be called at different + momment + * [#4243] Init init local crypto context in when initializing audiortp + * [#4243] Change key length according to sdes negociation + * [#4243] Associate callid to accountid for incoming calls + * [#4242] Fix no SDES keys in IP2IP calls + * [#4242] Fix no SDES keys in IP2IP calls + * [#4233] Test for call on/off hold + * [#4233] Add two incoming call test + * [#4233] + * [#4233] Add 2 outgoing simultaneous call unit tests + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Fri, 30 Sep 2011 13:51:04 -0400 + +sflphone-common (0.9.7~rc1~ppa1~SYSTEM) SYSTEM; urgency=low + + ** 0.9.7~rc1~ppa1~SYSTEM ** + + * [#2462] Set explicitly the transport on incoming call too + * [#2462] fix typo + * [#2462] Use different address for SDP and call IP + * [#2462] Use published address in SIP-SDP + * [#2181] Fixed changelog files + * [#2181] Updated spec file + * [#2402] Fix pointer to int conversion warning (atoi) + * [#2402] Remove daemon warnings, make indent + * [#2459] Make sure the stream is opened when the call is answered + * [#2402] Add conference related picture in documentation + * [#2443] Not much ... + * [#2399] Fix dialing display problem + * [#2450] Fix incoming call already in conference crash + * [#2399] Display peer name on the first line and peer number on the + second + * [#2450] Handle 403 FORBIDDEN when refused + * [#2447] Bind offHold/onHold actions to button in gtk client + * [#2447] Bind hangup action to button for conference + * [#2447] Add conference action in gtk client's ToolBar + * [#2381] Disable the password hashing in config file + * [#2402] Cleanup + * [#2366] Set callback to null when deleting Pulseaudio streams + * [#1313] Fix main buffer unit test + * [#1313] Fix audio layer unit test + * [#2315] Hide pw in security tab, display when editing, sync with + basic tab + * [#1313] UnitTest change AudioRtpSession for AudioSymetricRtpSession + instance + * [#2402] Code cleanup + * [#2444] Add debug to catch occasional crash when loading client's + config + * [#2444] Add debug info to catch occasional crash when loading config + dialog + * [#2402] Restore Call menu translations + * [#2403] Use the published address if checked in GUI + * [#2442] Add protection test in sdp + * [#1841] Reapply pjsip patch concerning DNS SRV resolution + * [#2384] Tags incoming call as direct SIP call, if applicable + * [#2402] Change the monkey face + * [#2315] Enable user to display password in clear text + * [#2434] Force optimization level at 2 + * [#2284] Fix dbus_get_all_ip_interface compilation warnings + * [#2431] Popup main window on incoming if applicable + * [$2402] Fix simple warnings + * [#2402] Fix implicit variable init order in LibraryManagerException + * [#2402] Fixing implicit variable initialization warnings in + AudioRtpSession + * [#2402] Revert atoi change, fixing codec list doubled entries + * [#2402] Fix gpointer to gint conversion + * [#2402] Fix pointer casting to integer different size warning in + codec list + * [#2402] Fix warning discarting qualifiers from pointer target + * [#2402] Fix gtk tree view assignement from incompatible type warning + * [#1669] Fix audio recording folder utf-8 non compatibility issue + * [#2414] Clean up debugs + * [#2414] Use transport set in iptoip Account and update it frm + preference + * [#2348] Use macro N_() to mark ui.xml strings as translatable + * [#2414] Rename getSipAddress/setSipAddress functions + * [#2407] Fix volume controls display + * [#2407] Fixes dialpad + * [#2383] Set ip to ip config when clicking apply button + * [#2404] Update call-to script - Maxime Chambreuil + * [#2405] Client handles unknown call in current state as well + * [#2383] Add DBUS signal to send IPtoIP local address and port as + string + * [#2383] Add Ip to IP config change apply call back + * Clonflict + * [#2402] Code cleanup + * [#2383] Do the same for IPtoIP (init localn ip with first in the + list) + * [#2383] Use first interface in the list if local addresss is not + defined + * [#2403] Clean up unuseful addresses/ports + * [#2403] Use the IP profile SIP port as global SIP port + * [#2383] Fix dbus_get_all_ip_interface warnings + * [#2383] Take into account sameAsLocal when loading published address + * [#2383] Tsake into account sameAsLocal option when saving published + address + * [#2383] Update local ip address in ip to ip config + * [#2383] Save ip 2 ip local port in config + * [#2406] Update toolbar at startup + * [#2284] Remove redefinition warnings + speex warnings + * [#2383] Fix security table in account config + * [#2383] Save ip 2 ip network interface parameters in config + * [#2403] Restore sip transport selector + * [#2383] Fix filling the Localt IP Address on account creation + * [#2383] Fix Gtk-Critical when checking STUN + * [#2383] Fix reopening account configuration display issue + * [#2383] Load IPtoIP local address and port in preference iptoiptab + * [#2383] Add LocalAddress and Localport in Preference IpToIp tab + * [#2403] Use the address and port associated to the account as often + as possible + * [#1753] Removed pjsip generated files + * [#1753] Removed remaining milenage lib references + * [#2383] Add _publishedSameasLocal variable in sipaccount + * [#2383] Add PUBLISHED_SAMEAS_LOCAL variable in config + * [#2383] Fix stun set active or not when opening config + * [#2181] Added RPM 64bits dbus patch + * [#2402] Code indentation + * [#2313] Force $(HOME).cache directory creation at startup + * [#2383] Separate network interface and published address in account + config + * [#2400] Change dbus service installation path to libdir + * [#2382] Move TLS related published address options in security tab + * [#2382] Indent accountconfigdialog.c + * [#2181] Install libdbus-c++ in $pkglib instead of $lib + * [#1753] Remove ILBC code and disable it by default in the configure + * [#1753] Remove milenage directory + * [#2382] Fix switching interaface instabilities + * [#2396] Save local ip in account creation wizard + * [#2284] Remove warning on hold + * [#2387] Fixes history searching and filtering + * [#1215] Add samplerate display in the GUI + * [#1663] Voicemail icon reflects voice messages + * [#2395] Fix account registration ( specifically with callcentric) + * [#2386] Strip "sip:" on incoming call, fixing history call back + * [#2181] Updated spec files + * [#1215] Display codec name in calltree instead of status bar + * [#2390] Move back nbCalls and stopStream higher in refuseCall + * [#2392] Fix ringtone during call in IAX + * [#2391] Stop audio streams when there is 0 calls only + * [#2391] Add debug when call state is not valid + * [#2390] Clear returns in IAXvoipLink::sendAudioFromMic() method + * [#2380] Fixing IncomingCallNotification not regular + * [#2339] Query conference at client startup + * [#2339] Working conference querying at startup + * [#2339] Add conference in call tree + * [#2339] Primitives to query conferences at client startup + * [#2320] Add account selection in history + * [#2355] Temporary solution: do not delete pointer when removing + account + * [#2380] Change algorithm in AudioRtp to trigger an + IncomingCallNotification + * [#2274] Comment sdebug in MainBuffer flush method + * [#2274] Add flushMain() in ManagerImpl::addStream + * [#2274] Add getBufferID() method in ring buffer + * [#2274] Fix warning, comment debug in ringbuffer's flush method + * [#2274] Use AudioLayer flushMain() and flushUrgent() in ALSA + * [#2274] Clean up unused variable warning + * [#2274] Protect minbudffer pointer on flushing + * [#2274] Fix playATone method which writing empty buffer in urgent + ringbuffer + * [#2274] Use audio layer flushUrgent and flushMain in createStreams + * [#2274] Use flush audio calls from audiolayer + * [#2274] Flush when peer answered call + * [#2375] Flush main buffer in iax when answering a call + * [#2274] Parse displayname using c++ string method + * [#2375] Flush main buffer when off holding calls + * [#2375] Flush main buffer mon RTP startup + * [#2376] Use now Pulseaudio module-cork-music-on-phone + * Updated OSC packaging + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Fri, 20 Nov 2009 14:00:02 -0500 + +sflphone-common (0.9.7~beta~ppa1~SYSTEM) SYSTEM; urgency=low + + ** 0.9.7~beta~ppa1~SYSTEM ** + + * [#1933] Cleanup debug + * [#1933] Clean up debug + * Fix mic + * [#1933] Set the IAx format earlier + * [#1933] Move IAX sendAudioFromMic outside if (call) statement + * [#1933] Fix startstream when offhold in iax and add debug concerning + codec neg. + * [#2371] sflphone_notify_voice_mail: minor gettext message formatting + cleanup + * [#2371] select_account_cb: properly gettextize status message + * [#2371] show_account_list_config_dialog: properly gettextize status + message + * INSTALL: Minor tidyup of core install guide + * Add /sflphone-client-gnome/src/icons/Makefile to .gitignore + * [#2181] Updated OpenSUSE files (tmp) + * [#1933] Add debug for codec negociation for iax + * [#1933] Get rid of getMicAvail and getMicData in audiolayer (not + used anymore) + * [#1933] Add "audio codec not determined" error in IAX + * [#1933] Test flush data + * [#1933] Do not need to start audio stream in iax anymore + * [#1933] Protecting pointer + * [#2284] Remove more compilation/execution warnings + * [#2284] Cleanup debug in client, use DEBUG instead of g_print + * [#2284] Clean up uimanager + * [#2370] Remove warnings + * [#2366] Clean up other debug + * [#2366] Clean up debug + * [#2366] Call pa_xfree explicitely in writeToSpeaker + * [#2284] Remove address book warnings + * [#2365] Fixes bad cast + * [#2352] Fix continuous ringing when peer hangup and call not yet + answered + * [#2181] Added version support + * [#2181] Fixed some minor issues + * [#2360] Moved MainBuffer from AudioLayer to ManagerImpl + * [#2352] Makes getMainBuffer() everywhere + * [#2352] Use 50 sec latency on pulseaudio stream creation + * [#2352] Add alsa debug + * [#2359] Update repository documentation + * [#2354] Move pulseaudio disconnectAudioStream after stopping main + loop + * [#2352] Adjust nb byte copied in pulseaudio according to + writeableSize + * [#2352] Specify pulseaudio tlength parameters using pa_usec_to_bytes + * [#2322] Convert italian translation to UTF-8 + * [#2357] Fixes window size + * [#2357] Display only actionnable tool item + * [#2333] Update streams parameters + * [#2347] Use GNOME user settings for Menu and Toolbar appareance + * [#2349] Load/Save properly audio params + * [#2322] Update translations from Launchpad + * [#2181] Added Francois Marier script + * [#2350] Remove non-valid test + * [#2181] Updated launchpad packaging + * [#2333] Fix Pulseaudio Capture + * [#2333] Use pulseaudio ADJUST_LATENCY flag and ALSA RT-SCHEDULING + * [#2333] Pulseaudio Interpolate timing + * [#2333] Change (again) Pulseaudio settings to fit logiteck usb hdw + requirement + * [#2333] Adjust pulseaudio fragment size to 4096 (max sflphone's + frames per buffer) + * [#2284] Remove recurrent compilation warning (g++ linker problem) + * [#2333] Safer Audiostream parameters + * [#2333] Fix alsa playback to reduce underrun + * [#2333] Better audiostream parameters + * [#2181] Updated version management + * [#2333] Exclusive test in playback loop + * [#2181] Updated build system + * [#2333] Less underrun with these value + * [#2333] Update playback audiostream parameters + * [#2333] Lengthen the audio buffer reduce number of underrun in + pulseaudio + * [#2333] Add ALSA recovery functions for underrun (begin) + * [#2333] Add pa_stream_trigger in pulse audio underrun callabck + * [#2048] Reduce prebuffering in pulseaudio (which affect incomming + calls' plbck) + * [#2316] Do not display any icons to the right on the history tab + * [#2333] Comment pa_stream_trigger in pulseaudio underrun + * [#2333] Modify pulseaudio streams parameters + * [#2318] Fix transfer tool button double signal + * [#2181] Updated + * [#2333] Fix ALSA ringtone + * [#2333] Flush all main buffer before starting audio + * [#2333] Open/Close Alsa thread between calls while there is no audio + * [#2333] Add debug message and test condition on starting playback + and capture + * [#2181] Fixed gnome client makefile + * [#2181] Updated + * [#2308] Remove getTelephoneTone debug + * [#2308] Change plughw for default in ALSA + * [#2308] Oups, forgot to change function name in audiolayertest.cpp + * [#2308] Cleanup in pulseaudio code (debug, function name) + * [#2308] Fix pulseaudio stream closing assertion failure + * [#2308] Moved pulseaudio mainloop locking from AudioStream + disconnect stream + * [2308] Fix latency at the beginning of a call, when playing DTMF and + wehn starting tone + * [#2181] Updated karmic + * [#2317] [#2319] Fix address book toggle button contextual behaviour + * [#2308] Stop stream when refusing a call + * [#2308] Stop pulseaudio stream when peer hungup + * [#2308] Fix tone and ringtone + * [#2312] Display the STUN entry widget when opening the tab + * [#2308] Implement two different callbacks for capture/playback in + pulseaudio + * [#2309] Open/close pulseaudio connections in startStream/stopStream + * [2308] Leave pulseaudio stream running, do not cork/uncork them + anymore + * [#2295] Set gtk file chooser to None if nothing is set in + configuration + * [#1976] Add codec and conference documentation + * [#2209] Fix recording in regard of resamling + * [#2297] Update .gitignore + * [#2297] Update translation files + * [#2297] Add reference to our coding standards + * [#2297] Remove old docbook code + * [#2296] Reinit tls account settings after modification + * [#2253] Add DcBlocker class to remove capture's dc offset + * [#2034] Fixes for TLS transport to initialize + * [#2284] Add silent build rule + client clean warnings + * [#2274] Fix unserialize history items in cilent at startup + * [#2274] Complete display name parsing and displaying + * [#2274] Parse the Display Name in sip INVITE message + * [#2050] Fix capture volume control in ALSA + * [#1970] Volume controls disable when using pulseaudio + * [#1970] Disable volume controls when using pulseaudio + * [#2277] Fix direct ip2ip ZRTP enabling/disabling in ip2ip + preferences + * [#2181] Added launchpad debian files + * [#2181] Added spec files for OSC + * [#2274] Set display name for "Contact" sip header as the hostname + * [#2181] Fixed daemon issues + * [#2181] Fixed gnome client issues + * [#1976] Remove warnings - need to fix the transfer + * [#2006] Add init is_rec variable in ManagerImpl + * [#2006] Update codec display on call selection + * [#2006] Restore double click actions in history and contact calltree + (GTK) + * [#2176] use XDG_CACHE_HOME when initializing sfl.zid file + * [#1976] Fix calltree switching from history + * [#2209] (Re)Fix cache for zid + * [#2209] Clean up debug messages + * [#2209] Clean debug messages + * [#2209] Fix trasnfering a call during a conference + * [#2209] Speex decode must return the number of bytes + * [#2209] Change frameSize speex 32kHz + * [#2209] Fix speex codec framesize + * [#2209] Reinit converterSamplingRate in RTP sessions + * [#2209] Change speex ultra wide band framesize + * [#1747] Add pixmap data + * [#2252] Fix Receiving a server error 488 crashes the callee + * [#2209] Fix iax low rate packate sending + * [#2209] Clean up debug messages + * [#2209] Add resampling changes for IAX + * [#2209] Clean up resampling code + * [#2209] Fix latency introduced by pulseaudio + * [#2209] Fix initialization of mainbuffer's internal sampling rate + * [#2176] Fix upsampling buffer size in audiolayer + * [#2209] Add dynamic converter sampling rate in audiortp sessions + * [#1747] Fixes runtime warnings + * [#1747] Remove from repo + * [#1747] register our icons to be used as stock icons + * [#2209] Fix number of byte in alsa's write to speaker + * [#2209] Fix putting non-resampled data in RTP's mainbuffer + * [#2209] Add alsa resampler + * [#2209] Add a samplerate converter in PulseLayer + * [#2209] Add mainbuffer's internal sampling rate and flushall method + * [#2176] Add mainbuffer stateInfo debug method + * [#2209] Resampling is optimal using SRC_LINEAR not SRC_FASTEST + * [#2176] Remove debug recordings + * [#2176] Fix Holding a conference participant on new calls + * [#2224] Add confID in callable object + * [#2176] Fix putting onhold a call participating to a conference when + pressing new call + * [#2176] Reset auidio buffers when adding streams (rtp, audiolayer) + * [#1976] Use xml to describe toolbars - Add a naviguation toolbar + * [#2176] Remove conference default_id in joinParticipant + * [#2176] Display error message in alsa's snd_pcm_avail_update call + * [#2176] Alsa mic avail data debug + * [#2176] Add some debug message for mic loss problem + * [#2176] Flush mic ring buffer when offholding a call + * [#2176] Reset ringbuffers' readpointer when adding main participant + * [#2176] Fix getAvailData algorithm + * [#2176] Reset ringbuffer's readpointer when adding a new participant + to a conference + * [#1744] Regex object renamed to Pattern. Previous attempt at + providing + * [#2176] Fix detach main participant problem when adding new one + * [#1976] Use right domain to translate + * [#1976] Add xml menu description + * [#2176] Store a list of confernece participant in client + * [#2176] Fix add participant, joinparticipant methods + * [#2181] Do not install dbus-c++ headers + add return value + * [#2176] Fix minor call handling instabilities + * [#2174] Fix incoming IP call contact address + * [#2211] Add test to protect NULL pointer + * [#1163] Add Advanced account configuration section + * [#2176] Add some usefull comments and debugging info + * [#2176] Add conditions to display security icons in conference + * [#2176] Fix detaching one participant while keeping communication to + others + * [#2176] Reenable userActive.svg in call tree + * [#2176] Make user active blue (not red) + * [#2176] Fix user active picture + * [#2176] Fix "hidden" merge conflict in sipvoiplink + * [#2176] Remove iax audio stream on peer hungup + * [#2174] Multiple UDP transports functional (TESTED with 2 accounts + and 3 calls) + * [#2176] Fix fix audio stream binding in iax + * [#2174] Create a default UDP transport + use tp selector for dialogs + also + * [#2176] Register iax audio stream in mainbuffer + * [#2176] Fix getAudioCodecName in IAXvoipLink + * [#2176] Fix iax account init + * [#2176] Handle multiple account using the same sip transport + * [#2165] Add .png files + * [#2176] Small fixes concerning dtmf + * [#2176] Fix make uninstall in codecs + * [#2174] remove stund makefile generation + * [#2176] Add conference lock + * [#2174] Add transport selector for multiple accounts + * [#2176] Change userActive picture from red to blue + * [#2176] Fix security pixbuff in calltree + * [#2176] Replace sfl.zid in .cache/sflphone instead of .sflphone + * [#2176] Fix add call description + * [#2176] Remove detach button from toolbar + * [#2176] Fix calltree call description state and state code in + conferences + * [#2176] Fix pulse audio double free + * [#2176] Fix conference selection + * [#2174] Clean up - remove stun settings in client network + configuration panel + * [#2174] Remove voviva stun code + * [#2174] Rsolve STUN with pjsip - DO NOT WORK + * [#2165] Add user svg + * [#2165] Debugging sip call failed + * [#929] Link against uuid if installed + * Oops + * Fixed bugs related to libsexy (with GTK < 2.16) + * [#929] Remove uuid-dev dependency in the core + * [#2165] Debugging no negociated codecs at communicatio start + * [#2165] Fix calltree bug (gtktreestore instead of gtkliststore) + * [#2165] Fix several merge problems + * Updated opensuse packaging script + * [#1163] Add missing figures + * [#1163] Update INSTALL file + * [#2165] Fix IAX + * [#2165] Add recordabe interface + * [#2165] Finish recording refactoring for call (not for conference) + * [#2165] Enable speaker recording for two different calls + simultanously + * [#2165] Implement call recording using the Recordable interface + * [#2165] Add get and set to AudioLayer's audio recorder + * [#2165] Add class recordable from which inherit call and conference + * [#2006] Fix G722 and Speex 8khz codec conferencing + * [#2006] add recording of audio buffers + * [#1163] Add general settings section + * [#1163] Fixes makefile error + * [#2006] Fix some minor issues + * [#2006] Drag a conference call on another conference call + (difference conferences) + * [#2006] Fix dragging a conference on itself + * [#1744] Integrating some of the needed regular expression patterns + in order + * COmplete call features + * [#1744] Added support for named subgroup in the Regex object. Also, + new + * [#1744] Adds thread safety features, compile() and setPattern() + methods to the Regex class. + * [#1744] Fix inconsistency in the finditer method from the last + commit. + * [#1744] Added regex pattern object built on top of libpcre. To be + used + * [#1744] Initial commit towards implementing RFC4568. Unimplemented + in the + * [#2157] Hide "security" and "advanced" tabs for IAX under account + * [#1163] Add call features section + * [#2006] Add joinConference capabilities + * [#2006] Add dbus joinConference signal + * [#2006] Drag a conference call onto a conference to add it + * [#1163] Add addressbook section + * [#2006] Drag a conference call onto a single call to create a + conference + * [#2006] Expand rows automatically + * [#2006] Add minimal multiple conference handling + * [#2006] Add atached/detached conference icons + * [#2006] Add function processRemainingParticipant + * [#2006] Deep refactoring, fix hangup bug + * [#1163] Update documentation - Accounts part + * [#1976] Integrate user doc to gnome client build system + * [#2122] Remove double inclusion in dbus-c++/src/Makefile.am + * Remove pjproject version number + * [#2006] Fix peerHungup + * [#1976] Make Yelp accessible from the GNOME client (need to install + the sflphone.xml first) + * [#2006] Fix multiconferencing hangup + * [#2006] Fix hangup calls in a conference + * [#2150] Make IAx2 reappear + * [#2006] Fix detach participant on multiple call + * [#2006] Can remove rining call from a conference + * [#2006] Reinit confID when removing a participant + * [#2006] Remove get isCurrentCAll in hangup/peerhungup (SipVoipLink) + * [#2006] Fix refuse call + * [#2006] Fix answerring incoming call + * [#2006] Refactor conference's participant list + * [#2101] Re-integrate test compilation in main build system + * [#2101] Make the test directory compile + * [#2136] Restore history functionality + * [#2006] Fix binding main participant to himself + * [#2006] Fix add current/incoming/onHold participant to an existing + conference + * [#2006] Fix add incoming calls to an already created conference + * [#2006] Fix remove stream + * [#2006] Fix detachParticipant/removeParticipant switchCall ids + * [#2006] Fix adding a call in conference having state "CURRENT" + * [#2006] Remove/add main participant from conferences + * [#2006] Hold/unHold conference + * [#2006] Detach a partcipant from drag n drop + * [#2006] Hangup a conference + * [#2006] Add hold/unhold conference dbus messages + * [#2034] gtk-ui fix under the "basic" tab. + * [#2006] Fix dragging calls on conference calls + * [#2006] Fix detach participant from a conference + * [#2034] Added default message is status bar under the account config + dialog + * [#2112] Fix a crashed caused when a non-md5 password was sent to + pjsip. + * [#2006] Detach participant by ID + * [#2006] Fix addParticipant method in managerImpl to handle + incoming/answered calls + * [#2006] Add addParticipant method in managerimpl and related dbus + messages + * [#2111] Added the ability to configure zrtp on sip.sflphone.org from + * [#2106] Fixed problem in the account assistant under gtk-ui. Also, + assistant.c + * [#2006] Fix dragging a conference call on another conference call + (same conference) + * [#1904] Small UI fix. Assistant was moved from "Call" to "Edit" + menu. + * [#1904] Fix a wrong label under gtk-ui. + * [#2034] Renaming and source code splitting. + * [#2034] Status bar added to account window to better reflect the + registration + * [#2006] Make calltree_remove_call recursive (for GtkTreeStore) + * [#1110] Small gtk-UI fix in the account window (alignment). + * [#2006] Fix remove conference, display children which are still + active + * [#2006] Recursive function call in calltree_update_call + * [#2006] Add multilayered capabilities to calltree (GtkTreeStore) + * [#2006] Implement remove conference in calltree + * [#2034] Now useless as Direct Ip calls settings moved under + Preferences. + * [#2034] Edit/add buttons were set insensitive all the time under + gtk-ui. + * [#1887] Information about the state of the current SIP call is + displayed + * [#2006] Add call tree remove callback + * [#2006] Fix create_conference function + * [#2006] Update conference_added_cb to add new conference to the list + * [#812] Added new tab under GTK-ui Preferences. Moving Direct Ip + Calls from + * [#2121] Disable temporarily test compilation + * [#2006] Fix conferencelist to handle conference_obj_t instead of + gchar + * [#2006] Add conference_obj structure + * [#2121] Update version + * [#2006] Fix conference selection + * [#2101] Use the new source tree to fetch the right object files + * [#2006] Add conference in calltree + * [#2006] Add Dbus signal conference added/removed/changed + * [#2006] Add getConferenceDetails call on dbus + * [#1904] Registration expire now appears as a spin box under gtk-ui. + * [#812] Fixing a segmentation fault caused by a non-existing account + ID + * [#2006] Add getConfList method over dbus + * [#2006] Add a conferencelist data structure in client-gnome + * [#812] Defaults value are now sent if a non-existing account is + requested + * [#2006] Add sflphone action sflphone_join_participant + * [#2006] Fix buffer read pointer problem deletion + * [pjsip] Attempt at fixing via header incompatibility with + Freeswitch. + * [#1797] forget something + * [#2006] Add call new state conferencing in deamon + * [#2006] Remove addParticipant method for conference, use + joinParticipant only + * [#1163] Update INSTALL documentation + * [#812] Msec/sec values were not taken into account. + * [#1797] Make pjproject-1.4 compile + * [#2006] Add Detach participant method + * [#2006] Dragndrop fully functional with INCOMING and HOLD call + * [#1797] Add pjproject-1.4 + * [#1797] Remove pjproject-1.0.3 + * [#2006] Get call state in conference related function + * [#2006] Add joinParticipant (conference) method in ManagerImpl + * [#2006] Add joinConference DBUS message + * [#2006] Store the previously selected call_id on dragndrop + * [#2006] Fix GValue pointer unref in selection callback + * [#2006] Store dragged call_id + * [#2006] Update drag_data_received_cb callback to manipulate CallIDs + * [#2006] Add dragndrop signals + * [#2006] Set calltree reordable + * [#812] Adds the ability to create a TLS listener in case the user + requests + * [#812] Adds the ability to configure local/published address from + * [#1883] Move switchCall in onHoldCall function + * [#812] Deals with the published address/port problem when + integrating TLS. + * [#1883] Switch call id in managerimpl when peerHungUp + * [#1883] Switch call id before hangup + * [#1883] Add usefull and permanent debug info for conference + cretion/deletion + * [#812] Fix various segmentation faults related to Direct IP kind of + calls. + * [#1883] Fix deletion of std::map elements using iterators + * [#2014] Add libzrtpcpp build dependency + * [#1883] Still some for loop test ambiguity (while loop instead) + * [#1883] Fix for loop initial test ambiguity (use while loop instead) + * [#1883] We must discard data in urgent ring buffer if data is get in + mainbuf + * [#1883] Fix availForGet same id for ringbuffer and readpointer + * [#812] Match "sips" as a Direct IP Call when the user enter a sip + uri + * [#812] Fix segmentation fault related to SIP URI creation. + * [#812] Towards integrating multiple tls listeners at the same time. + This + * [#1883] Add debug messages in conference and fix mainbufferTest + * [#812] gkt-ui fix. Private key must be fed as a filename and not as- + is. + * [#812] TLS integration within sipvoiplink and pjsip. Also, + configure.ac + * [#1883] Fix Alsa/Pulse mallocation + * [#1883] Fix data corruption in AudioRtp's micData buffer + * [#812] Full dbus integration for all the tls related options under + gtk-ui. + * [#1883] Fix memory leaks in audiortp session + * [#1883] Fix mem leaks in audio rtp + * [#812] Fix setAccountDetails where TLS_ENABLE was set to the value + * [#812] Small gtk-ui fix. + * [#811][#812] Small gtk-ui fix. + * [#812] Introduced a mechanism for configuration files that makes + possible + * [#812] New dbus bindings added. Also, configuration compliance was + enforced + * [#1881] Remove default buffer from MainBuffer (update unit-tests) + * [#1881] Add ring buffer read pointer tests + * [#1883] Fix issues in ringbuffer reader pointers + * [#2034] Implementing a new configuration dialogue for TLS transport + settings + * [#1883] Add some usefull debug and safety checks + * [#2028] Notify the client with libnotify when the zrtp negotiation + failed. + * [#811] Harmless no to throw an exception, an makes the application + less + * [#2028] A minidialog is showed to the user under sflphone-client- + gnome + * Removed useless file. + * Ignoring Makefile in src/widget + * [#2027] Fix segmentation fault when showMessage callback is called + after + * [#2026] keyExchange was set to ZRTP instead of "1" + * [#2024] Fix the wrong summary at the end of the assistant. + * [#1883] Fix mnagerimpl conference map insertion + * [#1883] Add Mutexes in MainBuffer + * [#811] Gtk ui was not presenting the right information about zrtp + for + * [#2023] security icons were not installed in sflphone-client-gnome. + * [#2021] Fix a mistake in the readme from sflphone-common that gives + wrong + * [#811] The current SRTP mode was not properly displayed for the + IP2IP + * [#1743] Re-implementation of the "automatically remove error dialogs + [...]" + * [#2017] [#2019] Fix the inability to dial a number and place a + registered + * [#811] Final re-integration of ZRTP support in the main branch from + 0.9.6 + * [#1883] Fix map insertion methods + * [#811] Combo box now is now set to the active key exchange method + * [#811] ZRTP options now configurable back again from the Gtk UI. + IP2IP + * Updated hostname for git clone + * [#1883] Add minimal functionalities to create a conference + * [#811] re-integration of all the methods and signals on dbus. + ManagerImpl + * [#811] Got out of a precarious position were nothing would compile. + * [#1976] Build documentation squeleton with docbook + * [#1883] Add sflphone-client "addParticipant" button for conference + * [#1994] Better organize the source directory structure. New + subdirectories + * [#1883] Add a simple Conference class + * [#1882] Use static audio buffer in Pulse and ALSA layer (instead of + malloc) + * [#811] First commit toward re-integration and refactoring of ZRTP + * [#1882] Flush RTP ring buffer before entering mainloop + * [#1882] Fixed MainBuffer::UnBinCallID() in case there is no + ringbuffer + * [#1882] Test (and fixe) high level conference and mixing + functionalities + * [#1772] Apply patch to compile on fedora (sent by Marcin + Zajączkowski <mszpak@wp.pl>) + * [#1882] Update Bind, unBind call_id in MainBuffer + * [#1959] This adds the ability to store password as an MD5 Hash in + the + * [#1538] Fixes rules compilation + * [#1930][#1931] Fixed a mistake (again) related to index and + credential count + * [#1753] Remove ILBC from pjproject - Hacks in pjsip + * [#1930][#1931] Credential was not selected properly using realm + * [#1882] Finilize multiple reading pointer in RingBuffer + * [#1538] Remove configure from autogen.sh to respect debian upstream + authors policy + * [#1773] Remove generated files from repo + * [#1791] Use XDG_CACHE_HOME to save pid file + * [#1791] Fixes path to save history + * [#1791] Fix debian installation scripts + * [#1930][#1931] Settings are now taken into account in the server. + * [#1882] Add ringbuffer default ring buffer pointer in methods + involving mStart + * [#1882] Add default ringbuffer pointer + * [#1882] Add RingBuffer multiple read pointer basic functionnalities + * [#1882] Fix MainBuffer flushData unit test + * [#1930][#1931] Ability to save and retreive the configuration from + * [#1882] Added Multiple CallID mapping to MainBuffer + * [#1791] Not much + * [#1791] If XDG env variables are not null but empty, use default + ones + * [#1791] Make XDG_CONFIG_HOME writable + * [#1930][#1931] Partial commit. Not working yet. Cannot delete + account + * [#1881] Fixed alsa capture latency problem + * [#1881] Fixed Alsa capture temporarily + * [#1930] [#1931] Partial unbroken commit providing the ability to + * [#1881] MainBuffer implemented in AudioLayer/AudioRTP + * [#1881] Add discard and flush unit-tests + * [#1881] Add discard and flush functionnalites to MainRingBuffer + * [#1881] Add availForGet in MainBuffer + * [#1881] Add availForPut function to MainBuffer + * [#1880] Remove AudioRTP* pointer from SipVoIP (reapered while + merging master) + * [#1881] Add a map between call id and coresponding ring buffer + * [#1855] Refresh pot file and upload on Launchpad + * [#1881] MainBuffe now robust to false ids on getData and putData + * [#1881] Fix big big big memory leak + * [#1881] Add getData and putData to mainBuffer + * [#1881] Unit-test basic ring buffer functionnaities + * [#1881] Add class MainBuffer and basic buffer creation unit-tests + * [#1880] Fix call transfer (step2) issues + * [#1880] Moved AudioRtp* pointer from SIPVoIPLink to SIPCall class + * [#1791] Add postinst script to keep user data when migrating + config/history file + * [#1797] Make pjsip compile + * [#1777] Code indentation + * [#1791] Use XDG_DATA_HOME and XDG_CONFIG_HOME for sflphonedrc and + history + unit tests + * [#1746] Useless space does not appear anymore when volume sliders + and + * [#1643] GtkCheckMenuItem is used instead of icons for elements in + the + * [#1110] [#1668] STUN parameters are now located in the preferences, + under + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Fri, 06 Nov 2009 11:23:15 -0500 + +sflphone-common (0.9.6-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6 ** + + * Documentation on echo test + * [redmine_down] codec names not displayed in total + * [redmine_down] crash when hanging up a dialing call because tries to + add it to history whereas no starttime + * [#1927] alternate every time screen changed to call history + * [#1886] clean code + * [#1886] debug messages when loading history removed + * [redmine_down] sflphone-kde icons + * [#1855] Update language files + * [#1502] Update version number + * [redmine_down] setHistory at close + * [#redmine_down] Handle PJ_DECLINE_SC as failure + * [#1923] Fix segmentation fault when adding a new account + * [#1923] Check on iterator before setting the config + * [#1904] Added mnemonic to tabs in sflphone-client-gnome. + * [#1905] The daemon was not sending the currentSelectedCodec signal + on dbus when answering a call. + * [#1922] Default values set to all account details + * [#1886] Spinbox reg expire enables apply, and address book is not + visible when disabled + * [#1905] Bug fix for segmentation fault caused by an empty string, + * [#1910] Warnings in test directory + * [#1919] Error fixed + * [#1855] Update russian translation - Hussein Abdallah + * [#1910] Remove files + * [#1919] fixed + * [#1777] Code indentation + * [#1918] fixed + * [#1917] fixed + * [#1910] Remove warnings compilation in src + * [#1886] removed AccountListModel in configskeleton + * [#1914] + * [#1911] check previous and new port + * [#1910] Remove compilation warnings in src/dbus and src/history + * [#1910] Remove compilation warnings in src/audio + * [1855] Update german translation - Sven Werlen + * [#1909] removed + * [#1906] Done + * [#1904] The registration expire value is now configurable from the + * Cleaned up debug messages. + * [#1886] separated initCallItem in two functions + * [#1886] reversed error in commit + * [#1886] clean debug + * [#1886] changed Name of classes and files + * [#1886] clean + * [#1870] In call_state_cb (dbus.c:126), _time_stop was overridden by + the actual time. + * [#1884] Added some new gpg flags to prevent tty warnings + * [#1886] Clean audio config dialog + * [#1886] No more compile warnings. + 1 comm + * [#1872] Check if the user input is smaller than PJ_MAX_HOSTNAME. + * [#1886] + * [#1785] Fixed build when no new commit + * [#1852] If chosen by the user, the hostname can now be solved and + used + * [#1871] * and # inverted back + * [#1869] Conditional compilation that checks if + * [#1309] removed test in main + * [#1425] Put actions in SFLPhone window class instead of ui view, + made a separate toolbar for screens. + + -- SFLphone Automatic Build System <team@sflphone.org> Mon, 27 Jul 2009 09:53:00 -0400 + +sflphone-common (0.9.6~rc2-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6~rc2 ** + + * [#1755] Remove generated file + * [#1753] restore ilbc ... + * [#1866] Methods getSipPort and setSipPort now have an effect on the + * [#1753] make pjsip compile without ilbc. Use ./autogen.sh --disable- + ilbc-codec + * [#1855] Fix error in russian translation + * [#1805] Remove the old flawed signal mechanism which was failing in + * [#1855] Refresh translation + * Spanish translation finished + po README files updated + echo's in + copy-in-clients + * [#1850] Yun made the chinese HK-CN translation + * [#1848] Fix transfer interface bug + * [#1862] At install, kde client installs only french translation file + * [#1841] A new fallback mechanism was added to the internal resolver + in PJSIP. + * Started AccountList model/view + * [#1855] Remove po subdir in Makefile.am + * [#1855] Fix typo error in sflphone-client-gnome + * [#1855] Do not generate Makefile in sflphone-common/po + * [#1855] Copy translation files into both clients dirs + * [#1855] Remove po dir from sflphone-common + * Comments added + * [#1860] mailbox->voicemail... + * make scripts executable + * [#1855] French translation + * [#1855] Chinese zh_HK partially filled... + * [#1859] An unnamed pipe monitored by poll() was added. When we want + to + * [#1855] Sven completed the first part of the german translation + * [#1855] Cantonese manually filled for already translated, almost + equal strings + * [#1855] Merge russian translation + * [#1855] Spanish manually filled for already translated, almost equal + strings + * [#1855] Update german translation in ./lang/de + * [#1858] This problem was fixed by removing a useless line in + * [#1855] merged existing translations in lang/ sflphone.po's + * [#1842] [#1843] An attempt at improving the expected behaviour that + can't + * [#1855] added po folder in gnome client and scripts for copying from + common lang folder to clients + * [#1853] Edit before call does nothing on call history + * Put most language entries possible in common. From 300 to 250 + entries. Stays underscores problem. Scripts for copy in clients. + * commit to merge master + * [#1825] Changed "Bad authentification" to "Authentication Failed". + * common po files + * [#1753] Remove ILBC from pjproject + + -- SFLphone Automatic Build System <team@sflphone.org> Fri, 17 Jul 2009 19:12:44 -0400 + +sflphone-common (0.9.6~rc1-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6~rc1 ** + + * Update some version number + * [#1792] Creates .sflphone directory with permission 600. Also, + "chmod 600" after + * [#1810] GUI is now notified that the call failed. Also, a segfault + was + * [#1816] Address book search disabled when disabled address book and + enabled it back plus button stays triggered + * codeclistmodel + asynchronous loading of address book + + enable/disable address book + * [#1810] Now checking SDP answer after 200 OK. Still need to + implement full + * [#1794] Can't use the interface during a call + * Updated translation files + * Russian translation integrated + * Codec list model/view started. + * [#1807] Add configure.ac in pjproject-1.0.3 + * [#1787] closeRtpSession added in some places where it should have + been + * Use Item class for contacts and accounts + * Comments + clean code + * [#1794] Improved debug messages + * [#1805] Replaced the old and unreliable mecanism that was was + waiting for + * [#1794] Can't use the interface during a call + * [#1787] For those cases where no registered SIP account is + configured + * [#1797] Make pjsip compile + * [#1787] Minor changes. Removed useless commented line. Changed order + of + * [#1777] Code indentation + * [#1797] Update package generation with new pjsip version + * [#1798] Does not hang up when the call is building up + * [#1797] Update .gitignore with new pjsip version + * [#1797] Remove generated files from repo + * [#1797] Main build system now uses pjproject-1.0.3 + * [#1797] Add pjproject-1.0.3 + * [#1797] Remove pjproject-1.0.2 + * [#1796] Computing time optimization (samplerate conversion) + * [#1787] _audiortp->start() moved away from offhold(), + SIPCallAnswered() + * [#1312] Added new states for calls initialized by other clients + * [#1795] Crashes when adding a new account, checking it and applying + * [#1782] Missing icons + * [#1793] KDE client compilation problem + * Fake ringtone files can no longer be set. + * indentation + * [#1312] Able to fetch to differentiate incoming/ringing call state + * [#1784] Use DESTDIR variable in po Makefile - fix language file + installation + * [#1785] Fixed typo + * [#1785] Fixed changelog update + * [#1759] ./autogen.sh --prefix=/usr --with-debug to use optimization + level 0 + * [#1773] Changed snapshot naming convention + * [#1773] Removed gpg agent use, added repository cache cleaning + * [#1759] Use optimization level 0 for repository, 2 for packages + * [#1777] Code indentation/formatting + * Translated new features in french + * [#1785] Added missing changelog entry + * [#1781] Window title is SFLPhone + * [#1777] Add code indentation/formatting in the buil system + * [#1774] Can't set voicemail number in KDE account creation wizard + * [#1775] Can't modify account information for account created with + the wizard + * [#1771] Add a "Default" button in context menu to disable chosen + prior account + * [#1705] + * [#1224] Remove generated file from the repo + * [#1224] Remove generated file from the repo + * [#1762] distclean target should remove kconfig generated files + (settings.h, settings.cpp). Rename them? + * [#1761] clear history button should really clear history + * Dialpad works. + * Implemented Dialpad widget instead of building it in main view. + * Removed last occurence of the old config dialog, that made the build + crash. + * [#1755] Do not consider G722 as a dynamic payload elsewhere than in + RTP layer + * [#1753] Remove ilbc Makefile generation + * [#1756] Implement a kde configuration dialog with kconfig xt and + kconfigdialog class + * [#1755] fix audiocodec folder parsing problem + * [#1450] Reinit timestamp comparison in RTP, create session in + newOutgoingCall + * [#1753] Remove milenage third party code from pjsip + * New Config Dialog integrated in GUI.(without codecs) + * [#1753] Remove ILBC codec + * kconfig started, tr2i18n -> i18n, icons folder, accountList changed + * [#1705] Fixed Audio RTP thread creation/start + * [#1714] Fix codec negociation result handling + * [#1678] Fix audiortp payload setting + * [#1678] Put bac putData method in rtp + * [#1669] gtk_file_chooser_get_filename() support UTF-8 by default + * [#1735] Add conditions to sdp update call if call declined + * [#1737] substr of recordings destination folder to remove "file://" + should be done in client rather than in daemon + * [#1731] Enlarge audio stream buffer size + * [#1714] Missing true + * [#1317] Fixed Mandriva timeout + * [#1317] Changed tag convention + * [#1317] Cleaned git-dch + + -- SFLphone Automatic Build System <team@sflphone.org> Fri, 10 Jul 2009 15:49:56 -0400 + +sflphone-common (0.9.6~beta-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6~beta ** + + * spec files for mandriva and opensuse updated with buildrequires + libqt4-dev >=4.3 + * [#1700] Cannot build on ubuntu 8.10 and a few other distribs + * [#1502] Update version number where applicable + * [#1642] Update client icons + * [#1450] Clean up useless debug and comments in sipvoiplink and + audiortp + * [#1450] Remove Semaphore object in AudioRtp thread deletion + * [#1450] Audio RTP init now synchronized with Sip/SDP + * [#1693] kde client crashes when changing codecs order/activation + * [#1450] Deep refactoring of audiortp + * [#1450] setRtpSessionRemoteIp + * [#1689] getCallList at start + * [#1224] Change path in package files + * [#1450] Audio RTP initialized only once, payload and remote ip set + at runtime + * [#1450] Add setRtpSessionMedia and setRtpSessionRemoteIp address + * [#1642] Make GNOME GUI fresher and younger ;) + * [#1686] Status bar displaying used account + * added sflphone-kde icon so that it compiles + * [#1659] Ending a call causes the daemon to crash + * corrected introspection XMLs, po files... + * [#1211] g722 media descriptor in codecDescriptor + * [#1310] Install sflphoned in $(prefix)/lib/sflphone + * [#1502] Do not install test binaries and dbus utilitaries + * [#1224] hack for pjsip build system! + * [#1224] Remove pjsip binaries from repo + * [#1224] Upgrade to pjsip 1.0.2 + * [#1658] About SFLphone (bugs) + * [#1658] About SFLphone + * [#1660] Displaying all dialed numbers in a call + * Tested status bar. + * [#790] Optimize pulse audio streams parameters + * [#1678] Some usefull debug messages for mutex/semaphore deadlock + problem + * [#1669] Add/remove some usefull/unusefull debug + * [#1665] Fix latency related to pulse audio stream openning/closing + * [#1457] Make the menus and panels accessible in french + * [#1457] Improve broken keyboard accessibility in menus and conf + panels + * [#961] Instanciate only once the searchbar icons + * [#961] Restore transfer fonction + * [#961] Filter on the history type OK + * [#961] Fix compilation problems on hardy/intrepid + * [#1157] Commit missing files + * [#790] Reduce number of start/stop streams call on pulse audio + * [#1639] kde client crashes when no account registered + * [#1620] Fix the searchbar + * [#1620] Get back caltree as it was during gtkcritical area + * [#1620] Add history filter reinit function + * [#1335] Add a missing label in address book preferences + * [#1561] Update russian translation - Hussein Abdallah + * [#1605] Fix edit menu french translation + * [#961] Enable to search in the history according to the call type + * [#1449] Searchbar does not work anymore + * [#961] Add popup menu on the entry primary icon for history + * [#1317] Fixed KDE client package dependency + * [#936] speex 32 khz integration completed + * [#936] Use 320 frame size + * [#936] Test using a frame size at 320 smpls + * [#1214] Enable / Disable history + * [#1607] Fix compilation problem for ubuntu 8.10 (libsexy) + * [#1313] Implement processDataEncode processDataDecode in audiortp + * [#1613] codec list order can't be set + * Better handling of localisation + added languages + corrected + warnings + begginning of new config dialog with kconfig + 14px + account leds + * [#1214] Save and load history according to the limit timestamp + + unit tests + * [1609] Fix call number copy/paste feature + * [1607] Restore clear action icon in searchbar + * [#936] Try to decode using 1280 samples + * [#936] Add some debug + * [#936] Add .cpp file + * [#936] Oops Forgot speex 32 khz + * [#1214] Add configuration panel for history + D-Bus calls + * [#1313] Test rtp thread function, frame size, nbbytes, resampling + * [#790] Flush audio data before closing audio streams + * [#1214] History displays local time + * [#1214] Skip empty field on display + * [#1214] Associate an account to an history entry + * [#1342] Get addressbook options sensitive/non-sensitive + * [#1211] Clean up and comments + * [#1211] Get back to 20 ms framesize + * [#1211] Use sendImmediate instead of putData in RTP + * [#1211] Fix nb byte available in RTP + * [#1211] Clear condition on maxNbSamples in RTP + * [#1211] Fix max byte available in RTP session + * [#1211] G722: Use 160 samples per frame instead of 320 + * [#1211] Test using a dynamic payload + * [#1211] Test using a dynamic payload type + * [#1211] Rename size variable (nb_samples, nb_bytes) + * [#1211] Test g722 ip-to-ip sending twice the data lenth + * [#1211] Test g722 ip-to-ip + * [#1214] Do not select an history item by default at startup + * [#1214] Remove some compilation warnings + * [#1214] Handle empty field - remove g_print + * [#1214] Add each history item only once + * [#1214] Handle call timestamps properlier + * [#1214] Do not need timestamp files anymore + * [#1214] Use the saved date for history entry + * Clean up + * [#1214] Client doesn't crash if the D-Bus call fails + * [#1214] Client is able to save its history - still some glitches + * [#1211] Forgot 16000 for g722 + * [#1211] G722 initialization + * [#1214] Save name/number, successfully load the history if no fields + are empty + * [#1499] Fixed destination directory bug + * [#1214] Restore all the functionalities; peer name/number way more + easy to handle !! + * [#1214] Add callable_object instead of call_t, refactoring + * [#1211] Test with polycom soundstation 16000 + * [#1211] Remove C like inline function in g722 codec + * [#1342] Finalize gnome client preference window formating + * [#1214] Retrieve the history when the gnome client startsup + * [#1306] Implement localization for KDE client + * [#1593] enable accounts apply button when account checked/unchecked + * [#1214] Implement the dbus calls on server side + * [#1214] Add serialized/unserialized functions to pass data on DBUS + * [#1342] Formating gnome client configuration windows + * [#1214] Save sucessfully a map of history items + * [#1499] Removed multiple jobs compilation for KDE client (2) + * [#1214] Load history from file into memory, add unit tests + * [#1534] Throws a length_error exception in case URL exceeds + std::string max_size + * [#1499] Removed multiple jobs compilation for KDE client + * [#1565] make account leds smaller + * [1430] Fix dbus debug + * [#1562] crashes when trying to change item of a call of state "OVER" + * [#1116] Fix compilation bug + * [#1317] Added mandriva and opensuse-11 64 bits + * [#1108] Add messges in main window concerning transfer success + failure + * [#1116] Fix compilation problems + * [#1211] g722 Makefile + * [#1108] Client side transferFailed/trasferSucceded signals handling + * [#1211] G722 mostly completed, + * [#1555] make bigger toolbar (24x24) + * [#1551] remove default mailbox number in wizard and disable mailbox + button when first account doesn't have mailbox number + * [#1342] Re-add sflphone manpages + * [#1116] Fix compilation on non-jaunty distros + * [#1317] Fixed opensuse startup sleep + * [#1108] Add a signal in the client to notify successful or failed + transfer + * [#1108] Dbus signals concerning call transfer success/failure + * [#1317] Added opensuse to automatic build system + * [#1223] Fix manpages bug + * [#1060] german translation glitch + * Clean up some gnome client warnings + * [#1547] replace ugly account leds by beautiful icons + * [#1548] add close button that hides windowand just hide on clicking + the cross + * [#1549] put introspec XMLs in the client's source + * [#1312] Implement getCallList D-BUS method + * [#1116] Clear text in history and contacts + * [#1499] KDE integration + * [#1469] Modify header linkers in dbus-c++'s Makefile.am's + * [#1469] Remove examples folder from dbus-c++ + * [#1214] History integration in build system; unit test squeleton + * [#1317] Cleaning + * [#1469] Remove configure stuff in dbus-c++ + * [#1469] Add unofficial mainline dbus-c++ + * [#1469] Remove dbus-c++ from freedesktop + * [#1430] Bring account changed signal/callback back to normal + * [#1060] Update german translation - Sven Werlen + * [#1430] Add marshaller one string define + * [#1430] Send account change signal broadcast using account id + * [#1430] Remove condition on setRegistrationState, cause stun to + crash + * [#1317] Centralized version handling + * [#1317] Fixed version number on sfl-git-dch + * [#1317] Refactoring for new distributions + * [#1215] Fix account order at startup if latency + * [#1088] Restore sip dns srv + * [#1214] Add squeleton for history manager + * [#1430] Add accout id to accout changed method + * [#1430] No connectionStatusNotification (account changed) if no + changes + * [#1538] Add COPYING file + * [#1430] Add audio rtp thread tests + * [#1317] Changed version detection + * [#1538] Document license in libs/stund + * [#1317] Added version files + * [#1538] Apply François patches - debian packages + * [#1317] Updated spec files + * add files + * [#1538] Apply François patches - debian packages + * [#1535] Change program file structure (directory src...) + * [#1317] Updated build system scripts + * [#1317] Cleaning + * [#1317] Copied introspect files to gnome client + * [#1317] Added opensuse to build-system : first-shot + * [#1317] Remove spec files from configure + * [#1317] Added missing prefix + * removed debug for daemon account fix + * [#1430] Add a connection reference which most likely belong to + libdbus + * [#1430] Use shared connection instead of private + * make daemon find the account, added userMatch + * Clean code, add comments... + * [#1317] Fixed packaging rules + * [#1317] Updated autogen + * Updated autogen.sh for pjsip + * [#1526] Set accounts order + * [#1317] Fixed pjsip lib dirs + * [#1317] Updated debian packaging for new pjsip configuration script + * [#1317] Switch to autogenerated guess and sub files + * [#1317] Updated pjsip inclusion in build system + * [#1317] Replaced pjsip guess and sub files + * [#1317] Fixed compilation issues on opensuse 11 + * [#1505] account list seem to crash the application when clicking + Apply very fast... + * [#1456] Add a flag to be replaced in the control files + * [#1456] Added version dependancy handling + * put account alias in AccountWidgetItem rather than in the item with + " " before. + * [#1034] The KDE client should start sflphoned if it is not started + * [#1500] Handle options for notifications and display on incoming + call. + * [#1443] Client should not crash when receive an unexpected + stateChanged signal + * [#1403] Do not stop the notification anymore + * [#1456] Added version dependancy handling + * [#1426] Daemon crashes when get alsa plugin + * [#1422] Improved error messages + * commit for merge + * [#1424] Change logo in tray icon and put a different one when + incoming call + * [#1425] first part done, window title... + * [#1413] add manpages creating and installing in build system + * [#1417] The client should start the account creation wizard if + started for the first time (if config file doesn't exist) + * [#1421] Make volume bars horizontal when dialpad is hidden. + * Changed main window title and fixed a mistake in sflphone_const.h + * [#1412] make debian package building work + * changelog changed. + * Changed addAccount method in gnome client. + * Debian and man folders added. + * [#1388] Change project name from sflphone_kde to sflphone-client-kde + * Better handle of kabc check. + * [#1351] Automatic generation of dbus interfaces in makefile + generated by cmake + * [#1307] Implement "edit before call" in history and address book. + * [#1344] change action_call label in call history from "call" to + "call back". + * [#1308] Implement Hook feature in kde client + * Improved build system. + * #1219 : Add address book configuration page + * Better handling of registration to the daemon. + * #1039 : Add tray icon in kde. + * Issue no 1216 : Double click on item in history or address book + causes call. + * display peer name in call list and call history when called from + address book. + * Address book functionnal with photo displayed. + * Help menu kde available but actions disappeared. All fonctions in + view. + * Address book functionnal but ugly and making its own sort in the + complete address book. + * Account choice on right click, clean out includes, page address + book, fixed bugs... + * Wizard, double click, context menu... + * Removed sflphone_kde.kdevelop.filelist + * Added account creation wizard and translated interface in english. + * Transfer functionnal but ugly. + * transfer not functionnal + * Bug fixed : unholding (UNHOLD_CURRENT, UNHOLD_RECORD) + * Commit functional for push. With install.sh + * Before merge. + * Problem with enable accounts. Account display increased. + * Functional with codec order working , playDTMF. + * Commit functional. + * sflphone_kde/build added in .gitignore. + * complete commit for checkout previous. + * Commit before checkout previous version to check the display + bug(little font everywhere...) + * Functionnal client. Rest : history icons, config icons and + functionalities + * commit before merge asavard for isRecording. + * Call and Automate fusion done and seems to work. + * Commiting before putting Automate class in Call class. + * Functionnal main window without recording, history, voicemail, kio + widgets. + * client kde avec kdevelop. + * Config Dialog almost finished. + * Base of QT client + + -- SFLphone Automatic Build System <team@sflphone.org> Tue, 23 Jun 2009 11:12:06 -0400 + +sflphone-common (0.9.5-SYSTEM) SYSTEM; urgency=low + + ** 0.9.5 release ** + + * [#1060] FIx bug in chinese translation + * [#1313] git add rtpTest.cpp rtpTest.h + * [#1313] Add init/close rtp tests + * [#1313] Basic instanciation of the rtp layer + * [#1449] Gtk-Critical concerning history filters and new calls + * [#1400] Make the match with the hostname instead of username + * [#1324] Change status bar label for "Using %s (%s)" + * [#1403] Icon size: 60x60 px + * [#1403] Do not remove notification, improve icon quality + * [#1403] Add smaller icon for gnome notifications + * [#1403] Prevent crash when hangup && no notification + * [#1403] Remove all actions on notifications; code refactoring + * [#1451] Use stun.sflphone.org as default STUN server + * [#1060] New po files - need to be translated + * [#1060] Update french translation - Rebuild template file + * [#1456] Add a flag to be replaced in the control files + * [#1454] Make cppunit optional; remove from build deps in control + files + * [#1401] Add libexpat1-dev dependency in control files + * [#1448] Take off these ugly debug messages + * [#1448] fixed getTelephoneTone and getTelephoneFile() called + repeatedly + * [#1406] add liblog4c-dev in build-depends + * [#1409] Restore .desktop icon + + -- SFLphone Automatic Build System <team@sflphone.org> Mon, 25 May 2009 11:34:40 -0400 + +sflphone-common (0.9.5-SYSTEM~rc2) SYSTEM; urgency=low + + ** 0.9.5 rc2 ** + + * [#1422] Improved error message + * [#1402] Fix pjsip build + * [#1404] Clear GTK-Critical Bug at client startup + * [#1422] Added automatic VM shutdown when building on more than one + VM + * [#1422] Fixed some issues with new changelog generation script + * [#1422] Moved distribution update to specific file + * [#1422] Dropped git-dch, replace by home made implementation + * [#1402] Fix pjsip build + * [#1404] Clear GTK-Critical Bug at client startup + * Changes for name based dbus connection + * Clean changelogs + * [#1343] Gnome: Implement a callback system to handle focus on + different widgets + * Debus Session + * Refactoring Python code, PEP8 + * [#1430] Get back dbus_g_proxy_new_for_name + * [#1430] Get back DBUS_BUS_SESSION type + * [#1430] Dbus fixed owner message binding + * Second test with DBUS owner + * [#1404] Gnome -> Preferences -> Hooks + * [#1404] Gnome -> Preferences -> Recordings + * [#1404] Call History + * [#1404] Gnome -> Preferences -> Address Book + * [#1404] IF the first notification option disable the second + notification + * Dbus with fixed owner does not automatically start the deamon + * Add codec debug tests in pysflphone + * [#1407] Some print info + * [#1407] Add a scenario to pick_up action + * Test client dbus connection to a fixed owner + * Add python dbus test suite + * [#1161] Modified version handling in build system + * [#1314] Test pulse audio and audio streams connect and disconnect + * [#1402] Add info message after configure + * [#1402] Build the daemon with the local pjsip library (vs the + installed one) + * [#1009] Fix Codec Sampling Rate set to zeros + * [#1314] Add mutex to pulse layer audio streams + * [#1314] Refactoring pulseaudio stream to test connect disconnect + * [#1314] Refactoring of pulselayer to test conect/disconnect + * Add debug messages in debus calls concerning account + * [#1314] Add some return values to audio init functions + * [#1406] add liblog4c-dev in build-depends + * [#1409] Restore .desktop icon + * Bug #1405: Fix strings as requested. + * Bug #1404: Fix strings in preferences panel. + + -- SFLphone Automatic Build System <team@sflphone.org> Tue, 19 May 2009 12:08:03 -0400 + +sflphone-common (0.9.5-0ubuntu1~rc1) SYSTEM; urgency=low + + [ SFLphone Project ] + * [#1262] Updated changelogs for version 0.9.5-0ubuntu1 Snapshot 2009- + 05-05 + + [ Emmanuel Milou ] + * Add some python CLI client code; not really functional + * [#1108] Fix peerHungup method for IP to IP call + + [ Alexandre Savard ] + * [#1108] Correct setting of SIP contact for direct IP call + * [#1108] SIP user agent handles incoming REFER + + [ Emmanuel Milou ] + * Remove website from repository + * Update translation + + [ Alexandre Savard ] + * Sflphone icon's tooltip changed for "configured" instead of + "registered" + + [ Emmanuel Milou ] + * Update translation + + [ Sflphone Project ] + + -- Sflphone Project <sflphone@mtl.savoirfairelinux.net> Tue, 05 May 2009 19:16:09 -0400 + +sflphone-common (0.9.5-0ubuntu1~beta) SYSTEM; urgency=low + + [ Julien Bonjean ] + * Updated Eclipse stuff + * Improved addressbook config window + * Added sflphone Eclipse stuff + * Implemented addressbook list server side + * Moved dbus stuff in dbus directory + * Updated addressbook configuration + + [ Emmanuel Milou ] + * Remove unuseful installation scripts. Use apt-get build-dep sflphone + instead + * fix bug #1090 + + [ Alexandre Savard ] + * defining speex 16khz + + [ Emmanuel Milou ] + * Remove unuseful file from build system + * Start dns srv resolver + + [ Alexandre Savard ] + * Basic ogg/vorbis initialization + + [ Emmanuel Milou ] + * Handle incoming IP-to-IP invite correctly + + [ Alexandre Savard ] + * speex wideband 16000 + + [ Emmanuel Milou ] + * Better handling of incoming IP to IP call + * DNS SRV resolution functional + * Implement IAX2 incoming URL + * Allow user to make IP call without any accounts configured + * Add a contextual menu to edit a number from the contacts tab + * Add comments, tooltip and new button to the contextual menu + * add delete event, migrate to GTK 2.16 for sexy icons + * Resolve ticket #1118 + * Update suse spec file + * Add phone number cleanup functions, unit tests and panel + configuration + * Add pertinent test that fails + * fix dependencies for suse package + * Add contextual edit menu in history - #1120 + + [ Alexandre Savard ] + * Temporary comit: make speex wideband (16 khz) + * Temporary: shared object for speex narrow band + * Temporary: speex narrowband and wideband coexist + + [ Julien Bonjean ] + * Fixed bug when no book selected + * Fixed addressbook related compilation warnings + * Fixed GTK client remaining compilation warnings + * Fixed segfault when book removed since last sflphone run + * Fixed bug when book is unreachable (ldap error) + + [ Alexandre Savard ] + * Fix codec list in audio config window + * Active/inactive speex codec by payload + + [ Julien Bonjean ] + * Updated gitignore + * Added some comments + + [ Emmanuel Milou ] + * Add callto: handler script for browsers and al. + * Integrate test compilation in the daemon build-system + + [ Julien Bonjean ] + * Fixed g_object_unref warning for pixbuf + * Cleaned too verbose output + * Fixed toolbar update warning + * Added support for asynchornous books open (first shot) + + [ Emmanuel Milou ] + * Add a DBus call to fetch the call details from a call ID - Ticket + #928 + + [ Julien Bonjean ] + * Improved async open books + * Fixed bug #1139 + + [ Emmanuel Milou ] + * Add a way to save account order + * commit missing files + + [ Julien Bonjean ] + * Introduced log4c (ticket #1162) + + [ Emmanuel Milou ] + * Load/save account order functionnal - ticket #813 + + [ Alexandre Savard ] + * Add CELT codec (#1143) + * Make celt frame size 256 (*1143) + + [ Julien Bonjean ] + * Switched everything to log4c (ticket #1162) + * Updated eclipse settings + + [ Emmanuel Milou ] + * Restore adding account - ticket #1172 + * Add liblog4c dependecy - ticket #1179 + + [ Alexandre Savard ] + * Double maxAvailByte for frame size in rtp (#1143) + + [ Emmanuel Milou ] + * Add User-Agent SIP header - Ticket #1173 + + [ Julien Bonjean ] + * Fixed autoresize issue (#708) + + [ Emmanuel Milou ] + * Remove libcppuint dependency for the debian packages + * Look for libsexy only if gtk version < 2.16 - Ticket #1116 + * Remove libsexy dependency for jaunty. ticket #1116 + + [ Julien Bonjean ] + * Introduced unit tests (#1146) + * Updated gitignore + * Fixed Makefile (#1146) + + [ Emmanuel Milou ] + * [TICKET #1112] Add a test on the voice buffer to send through iax + packets + * Remove doublon in dependencies + * Remove warnings from the client test framework + * Update version number to 0.9.5~beta + * Update build-package script + * Add check dependency in build-deps control file field + * Create debian files for the new sflphone-client-gnome + * [TICKET #1212] Add Replaces field in control files + * [TICKET #1212] Fix manpages installation path + * [TICKET #1212] Add maintainer scripts to create alternatives + * [#1212] Update the manpages generation - edit preinst maintainer + script + * [#1212] Fix reference error in manpage + * [#1212] Add missing files on the client side + * [#1212] Fix debian docs files - no TODO file + * [1212] Fix manpage creation problem + * [#1220] Generate client-side glue files and marshaller at + compilation time + * [#1220] Generate server-side glue files at compilation time + * [#1212] Change binary name to sflphone-client-gnome + * [#1212] Update .gitignore to fit the new working tree + * [#1220] Explicitly generate glue files before building the library + * [#1220] Compile dbus directory before audio + * [#1212] Create sflphone-common at the root of the repository + * [#1212] Re-add pjproject + * [#1212] Remove Makefile from repo + * [#1220] Fix Makefile.am + * [#1212] New working directory functional + * [#1212] Update .gitignore + * [#1212] Hack to make pjsip compile.. + * [#1220] Use non-installed binary for dbusxx-xml2cpp + * [#1212] Add descriptive files, remove unuseful scripts from tools/ + + [ Alexandre Savard ] + * Restore speex codecs + * add frame size for celt (#1143) + * add framesize to codec, independant from audiolayer (#1143) + * use codec frame size in rtp (#1143) + * compute fixed_codec_framesize (#1143) + * do not resample if not required (#1143) + * add condition on resampling for decoder (#1143) + * add a condition on bytesAvail == 0 from mic data + * no maximum in rtp decode (#1143) + * compute maximum for decoding (#1143) + + [ Emmanuel Milou ] + * [#1146] Implement unitary tests on the client-side + + [ Alexandre Savard ] + * use float instead of int to compute max nb of sample (#1143) + * add nbSampleMax for unresampled data (#1143) + * make thread sleep during 5 ms insead of 20 (#1143) + * use unix usleep (#1143) + * 50 usecond thread!!!!! (#1143) + * try with the smallest compression (#1143) + * use timer set at framesize (#1143) + + [ Emmanuel Milou ] + * [#1161] Restore changelog version + + [ Alexandre Savard ] + * Remove celt stuff + + [ Emmanuel Milou ] + * [#1161] Update changelog + * [#1220] Add Conflicts: sflphone in debian control files + * [#1179] Add liblog4c3 runtime dependency + * [#1212] FIx typo error in dependency list for itnrepid + * [#1212] FIx .desktop file to point on the right exec + * [#1212] Modify changelog replacing tag + + [ Sflphone Project ] + * "[#1262] Updated changelogs for version 0.9.5-0ubuntu1~beta" + + [ Emmanuel Milou ] + * [#1212] restore changelogs + + [ Sflphone Project ] + * [#1262] Updated changelogs for version 0.9.5-0ubuntu1 Snapshot 2009- + 04-27 + + [ Emmanuel Milou ] + * [#1212] restore changelogs + + [ Sflphone Project ] + * [#1262] Updated changelogs for version 0.9.5-0ubuntu1~beta + + [ Emmanuel Milou ] + * [#1212] restore changelogs + + [ Sflphone Project ] + + -- Sflphone Project <sflphone@mtl.savoirfairelinux.net> Mon, 27 Apr 2009 16:57:00 -0400 + +sflphone-common (0.9.4-0ubuntu2) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Restore speex and GSM detection + + [ Emmanuel Milou ] + * Fix bug #1090 + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 8 Apr 2009 11:29:15 -0500 + +sflphone (0.9.4-0ubuntu1) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Integrate DBus-c++ and libiax2 in the main build system + * Clean up in the working repository + * Reorder hooks configuration panel + * Protect case when no codecs are active + * Fix some return values + * Add unitary tests for the hook manager (premisces) + + [Yun Liu] + * Update chinese translation + + [Sven Werlen] + * Update german translation + + [Hussein Abdallah] + * Update russian translation + + [Maxime Chambreuil] + * Update spanish translation + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 3 Apr 2009 18:29:15 -0500 + + +sflphone (0.9.4-rc1) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Fix bug while trying to hold/unhold several simultaneous call + * Improve address book build system + * Implement SIP url popup on incoming call + * Improve GTK+ panel configuration + [ Julien Bonjean ] + * GTK+ client refactoring + * GTK+ clean up + * Address book improvment + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 27 Mar 2009 18:29:15 -0500 + +sflphone (0.9.4-0beta1) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Display codec used during conversation on the GUI + * Enable/disable STUN parameters at runtime + * Refactor search bar use + [ Emmanuel Milou ] + * Build system fixes + * Implement SIP re-invite + * Implement IP to IP call + [ Julien Bonjean ] + * Integrate GNOME address book based on evolution data server + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 20 Mar 2009 18:29:15 -0500 + + +sflphone (0.9.3-0ubuntu3) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Both playback and record streams in PA_STREAM_CORKED (pulseaudio) + * Use PLUGHW device for ALSA capture + * Functional IAX and SIP recording for voicemail + * Use the less CPU-consuming interpolator algorithm for resampling + * Display in GTK GUI the codec used in conversation + * GTK GUI use ASCII instread of utf-8 + * Add record menus in GTK GUI + * Put on hold when dialing a new number + * AccountID's are saved in the history + + [ Emmanuel Milou ] + * Integrate DBUS C++, libiax2 in the git repository + * Update website + * Use libspeexdsp only if available on the system + * Updated .gitignore file + + [Cyrille Béraud] + * Account assistant manager improvment + * Add an email request when creating a new account to receive voicemails + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Sat, 14 Feb 2009 13:29:15 -0500 + +sflphone (0.9.3-0ubuntu2) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Add compilation note in README + * Use default ALSA plugin for capture + * Fix the ALSA capture problem one more time + * Clean up debug messages in dbus.c + * Add libspeexdsp dependency + * Remove implicit declaration compilation warnings + * Fix links in the website, add release note + * Change capture for the website front page + * Add alsa devel dependency in build-depends control file field + * Clean up, indentation, try to handle latency problems in iax/pulseaudio + * Remove pjsip generated files from the repo + * Use the previous declared curAlias function in accountwindow + * Fix bug in history call duration when the call fails + * Remove runtime warning in the GTK+ client + * Add librsvg2-common dependency to load SVG under KDE + * Refresh .gitignore + * Update locales files + french translation + * Add configuration panel for future noise reduction + * Add configuration panel for audio record module + * Daemon less verbose; accounts don't try to access STUn options anymore + * Fix typo in configwindow + * Add content in the official website + * use a GTK_STOCK icon for the record button + * Complete description text in the assistant manager + * Add libtool flags in client configure.ac + * Remove unuseful dependency (snd) + * Fix SIP transfer problems + * Remove previous version of PJSIP from the repo + * Upgrade PJSIP to version 1.0.1 + * Add the new website source in the repository + * Use libspeexdsp for silence detection only if available + + [ Loïc Faure-Lacroix ] + * Ajout du logo gpl3 + * Ajout des images + * Ajout de la section screenshot pour le site + * Ajout du favicon dans le header + * Modification des cartes + + [ Alexandre Savard ] + * Clean up <speex/libspeexdsp> + * Small cleanup + * Save Wave fixed + * Fix new call button when recording + * libspeexdsp added + * Recording: default home folder at startup + * Minor changes to config window + * IAX recording fixed + * Set / get recording path, still need some GTK for client + * AudioRecord file name format + * Now recording in HOME folder + + [ Cyrille Béraud ] + * Fix bug in reqaccount.c + + [ Maxime Chambreuil ] + * Update spanish translation + + [Yun Liu ] + * Update chinese translation + + [ Hussein Abdallah ] + * Update russian translation + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Sat, 14 Feb 2009 13:29:15 -0500 + +sflphone (0.9.3-0ubuntu1) SYSTEM; urgency=low + + * Remove debug + * Join thread before leaving + * Fix implicit declaration in reqaccount + * Add REST code to build the request to server + * Fix GValue initialization warnings + * Update version number, fix implicit declaration, fix GTK markup + warnings + * Apply patch to create custom SIP account from our own server + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 06 Feb 2009 19:17:32 -0500 + +sflphone (0.9.2-2ubuntu9) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Speex audio codec preprocessing initialization + * peer hung up segmentation fault solved + * Stop recording when transfering + * Terminate only one call + * Add isRecording() function + * Fix call_icon GTK client + * Fix SIPCallClose() function, recorded file now close properly + * Function terminateSIPCall added in sipvoiplink and managerimpl + * Fix thread destructor + * setRecordingOption function implement in audiorecord + * Record now implemented in Call class + * Record interface complete (on hold erase previous recording) + * Added recButton in client + * Added: record button related icons + * Record button added + * Overload AudioRecord::recData to get mic and speaker data mixed + * Recording now in audiortp::run() method + * Audio recording working in AudioRTP: receiveSessionForSpeaker + * Open/close a wave file when pulse audio stream start/stop + + [ Emmanuel Milou ] + * Fix path for GTK+ icons; clean up + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Thu, 05 Feb 2009 18:27:53 -0500 + +sflphone (0.9.2-2ubuntu8) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Update changelogs + * Fix bug in merge and in Makefile.am + * Terminate only one call + * Disable PJsip shutdown when changing STUN parameters + * Function terminateSIPCall added in sipvoiplink and managerimpl + * Add a timer to the alsa thread to not jam the CPU load + * Fix bug in sipvoiplink.cpp + * Clean shutdown of pulseaudio on quiting + * Fix DTMF at first start with Pulseaudio + * Remove zeroconf from the build system + * Add a library manager + exception handling + * Clean up in the working directory + * Better handling of capture XRUNs + * Restore mic adjust volume on ALSA layer + * Protect device ALSA operation if not opened + * Fix the switching layer bug + * Use dynamic_cast<> to use audiolayer-specific methods + * Open the audio devices only once at startup + * Refactoring of the ALSA part + * Functional plug-in manager + * Use a C++ thread to handle tones and DTMF in ALSA + * Restore IAXVoIPLink, restore Mutex + * Make the plugins registering against the plugin manager + * Migrate to 1->N relationship between voiplink and accounts + * API plugin for registration + * Use C++ thread in SIP, move everything in sipvoiplink + * Complete singleton pattern for the plugin manager + * Add -Wno-return-type compilation flag to remove warnings; Update + version number in configure.ac + * Add the dynamic loading for the plugin framework; integate unittest + + [ Yun Liu ] + * Update rpm spec file + * modify build package script and spec file for suse + + [ Alexandre Savard ] + * Add audiorecorder plugin and testaudiorecorder + * Add audio Recording class, edit global.h + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 04 Feb 2009 14:00:30 -0500 + +sflphone (0.9.2-2ubuntu7) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Update changelog to 0.9.2-6 + * Fix some dbus-glib implementation details on the client side + * Init history after dbus initialization + * Add error checking in useragent; Clean sipvoiplink + * Prevent crash when trying to call an empty number + * Set the volume of the playback stream to PA_VOLUME_NORM at startup + * Fix GTK+ generic value double initialization + * Fix jaunty control file dependency problems + * Fix jaunty control file dependency problems + + [ Yun Liu ] + * Fix bug ticket # 137 + * Tolerant to gsm library of OpenSuse 11 + + [ Sven Werlen ] + * Update german translation + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 23 Jan 2009 17:48:13 -0500 + +sflphone (0.9.2-2ubuntu6) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Migrate STUN configuration to the main config window + * Update french translation + * Other tiny memory leaks + * Fix memory leak in sampleconverter.cpp + * Generate packages from the release branch + * update the build package script + * modify the control files with architecture=any + * Remove valgring uninitialized value + * IAX and SIP use the same global variables to set account + configuration ; fix broken code + + [ Maxime Chambreuil ] + * Update spanish translation + + [ Hussein Abdallah ] + * Update russian translation + + [ Yun Liu ] + * Update translation files + * Fix the bug when user uncheck the account which fails in the + previous registration + * Add stun error status + * Fix bug ticket #143 + * Script for auto-install dependencies + * Fix bug ticket #140 + * Fix bug ticket 141 + * Fix the reregister process when user change the details of an + account + + -- Emmanuel Milou <manu@sulfur.inside.savoirfairelinux.net> Fri, 16 Jan 2009 18:19:05 -0500 + +sflphone (0.9.2-2ubuntu5) SYSTEM; urgency=low + + * Fix memory leak in the pulseaudio callback + * Update debian package generation script + * Warnings removal in GTK+ client + * Clean adjust volume method in alsalayer + * Plug the sflphone playback volume control to the pulseaudio volume + manager + * Display the date in history according to the current locale + * Generate the changelog according to the git commit messages + * Complete header in chinese translation file + * Use the right gpg key to sign the packages + * add debian jaunty jackalope support + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 14 Jan 2009 21:17:20 -0500 + +sflphone (0.9.2-2ubuntu4) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * add german translation + + [ Yun Liu ] + * Fix GUI crash in Ubuntu8.10 64bit system + + -- Yun Liu <yun.liu@savoirfairelinux.com> Thu, 08 Jan 2009 13:08:51 -0500 + +sflphone (0.9.2-2ubuntu3) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * The main thread synchronizes the ringtone thread + * disable custom ringtone for the ALSA layer + * Fix the Makefile.am in man directory, add a SEE ALSO section + + [ Yun Liu ] + * Fix daemon crash caused by the previous patch ( for bug ticket #129) + + -- Yun Liu <yun.liu@savoirfairelinux.com> Tue, 06 Jan 2009 16:18:38 -0500 + +sflphone (0.9.2-2ubuntu2) SYSTEM; urgency=low + + * Fix bug ticket #129 + + -- Yun Liu <yun.liu@savoirfairelinux.com> Wed, 5 Jan 2009 15:54:53 -0500 + +sflphone (0.9.2-2ubuntu1) SYSTEM; urgency=low + + * Migrate from eXosip library to pjsip + * Add multiple SIP accounts support + * Fix ringtones problems + * Add a pulseaudio support + * Improve audio quality with ALSA + * Add chinese translation + * Improve spanish translation + * Migrate to a maintained C++ DBus bindings + * Clean and improve the build system + * Add build-dependency on Perl because we need pod2man to generate manpages + + -- Yun Liu <yun.liu@savoirfairelinux.com> Wed, 26 Nov 2008 09:47:53 -0500 + +sflphone (0.9.1) unstable; urgency=low + * Add a search tool in the history + * Migrate some gtk_entry_new to sexy_icon_entry_new + * Bug fix (Ticket #78): The voicemail password isn't displayed anymore in + the history tab + * Add the SIP registration expire value in the user file. + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Thu, 22 May 2008 11:14:25 -0500 + +sflphone (0.9.0) unstable; urgency=low + * Add history features + * Call date + * Call duration + * Mouse events in the history tab + * Smooth switch from the history tab to the calls tab + * Remove most of GTK-Critical warnings + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 13 May 2008 16:58:25 -0500 + +sflphone (0.9-2008-06-06) unstable; urgency=low + * Audio bug correction: capture stopped after a few minutes of conversation + with USB Plantronics sound card + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Tue, 06 May 2008 16:58:25 -0500 + +sflphone (0.9-2008-05-06) unstable; urgency=low + * Bug correction: account creation with the assistant + * GTK+ warnings removal + * libnotify warnings removal + * Remove aliasing on the SFLphone logo + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Mon, 05 May 2008 16:58:25 -0500 + +sflphone (0.9) unstable; urgency=low + * Clean dependencies ( removal of libboost ) + * Several GTK improvement and updates + -account window + -configuration window + * Migrate from GtkCheckMenuItem to GtkImageMenuItem + * ALSA standard I/O transfers: MMAP instead of R/W + * Fix speex audio quality + * IAX2 protocol + -Fix hold/unhold situation + -Add on hold music + * SIP protocol + -Ringtone on incoming call + -Fix transfer situation + * Add desktop notification ( libnotify ) + * Improve the system tray icon behaviour + * Improve registration error handling + * Register/unregister from the account window takes effect without starting back SFLphone + * Compilation warnings removal + * Call history + * Add an account configuration wizard + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 30 Apr 2008 16:58:25 -0500 + +sflphone (0.8.2) unstable; urgency=low + * Internationalization of the GTK GUI + * English / French + * STUN support + * Slight modifications of the graphical interface ( tooltips, dialpad, ...) + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 21 Mar 2008 11:37:53 -0500 diff --git a/tools/build-system/launchpad/sflphone-video/debian/control b/tools/build-system/launchpad/sflphone-video/debian/control new file mode 100644 index 0000000000..11e6259f6a --- /dev/null +++ b/tools/build-system/launchpad/sflphone-video/debian/control @@ -0,0 +1,12 @@ +Package: sflphone-video +Section: gnome +Priority: optional +Architecture: all +Depends: sflphone-daemon-video, sflphone-gnome-video +Maintainer: Savoir-faire Linux Inc <emmanuel.milou@savoirfairelinux.com> +Description: GNOME client for SFLphone, with video support + Provide a GNOME client for SFLphone. + SFLphone is meant to be a robust enterprise-class desktop phone. + SFLphone is released under the GNU General Public License. + SFLphone is being developed by the global community, and maintained by + Savoir-faire Linux, a Montreal, Quebec, Canada-based Linux consulting company. diff --git a/tools/build-system/launchpad/sflphone/debian/changelog b/tools/build-system/launchpad/sflphone/debian/changelog new file mode 100644 index 0000000000..78cc17db26 --- /dev/null +++ b/tools/build-system/launchpad/sflphone/debian/changelog @@ -0,0 +1,3138 @@ +sflphone (1.1.0-rc20120607~ppa1~SYSTEM) SYSTEM; urgency=low + + ** SNAPSHOT 1.1.0-rc20120607~ppa1~SYSTEM ** + + * gnome: cleanup logging + * * #12061: gnome: fix video codec list display + * * #12050: gnome: fixed regression in active codec handling + * #11732: Do not align menu with other interface component + * * #11818: gnome: stop daemon on SIGTERM, SIGINT or SIGHUP + * #10304: updatePlaybackScale dbus method uses int 32 bit for size and + position (allow for 24 days long recording playback) + * #10304: Add time lable for seekslider + * * #10725: gnome: seekslider: fixed compiler warning due to missing + header + * * #11732: gnome:calltree: fix warning when searchbar is not created + * * #11252: daemon: removed deprecated zrtp code + * #10725: Consolidate test to determine if there is a recording, + remove log on NULL pointer + * #10725: Remove logging from stop callback in seekslider + * #10725: No need of playback related callbacks in uimanager + * #10725: Remove logging in seekslider when no selected call + * #11732: Align Icons with dialpad in gnome client + * #10304: Make sure that the playback won't be updated after reset + * #10304: Prevent playback widget reset from stoping dtmf + * #10304: Move Playback widget at the bottom of the main window + * #10304: Fix logic to determine outgoing/incoming calls for recording + icon + * * #11685: gnome: fixed memory leaks in config menus + * #10304: Fix reset seekslider that prevent scale to be updated at + first read + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Thu, 07 Jun 2012 16:04:59 -0400 + +sflphone (1.0.0-rc20110930~ppa1~SYSTEM) SYSTEM; urgency=low + + ** SNAPSHOT 1.0.0-rc20110930~ppa1~SYSTEM ** + + * update kde .gitignore + * Fix bug in volume widget + * More polishing for release + * Bump version to 1.0.0 + * [#7023] Add the ability to load an abstract contact backend in the + library to resolve more data, polish code + * [#7021] More cleanup for release + * Cleanup + * [#7021] Refactor KDE client dbus handling, add a missing call in + daemon and port the DataEngine to the new API + * Remove some annoying debug + * merge language scripts + * remove obsolete 'VERSION' files + * update install instructions + * Add missing translations to gnome + * language update + * Revert "Don't reference count DBus clients, exit core immediately + when one of them request it" + * Don't reference count DBus clients, exit core immediately when one + of them request it + * [7021] Add contact abstraction support + * [#7121] Polishing library (over). Indentation, spacing and naming + are now consistent + * codecs: link to libccrtp, don't use logger + * Fix a daemon bug + * [#7038] Fix adding contact + * * #7037 : stop audio stream after all calls have been hanged up + * [#7025] Add full support for bookmark + * SFLPhone KDE do not destroy history anymore + * Fix config skeleton + * Close the daemon once and for all, no more automatic respawning + * Fix "unregistered account" bug (I hope so) + * Close SFLPhone at the right place, it still respawn, I don't know + why + * Remove dead code + * Fix regressions introduced in the last commit + * Dead code elimination 1/3 + * Fix bug, add "add contact" option, fix warning + * * #7019: Fix IAX codec negociation + * Remove or comment unnecessary/unhelpful debug output + * Fix "same as local" account setting, fix IP2IP LED color + * Add support for some more advanced config options and add missing + config dialog icons + * Fix crash with noise suppressor + * Alternative can now be selected from the call view context menu + * Add drag and drop support, initial context menu and fix 3 bugs in + the account dialog + * Add basic history drag and drop support + * Complete contact support is back + * * #6991 : fix IAX problems + * Fix IAX accounts being disabled by default + * Revert "deb: forge -g flags for pjsip" + * * #5884: Disable debug code in pjsip + * echo suppressor : more assertions + * Don't let the daemon think crypto is enabled when it's not + * Simplify ToneList + * Some progress on contact support + * Remove unused getRegistrationCount() + * remove annoying debug + * revert SIP bit of e27e5c39bad27bae28f574eb2cba7717e8956229 + * Simplify CallManager::placeCallFirstAccount + * Fix crash on hold + * * #6905 : SIP refactor + * gnome client: be sure key exchange is set correctly + * Move code into createSipTransport + * Fix account registration on start + * ManagerImpl::registerAccounts(): simplify + * * #5884: don't mess with pjsip threads in echo suppressor + * * #6905 : simplify udp/stun/tls pjsip transport creation + * Restore and improve support for Call history + * fix launchpad build + * SIPVoIPLink: simplify / refactor + * Fix libwidget linking + * SIP: simplify + * IM : simplify + * gnome: remove some debug + * AudioRtpFactory::stop() cannot fail + * * #6905: simplify SIP code + * pjlib: fix build without SSLv2, fix warnings + * Port history to the new syntax + * Test a dock widget based implementation for contact and history + * Disable SSLv2 support from pjsip and sflphone + * deb: forge -g flags for pjsip + * Fix deb packaging to get debug symbols + * remove debug + * pjproject: update to last stable release (1.10) + * Require gtk >= 2.20 and glib >= 2.24 + * tlsadvanceddialog: simplify + * * #6902 : fix errors spotted by -DGSEAL_ENABLE + * Update daemon dbus XML and port KDE config backend from dbus to + local + * Remove unused but set variables + * * #6929 : fix IM widget, cleanup + * Unconditionally enable debug symbols + * Should fix many KDE issues + * * #6886 : hitting backspace on empty number have no side effects + * * #6905 : fix AudioCodecFactory access in optimized builds (-O > 0) + * Remove unsupported and broken jaunty/karmic packages + * * #6902 : avoid using some gtk deprecated functions + * Update dbus introspection files + * * #6904: removed unused contactmanager + * * #6903 : use correct dbus-cxx package name + * * #6902: don't use individual gtk headers + * Fix a segfault when config is not present + * Merge latest (0.9.13) KDE code. This version is not yet ready for + git master, but better than the previous one + * addressbook : simplify + * * #5659 : sflphone-plugins doesn't depend on libedataserverui + * * #5659 : addressbook doesn't use libedataserverui + * gnome client doesn't depend on evolution + * * #5695: addressbook: simplify + * * #5695: addressbook : remove AddrBookHandle from plugin + * * #5695 : addressbook : remove unused stuff in the client + * * #5695 : addressbook : remove unused stuff, use static mutex + * gnome client doesn't use evolution + * gnome: use proper API to set GTK_CAN_FOCUS + * * #6897: removed unused focus state vars/callbacks + * gnome: fix calls to sflphone_fill_codec_list_per_account + * * #6623: gnome: don't leak in mainwindow + * gnome: mainwindow whitespace cleanup + * gnome: actions.c parameter doesn't have to be a double pointer + * * #6895: fix memleaks, cleanup in accountconfigdialog + * * #6893: fixes segfault in client on clean history + * * #6894: fix leaks, cleanup in sflnotify + * daemon: fixed prints in main + * * #6892: simplify, fix leaks in dialpad + * * #6887: audiopreference creates audio layer + * * #6660: use const char * const, not std::string for globally + visible constants + * * #6852: Preferences now solely responsible for audiolayer creation. + * * #6860: refactor uimanager, also fixes #6865 + * * #6853: hangup as soon as all digits have been deleted + * * #6852: alsa: retry if device is busy + * * #6852: audiolayer creation depends only on preference.audioApi + * * #6850: gnome: fix build for gtk < 2.22.0 + * cleanup in iax + * alsa: typo + * pulse: if we can't peek in audio input, we can't drop samples + * * #6849: show error window if codecs are missing, instead of dying + * EchoCancel: unused, remove + * * #6629 : use number of samples as arguments for audio filters + * * #6629 : remove unused Algorithm interface + * * #6629 : use helper to call alsa functions and display error msgs + * Remove unused type + * * #6841: fix some error handling + * * #6629: simplify AlsaLayer::alsa_set_params() + * Get gdk key definition from header + * * #6828: Replace raw key codes by gdk defines + * remove some debug, enhance some other + * mainbuffer: simplify + * * #6561 : fix phantom call after transfer + * Conference Participant set : simplify + * SIPCall: remove unused functions, make invite session public + * * #6229 : remove malloc/free from pulse audio loop + * * #6629 : simplify pulse callbacks + * * #6629 + * Simplify widgets + * * #6629 : keep the correct audio module when frequency changes + * * #6751: fixed erroneous debug msgs + * callable_obj.h: removed unneeded pthread header + * alsalayer: cleanup + * * #6629: Always restart audio driver when changing parameters (ALSA + only) + * gnome GUI: don't block in DBus signal errorAlert() + * * #6629 : simplify AudioLayer creation + * * #6629 : remove unused and unconfigurable frameSize from audiolayer + * * #6629 : remove unused error message from audio layer + * Fix logic error when switching audio API + * Remove unused AudioProcessing class + * AudioRtpRecordHandler::initNoiseSuppress() : use noiseSuppress + directly + * * #6629 : use DC blocker directly in audio layers + * * #6629 : clean AudioLayer + * * #6629 : don't store mainbuffer inside audiolayer + * * #6629 : correct AudioLayer::notifyincomingCall() + * * #6554: cleanup, refactoring in sipvoiplink + * * #6554: cleanup in iaxvoiplink + * * #6554: throw exception in getSIPCall if pointer is NULL + * * #6554: make some methods of sipvoiplink static + * * #6655: cleanup in managerimpl + * * #6554: refactoring, fix memleaks in sipvoiplink + * * #6478: remove throw specs, cleanup in voiplink + * * #6629 : remove unused AudioDevice + * * #6655: removed more dependencies from managerimpl + * * #6744: simplified numbercleaner + * conference : remove one prototype + * * #6743: fix ip2ip + * Don't give glib warnings if icons are not found + * gnome: fixed includes + * Codec.h: removed unused function + * * #6742 : clean dbus & icons + * * #6699: refactor/cleanup accounts + * icons: cleanup + * timer : use second precision, not millisecond + * calltree_update_clock : use correct type, returns something + * * #6737: fixed typo in dbus call + * * #6737: removed tests for removed API + * * #6737: dbus: fixed bug from merge + * * #6737: cleanup in accountlist + * * #6737: cleanup in dbus + * * #6740 : fix history double free + * * #6740 : remove time updating thread from calls + * * #6737 : use c99 for client + * * #6738 : make history loading faster + * sipvoiplink : don't crash on transfers + * fixed typo + * Remove unused file + * Don't build networkmanager.cpp at all if NM is disabled + * _debug* -> _debug + * * #6554 : simplify sipvoiplink + * hudson: added -x to git clean command + * added git clean to hudson script + * audiocodecfactory: cleanup + * * #6718: refactored setTlsSettings into SIPAccount + * * #6718: removed more unused methods + * * #6718: refactored confmanager code into sipaccount + * remove unused functions + * * #6718: confmanager: removed more unused methods + * AudioCodecFactory : cleanup + * #6697 : Turn callableElement struct into union + * * #6718: confmanager: removed more unused methods + * * #6718: confmanager: removed more unused methods + * * #6718: removed unused dbus methods, refactoring + * * #6699: accounts: cleanup/refactoring + * * #6699: refactoring, cleanup in accounts + * * #6699: more account cleanup + * remove unused autoconf variable + * * #6714: fixed hudson script + * make distclean in hudson + * added || exit 1 to run_tests.sh call + * * #6714: fixed make distcheck for sflphone-plugins + * * #6714: fixed make distcheck for gnome client + * * #6714: fixed make distcheck for daemon + * git: #6698 split the main .gitignore file + * gnome: gpointer is already a pointer + * gnome: calltab_init: use calloc instead of malloc + * * #6699: more account cleanup + * * #6699: cleanup account + * * #6554 : more *voiplink cleanup + * * #6558 : more sipvoiplink simplification + * * #6558: saner loadSIPLocalIP prototype + * gnome: #6623 clean calllists + * * #6692: more audiolayer cleanup + * * #6692: cleanup/refactoring in audiolayers + * * #6692: more forward declarations, AudioThread->AlsaThread + * * #6692: audiolayer cleanup + * * #6692: alsalayer cleanup + * * #6558 : remove account creator + * * #6558 : clean sipvoiplink + * * #6554 : cleanup sipvoiplink + * audiortp: cleanup + * * #6657 : fix launchpad builds for good + * * #6675 : send RTP dtmf events only once + * * #6655: more cleanup + * AudioRtpSession::updateSessionMedia() : simplify + * * #6655: more cleanup in managerimpl + * * #6655: removed more code, cleanup + * * #6655: more cleanup, fixed infinite loop + * * #6655: removed more unused files + * * #6655: removed unused mutex + * * #6655 removed more unused code + * * #6655: removed unused methods + * * #6655: cleanup in main + * * #6663: fixed segfault when off hold from transfer + * * #6658: user's active codec selection is respected + * * #6660: static global string should be static const char* const + class member + * * #6659: use g_strcmp0, not strcmp for vals that may be null + * callable_obj: fix double free + * calltree_display_call_info() : simplify + * * #6657: Fix launchpad builds + * Logger::log() : simplify + * AudioRtpSession : privatize members + * * #6655: more constness, cleaned up/simplified methods + * * #6654: call DBus::_init_threading so that dbus-c++ to make it + threadaware + * set default credentials on account creation + * AudioCodecFactory::scanCodecDirectory() : simplify and correct + * * #6623: fixed typos + * * #6623: fixed more leaks + * * #6623: fixed more leaks + * * #6623: fixed more leaks, don't print codec name if null + * * #6623: more leaks fixed in client + * * #6623: fix more leaks, fixed some warnings + * * #6623: fixed leak in history + * updated gitignore + * initialize dbus dispatcher correctly + * Fix tests, hudson doesn't have a dbus daemon running + * remove unused code + * removeCall() : simplify , fix leak + * stopRtpThread() : simplify + * *CurrentCall : simplify + * Fix memleak + * fix serialization of audio api (pulse / alsa) + * account map : simplify + * remove call from callmap before terminating it, avoid use after free + * * #6630 : don't make DBusManager a singleton + * call: return confID by value + * add back history code deleted by error + * history : reverse logic + * simplify history serialization and remove some debug + * remove annoying debug + * * #6464 : replace cerr with _error + * * #6464: replace cout with logger macros + * replace printf() with logger macros + * update .gitignore + * remove unused function + * update eclipse projects + * uimanager_new() : simplify + * rename directories + * celt: simplify a bit + * Fix CELT configure.ac test + * * #6612 : template speex codecs + * * #6623: refactored conference obj + * * #6623: refactored callable object, removed leaks + * * #6623: more cleanup, fix leaks, make global vars static and rename + them + * * #6623: calltree: fixed memleaks, simplified code. + * audiolayer: init pointer members + * manager: catch exception on invalid hangup + * * #6623: don't leak on calls to create_new_call + * * #6611 : clarify codecs prototypes + * ringtones : .au and .ul files are both ulaw + * * #6611 : make sure samplerate converters are called correctly + * ManagerImpl::switchAudioManager() : simplify + * * #6623: fixed more leaks + * * #6623: fixed more leaks + * * #6623: fixed more leaks + * * #6623: fixed leak, line-endings in imwidget + * * #6627: zero-initialize pointers if they're going to be deleted + * * #6628: don't leak calls on exceptions + * Revert "audiortp: call join after calling stop on RtpThread" + * sflphone-client: more constness + * audiortp: call join after calling stop on RtpThread + * * #6625: return 0 on successful completion + * * #6624: fix segfault on servercallfailure + * * #6621: Fixed double free, unlock mutex in ManagerImpl::terminate + * * #6220: remove audio stream when peer hangs up + * * #6596: AudioSymmetricSession shouldn't self-delete + * resampler: grow internal buffers dynamically + * merge up and down sampling => resampling + * Leave test directory unchanged when running make check + * audio algorithms : remove unused prototype + * ringtone: detect codec from file extension + * *AudioFile : simplify + * * #6596: create local SDP on the stack, not the heap + * * #6596: don't call Ost::Thread::terminate from dtor + * audiofile: cleanup (samplerate -> unsigned) + * remove unused func + * samplerateconverter: cleanup + * RingBuffer::Put() : remove unused return value + * MainBuffer::putData() : remove unused return argument + * audiolayer::putMain() : remove unused func + * AudioLayer::putUrgent() : remove unused return value + * * #6618: delete any remaining ringbuffers in destructor + * RingBuffer::availForPut() : remove + * * #6617: return from main rather than calling exit + * MainBuffer::availForPut(): remove + * RingBuffer: simplify + * alsa : remove write only variable + * fix memcpy declaration + * bcopy(src, dst) -> memcpy(dst, src) + * RingBuffer::Get() : remove constant volume argument + * return a copy of the call ID, not just a reference. + * MainBuffer::getDataById() : remove volume argument (always 100) + * MainBuffer::getData() : remove constant volume argument + * RingBuffer::Put() : remove constant volume argument + * MainBuffer::putData() : remove constant (=100) volume argument + * audiolayer: remove constant _defaultvolume + * AudioRtpRecordHandler / AudioRtpSession : simplify + * mainbuffer: fix test + * iaxvoiplink : simplify + * sip registration callback: fix a dbus crash + * MainBuffer: simplify + * AudioRtpFactory: return cached type of rtp session. The rtp session + can have disappeared if the call was put on hold + * AudioRtpFactory: remove unused setters + * Fix launchpad builds + * * #6611 : remove unused bandwidth codec information + * * #6611: AudioCodec: remove useless/unused setters + * make sure buffer string is initialized correctly + * * #6596: declare certain destructors virtual + * audiolayer : cleanup + * Simplify doc build rules + * * #6270: don't build dbus-api doc with make, should require make all + * configure.ac: cleanup + * Remove copy of dbus-c++ from libs/ + * * #6596: stop clock thread when peer hangs up + * removed unused Fmtp.h + * * #6595: more logical initialization order + * * #6600 : fix account creation + * * #6601 : fix configure.ac tests + * remove unused variable + * Don't mix stack and heap based allocations + * Fix copyright (2009, 2008, 2009 -> 2008, 2009) + * Fix warnings found by clang + * * #6595: fix initialization order for AudioRTP + * * #6592: removed typedef std::string CallID + * * #6586: implement local g_slist_free_full for older glib versions + * * #6579: fix memory leaks in client (there's a lot left) + * ShortcutPreferences::setShortcuts() : simplify + * Fix merge + * * #6548: remove call to non thread-safe strerror() + * AudioRtpFactory: each instance is associated to exactly one SipCall + * create_audiocodecs_configuration() : make static + * * #6269 : refactor AudioRtpSession + * Fix AudioSymmetricRtpSession.h inclusion guard (cherry picked from + commit c3081dce1cc1370d6d3558a4c4ef5cfac0d21caf) + * * #6269: Rename AudioRtpSession to AudioSymmetricRtpSession + * * #6574: Don't exit when connection to pulseaudio server fails + * accountconfigdialog.h : remove some stuff from header + * * #6560: fix configuration test + * Fix warning in test + * * #6560: don't hide password entry in security tab + * * #6560: set initial password for SIP accounts + * * #6506: remove useless pointer indirection + * * 6560: password is now specific to IAX accounts + * * #6560 : actually use, store, restore, transmit SIP credentials + * * #6560: YamlEmitter: serialize sequences + * YamlEmitterException: typo + * ManagerImpl::computeMd5HashFromCredential() : simplify, fix memleak + * * #6561: invite_session_state_changed_cb() : simplify + * * #6561: More useful debug in VoIPLink::removeCall + * * #6561 : fix ghost call reappearing in GUI after transfer + * while -> for (make the code smaller) + * * #6558 : Account::loadConfig() : move IAX code to IAXAccount + * IAXVoIPLink::getAccountPtr : simplify + * * #6554 : access the SIPVoIPLink directly, not per account + * SIPVoIPLink is instanciated only once and is not associated to a + single account + * yamlnode: use const references when possible (still some left to do) + * Account::_accountID: constify + * VoIPLink: simplify, remove unused method + * hudson test : no need to call run_tests.sh anymore + * Remove AccountID type and AccountNULL define + * Make check runs the test (no need to call run_tests.sh manually + anymore) + * gnome GUI: Fix tests + * Revert "Move registration information from SIPAccount to + SIPVoIPLink" + * * #6392: pluginmanagertest: fix warnings reported by valgrind + * * #6547 : remove unused exceptions + * * #6547: CallManagerException: use runtime exceptions + * * #6547: InstantMessageException: use runtime exceptions + * * #6547: do not throw exceptions if some settings are not present in + config file + * * #6547: YamlParserException: use runtime exceptions + * * #6547: VoipLinkException: use runtime exceptions + * * #6547: YamlEmitterException: use runtime exceptions + * * #6547: DTMFException: use runtime exceptions + * * #6547: AudioFile: use runtime exceptions + * * 6547: AudioZRtpSession: remove impossible error case + * * #6547 : AudioRtpSession: remove impossible error case + * * #6547: AudioZrtp: use runtime exceptions + * * #6408 : send authenticationUsername to GUI + * * #6408 : store/restore authenticationUsername from config file + * SIPAccount: simplify + * Move registration information from SIPAccount to SIPVoIPLink + * SIPAccount::getAccountDetails : simplify + * * #6540: yaml parser: simplify + * sdp.cpp : fix a warning + * * #6540: yaml parser : remove std::string typedefs + * * #6540: Simplify yaml unserialization + * * #6540 : add a Conf::ScalarNode constructor for booleans + * setAccountDetails(): simplify + * * #6408: store authentication username in daemon + * * #6408: Be able to set the authentication username in the GUI + * * #6507 : do not crash if the program is not sflphoned + * Fix tests + * macroify SIPAccount::unserialize() + * Move all .cpp files from sflphoned target to libsflphone.la, except + main.c + * main() : simplify, return positive error codes + * * #6507 : find codecs dir in build directory + * * #6392: Sdp: move clean functions to destructor + * AlsaLayer::adjustVolume() : simplify + * alsalayer : reduce indentation + * malloc/free -> new/delete + * malloc/free -> new[]/delete[] + * malloc/free -> new/delete + * AudioSrtpSession: simplify base64 encoding + * * #6392: Initialize std::string from pj_str_t correctly + * * #6392: AudioRtpSession: Initialize remote port + * Audio settings : Initialize _echoCancelTailLength and + _echoCancelDelay(0) + * Initialize variable + * YamlParserException : fix use of stack variable after it has been + deallocated + * * #6392: fix memory leak in history + * * #6392 AudioCodec : fix memory leak + * * #6392 : fix memory leak in sip account + * * #6408: clean up sipaccount (cosmetics mostly) + * sipaccount.cpp serialize() : reduce number of lines + * * #6392: invalid memory access + * * #6392 : fix invalid memory access + * * #6479: merged useful code from MimeParameters into Codec interface + * * #6462: fixed hangup on IP2IP call + * added run_daemon.sh script + * test: remove unused variable + * Remove functions only used by a failing test (cherry picked from + commit fcf718cb75de7f1882dc61c07bb8d300dfa10f85) + * * #6360 : make client tests build (cherry picked from commit + 028b2835f040e51ab8ab979b32732b07b8798fce) + * * #6360 : fix warnings in check_global test (cherry picked from + commit 9e2bd6a7496dd64f6f48595e385760019aab1193) + * * 6360: updated API calls in tests, but they're not building yet + (cherry picked from commit 548f6f0f919b43772a3e9c667e5e292791281795) + * Fixed include in tests (cherry picked from commit + aeadc7525c1e31f936670ac8b02f0bcf387c38a8) + * Remove unused variables and functions + * IAX: fix warnings (cherry picked from commit + fd7a113a11cac2cd9a7c36929e88ad28195c4c35) + * Remove unused DEBUG define which interferes with logger.h (cherry + picked from commit b2f72b91d0f43cb1dd94d138882a8caa9c841c24) + * * #6392: no need to check for account NULLity since it is + dereferenced above + * * #6392: fix a memory leak, replace by stack allocation + * * #6392: remove a variable assignement which confuses cppcheck + * process_conference_participant_from_serialized() : remove unused + function + * * #6392: s/free/g_free/ + * * #6392: fix a memory leak in abookfactory_load_module() + * * #6392: remove generate_call_id() used only once + * * #6392: fix memory leak (opendir() without closedir()) + * * #6392: AudioRecorder(): ensures mbuffer is set + * Remove SFLPHONED_VERSION from global.h, use autoconf PACKAGE_VERSION + * #6298: Cleanup + * #6331: Fix deleting ringtone file after call have been answered + * * #6330: merged user_cfg into headers + * #6298: Fix conference recording file update at conference end + * #6298: Fix record file name serialization for conference + * * #6295: cleanup of codec hierarchy + * #6298: Fix gtk warnings + * * #6300: added script to run tests + * #6109: Add recording playback for conference + * * #6300: tests do not require an installed sflphone + * * #6295: re-removed clone methods + * #6109: Fix gtk_critical warnings for incoming calls + * #6109: Fix GTK_CRITICAL warning + * #6109: Fix icons when history is not activated + * #6109: Fix warnings + * #6109: Implement stop recorded file playback signal + * Revert "* #6295: removed unused clone method" + * * #6295: removed unused clone method + * * #6296: removed non existant file from Makefile.am + * #6109: Stop fileplayback for outgoing call + * #6109: Implement stop recording playback button + * Fix binding names errors in dbus introspection file + * #6109: Implement playback recorded file callback in client + * #6109: Store recorded file path on client side + * #6109: Add dbus methods for call recording playback + * * #6290: remove unused classes from utilspp + * * #6288: cleanup sdp + * * #6288: fix exception usage + * * #6288: simplify SdpException + * * #6288: cleanup in sdp.cpp/h + * #6109: Only display playback button if record file is set and valid + * * 6290: updated configure.ac to remove functor Makefile + * * #6290, #6289: removed unused classes from utilspp, fixed make + check + * #6109: Add button for history playback of recorded file + * * #6289: removed unused observer class + * * #6282: forward declare sdpMedia in sdp.h + * * #6281: renamed setCallAudioLocal->setCallMediaLocal + * #6183: Handle conference with more tahn two calls + * #6183: Fix history icons when calling back a conference from history + * #6183: Fix icons inconsistencies in history for conference hang up + * #6183: Fix toolbar actions when selecting a conference in history + * #6183: Fix conference serialization + * #6268: Serialize only calls + * * #6269: removed useless type testing + * ignore some files in test/ + * * #6268: Remove dead class AudioSymmetricRtpSession + * #6251: Do not had history calls in calllist when loading history + file + * #6251: Fix insertion in history map in before saving history file in + daemon + * #6251: Fix history unit tests + * #6251: Order the list before serailization, get rid of the hashtable + in history + * #6251: Implement history serialization using a list wether than a + map + * * #6253: remove external audioport from header, make all members + private + * * #6253: don't store external local audio port (used for NAT) in + Call + * #6251: Add start_time timestamp in history serialization + * #6251: Fix call insertion in conference items + * #6233: Fix serialized account list terminated with a ";" character + * #6238: Fix draggable history calls into current calls + * #6233: Fix toolbar updates + * #6233: Fix history + * * #6235: remove pyc files from git tree + * #6233: Handle cases when one or manuy calls are unreachable in + createConfFomrParticipantList + * #6233: Handle wrong numbers in createConferenceFromParticipantList + * #6231: Fix drag-n-drop issue + * * #6173 : move sippxml in tools + * #6231: Fix merging issue + * #6183: Implement conference unserialize + * * #6212: remove extraneous flags from globals.mak + * #6183: Unserialize conference data in conference + * #6183: Add account information in request for conference call from + history + * #5755: Add -ldl to liker in sflphone + * #5755: Fix fedora 15 compilation issue + * #6183: Serialize conference participant phone number and account + * #6183: Add conference timestamp in serialization + * * #6186: don't include global.h, just logger.h + * #6183: Fix saving history to file + * #6183: Fix removing call from calllist + * * #6184: remove pointers to Manager from AudioRtpSessions + * #6183: Calling calltree_add_call explicitely for history + * #6183: Ability to store conference inside history tab queue + * * 6181: remove unused API from sipcall + * #6171: Implment nreCallCreated callback + * #6167: Fix participant list NULL ending + * #6149: First draft of conference creation from history + * #6149: Fix multiple call/conf selection callbacks ... + * #6129: Fix place_call function called twice for pressing enter + action + * #6129: Fix double click action for history + * #6149: Add dbus call for creating conference from history + * #6129: Fix placing call from history and addressbook (still need to + fix icon) + * * #6148: removed unused AudioRtpFactory constructor + * * #6145: remove unused isAudioStarted + * * #6145: remove unused isAudioStarted + * #6129: Add conference into history, fix call/conference selection + * * #6143: don't use getType outside of serialization methods + * * #6132: forward declarations instead of includes + * * #6132: add constness, remove redundant "inline" keywords + * #6129: Add timestamp to conference object to order history entries + * * #6128: remove unused forward declarations from header + * * #6127: make noncopyable class actually noncopyable + * * #6125: don't include AudioRtpFactory in sipcall.h + * #6123: Fix alsa ringback audio file + * #6123: Fix raw audio file loading problem + * #6109: Fix daemon plugin manager unit test + * #6109: Fix history manager unit tests + * #6109: Recording filename in daemon and client for history items + + serialization + * #6109: Refactor AudioFile to play recorded call + * * #6104: AudioCodec moved to sfl namespace + * * #6099: remove active flags from codec classes + * #6095: Add notification-daemon as a runtime dependencies for rpm + packages + * #6095: Fix fedora 15 compilation in MineParameters.h + * #6095: Declare static variable explicitely for client + * #6095: Add logs to build OSC build machine + * * #6098: global variables should have file-scope to avoid name + conflicts + * #6095: Fix compilation error for Fedora 15 + * #6095: Update SFLphone version to 0.9.14 + * #6095: Add specification file in opensusse build service for + sflphone-plugins + * #6073: Fix sflphone-plugins build on launchpad + * #6093: Rename CodecDescriptor for AudioCodecFactory + * * #6089: fix warnings in make check + * * #6086: renamed codecs methods to audio_codecs + * * #6085: renamed codec related dbus calls to audio_codec + * #6065: Remove g_print from client, use DEBUG instead + * #6065: Add actions name for addressbook + * * #6085: renamed codecs* widgets/functions audiocodecs* + * #6065: Fix Addressbook runtime warnings + * #6065: Replace Codecs tab for Audio in account preference dialog + * #6065: Fix "transfert" typo + * #6065: Fix addressbook action runtime warning in uimanager + * * #6082: fixes make check by adding libcrypto libs to test + dependencies + * #6073: Rename plugin/addressbook folders for addressbook/evolution + in sflphone-plugins + * #6074: Removed AC_SUBST from configure.ac when using + PKG_CHECK_MODULE + * #6073: Fix sflphone-plugins package build + * #6073: Fix sflphone-common build + * #6065: Fix runtime gtk warning when initializing searchbar without + addressbook + * #6063: Fix mozilla-tellify gitignore + * #6063: Remove stream copy file using ifdef macro + * * #6012: fix make dist for sflphone-common + * #6063: Update .gitignore file + * #6058: Fix base64 encoding related warnings + * #6056: Fix SdpException handling + * #6055: Fix unknown pargma warning for gcc <= 4.5 + * * #5949: test gcc version before disabling unused-but-set warning + * #6054: Fix addressbook plugin compilation warning + * #6048: Fix uimanager static initialization + * #6046: Fix addressbook factory static initialization of member + addrbook + * #5979: Fix implicit function declaration warning + * #6042: Fixed discarding qualifier warnings in client + * #6041: Fix instant messaging unhandled case warning + * #5994: Implement set current addressbook name and search type in + addressbook plugin + * #5994: add rules for launchpad packaging of addressbook plugin + * #5994: Fix addressbook plugin configuration loading + * #6027: Fix addressbook enabled test from configuration + * #6027: No need of gnomedoc related macros in addressbook plugin + * #6027: Add NEWS file required for build + * #6027: Add addressbook plugin autogen.sh script + * #6027: Remove plugins from client + * #6027: Add sflphone-plugins folder at project's root level + * #5994: Move addressbook folder from contacts to plugin folder + * * #6011: removed unused Makefiles + * * #6010: remove unused headers + * * #5952: fix "string constant to char*" warnings + * * #6009 fixed warnings + * * #6003: finished cleanup of account classes + * * #6003, #6004: cleanup of account classes, defaultAccount no longer + global + * * #6000: fix memory leak of args object + * * #5998: removed using namespace std from networkmanager + * * #5998: removed "using namespace std" from ZrtpSessionCallback + * * #5998: removed using namespacestd from AudioZrtpSession.h + * * #5998: remove "using namespace std" from auriorecord.h and + MimeParameters.h + * * #5998: remove using namespace std in main + * * #5998: removed "using namespace std" from logger + * * #5949: test gcc version before disabling unused-but-set warning + * #5994: Installation of addressbook plugin + * #5979: Implement codec full addressbook search from plugin + * #5979: Implement addressbook factory and plugin + * * #5981: unused webwidget removed + * #5966: Account config synchronization fix (for stun) + * #5954: Handle media name exception + * #5954: Fix audio codec name display in client + * #5954: Clean up getSessionMedia methods + * * #5957: getRecordingSmplRate returns a value + * #5954: Clean up getCurrentCodec methods + * * #5950: remove "converting to non-pointer type 'int' from NULL" + warnings + * #5915: Full gain control version + * * #5949: remove more unused variable warnings + * * #5949: remove unused/unused-but-set variable warnings + * * #5949: show_preferences_dialog returns a success value + * * #5946: cleanup of include directives, undefined function + * * #5515: comment out SSLv2 calls in pjsip + * #5915: Implement different slope for attack tme and release time for + gain control + * #5915: use only one input signal for gain control (removed output + buffer) + * #5921: Fix no audio after holding a conference + * #5916: Add gaincontrol files + * #5916: Implement FFMPEG/CCRTP video streaming prototype + * #5903: Fix call transfer during a conference + * #5915: implement rms detector, first order averager, limiter for + gain control + * #5914: Fix call transfer when no notification request is required + * #5899: Fix conference right-click segfault + * #5884: temporary fix segfault in pjsip memory pool + * #5883: Fix compilation issues on maverick and lucid + * #5755: Fix fedora 15 compilation without patching ccrtp + * [#5855] Make echo canceller optional + * #5855: Fix echo suppression activation/deactivation + * #5855: Implement pjsip echo canceller + * #5814: Speex initialization function uses samples, not bytes + * #5814: Test using more unbalanced signals + * #5814: Fix buffer size for long echo length or long echo delay + * #5814: Adjust level for echo cancellation at runtime + * #5814: Process noise reduction before echo cancelling + * #5814: Implement speex post echo canceller processing + * #5814: Dump echo cancel file to disk + * #5814: Add parameters for echo cancel + * #5809: Add configuration parameters + * #5809: Implement speex echo canceller in audio rtp session + * #5814: Code cleanup + * #5814: Fix conf creation with several incomming ringing calls + * #5814: Fix conf creation segfault when dragging a call on hold on a + ringing call + * #5809: Added unit test for echo cancellation and implemented + "process" virtual method + * #5709: Add always recording option in configuration + * #5709: Add always recording option in audio conference panel + * #5709: Add core functionnality for always recording (missing config + options) + * #5769: Fix conference participant handling (detach/attach) and hold + actions + * #5747: Fix recording icons and state for conference when adding new + participant + * #5769: Code cleanup + * #5769: Fix hangup unsent calls + * #5769: Fix remove/add additional participant to conference + * 5769: Several fixes concerning confererence handling + * #5769: Fix compilation error + * [#5769] Fix audio streams binding in main buffer + * #5769: Removed access to audio mixer from audio layer + * #5765: Fix audio crash for illformated wavefiles + * #5765: Add maximum iteration for finding fmt and data "chunck" + * #5589: Fix compilation of libnotify under + * #5757: Fix abort signal when receiving INFO + * #5747: Add usersDetached.svg + * #5747: Handle offhold action for recording conference + * #5747: Fix off hold action for conferences + * #5747: Implement update conference in record action in calltree + * #5747: Add new icons for recording conferences + * #5747: Add recording state for conferences + * [#5738] Remove getAudioDriver call from manager (replace by + _audiodriver var) + * [#5738] Refactor mutex protecting audiolayer + * [#5737] Fix HD conference recording + * [#5730] Fix start audio session after changing sampling rate + * [#5714] Fix enter keyboard event for addressbbok and history + * [5695] Fix addressbook combo box update when no addressbook selected + * [#5695] Fix addressbook initialization and search bar update + * [#5695] Add mutex for books_data in addressbook to protect async + calls + * [#5695] Get back addressbook open from uri + * [#5695] Fix absolute addressbook URI for local addressbooks + * [#5695] Implement libebook 3.0 interface + * [#5571] Better logic for hangup (for case where call have not been + sent yet) + * [#5571] Update error handling in voip links + * [#5571] Fix compile time warnings + * [#5696] Fix installation dependencies for Natty + * [#5669] Add mention that sflphone.org is for testing only + * [#5693] Add natty in teh dput.conf file + * [#5690] Remove not useful logs + * [#5670] Use dynamic payload type for rtp dtmf + * [#5668] Clean up sflphone configuration logging + * [#5668] Fix hook checkbox configuration update + * [#5666] Fix unit tests + * [#5666] Manage event subscription + * [#5666] Emit bye request when subscription is terminated + * [#5666] Bye request should be sent after event subscription + notification is done on transfer + * [#5666] Make reinvite method static (to be called in pjsip + callbacks) + * [#5666] Hangup Call in manager for AccountNULL and IP2IP + * [#5589] Use PKG_CHECK_MODULE for every client's dependencies + * [#5623] Enlarge initial size of pjsip memory pool for calls (16k) + * [#5564] Fix audio recording resampling for g722 + * [#5571] Move attribute handling for onhold/offhold actions in SDP + session + * [#5571] Codec negotiation refactored and unittested + * [#5571] Implement tests + * [#5571] Implement pjsip negociator + * [#5571] Fix unit tests + * [#5571] Add Fmtp.h to repository + * [#5571] Integrate mime types and codec factory + * [#5571] Handle exception when SDP negotiation fails + * [#5570] Add sflphoned-sample.yml in repository + * [#5564]: Implement stereo to mono mixing for rigntone + * [#5342] Update audio stream initialization + * [#5514] Restore test ni historytest suite + * [#5514] Fix + * [#5514] Disable test_create_history_path + * [#5514] use pulseaudio in sample config file + * [#5514] Fix test: load history from file + * [#5514] Do not use X + * [#5513] Make unit tests compile successfully + * [#3947] Enable unit tests in Jenkins + * [#5454] Fix build system to handle new version number + * [#5454] Update languages from launchpad + * [#5454] Add --without-celt in OpenSuse build service + * [#5454] Change version number + * [#5331] Added first SDP session tests + * [#5273] Update nightly build version tags to conform dpkg rules + * [#5211] Refactor send register method for iaxvoiplink and + sipvoiplink + * [#3950] Remove call being transfered from calltree + * [#5211] Use appropriate memory pool for transport selector + * [#5211] Fix strict aliasing rules warning in pjsip + * [#5211] Bring back pjsip shutting down sleep to 1000 ms + * [#5211] Fix registration callback segfault when closing the + application + * [#5211] Use the dialog memory pool for Route header in INVITE + request + * [#5211] Add temporary memory pool for findLocalAddressFromUri and + findLocalPortFromUri + * [#5211] Use individual memory pool for dtmfs + * [#5211] SipVoipLink refactoring + * [#3950] Attended transfer for conference calls + * [#5284] Fix DNS resolution for Route with specified port number + * [#5284] Some code cleanup + * [#3947] Fix typo in hudson script + * [#5284] Added sip route to REGISTER, INVITE, BYE request, plus DNS + resolution + * [#5266] Use RTP dtmf as default + * [#5284] Added pjsip_process_route_set after setting routes in regc + structure + * [#5286] Fix parsing error due to long configuration file (removed + max event) + * [#5286] Fix false test in configuration emmiter + * [#5286] Code cleanup + * [#5286] Updated exception handling in configuration system + * [#4969] Fix put SRTP call on hold + * [#3950] Add debug messages + * [#3950] Ability to perform an attended transfer + * [#5276] Fix initialization problem in g722 + * [#3950] Add replace header in SIPVoIPLink::transferWithReplaces + method + * [#3950] Implemented attended method in SIPVoIPLink + * [#3950] Cleanup transaction request received callback + * [#3950] Implement dummy attended transfer in gnome-client + * [#5249] Fix audio samplerate update algorithm for g722 + * [#5249] Fix uninitialized variable used in conditional jumps + * [#5249] Fix conditional jump error in audiolayer (uninitialized + value) + * [#5267] Use autoconf 2.65 as a requirement (instead of 2.67) + * [#5267] Restore manual pjsip configuration and compilation + * [#5267] Autodetect celt version (0.9.1, 0.7.1) + * [#5267] Fix deprecated macros in gnome client configure.ac + * [#5267] Update configuration for libcelt-dev + * [#5267] Fix build autoconf and automake + * [#5227] Deactivate automatic call to astyle after compilation + * [#5242] Hangup every calls before leaving + * [#5237] Will now nightly-build for natty, Karmic deprecated + * [#5229] Use inner class for rtp thread instead of inheritance + * [#5211] Move mainbuffer unbind call in rtp final method + * [#5211] Initialize sip call memory pool using 16 kb + * [#5211] Use call memory pool in session reinvite + * [#5211] Add debug messages + * [#5211] Use and internal pool for calls + * [#5211] Reduce pjsip memory pool usage for stateless error messages + * [#5211] Refactor call deletion + * [#5212] + * [#5208] Refactor codec management for accounts + * [#5168] Remove printf from codec's encode & decode method + * [#5168] Fix celt compilation on launchpad + * [#5168] Fix sflphoned compilation warnings in audiocodec.h + * [#[#5168] Must keep the g722 specific RTP rate to avoid incoming + packet timeout + * [#5168] Fix static/dynamic payload rtp session update + * [#5168] Throw SIPVoipLink Error if codec not instantiated in new + outgoing call + * [#5168] Fix dynamic/static codec payload type ambiguity + * [#5169] Fix doubled IP2IP profile when no config file + * [#4867] Add gtkinfobar in configuration panel + * [#4867] Disable input/output/ringtone selection when using default + alsa plugin + * [#4952] Patches for possible buffer overflows + * [$4885] Fix schemas problem + * [#4885] sflphone.schemas not present during build + * [#4885] Add gconf shemas directories in opensuse build system + * [#4885] Add file/folder ownership for opensuse-factory build system + * [#4906] Fix opensuse-factory build + * [#4885] Update name dependency for libedataserver + * [#4885] Fix non-void function without return in dbus-c++ + * [#4895] Update language translation + * [#4896] Update session timestamp when updating media + * [#4896] Reapply RTP hack for G722 payload type + * [#4896] Update recording sampling rate when updating codec + * [#4897] Save codecs in config for each configuration changes + * [#4895] Do not save config when sflphone quit + * [#4885] Update date for copyright + * [#4885] Deactivate siptest that require more than one sipp instance + * [#4879] Remove inmcoming call notification from IAX + * [#4885] Some cleanup + * [#4874] Add setCancel immediate/deffered for ost::Thread + * [#4879] Fix incoming call notification + * [#4878] Set keyboard focus on searchbar when selecting addressbook + * [#4874] Fixed compilation warning + * [#4874] Fixed compilation warning in sipvoiplink + * [#4874] Fix compile time warning in RTP record handler + * [#4874] Fix conditional jump in SDP + * [#4874] Fix conditional jump based on uninitialized value + * [#4874] Store call id within rtp thread context + * [#4874] Fixed conditional jump based on uninitialised value in + conference + * [#4871] Fix default account fetching + * [#4870] Delete RTP session when Refusing an incoming call + * Restore IP to IP call + * [#4857] Fix audio codec negotiation problem + * [#3947] Adjust ressources allocated to compilation + * [#3947] Disable unit tests in Hudson + * [#4305] Free mutex only when really quiting SFLphone + * [#4859] Update copyright to 2011 in every source file + * [#3218] Character '.' stripped by the caller engine + * [#4854] Fix typos, desktop entry + * [#4847] Apply RTP modification to ZRTP session + * [#4852] Update Karmic and Lucid dependencies + * [#4852] Add Libedataserver and libedataserverui as gnome client + dependencies + * [#4852] Add authentication mechanism for EDS + * [#4851] Fix segfault when closing pulseaudio layer too rapidly + * [#4808] Some otehr cleanup + * [#4808] Made some cleanup + * [#4808] Added mutex in rtp session for codecs and noise process + * [#4847] Update audio processing when updating RTP media + * [#4842] Add support for linking with gold/ld --no-add-needed + * [#4808] Make update g722 related static/dynamic payload logic + * [#4827] Upper limit on the number of contacts to import from EDS is + hard-coded to 500 + * [#4808] Fix put call on/off hold + * [#4808] Implement early RTP start for incoming calls + * [#4808] Audio stream is no longer start within RTP session. + * [#4808] Removed coupling between audio layer and and RTP session + * [#4702] Start audio rtp session as soon as it is created + * [#4702] Init timestamp to 0 + * #4702: Send RTP packets immediately, no need of outgoing queue + * [#4784] Update dbus-c++ version from gitorious + * [#4702] Update RTP timeouts + * [#4702] Lengthen RTP timeouts + * [PATCH] Fixed compatibility with old libtool versions. + * [PATCH] Accept older libebook (Maemo 5 has 1.4.2) + * [PATCH] Fixed double-free error in preferences dialog + * [PATCH] Fixed building of sflphone-common on Maemo5 + * [PATCH] Improved Gnome client initialization error handling. 1. It + no longer segfaults when sflphoned isn't available. 2. User is + provided with GUI error dialog. + * [PATCH] Improved autogen.sh scripts 1. They do not require bash + anymore 2. Added workaround for Debian bug #565663 3. Replaced + manual autotools invocations with single autoreconf call 4. Non-zero + return status on failure + * Revert "[#4468] libtool <= 2.2 doesn't have LT_INIT macro so + AC_PROG_LIBTOOL should be used instead." + * Revert "[#4468] Libebook 1.4 is sufficient" + * Revert "[#4468] Apply big path on dbus communication system" + * [#4468] Apply big path on dbus communication system + * [#4468] Libebook 1.4 is sufficient + * [#4468] libtool <= 2.2 doesn't have LT_INIT macro so AC_PROG_LIBTOOL + should be used instead. + * [#4639] Fix determining default addressbook if this property is not + set in gconf + * [#4639] Fix memory leaks in Addressbook + * [#4637] Fix opening default addressbook at sflphone init + * [#4622] Free yaml events while parsing configuration file + * [#4623] Fix conditional jumps based on uninitialized variable + * [#4622] Fix leaks in yaml serialization engine + * [#4616] Fix addressbook warnings + * [#4514] Adjust RTP timestamp + * #4527: Rename Karmic libyaml and Celt package in debian control file + * #4495: Rework addressbook opening loop + * [#4524] Increment RTP count when sending data + * [#4524] DO NOT start RTP session twice + * [#4367] Use PKG_CHECK_MODULE for celt + * [#4367] Fedora package celt as celt (not libcelt) + * [#4367] Astyling + * [#4367] Update .po files + * [#4367] Fix segfault in gensin + * [#4354] Make celt a direct dependency on launchpad opensuse build + service + * [#4367] Make celt a required package, option --without-celt valid + * [#4367] Fix zrtp timestamping error + * [#4367] Fix audio zrtp timing + * [#4367] Dispatch ZRTP packets + * [#4367] Fix segfault when unloading account map + * [#4367] Fix zrtp session + * [#4367] Implement on packet receive + * [#4367] use symetric audio rtp session, not dual + * [#4367] Reduce packet receive/sent timeout + * [#4367] Reduce RTP timeouts + * [#4367] Move speaker data receive + * [#4367] Move speaker data receive + * [#4367] Move receive speaker data method + * [#4367] Remove debug in rtp session + * [#4367] Fix g722 codec clock rate + * [#4367] Fix noise suppression initialization + * [#4367] Fix segfault in RTP mic fadein method + * [#4367] Refactor mic data encoding in rtp session + * [#4367] Implement RTP main loop + * [#4367] Fix compilation problem + * [#4367] Fix AudioRtpclass using TRTPSessionBase + * [#4367] Fix AudioRtpSession putDtmfEvent shadowing + * [#4367] Fix AudioRtpSession putDtmfEvent shadowing + * [#4367] Refactor RTP session (phase 2) + * [#4367] Refactor RTP session (phase 1) + * [#4367] Remove Redeclaration of SymetricAudioRtpSession in + rtpfactory + * [#4265] Add continue statement in for loop for invalid addressbook + * [#4261] Makes addressbook initialization more robust + * [#4257] Add maverick in build system + * [#4233] Add sdp related unit tests + * [#4233] Add condition and signal in two incoming call test + * [#4243] Fix segfault in AudioSrtpSession + * [#4243] Fix memory leak in AudioSrtpSession + * [#4243] Make audio srtp optional in for incoming call + * [#4243] Add boolean variable to make sure remote crypto context + initialized only once + * [#4243] Add documentation to AudioSrtpSession + * [#4243] Use 80 bits authentication tags by default + * [#4243] Init audio srtp remote crypto context in + call_on_media_update + * [#4243] Move SDP negotiastion in mod_on_rx_request + * [#4243] Implement initLocalCryptoInfo to be called at different + momment + * [#4243] Init init local crypto context in when initializing audiortp + * [#4243] Change key length according to sdes negociation + * [#4243] Associate callid to accountid for incoming calls + * [#4242] Fix no SDES keys in IP2IP calls + * [#4242] Fix no SDES keys in IP2IP calls + * [#4233] Test for call on/off hold + * [#4233] Add two incoming call test + * [#4233] + * [#4233] Add 2 outgoing simultaneous call unit tests + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Fri, 30 Sep 2011 13:44:57 -0400 + +sflphone (0.9.7~rc1~ppa1~SYSTEM) SYSTEM; urgency=low + + ** 0.9.7~rc1~ppa1~SYSTEM ** + + * [#2462] Set explicitly the transport on incoming call too + * [#2462] fix typo + * [#2462] Use different address for SDP and call IP + * [#2462] Use published address in SIP-SDP + * [#2181] Fixed changelog files + * [#2181] Updated spec file + * [#2402] Fix pointer to int conversion warning (atoi) + * [#2402] Remove daemon warnings, make indent + * [#2459] Make sure the stream is opened when the call is answered + * [#2402] Add conference related picture in documentation + * [#2443] Not much ... + * [#2399] Fix dialing display problem + * [#2450] Fix incoming call already in conference crash + * [#2399] Display peer name on the first line and peer number on the + second + * [#2450] Handle 403 FORBIDDEN when refused + * [#2447] Bind offHold/onHold actions to button in gtk client + * [#2447] Bind hangup action to button for conference + * [#2447] Add conference action in gtk client's ToolBar + * [#2381] Disable the password hashing in config file + * [#2402] Cleanup + * [#2366] Set callback to null when deleting Pulseaudio streams + * [#1313] Fix main buffer unit test + * [#1313] Fix audio layer unit test + * [#2315] Hide pw in security tab, display when editing, sync with + basic tab + * [#1313] UnitTest change AudioRtpSession for AudioSymetricRtpSession + instance + * [#2402] Code cleanup + * [#2444] Add debug to catch occasional crash when loading client's + config + * [#2444] Add debug info to catch occasional crash when loading config + dialog + * [#2402] Restore Call menu translations + * [#2403] Use the published address if checked in GUI + * [#2442] Add protection test in sdp + * [#1841] Reapply pjsip patch concerning DNS SRV resolution + * [#2384] Tags incoming call as direct SIP call, if applicable + * [#2402] Change the monkey face + * [#2315] Enable user to display password in clear text + * [#2434] Force optimization level at 2 + * [#2284] Fix dbus_get_all_ip_interface compilation warnings + * [#2431] Popup main window on incoming if applicable + * [$2402] Fix simple warnings + * [#2402] Fix implicit variable init order in LibraryManagerException + * [#2402] Fixing implicit variable initialization warnings in + AudioRtpSession + * [#2402] Revert atoi change, fixing codec list doubled entries + * [#2402] Fix gpointer to gint conversion + * [#2402] Fix pointer casting to integer different size warning in + codec list + * [#2402] Fix warning discarting qualifiers from pointer target + * [#2402] Fix gtk tree view assignement from incompatible type warning + * [#1669] Fix audio recording folder utf-8 non compatibility issue + * [#2414] Clean up debugs + * [#2414] Use transport set in iptoip Account and update it frm + preference + * [#2348] Use macro N_() to mark ui.xml strings as translatable + * [#2414] Rename getSipAddress/setSipAddress functions + * [#2407] Fix volume controls display + * [#2407] Fixes dialpad + * [#2383] Set ip to ip config when clicking apply button + * [#2404] Update call-to script - Maxime Chambreuil + * [#2405] Client handles unknown call in current state as well + * [#2383] Add DBUS signal to send IPtoIP local address and port as + string + * [#2383] Add Ip to IP config change apply call back + * Clonflict + * [#2402] Code cleanup + * [#2383] Do the same for IPtoIP (init localn ip with first in the + list) + * [#2383] Use first interface in the list if local addresss is not + defined + * [#2403] Clean up unuseful addresses/ports + * [#2403] Use the IP profile SIP port as global SIP port + * [#2383] Fix dbus_get_all_ip_interface warnings + * [#2383] Take into account sameAsLocal when loading published address + * [#2383] Tsake into account sameAsLocal option when saving published + address + * [#2383] Update local ip address in ip to ip config + * [#2383] Save ip 2 ip local port in config + * [#2406] Update toolbar at startup + * [#2284] Remove redefinition warnings + speex warnings + * [#2383] Fix security table in account config + * [#2383] Save ip 2 ip network interface parameters in config + * [#2403] Restore sip transport selector + * [#2383] Fix filling the Localt IP Address on account creation + * [#2383] Fix Gtk-Critical when checking STUN + * [#2383] Fix reopening account configuration display issue + * [#2383] Load IPtoIP local address and port in preference iptoiptab + * [#2383] Add LocalAddress and Localport in Preference IpToIp tab + * [#2403] Use the address and port associated to the account as often + as possible + * [#1753] Removed pjsip generated files + * [#1753] Removed remaining milenage lib references + * [#2383] Add _publishedSameasLocal variable in sipaccount + * [#2383] Add PUBLISHED_SAMEAS_LOCAL variable in config + * [#2383] Fix stun set active or not when opening config + * [#2181] Added RPM 64bits dbus patch + * [#2402] Code indentation + * [#2313] Force $(HOME).cache directory creation at startup + * [#2383] Separate network interface and published address in account + config + * [#2400] Change dbus service installation path to libdir + * [#2382] Move TLS related published address options in security tab + * [#2382] Indent accountconfigdialog.c + * [#2181] Install libdbus-c++ in $pkglib instead of $lib + * [#1753] Remove ILBC code and disable it by default in the configure + * [#1753] Remove milenage directory + * [#2382] Fix switching interaface instabilities + * [#2396] Save local ip in account creation wizard + * [#2284] Remove warning on hold + * [#2387] Fixes history searching and filtering + * [#1215] Add samplerate display in the GUI + * [#1663] Voicemail icon reflects voice messages + * [#2395] Fix account registration ( specifically with callcentric) + * [#2386] Strip "sip:" on incoming call, fixing history call back + * [#2181] Updated spec files + * [#1215] Display codec name in calltree instead of status bar + * [#2390] Move back nbCalls and stopStream higher in refuseCall + * [#2392] Fix ringtone during call in IAX + * [#2391] Stop audio streams when there is 0 calls only + * [#2391] Add debug when call state is not valid + * [#2390] Clear returns in IAXvoipLink::sendAudioFromMic() method + * [#2380] Fixing IncomingCallNotification not regular + * [#2339] Query conference at client startup + * [#2339] Working conference querying at startup + * [#2339] Add conference in call tree + * [#2339] Primitives to query conferences at client startup + * [#2320] Add account selection in history + * [#2355] Temporary solution: do not delete pointer when removing + account + * [#2380] Change algorithm in AudioRtp to trigger an + IncomingCallNotification + * [#2274] Comment sdebug in MainBuffer flush method + * [#2274] Add flushMain() in ManagerImpl::addStream + * [#2274] Add getBufferID() method in ring buffer + * [#2274] Fix warning, comment debug in ringbuffer's flush method + * [#2274] Use AudioLayer flushMain() and flushUrgent() in ALSA + * [#2274] Clean up unused variable warning + * [#2274] Protect minbudffer pointer on flushing + * [#2274] Fix playATone method which writing empty buffer in urgent + ringbuffer + * [#2274] Use audio layer flushUrgent and flushMain in createStreams + * [#2274] Use flush audio calls from audiolayer + * [#2274] Flush when peer answered call + * [#2375] Flush main buffer in iax when answering a call + * [#2274] Parse displayname using c++ string method + * [#2375] Flush main buffer when off holding calls + * [#2375] Flush main buffer mon RTP startup + * [#2376] Use now Pulseaudio module-cork-music-on-phone + * Updated OSC packaging + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Fri, 20 Nov 2009 13:59:02 -0500 + +sflphone (0.9.7~beta~ppa1~SYSTEM) SYSTEM; urgency=low + + ** 0.9.7~beta~ppa1~SYSTEM ** + + * [#1933] Cleanup debug + * [#1933] Clean up debug + * Fix mic + * [#1933] Set the IAx format earlier + * [#1933] Move IAX sendAudioFromMic outside if (call) statement + * [#1933] Fix startstream when offhold in iax and add debug concerning + codec neg. + * [#2371] sflphone_notify_voice_mail: minor gettext message formatting + cleanup + * [#2371] select_account_cb: properly gettextize status message + * [#2371] show_account_list_config_dialog: properly gettextize status + message + * INSTALL: Minor tidyup of core install guide + * Add /sflphone/src/icons/Makefile to .gitignore + * [#2181] Updated OpenSUSE files (tmp) + * [#1933] Add debug for codec negociation for iax + * [#1933] Get rid of getMicAvail and getMicData in audiolayer (not + used anymore) + * [#1933] Add "audio codec not determined" error in IAX + * [#1933] Test flush data + * [#1933] Do not need to start audio stream in iax anymore + * [#1933] Protecting pointer + * [#2284] Remove more compilation/execution warnings + * [#2284] Cleanup debug in client, use DEBUG instead of g_print + * [#2284] Clean up uimanager + * [#2370] Remove warnings + * [#2366] Clean up other debug + * [#2366] Clean up debug + * [#2366] Call pa_xfree explicitely in writeToSpeaker + * [#2284] Remove address book warnings + * [#2365] Fixes bad cast + * [#2352] Fix continuous ringing when peer hangup and call not yet + answered + * [#2181] Added version support + * [#2181] Fixed some minor issues + * [#2360] Moved MainBuffer from AudioLayer to ManagerImpl + * [#2352] Makes getMainBuffer() everywhere + * [#2352] Use 50 sec latency on pulseaudio stream creation + * [#2352] Add alsa debug + * [#2359] Update repository documentation + * [#2354] Move pulseaudio disconnectAudioStream after stopping main + loop + * [#2352] Adjust nb byte copied in pulseaudio according to + writeableSize + * [#2352] Specify pulseaudio tlength parameters using pa_usec_to_bytes + * [#2322] Convert italian translation to UTF-8 + * [#2357] Fixes window size + * [#2357] Display only actionnable tool item + * [#2333] Update streams parameters + * [#2347] Use GNOME user settings for Menu and Toolbar appareance + * [#2349] Load/Save properly audio params + * [#2322] Update translations from Launchpad + * [#2181] Added Francois Marier script + * [#2350] Remove non-valid test + * [#2181] Updated launchpad packaging + * [#2333] Fix Pulseaudio Capture + * [#2333] Use pulseaudio ADJUST_LATENCY flag and ALSA RT-SCHEDULING + * [#2333] Pulseaudio Interpolate timing + * [#2333] Change (again) Pulseaudio settings to fit logiteck usb hdw + requirement + * [#2333] Adjust pulseaudio fragment size to 4096 (max sflphone's + frames per buffer) + * [#2284] Remove recurrent compilation warning (g++ linker problem) + * [#2333] Safer Audiostream parameters + * [#2333] Fix alsa playback to reduce underrun + * [#2333] Better audiostream parameters + * [#2181] Updated version management + * [#2333] Exclusive test in playback loop + * [#2181] Updated build system + * [#2333] Less underrun with these value + * [#2333] Update playback audiostream parameters + * [#2333] Lengthen the audio buffer reduce number of underrun in + pulseaudio + * [#2333] Add ALSA recovery functions for underrun (begin) + * [#2333] Add pa_stream_trigger in pulse audio underrun callabck + * [#2048] Reduce prebuffering in pulseaudio (which affect incomming + calls' plbck) + * [#2316] Do not display any icons to the right on the history tab + * [#2333] Comment pa_stream_trigger in pulseaudio underrun + * [#2333] Modify pulseaudio streams parameters + * [#2318] Fix transfer tool button double signal + * [#2181] Updated + * [#2333] Fix ALSA ringtone + * [#2333] Flush all main buffer before starting audio + * [#2333] Open/Close Alsa thread between calls while there is no audio + * [#2333] Add debug message and test condition on starting playback + and capture + * [#2181] Fixed gnome client makefile + * [#2181] Updated + * [#2308] Remove getTelephoneTone debug + * [#2308] Change plughw for default in ALSA + * [#2308] Oups, forgot to change function name in audiolayertest.cpp + * [#2308] Cleanup in pulseaudio code (debug, function name) + * [#2308] Fix pulseaudio stream closing assertion failure + * [#2308] Moved pulseaudio mainloop locking from AudioStream + disconnect stream + * [2308] Fix latency at the beginning of a call, when playing DTMF and + wehn starting tone + * [#2181] Updated karmic + * [#2317] [#2319] Fix address book toggle button contextual behaviour + * [#2308] Stop stream when refusing a call + * [#2308] Stop pulseaudio stream when peer hungup + * [#2308] Fix tone and ringtone + * [#2312] Display the STUN entry widget when opening the tab + * [#2308] Implement two different callbacks for capture/playback in + pulseaudio + * [#2309] Open/close pulseaudio connections in startStream/stopStream + * [2308] Leave pulseaudio stream running, do not cork/uncork them + anymore + * [#2295] Set gtk file chooser to None if nothing is set in + configuration + * [#1976] Add codec and conference documentation + * [#2209] Fix recording in regard of resamling + * [#2297] Update .gitignore + * [#2297] Update translation files + * [#2297] Add reference to our coding standards + * [#2297] Remove old docbook code + * [#2296] Reinit tls account settings after modification + * [#2253] Add DcBlocker class to remove capture's dc offset + * [#2034] Fixes for TLS transport to initialize + * [#2284] Add silent build rule + client clean warnings + * [#2274] Fix unserialize history items in cilent at startup + * [#2274] Complete display name parsing and displaying + * [#2274] Parse the Display Name in sip INVITE message + * [#2050] Fix capture volume control in ALSA + * [#1970] Volume controls disable when using pulseaudio + * [#1970] Disable volume controls when using pulseaudio + * [#2277] Fix direct ip2ip ZRTP enabling/disabling in ip2ip + preferences + * [#2181] Added launchpad debian files + * [#2181] Added spec files for OSC + * [#2274] Set display name for "Contact" sip header as the hostname + * [#2181] Fixed daemon issues + * [#2181] Fixed gnome client issues + * [#1976] Remove warnings - need to fix the transfer + * [#2006] Add init is_rec variable in ManagerImpl + * [#2006] Update codec display on call selection + * [#2006] Restore double click actions in history and contact calltree + (GTK) + * [#2176] use XDG_CACHE_HOME when initializing sfl.zid file + * [#1976] Fix calltree switching from history + * [#2209] (Re)Fix cache for zid + * [#2209] Clean up debug messages + * [#2209] Clean debug messages + * [#2209] Fix trasnfering a call during a conference + * [#2209] Speex decode must return the number of bytes + * [#2209] Change frameSize speex 32kHz + * [#2209] Fix speex codec framesize + * [#2209] Reinit converterSamplingRate in RTP sessions + * [#2209] Change speex ultra wide band framesize + * [#1747] Add pixmap data + * [#2252] Fix Receiving a server error 488 crashes the callee + * [#2209] Fix iax low rate packate sending + * [#2209] Clean up debug messages + * [#2209] Add resampling changes for IAX + * [#2209] Clean up resampling code + * [#2209] Fix latency introduced by pulseaudio + * [#2209] Fix initialization of mainbuffer's internal sampling rate + * [#2176] Fix upsampling buffer size in audiolayer + * [#2209] Add dynamic converter sampling rate in audiortp sessions + * [#1747] Fixes runtime warnings + * [#1747] Remove from repo + * [#1747] register our icons to be used as stock icons + * [#2209] Fix number of byte in alsa's write to speaker + * [#2209] Fix putting non-resampled data in RTP's mainbuffer + * [#2209] Add alsa resampler + * [#2209] Add a samplerate converter in PulseLayer + * [#2209] Add mainbuffer's internal sampling rate and flushall method + * [#2176] Add mainbuffer stateInfo debug method + * [#2209] Resampling is optimal using SRC_LINEAR not SRC_FASTEST + * [#2176] Remove debug recordings + * [#2176] Fix Holding a conference participant on new calls + * [#2224] Add confID in callable object + * [#2176] Fix putting onhold a call participating to a conference when + pressing new call + * [#2176] Reset auidio buffers when adding streams (rtp, audiolayer) + * [#1976] Use xml to describe toolbars - Add a naviguation toolbar + * [#2176] Remove conference default_id in joinParticipant + * [#2176] Display error message in alsa's snd_pcm_avail_update call + * [#2176] Alsa mic avail data debug + * [#2176] Add some debug message for mic loss problem + * [#2176] Flush mic ring buffer when offholding a call + * [#2176] Reset ringbuffers' readpointer when adding main participant + * [#2176] Fix getAvailData algorithm + * [#2176] Reset ringbuffer's readpointer when adding a new participant + to a conference + * [#1744] Regex object renamed to Pattern. Previous attempt at + providing + * [#2176] Fix detach main participant problem when adding new one + * [#1976] Use right domain to translate + * [#1976] Add xml menu description + * [#2176] Store a list of confernece participant in client + * [#2176] Fix add participant, joinparticipant methods + * [#2181] Do not install dbus-c++ headers + add return value + * [#2176] Fix minor call handling instabilities + * [#2174] Fix incoming IP call contact address + * [#2211] Add test to protect NULL pointer + * [#1163] Add Advanced account configuration section + * [#2176] Add some usefull comments and debugging info + * [#2176] Add conditions to display security icons in conference + * [#2176] Fix detaching one participant while keeping communication to + others + * [#2176] Reenable userActive.svg in call tree + * [#2176] Make user active blue (not red) + * [#2176] Fix user active picture + * [#2176] Fix "hidden" merge conflict in sipvoiplink + * [#2176] Remove iax audio stream on peer hungup + * [#2174] Multiple UDP transports functional (TESTED with 2 accounts + and 3 calls) + * [#2176] Fix fix audio stream binding in iax + * [#2174] Create a default UDP transport + use tp selector for dialogs + also + * [#2176] Register iax audio stream in mainbuffer + * [#2176] Fix getAudioCodecName in IAXvoipLink + * [#2176] Fix iax account init + * [#2176] Handle multiple account using the same sip transport + * [#2165] Add .png files + * [#2176] Small fixes concerning dtmf + * [#2176] Fix make uninstall in codecs + * [#2174] remove stund makefile generation + * [#2176] Add conference lock + * [#2174] Add transport selector for multiple accounts + * [#2176] Change userActive picture from red to blue + * [#2176] Fix security pixbuff in calltree + * [#2176] Replace sfl.zid in .cache/sflphone instead of .sflphone + * [#2176] Fix add call description + * [#2176] Remove detach button from toolbar + * [#2176] Fix calltree call description state and state code in + conferences + * [#2176] Fix pulse audio double free + * [#2176] Fix conference selection + * [#2174] Clean up - remove stun settings in client network + configuration panel + * [#2174] Remove voviva stun code + * [#2174] Rsolve STUN with pjsip - DO NOT WORK + * [#2165] Add user svg + * [#2165] Debugging sip call failed + * [#929] Link against uuid if installed + * Oops + * Fixed bugs related to libsexy (with GTK < 2.16) + * [#929] Remove uuid-dev dependency in the core + * [#2165] Debugging no negociated codecs at communicatio start + * [#2165] Fix calltree bug (gtktreestore instead of gtkliststore) + * [#2165] Fix several merge problems + * Updated opensuse packaging script + * [#1163] Add missing figures + * [#1163] Update INSTALL file + * [#2165] Fix IAX + * [#2165] Add recordabe interface + * [#2165] Finish recording refactoring for call (not for conference) + * [#2165] Enable speaker recording for two different calls + simultanously + * [#2165] Implement call recording using the Recordable interface + * [#2165] Add get and set to AudioLayer's audio recorder + * [#2165] Add class recordable from which inherit call and conference + * [#2006] Fix G722 and Speex 8khz codec conferencing + * [#2006] add recording of audio buffers + * [#1163] Add general settings section + * [#1163] Fixes makefile error + * [#2006] Fix some minor issues + * [#2006] Drag a conference call on another conference call + (difference conferences) + * [#2006] Fix dragging a conference on itself + * [#1744] Integrating some of the needed regular expression patterns + in order + * COmplete call features + * [#1744] Added support for named subgroup in the Regex object. Also, + new + * [#1744] Adds thread safety features, compile() and setPattern() + methods to the Regex class. + * [#1744] Fix inconsistency in the finditer method from the last + commit. + * [#1744] Added regex pattern object built on top of libpcre. To be + used + * [#1744] Initial commit towards implementing RFC4568. Unimplemented + in the + * [#2157] Hide "security" and "advanced" tabs for IAX under account + * [#1163] Add call features section + * [#2006] Add joinConference capabilities + * [#2006] Add dbus joinConference signal + * [#2006] Drag a conference call onto a conference to add it + * [#1163] Add addressbook section + * [#2006] Drag a conference call onto a single call to create a + conference + * [#2006] Expand rows automatically + * [#2006] Add minimal multiple conference handling + * [#2006] Add atached/detached conference icons + * [#2006] Add function processRemainingParticipant + * [#2006] Deep refactoring, fix hangup bug + * [#1163] Update documentation - Accounts part + * [#1976] Integrate user doc to gnome client build system + * [#2122] Remove double inclusion in dbus-c++/src/Makefile.am + * Remove pjproject version number + * [#2006] Fix peerHungup + * [#1976] Make Yelp accessible from the GNOME client (need to install + the sflphone.xml first) + * [#2006] Fix multiconferencing hangup + * [#2006] Fix hangup calls in a conference + * [#2150] Make IAx2 reappear + * [#2006] Fix detach participant on multiple call + * [#2006] Can remove rining call from a conference + * [#2006] Reinit confID when removing a participant + * [#2006] Remove get isCurrentCAll in hangup/peerhungup (SipVoipLink) + * [#2006] Fix refuse call + * [#2006] Fix answerring incoming call + * [#2006] Refactor conference's participant list + * [#2101] Re-integrate test compilation in main build system + * [#2101] Make the test directory compile + * [#2136] Restore history functionality + * [#2006] Fix binding main participant to himself + * [#2006] Fix add current/incoming/onHold participant to an existing + conference + * [#2006] Fix add incoming calls to an already created conference + * [#2006] Fix remove stream + * [#2006] Fix detachParticipant/removeParticipant switchCall ids + * [#2006] Fix adding a call in conference having state "CURRENT" + * [#2006] Remove/add main participant from conferences + * [#2006] Hold/unHold conference + * [#2006] Detach a partcipant from drag n drop + * [#2006] Hangup a conference + * [#2006] Add hold/unhold conference dbus messages + * [#2034] gtk-ui fix under the "basic" tab. + * [#2006] Fix dragging calls on conference calls + * [#2006] Fix detach participant from a conference + * [#2034] Added default message is status bar under the account config + dialog + * [#2112] Fix a crashed caused when a non-md5 password was sent to + pjsip. + * [#2006] Detach participant by ID + * [#2006] Fix addParticipant method in managerImpl to handle + incoming/answered calls + * [#2006] Add addParticipant method in managerimpl and related dbus + messages + * [#2111] Added the ability to configure zrtp on sip.sflphone.org from + * [#2106] Fixed problem in the account assistant under gtk-ui. Also, + assistant.c + * [#2006] Fix dragging a conference call on another conference call + (same conference) + * [#1904] Small UI fix. Assistant was moved from "Call" to "Edit" + menu. + * [#1904] Fix a wrong label under gtk-ui. + * [#2034] Renaming and source code splitting. + * [#2034] Status bar added to account window to better reflect the + registration + * [#2006] Make calltree_remove_call recursive (for GtkTreeStore) + * [#1110] Small gtk-UI fix in the account window (alignment). + * [#2006] Fix remove conference, display children which are still + active + * [#2006] Recursive function call in calltree_update_call + * [#2006] Add multilayered capabilities to calltree (GtkTreeStore) + * [#2006] Implement remove conference in calltree + * [#2034] Now useless as Direct Ip calls settings moved under + Preferences. + * [#2034] Edit/add buttons were set insensitive all the time under + gtk-ui. + * [#1887] Information about the state of the current SIP call is + displayed + * [#2006] Add call tree remove callback + * [#2006] Fix create_conference function + * [#2006] Update conference_added_cb to add new conference to the list + * [#812] Added new tab under GTK-ui Preferences. Moving Direct Ip + Calls from + * [#2121] Disable temporarily test compilation + * [#2006] Fix conferencelist to handle conference_obj_t instead of + gchar + * [#2006] Add conference_obj structure + * [#2121] Update version + * [#2006] Fix conference selection + * [#2101] Use the new source tree to fetch the right object files + * [#2006] Add conference in calltree + * [#2006] Add Dbus signal conference added/removed/changed + * [#2006] Add getConferenceDetails call on dbus + * [#1904] Registration expire now appears as a spin box under gtk-ui. + * [#812] Fixing a segmentation fault caused by a non-existing account + ID + * [#2006] Add getConfList method over dbus + * [#2006] Add a conferencelist data structure in client-gnome + * [#812] Defaults value are now sent if a non-existing account is + requested + * [#2006] Add sflphone action sflphone_join_participant + * [#2006] Fix buffer read pointer problem deletion + * [pjsip] Attempt at fixing via header incompatibility with + Freeswitch. + * [#1797] forget something + * [#2006] Add call new state conferencing in deamon + * [#2006] Remove addParticipant method for conference, use + joinParticipant only + * [#1163] Update INSTALL documentation + * [#812] Msec/sec values were not taken into account. + * [#1797] Make pjproject-1.4 compile + * [#2006] Add Detach participant method + * [#2006] Dragndrop fully functional with INCOMING and HOLD call + * [#1797] Add pjproject-1.4 + * [#1797] Remove pjproject-1.0.3 + * [#2006] Get call state in conference related function + * [#2006] Add joinParticipant (conference) method in ManagerImpl + * [#2006] Add joinConference DBUS message + * [#2006] Store the previously selected call_id on dragndrop + * [#2006] Fix GValue pointer unref in selection callback + * [#2006] Store dragged call_id + * [#2006] Update drag_data_received_cb callback to manipulate CallIDs + * [#2006] Add dragndrop signals + * [#2006] Set calltree reordable + * [#812] Adds the ability to create a TLS listener in case the user + requests + * [#812] Adds the ability to configure local/published address from + * [#1883] Move switchCall in onHoldCall function + * [#812] Deals with the published address/port problem when + integrating TLS. + * [#1883] Switch call id in managerimpl when peerHungUp + * [#1883] Switch call id before hangup + * [#1883] Add usefull and permanent debug info for conference + cretion/deletion + * [#812] Fix various segmentation faults related to Direct IP kind of + calls. + * [#1883] Fix deletion of std::map elements using iterators + * [#2014] Add libzrtpcpp build dependency + * [#1883] Still some for loop test ambiguity (while loop instead) + * [#1883] Fix for loop initial test ambiguity (use while loop instead) + * [#1883] We must discard data in urgent ring buffer if data is get in + mainbuf + * [#1883] Fix availForGet same id for ringbuffer and readpointer + * [#812] Match "sips" as a Direct IP Call when the user enter a sip + uri + * [#812] Fix segmentation fault related to SIP URI creation. + * [#812] Towards integrating multiple tls listeners at the same time. + This + * [#1883] Add debug messages in conference and fix mainbufferTest + * [#812] gkt-ui fix. Private key must be fed as a filename and not as- + is. + * [#812] TLS integration within sipvoiplink and pjsip. Also, + configure.ac + * [#1883] Fix Alsa/Pulse mallocation + * [#1883] Fix data corruption in AudioRtp's micData buffer + * [#812] Full dbus integration for all the tls related options under + gtk-ui. + * [#1883] Fix memory leaks in audiortp session + * [#1883] Fix mem leaks in audio rtp + * [#812] Fix setAccountDetails where TLS_ENABLE was set to the value + * [#812] Small gtk-ui fix. + * [#811][#812] Small gtk-ui fix. + * [#812] Introduced a mechanism for configuration files that makes + possible + * [#812] New dbus bindings added. Also, configuration compliance was + enforced + * [#1881] Remove default buffer from MainBuffer (update unit-tests) + * [#1881] Add ring buffer read pointer tests + * [#1883] Fix issues in ringbuffer reader pointers + * [#2034] Implementing a new configuration dialogue for TLS transport + settings + * [#1883] Add some usefull debug and safety checks + * [#2028] Notify the client with libnotify when the zrtp negotiation + failed. + * [#811] Harmless no to throw an exception, an makes the application + less + * [#2028] A minidialog is showed to the user under sflphone-client- + gnome + * Removed useless file. + * Ignoring Makefile in src/widget + * [#2027] Fix segmentation fault when showMessage callback is called + after + * [#2026] keyExchange was set to ZRTP instead of "1" + * [#2024] Fix the wrong summary at the end of the assistant. + * [#1883] Fix mnagerimpl conference map insertion + * [#1883] Add Mutexes in MainBuffer + * [#811] Gtk ui was not presenting the right information about zrtp + for + * [#2023] security icons were not installed in sflphone. + * [#2021] Fix a mistake in the readme from sflphone-common that gives + wrong + * [#811] The current SRTP mode was not properly displayed for the + IP2IP + * [#1743] Re-implementation of the "automatically remove error dialogs + [...]" + * [#2017] [#2019] Fix the inability to dial a number and place a + registered + * [#811] Final re-integration of ZRTP support in the main branch from + 0.9.6 + * [#1883] Fix map insertion methods + * [#811] Combo box now is now set to the active key exchange method + * [#811] ZRTP options now configurable back again from the Gtk UI. + IP2IP + * Updated hostname for git clone + * [#1883] Add minimal functionalities to create a conference + * [#811] re-integration of all the methods and signals on dbus. + ManagerImpl + * [#811] Got out of a precarious position were nothing would compile. + * [#1976] Build documentation squeleton with docbook + * [#1883] Add sflphone-client "addParticipant" button for conference + * [#1994] Better organize the source directory structure. New + subdirectories + * [#1883] Add a simple Conference class + * [#1882] Use static audio buffer in Pulse and ALSA layer (instead of + malloc) + * [#811] First commit toward re-integration and refactoring of ZRTP + * [#1882] Flush RTP ring buffer before entering mainloop + * [#1882] Fixed MainBuffer::UnBinCallID() in case there is no + ringbuffer + * [#1882] Test (and fixe) high level conference and mixing + functionalities + * [#1772] Apply patch to compile on fedora (sent by Marcin + Zajączkowski <mszpak@wp.pl>) + * [#1882] Update Bind, unBind call_id in MainBuffer + * [#1959] This adds the ability to store password as an MD5 Hash in + the + * [#1538] Fixes rules compilation + * [#1930][#1931] Fixed a mistake (again) related to index and + credential count + * [#1753] Remove ILBC from pjproject - Hacks in pjsip + * [#1930][#1931] Credential was not selected properly using realm + * [#1882] Finilize multiple reading pointer in RingBuffer + * [#1538] Remove configure from autogen.sh to respect debian upstream + authors policy + * [#1773] Remove generated files from repo + * [#1791] Use XDG_CACHE_HOME to save pid file + * [#1791] Fixes path to save history + * [#1791] Fix debian installation scripts + * [#1930][#1931] Settings are now taken into account in the server. + * [#1882] Add ringbuffer default ring buffer pointer in methods + involving mStart + * [#1882] Add default ringbuffer pointer + * [#1882] Add RingBuffer multiple read pointer basic functionnalities + * [#1882] Fix MainBuffer flushData unit test + * [#1930][#1931] Ability to save and retreive the configuration from + * [#1882] Added Multiple CallID mapping to MainBuffer + * [#1791] Not much + * [#1791] If XDG env variables are not null but empty, use default + ones + * [#1791] Make XDG_CONFIG_HOME writable + * [#1930][#1931] Partial commit. Not working yet. Cannot delete + account + * [#1881] Fixed alsa capture latency problem + * [#1881] Fixed Alsa capture temporarily + * [#1930] [#1931] Partial unbroken commit providing the ability to + * [#1881] MainBuffer implemented in AudioLayer/AudioRTP + * [#1881] Add discard and flush unit-tests + * [#1881] Add discard and flush functionnalites to MainRingBuffer + * [#1881] Add availForGet in MainBuffer + * [#1881] Add availForPut function to MainBuffer + * [#1880] Remove AudioRTP* pointer from SipVoIP (reapered while + merging master) + * [#1881] Add a map between call id and coresponding ring buffer + * [#1855] Refresh pot file and upload on Launchpad + * [#1881] MainBuffe now robust to false ids on getData and putData + * [#1881] Fix big big big memory leak + * [#1881] Add getData and putData to mainBuffer + * [#1881] Unit-test basic ring buffer functionnaities + * [#1881] Add class MainBuffer and basic buffer creation unit-tests + * [#1880] Fix call transfer (step2) issues + * [#1880] Moved AudioRtp* pointer from SIPVoIPLink to SIPCall class + * [#1791] Add postinst script to keep user data when migrating + config/history file + * [#1797] Make pjsip compile + * [#1777] Code indentation + * [#1791] Use XDG_DATA_HOME and XDG_CONFIG_HOME for sflphonedrc and + history + unit tests + * [#1746] Useless space does not appear anymore when volume sliders + and + * [#1643] GtkCheckMenuItem is used instead of icons for elements in + the + * [#1110] [#1668] STUN parameters are now located in the preferences, + under + + -- Julien Bonjean <julien.bonjean@savoirfairelinux.com> Fri, 06 Nov 2009 11:20:01 -0500 + +sflphone (0.9.6-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6 ** + + * Documentation on echo test + * [redmine_down] codec names not displayed in total + * [redmine_down] crash when hanging up a dialing call because tries to + add it to history whereas no starttime + * [#1927] alternate every time screen changed to call history + * [#1886] clean code + * [#1886] debug messages when loading history removed + * [redmine_down] sflphone-kde icons + * [#1855] Update language files + * [#1502] Update version number + * [redmine_down] setHistory at close + * [#redmine_down] Handle PJ_DECLINE_SC as failure + * [#1923] Fix segmentation fault when adding a new account + * [#1923] Check on iterator before setting the config + * [#1904] Added mnemonic to tabs in sflphone. + * [#1905] The daemon was not sending the currentSelectedCodec signal + on dbus when answering a call. + * [#1922] Default values set to all account details + * [#1886] Spinbox reg expire enables apply, and address book is not + visible when disabled + * [#1905] Bug fix for segmentation fault caused by an empty string, + * [#1910] Warnings in test directory + * [#1919] Error fixed + * [#1855] Update russian translation - Hussein Abdallah + * [#1910] Remove files + * [#1919] fixed + * [#1777] Code indentation + * [#1918] fixed + * [#1917] fixed + * [#1910] Remove warnings compilation in src + * [#1886] removed AccountListModel in configskeleton + * [#1914] + * [#1911] check previous and new port + * [#1910] Remove compilation warnings in src/dbus and src/history + * [#1910] Remove compilation warnings in src/audio + * [1855] Update german translation - Sven Werlen + * [#1909] removed + * [#1906] Done + * [#1904] The registration expire value is now configurable from the + * Cleaned up debug messages. + * [#1886] separated initCallItem in two functions + * [#1886] reversed error in commit + * [#1886] clean debug + * [#1886] changed Name of classes and files + * [#1886] clean + * [#1870] In call_state_cb (dbus.c:126), _time_stop was overridden by + the actual time. + * [#1884] Added some new gpg flags to prevent tty warnings + * [#1886] Clean audio config dialog + * [#1886] No more compile warnings. + 1 comm + * [#1872] Check if the user input is smaller than PJ_MAX_HOSTNAME. + * [#1886] + * [#1785] Fixed build when no new commit + * [#1852] If chosen by the user, the hostname can now be solved and + used + * [#1871] * and # inverted back + * [#1869] Conditional compilation that checks if + * [#1309] removed test in main + * [#1425] Put actions in SFLPhone window class instead of ui view, + made a separate toolbar for screens. + + -- SFLphone Automatic Build System <team@sflphone.org> Mon, 27 Jul 2009 09:53:19 -0400 + +sflphone (0.9.6~rc2-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6~rc2 ** + + * [#1755] Remove generated file + * [#1753] restore ilbc ... + * [#1866] Methods getSipPort and setSipPort now have an effect on the + * [#1753] make pjsip compile without ilbc. Use ./autogen.sh --disable- + ilbc-codec + * [#1855] Fix error in russian translation + * [#1805] Remove the old flawed signal mechanism which was failing in + * [#1855] Refresh translation + * Spanish translation finished + po README files updated + echo's in + copy-in-clients + * [#1850] Yun made the chinese HK-CN translation + * [#1848] Fix transfer interface bug + * [#1862] At install, kde client installs only french translation file + * [#1841] A new fallback mechanism was added to the internal resolver + in PJSIP. + * Started AccountList model/view + * [#1855] Remove po subdir in Makefile.am + * [#1855] Fix typo error in sflphone + * [#1855] Do not generate Makefile in sflphone-common/po + * [#1855] Copy translation files into both clients dirs + * [#1855] Remove po dir from sflphone-common + * Comments added + * [#1860] mailbox->voicemail... + * make scripts executable + * [#1855] French translation + * [#1855] Chinese zh_HK partially filled... + * [#1859] An unnamed pipe monitored by poll() was added. When we want + to + * [#1855] Sven completed the first part of the german translation + * [#1855] Cantonese manually filled for already translated, almost + equal strings + * [#1855] Merge russian translation + * [#1855] Spanish manually filled for already translated, almost equal + strings + * [#1855] Update german translation in ./lang/de + * [#1858] This problem was fixed by removing a useless line in + * [#1855] merged existing translations in lang/ sflphone.po's + * [#1842] [#1843] An attempt at improving the expected behaviour that + can't + * [#1855] added po folder in gnome client and scripts for copying from + common lang folder to clients + * [#1853] Edit before call does nothing on call history + * Put most language entries possible in common. From 300 to 250 + entries. Stays underscores problem. Scripts for copy in clients. + * commit to merge master + * [#1825] Changed "Bad authentification" to "Authentication Failed". + * common po files + * [#1753] Remove ILBC from pjproject + + -- SFLphone Automatic Build System <team@sflphone.org> Fri, 17 Jul 2009 19:12:58 -0400 + +sflphone (0.9.6~rc1-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6~rc1 ** + + * Update some version number + * [#1792] Creates .sflphone directory with permission 600. Also, + "chmod 600" after + * [#1810] GUI is now notified that the call failed. Also, a segfault + was + * [#1816] Address book search disabled when disabled address book and + enabled it back plus button stays triggered + * codeclistmodel + asynchronous loading of address book + + enable/disable address book + * [#1810] Now checking SDP answer after 200 OK. Still need to + implement full + * [#1794] Can't use the interface during a call + * Updated translation files + * Russian translation integrated + * Codec list model/view started. + * [#1807] Add configure.ac in pjproject-1.0.3 + * [#1787] closeRtpSession added in some places where it should have + been + * Use Item class for contacts and accounts + * Comments + clean code + * [#1794] Improved debug messages + * [#1805] Replaced the old and unreliable mecanism that was was + waiting for + * [#1794] Can't use the interface during a call + * [#1787] For those cases where no registered SIP account is + configured + * [#1797] Make pjsip compile + * [#1787] Minor changes. Removed useless commented line. Changed order + of + * [#1777] Code indentation + * [#1797] Update package generation with new pjsip version + * [#1798] Does not hang up when the call is building up + * [#1797] Update .gitignore with new pjsip version + * [#1797] Remove generated files from repo + * [#1797] Main build system now uses pjproject-1.0.3 + * [#1797] Add pjproject-1.0.3 + * [#1797] Remove pjproject-1.0.2 + * [#1796] Computing time optimization (samplerate conversion) + * [#1787] _audiortp->start() moved away from offhold(), + SIPCallAnswered() + * [#1312] Added new states for calls initialized by other clients + * [#1795] Crashes when adding a new account, checking it and applying + * [#1782] Missing icons + * [#1793] KDE client compilation problem + * Fake ringtone files can no longer be set. + * indentation + * [#1312] Able to fetch to differentiate incoming/ringing call state + * [#1784] Use DESTDIR variable in po Makefile - fix language file + installation + * [#1785] Fixed typo + * [#1785] Fixed changelog update + * [#1759] ./autogen.sh --prefix=/usr --with-debug to use optimization + level 0 + * [#1773] Changed snapshot naming convention + * [#1773] Removed gpg agent use, added repository cache cleaning + * [#1759] Use optimization level 0 for repository, 2 for packages + * [#1777] Code indentation/formatting + * Translated new features in french + * [#1785] Added missing changelog entry + * [#1781] Window title is SFLPhone + * [#1777] Add code indentation/formatting in the buil system + * [#1774] Can't set voicemail number in KDE account creation wizard + * [#1775] Can't modify account information for account created with + the wizard + * [#1771] Add a "Default" button in context menu to disable chosen + prior account + * [#1705] + * [#1224] Remove generated file from the repo + * [#1224] Remove generated file from the repo + * [#1762] distclean target should remove kconfig generated files + (settings.h, settings.cpp). Rename them? + * [#1761] clear history button should really clear history + * Dialpad works. + * Implemented Dialpad widget instead of building it in main view. + * Removed last occurence of the old config dialog, that made the build + crash. + * [#1755] Do not consider G722 as a dynamic payload elsewhere than in + RTP layer + * [#1753] Remove ilbc Makefile generation + * [#1756] Implement a kde configuration dialog with kconfig xt and + kconfigdialog class + * [#1755] fix audiocodec folder parsing problem + * [#1450] Reinit timestamp comparison in RTP, create session in + newOutgoingCall + * [#1753] Remove milenage third party code from pjsip + * New Config Dialog integrated in GUI.(without codecs) + * [#1753] Remove ILBC codec + * kconfig started, tr2i18n -> i18n, icons folder, accountList changed + * [#1705] Fixed Audio RTP thread creation/start + * [#1714] Fix codec negociation result handling + * [#1678] Fix audiortp payload setting + * [#1678] Put bac putData method in rtp + * [#1669] gtk_file_chooser_get_filename() support UTF-8 by default + * [#1735] Add conditions to sdp update call if call declined + * [#1737] substr of recordings destination folder to remove "file://" + should be done in client rather than in daemon + * [#1731] Enlarge audio stream buffer size + * [#1714] Missing true + * [#1317] Fixed Mandriva timeout + * [#1317] Changed tag convention + * [#1317] Cleaned git-dch + + -- SFLphone Automatic Build System <team@sflphone.org> Fri, 10 Jul 2009 15:50:26 -0400 + +sflphone (0.9.6~beta-SYSTEM) SYSTEM; urgency=low + + ** 0.9.6~beta ** + + * spec files for mandriva and opensuse updated with buildrequires + libqt4-dev >=4.3 + * [#1700] Cannot build on ubuntu 8.10 and a few other distribs + * [#1502] Update version number where applicable + * [#1642] Update client icons + * [#1450] Clean up useless debug and comments in sipvoiplink and + audiortp + * [#1450] Remove Semaphore object in AudioRtp thread deletion + * [#1450] Audio RTP init now synchronized with Sip/SDP + * [#1693] kde client crashes when changing codecs order/activation + * [#1450] Deep refactoring of audiortp + * [#1450] setRtpSessionRemoteIp + * [#1689] getCallList at start + * [#1224] Change path in package files + * [#1450] Audio RTP initialized only once, payload and remote ip set + at runtime + * [#1450] Add setRtpSessionMedia and setRtpSessionRemoteIp address + * [#1642] Make GNOME GUI fresher and younger ;) + * [#1686] Status bar displaying used account + * added sflphone-kde icon so that it compiles + * [#1659] Ending a call causes the daemon to crash + * corrected introspection XMLs, po files... + * [#1211] g722 media descriptor in codecDescriptor + * [#1310] Install sflphoned in $(prefix)/lib/sflphone + * [#1502] Do not install test binaries and dbus utilitaries + * [#1224] hack for pjsip build system! + * [#1224] Remove pjsip binaries from repo + * [#1224] Upgrade to pjsip 1.0.2 + * [#1658] About SFLphone (bugs) + * [#1658] About SFLphone + * [#1660] Displaying all dialed numbers in a call + * Tested status bar. + * [#790] Optimize pulse audio streams parameters + * [#1678] Some usefull debug messages for mutex/semaphore deadlock + problem + * [#1669] Add/remove some usefull/unusefull debug + * [#1665] Fix latency related to pulse audio stream openning/closing + * [#1457] Make the menus and panels accessible in french + * [#1457] Improve broken keyboard accessibility in menus and conf + panels + * [#961] Instanciate only once the searchbar icons + * [#961] Restore transfer fonction + * [#961] Filter on the history type OK + * [#961] Fix compilation problems on hardy/intrepid + * [#1157] Commit missing files + * [#790] Reduce number of start/stop streams call on pulse audio + * [#1639] kde client crashes when no account registered + * [#1620] Fix the searchbar + * [#1620] Get back caltree as it was during gtkcritical area + * [#1620] Add history filter reinit function + * [#1335] Add a missing label in address book preferences + * [#1561] Update russian translation - Hussein Abdallah + * [#1605] Fix edit menu french translation + * [#961] Enable to search in the history according to the call type + * [#1449] Searchbar does not work anymore + * [#961] Add popup menu on the entry primary icon for history + * [#1317] Fixed KDE client package dependency + * [#936] speex 32 khz integration completed + * [#936] Use 320 frame size + * [#936] Test using a frame size at 320 smpls + * [#1214] Enable / Disable history + * [#1607] Fix compilation problem for ubuntu 8.10 (libsexy) + * [#1313] Implement processDataEncode processDataDecode in audiortp + * [#1613] codec list order can't be set + * Better handling of localisation + added languages + corrected + warnings + begginning of new config dialog with kconfig + 14px + account leds + * [#1214] Save and load history according to the limit timestamp + + unit tests + * [1609] Fix call number copy/paste feature + * [1607] Restore clear action icon in searchbar + * [#936] Try to decode using 1280 samples + * [#936] Add some debug + * [#936] Add .cpp file + * [#936] Oops Forgot speex 32 khz + * [#1214] Add configuration panel for history + D-Bus calls + * [#1313] Test rtp thread function, frame size, nbbytes, resampling + * [#790] Flush audio data before closing audio streams + * [#1214] History displays local time + * [#1214] Skip empty field on display + * [#1214] Associate an account to an history entry + * [#1342] Get addressbook options sensitive/non-sensitive + * [#1211] Clean up and comments + * [#1211] Get back to 20 ms framesize + * [#1211] Use sendImmediate instead of putData in RTP + * [#1211] Fix nb byte available in RTP + * [#1211] Clear condition on maxNbSamples in RTP + * [#1211] Fix max byte available in RTP session + * [#1211] G722: Use 160 samples per frame instead of 320 + * [#1211] Test using a dynamic payload + * [#1211] Test using a dynamic payload type + * [#1211] Rename size variable (nb_samples, nb_bytes) + * [#1211] Test g722 ip-to-ip sending twice the data lenth + * [#1211] Test g722 ip-to-ip + * [#1214] Do not select an history item by default at startup + * [#1214] Remove some compilation warnings + * [#1214] Handle empty field - remove g_print + * [#1214] Add each history item only once + * [#1214] Handle call timestamps properlier + * [#1214] Do not need timestamp files anymore + * [#1214] Use the saved date for history entry + * Clean up + * [#1214] Client doesn't crash if the D-Bus call fails + * [#1214] Client is able to save its history - still some glitches + * [#1211] Forgot 16000 for g722 + * [#1211] G722 initialization + * [#1214] Save name/number, successfully load the history if no fields + are empty + * [#1499] Fixed destination directory bug + * [#1214] Restore all the functionalities; peer name/number way more + easy to handle !! + * [#1214] Add callable_object instead of call_t, refactoring + * [#1211] Test with polycom soundstation 16000 + * [#1211] Remove C like inline function in g722 codec + * [#1342] Finalize gnome client preference window formating + * [#1214] Retrieve the history when the gnome client startsup + * [#1306] Implement localization for KDE client + * [#1593] enable accounts apply button when account checked/unchecked + * [#1214] Implement the dbus calls on server side + * [#1214] Add serialized/unserialized functions to pass data on DBUS + * [#1342] Formating gnome client configuration windows + * [#1214] Save sucessfully a map of history items + * [#1499] Removed multiple jobs compilation for KDE client (2) + * [#1214] Load history from file into memory, add unit tests + * [#1534] Throws a length_error exception in case URL exceeds + std::string max_size + * [#1499] Removed multiple jobs compilation for KDE client + * [#1565] make account leds smaller + * [1430] Fix dbus debug + * [#1562] crashes when trying to change item of a call of state "OVER" + * [#1116] Fix compilation bug + * [#1317] Added mandriva and opensuse-11 64 bits + * [#1108] Add messges in main window concerning transfer success + failure + * [#1116] Fix compilation problems + * [#1211] g722 Makefile + * [#1108] Client side transferFailed/trasferSucceded signals handling + * [#1211] G722 mostly completed, + * [#1555] make bigger toolbar (24x24) + * [#1551] remove default mailbox number in wizard and disable mailbox + button when first account doesn't have mailbox number + * [#1342] Re-add sflphone manpages + * [#1116] Fix compilation on non-jaunty distros + * [#1317] Fixed opensuse startup sleep + * [#1108] Add a signal in the client to notify successful or failed + transfer + * [#1108] Dbus signals concerning call transfer success/failure + * [#1317] Added opensuse to automatic build system + * [#1223] Fix manpages bug + * [#1060] german translation glitch + * Clean up some gnome client warnings + * [#1547] replace ugly account leds by beautiful icons + * [#1548] add close button that hides windowand just hide on clicking + the cross + * [#1549] put introspec XMLs in the client's source + * [#1312] Implement getCallList D-BUS method + * [#1116] Clear text in history and contacts + * [#1499] KDE integration + * [#1469] Modify header linkers in dbus-c++'s Makefile.am's + * [#1469] Remove examples folder from dbus-c++ + * [#1214] History integration in build system; unit test squeleton + * [#1317] Cleaning + * [#1469] Remove configure stuff in dbus-c++ + * [#1469] Add unofficial mainline dbus-c++ + * [#1469] Remove dbus-c++ from freedesktop + * [#1430] Bring account changed signal/callback back to normal + * [#1060] Update german translation - Sven Werlen + * [#1430] Add marshaller one string define + * [#1430] Send account change signal broadcast using account id + * [#1430] Remove condition on setRegistrationState, cause stun to + crash + * [#1317] Centralized version handling + * [#1317] Fixed version number on sfl-git-dch + * [#1317] Refactoring for new distributions + * [#1215] Fix account order at startup if latency + * [#1088] Restore sip dns srv + * [#1214] Add squeleton for history manager + * [#1430] Add accout id to accout changed method + * [#1430] No connectionStatusNotification (account changed) if no + changes + * [#1538] Add COPYING file + * [#1430] Add audio rtp thread tests + * [#1317] Changed version detection + * [#1538] Document license in libs/stund + * [#1317] Added version files + * [#1538] Apply François patches - debian packages + * [#1317] Updated spec files + * add files + * [#1538] Apply François patches - debian packages + * [#1535] Change program file structure (directory src...) + * [#1317] Updated build system scripts + * [#1317] Cleaning + * [#1317] Copied introspect files to gnome client + * [#1317] Added opensuse to build-system : first-shot + * [#1317] Remove spec files from configure + * [#1317] Added missing prefix + * removed debug for daemon account fix + * [#1430] Add a connection reference which most likely belong to + libdbus + * [#1430] Use shared connection instead of private + * make daemon find the account, added userMatch + * Clean code, add comments... + * [#1317] Fixed packaging rules + * [#1317] Updated autogen + * Updated autogen.sh for pjsip + * [#1526] Set accounts order + * [#1317] Fixed pjsip lib dirs + * [#1317] Updated debian packaging for new pjsip configuration script + * [#1317] Switch to autogenerated guess and sub files + * [#1317] Updated pjsip inclusion in build system + * [#1317] Replaced pjsip guess and sub files + * [#1317] Fixed compilation issues on opensuse 11 + * [#1505] account list seem to crash the application when clicking + Apply very fast... + * [#1456] Add a flag to be replaced in the control files + * [#1456] Added version dependancy handling + * put account alias in AccountWidgetItem rather than in the item with + " " before. + * [#1034] The KDE client should start sflphoned if it is not started + * [#1500] Handle options for notifications and display on incoming + call. + * [#1443] Client should not crash when receive an unexpected + stateChanged signal + * [#1403] Do not stop the notification anymore + * [#1456] Added version dependancy handling + * [#1426] Daemon crashes when get alsa plugin + * [#1422] Improved error messages + * commit for merge + * [#1424] Change logo in tray icon and put a different one when + incoming call + * [#1425] first part done, window title... + * [#1413] add manpages creating and installing in build system + * [#1417] The client should start the account creation wizard if + started for the first time (if config file doesn't exist) + * [#1421] Make volume bars horizontal when dialpad is hidden. + * Changed main window title and fixed a mistake in sflphone_const.h + * [#1412] make debian package building work + * changelog changed. + * Changed addAccount method in gnome client. + * Debian and man folders added. + * [#1388] Change project name from sflphone_kde to sflphone-client-kde + * Better handle of kabc check. + * [#1351] Automatic generation of dbus interfaces in makefile + generated by cmake + * [#1307] Implement "edit before call" in history and address book. + * [#1344] change action_call label in call history from "call" to + "call back". + * [#1308] Implement Hook feature in kde client + * Improved build system. + * #1219 : Add address book configuration page + * Better handling of registration to the daemon. + * #1039 : Add tray icon in kde. + * Issue no 1216 : Double click on item in history or address book + causes call. + * display peer name in call list and call history when called from + address book. + * Address book functionnal with photo displayed. + * Help menu kde available but actions disappeared. All fonctions in + view. + * Address book functionnal but ugly and making its own sort in the + complete address book. + * Account choice on right click, clean out includes, page address + book, fixed bugs... + * Wizard, double click, context menu... + * Removed sflphone_kde.kdevelop.filelist + * Added account creation wizard and translated interface in english. + * Transfer functionnal but ugly. + * transfer not functionnal + * Bug fixed : unholding (UNHOLD_CURRENT, UNHOLD_RECORD) + * Commit functional for push. With install.sh + * Before merge. + * Problem with enable accounts. Account display increased. + * Functional with codec order working , playDTMF. + * Commit functional. + * sflphone_kde/build added in .gitignore. + * complete commit for checkout previous. + * Commit before checkout previous version to check the display + bug(little font everywhere...) + * Functionnal client. Rest : history icons, config icons and + functionalities + * commit before merge asavard for isRecording. + * Call and Automate fusion done and seems to work. + * Commiting before putting Automate class in Call class. + * Functionnal main window without recording, history, voicemail, kio + widgets. + * client kde avec kdevelop. + * Config Dialog almost finished. + * Base of QT client + + -- SFLphone Automatic Build System <team@sflphone.org> Tue, 23 Jun 2009 11:13:42 -0400 + +sflphone (0.9.5-SYSTEM) SYSTEM; urgency=low + + ** 0.9.5 release ** + + * [#1060] FIx bug in chinese translation + * [#1313] git add rtpTest.cpp rtpTest.h + * [#1313] Add init/close rtp tests + * [#1313] Basic instanciation of the rtp layer + * [#1449] Gtk-Critical concerning history filters and new calls + * [#1400] Make the match with the hostname instead of username + * [#1324] Change status bar label for "Using %s (%s)" + * [#1403] Icon size: 60x60 px + * [#1403] Do not remove notification, improve icon quality + * [#1403] Add smaller icon for gnome notifications + * [#1403] Prevent crash when hangup && no notification + * [#1403] Remove all actions on notifications; code refactoring + * [#1451] Use stun.sflphone.org as default STUN server + * [#1060] New po files - need to be translated + * [#1060] Update french translation - Rebuild template file + * [#1456] Add a flag to be replaced in the control files + * [#1454] Make cppunit optional; remove from build deps in control + files + * [#1401] Add libexpat1-dev dependency in control files + * [#1448] Take off these ugly debug messages + * [#1448] fixed getTelephoneTone and getTelephoneFile() called + repeatedly + * [#1406] add liblog4c-dev in build-depends + * [#1409] Restore .desktop icon + + -- SFLphone Automatic Build System <team@sflphone.org> Mon, 25 May 2009 11:34:48 -0400 + +sflphone (0.9.5-SYSTEM~rc2) SYSTEM; urgency=low + + ** 0.9.5 rc2 ** + + * [#1422] Improved error message + * [#1402] Fix pjsip build + * [#1404] Clear GTK-Critical Bug at client startup + * [#1422] Added automatic VM shutdown when building on more than one + VM + * [#1422] Fixed some issues with new changelog generation script + * [#1422] Moved distribution update to specific file + * [#1422] Dropped git-dch, replace by home made implementation + * [#1402] Fix pjsip build + * [#1404] Clear GTK-Critical Bug at client startup + * Changes for name based dbus connection + * Clean changelogs + * [#1343] Gnome: Implement a callback system to handle focus on + different widgets + * Debus Session + * Refactoring Python code, PEP8 + * [#1430] Get back dbus_g_proxy_new_for_name + * [#1430] Get back DBUS_BUS_SESSION type + * [#1430] Dbus fixed owner message binding + * Second test with DBUS owner + * [#1404] Gnome -> Preferences -> Hooks + * [#1404] Gnome -> Preferences -> Recordings + * [#1404] Call History + * [#1404] Gnome -> Preferences -> Address Book + * [#1404] IF the first notification option disable the second + notification + * Dbus with fixed owner does not automatically start the deamon + * Add codec debug tests in pysflphone + * [#1407] Some print info + * [#1407] Add a scenario to pick_up action + * Test client dbus connection to a fixed owner + * Add python dbus test suite + * [#1161] Modified version handling in build system + * [#1314] Test pulse audio and audio streams connect and disconnect + * [#1402] Add info message after configure + * [#1402] Build the daemon with the local pjsip library (vs the + installed one) + * [#1009] Fix Codec Sampling Rate set to zeros + * [#1314] Add mutex to pulse layer audio streams + * [#1314] Refactoring pulseaudio stream to test connect disconnect + * [#1314] Refactoring of pulselayer to test conect/disconnect + * Add debug messages in debus calls concerning account + * [#1314] Add some return values to audio init functions + * [#1406] add liblog4c-dev in build-depends + * [#1409] Restore .desktop icon + * Bug #1405: Fix strings as requested. + * Bug #1404: Fix strings in preferences panel. + + -- SFLphone Automatic Build System <team@sflphone.org> Tue, 19 May 2009 12:08:18 -0400 + +sflphone (0.9.5-0ubuntu1~rc1) SYSTEM; urgency=low + + [ SFLphone Project ] + * [#1262] Updated changelogs for version 0.9.5-0ubuntu1 Snapshot 2009- + 05-05 + + [ Emmanuel Milou ] + * Add some python CLI client code; not really functional + * [#1108] Fix peerHungup method for IP to IP call + + [ Alexandre Savard ] + * [#1108] Correct setting of SIP contact for direct IP call + * [#1108] SIP user agent handles incoming REFER + + [ Emmanuel Milou ] + * Remove website from repository + * Update translation + + [ Alexandre Savard ] + * Sflphone icon's tooltip changed for "configured" instead of + "registered" + + [ Emmanuel Milou ] + * Update translation + + [ Sflphone Project ] + + -- Sflphone Project <sflphone@mtl.savoirfairelinux.net> Tue, 05 May 2009 19:16:13 -0400 + +sflphone (0.9.5-0ubuntu1~beta) SYSTEM; urgency=low + + [ Julien Bonjean ] + * Updated Eclipse stuff + * Improved addressbook config window + * Added sflphone Eclipse stuff + * Implemented addressbook list server side + * Moved dbus stuff in dbus directory + * Updated addressbook configuration + + [ Emmanuel Milou ] + * Remove unuseful installation scripts. Use apt-get build-dep sflphone + instead + * fix bug #1090 + + [ Alexandre Savard ] + * defining speex 16khz + + [ Emmanuel Milou ] + * Remove unuseful file from build system + * Start dns srv resolver + + [ Alexandre Savard ] + * Basic ogg/vorbis initialization + + [ Emmanuel Milou ] + * Handle incoming IP-to-IP invite correctly + + [ Alexandre Savard ] + * speex wideband 16000 + + [ Emmanuel Milou ] + * Better handling of incoming IP to IP call + * DNS SRV resolution functional + * Implement IAX2 incoming URL + * Allow user to make IP call without any accounts configured + * Add a contextual menu to edit a number from the contacts tab + * Add comments, tooltip and new button to the contextual menu + * add delete event, migrate to GTK 2.16 for sexy icons + * Resolve ticket #1118 + * Update suse spec file + * Add phone number cleanup functions, unit tests and panel + configuration + * Add pertinent test that fails + * fix dependencies for suse package + * Add contextual edit menu in history - #1120 + + [ Alexandre Savard ] + * Temporary comit: make speex wideband (16 khz) + * Temporary: shared object for speex narrow band + * Temporary: speex narrowband and wideband coexist + + [ Julien Bonjean ] + * Fixed bug when no book selected + * Fixed addressbook related compilation warnings + * Fixed GTK client remaining compilation warnings + * Fixed segfault when book removed since last sflphone run + * Fixed bug when book is unreachable (ldap error) + + [ Alexandre Savard ] + * Fix codec list in audio config window + * Active/inactive speex codec by payload + + [ Julien Bonjean ] + * Updated gitignore + * Added some comments + + [ Emmanuel Milou ] + * Add callto: handler script for browsers and al. + * Integrate test compilation in the daemon build-system + + [ Julien Bonjean ] + * Fixed g_object_unref warning for pixbuf + * Cleaned too verbose output + * Fixed toolbar update warning + * Added support for asynchornous books open (first shot) + + [ Emmanuel Milou ] + * Add a DBus call to fetch the call details from a call ID - Ticket + #928 + + [ Julien Bonjean ] + * Improved async open books + * Fixed bug #1139 + + [ Emmanuel Milou ] + * Add a way to save account order + * commit missing files + + [ Julien Bonjean ] + * Introduced log4c (ticket #1162) + + [ Emmanuel Milou ] + * Load/save account order functionnal - ticket #813 + + [ Alexandre Savard ] + * Add CELT codec (#1143) + * Make celt frame size 256 (*1143) + + [ Julien Bonjean ] + * Switched everything to log4c (ticket #1162) + * Updated eclipse settings + + [ Emmanuel Milou ] + * Restore adding account - ticket #1172 + * Add liblog4c dependecy - ticket #1179 + + [ Alexandre Savard ] + * Double maxAvailByte for frame size in rtp (#1143) + + [ Emmanuel Milou ] + * Add User-Agent SIP header - Ticket #1173 + + [ Julien Bonjean ] + * Fixed autoresize issue (#708) + + [ Emmanuel Milou ] + * Remove libcppuint dependency for the debian packages + * Look for libsexy only if gtk version < 2.16 - Ticket #1116 + * Remove libsexy dependency for jaunty. ticket #1116 + + [ Julien Bonjean ] + * Introduced unit tests (#1146) + * Updated gitignore + * Fixed Makefile (#1146) + + [ Emmanuel Milou ] + * [TICKET #1112] Add a test on the voice buffer to send through iax + packets + * Remove doublon in dependencies + * Remove warnings from the client test framework + * Update version number to 0.9.5~beta + * Update build-package script + * Add check dependency in build-deps control file field + * Create debian files for the new sflphone + * [TICKET #1212] Add Replaces field in control files + * [TICKET #1212] Fix manpages installation path + * [TICKET #1212] Add maintainer scripts to create alternatives + * [#1212] Update the manpages generation - edit preinst maintainer + script + * [#1212] Fix reference error in manpage + * [#1212] Add missing files on the client side + * [#1212] Fix debian docs files - no TODO file + * [1212] Fix manpage creation problem + * [#1220] Generate client-side glue files and marshaller at + compilation time + * [#1220] Generate server-side glue files at compilation time + * [#1212] Change binary name to sflphone + * [#1212] Update .gitignore to fit the new working tree + * [#1220] Explicitly generate glue files before building the library + * [#1220] Compile dbus directory before audio + * [#1212] Create sflphone-common at the root of the repository + * [#1212] Re-add pjproject + * [#1212] Remove Makefile from repo + * [#1220] Fix Makefile.am + * [#1212] New working directory functional + * [#1212] Update .gitignore + * [#1212] Hack to make pjsip compile.. + * [#1220] Use non-installed binary for dbusxx-xml2cpp + * [#1212] Add descriptive files, remove unuseful scripts from tools/ + + [ Alexandre Savard ] + * Restore speex codecs + * add frame size for celt (#1143) + * add framesize to codec, independant from audiolayer (#1143) + * use codec frame size in rtp (#1143) + * compute fixed_codec_framesize (#1143) + * do not resample if not required (#1143) + * add condition on resampling for decoder (#1143) + * add a condition on bytesAvail == 0 from mic data + * no maximum in rtp decode (#1143) + * compute maximum for decoding (#1143) + + [ Emmanuel Milou ] + * [#1146] Implement unitary tests on the client-side + + [ Alexandre Savard ] + * use float instead of int to compute max nb of sample (#1143) + * add nbSampleMax for unresampled data (#1143) + * make thread sleep during 5 ms insead of 20 (#1143) + * use unix usleep (#1143) + * 50 usecond thread!!!!! (#1143) + * try with the smallest compression (#1143) + * use timer set at framesize (#1143) + + [ Emmanuel Milou ] + * [#1161] Restore changelog version + + [ Alexandre Savard ] + * Remove celt stuff + + [ Emmanuel Milou ] + * [#1161] Update changelog + * [#1220] Add Conflicts: sflphone in debian control files + * [#1179] Add liblog4c3 runtime dependency + * [#1212] FIx typo error in dependency list for itnrepid + * [#1212] FIx .desktop file to point on the right exec + * [#1212] Modify changelog replacing tag + + [ Sflphone Project ] + * "[#1262] Updated changelogs for version 0.9.5-0ubuntu1~beta" + + [ Emmanuel Milou ] + * [#1212] restore changelogs + + [ Sflphone Project ] + * [#1262] Updated changelogs for version 0.9.5-0ubuntu1 Snapshot 2009- + 04-27 + + [ Emmanuel Milou ] + * [#1212] restore changelogs + + [ Sflphone Project ] + * [#1262] Updated changelogs for version 0.9.5-0ubuntu1~beta + + [ Emmanuel Milou ] + * [#1212] restore changelogs + + [ Sflphone Project ] + + -- Sflphone Project <sflphone@mtl.savoirfairelinux.net> Mon, 27 Apr 2009 17:00:03 -0400 + +sflphone (0.9.4-0ubuntu2) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Restore speex and GSM detection + + [ Emmanuel Milou ] + * Fix bug #1090 + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 8 Apr 2009 11:29:15 -0500 + +sflphone (0.9.4-0ubuntu1) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Integrate DBus-c++ and libiax2 in the main build system + * Clean up in the working repository + * Reorder hooks configuration panel + * Protect case when no codecs are active + * Fix some return values + * Add unitary tests for the hook manager (premisces) + + [Yun Liu] + * Update chinese translation + + [Sven Werlen] + * Update german translation + + [Hussein Abdallah] + * Update russian translation + + [Maxime Chambreuil] + * Update spanish translation + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 3 Apr 2009 18:29:15 -0500 + + +sflphone (0.9.4-rc1) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Fix bug while trying to hold/unhold several simultaneous call + * Improve address book build system + * Implement SIP url popup on incoming call + * Improve GTK+ panel configuration + [ Julien Bonjean ] + * GTK+ client refactoring + * GTK+ clean up + * Address book improvment + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 27 Mar 2009 18:29:15 -0500 + +sflphone (0.9.4-0beta1) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Display codec used during conversation on the GUI + * Enable/disable STUN parameters at runtime + * Refactor search bar use + [ Emmanuel Milou ] + * Build system fixes + * Implement SIP re-invite + * Implement IP to IP call + [ Julien Bonjean ] + * Integrate GNOME address book based on evolution data server + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 20 Mar 2009 18:29:15 -0500 + + +sflphone (0.9.3-0ubuntu3) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Both playback and record streams in PA_STREAM_CORKED (pulseaudio) + * Use PLUGHW device for ALSA capture + * Functional IAX and SIP recording for voicemail + * Use the less CPU-consuming interpolator algorithm for resampling + * Display in GTK GUI the codec used in conversation + * GTK GUI use ASCII instread of utf-8 + * Add record menus in GTK GUI + * Put on hold when dialing a new number + * AccountID's are saved in the history + + [ Emmanuel Milou ] + * Integrate DBUS C++, libiax2 in the git repository + * Update website + * Use libspeexdsp only if available on the system + * Updated .gitignore file + + [Cyrille Béraud] + * Account assistant manager improvment + * Add an email request when creating a new account to receive voicemails + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Sat, 14 Feb 2009 13:29:15 -0500 + +sflphone (0.9.3-0ubuntu2) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Add compilation note in README + * Use default ALSA plugin for capture + * Fix the ALSA capture problem one more time + * Clean up debug messages in dbus.c + * Add libspeexdsp dependency + * Remove implicit declaration compilation warnings + * Fix links in the website, add release note + * Change capture for the website front page + * Add alsa devel dependency in build-depends control file field + * Clean up, indentation, try to handle latency problems in iax/pulseaudio + * Remove pjsip generated files from the repo + * Use the previous declared curAlias function in accountwindow + * Fix bug in history call duration when the call fails + * Remove runtime warning in the GTK+ client + * Add librsvg2-common dependency to load SVG under KDE + * Refresh .gitignore + * Update locales files + french translation + * Add configuration panel for future noise reduction + * Add configuration panel for audio record module + * Daemon less verbose; accounts don't try to access STUn options anymore + * Fix typo in configwindow + * Add content in the official website + * use a GTK_STOCK icon for the record button + * Complete description text in the assistant manager + * Add libtool flags in client configure.ac + * Remove unuseful dependency (snd) + * Fix SIP transfer problems + * Remove previous version of PJSIP from the repo + * Upgrade PJSIP to version 1.0.1 + * Add the new website source in the repository + * Use libspeexdsp for silence detection only if available + + [ Loïc Faure-Lacroix ] + * Ajout du logo gpl3 + * Ajout des images + * Ajout de la section screenshot pour le site + * Ajout du favicon dans le header + * Modification des cartes + + [ Alexandre Savard ] + * Clean up <speex/libspeexdsp> + * Small cleanup + * Save Wave fixed + * Fix new call button when recording + * libspeexdsp added + * Recording: default home folder at startup + * Minor changes to config window + * IAX recording fixed + * Set / get recording path, still need some GTK for client + * AudioRecord file name format + * Now recording in HOME folder + + [ Cyrille Béraud ] + * Fix bug in reqaccount.c + + [ Maxime Chambreuil ] + * Update spanish translation + + [Yun Liu ] + * Update chinese translation + + [ Hussein Abdallah ] + * Update russian translation + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Sat, 14 Feb 2009 13:29:15 -0500 + +sflphone (0.9.3-0ubuntu1) SYSTEM; urgency=low + + * Remove debug + * Join thread before leaving + * Fix implicit declaration in reqaccount + * Add REST code to build the request to server + * Fix GValue initialization warnings + * Update version number, fix implicit declaration, fix GTK markup + warnings + * Apply patch to create custom SIP account from our own server + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 06 Feb 2009 19:17:32 -0500 + +sflphone (0.9.2-2ubuntu9) SYSTEM; urgency=low + + [ Alexandre Savard ] + * Speex audio codec preprocessing initialization + * peer hung up segmentation fault solved + * Stop recording when transfering + * Terminate only one call + * Add isRecording() function + * Fix call_icon GTK client + * Fix SIPCallClose() function, recorded file now close properly + * Function terminateSIPCall added in sipvoiplink and managerimpl + * Fix thread destructor + * setRecordingOption function implement in audiorecord + * Record now implemented in Call class + * Record interface complete (on hold erase previous recording) + * Added recButton in client + * Added: record button related icons + * Record button added + * Overload AudioRecord::recData to get mic and speaker data mixed + * Recording now in audiortp::run() method + * Audio recording working in AudioRTP: receiveSessionForSpeaker + * Open/close a wave file when pulse audio stream start/stop + + [ Emmanuel Milou ] + * Fix path for GTK+ icons; clean up + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Thu, 05 Feb 2009 18:27:53 -0500 + +sflphone (0.9.2-2ubuntu8) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Update changelogs + * Fix bug in merge and in Makefile.am + * Terminate only one call + * Disable PJsip shutdown when changing STUN parameters + * Function terminateSIPCall added in sipvoiplink and managerimpl + * Add a timer to the alsa thread to not jam the CPU load + * Fix bug in sipvoiplink.cpp + * Clean shutdown of pulseaudio on quiting + * Fix DTMF at first start with Pulseaudio + * Remove zeroconf from the build system + * Add a library manager + exception handling + * Clean up in the working directory + * Better handling of capture XRUNs + * Restore mic adjust volume on ALSA layer + * Protect device ALSA operation if not opened + * Fix the switching layer bug + * Use dynamic_cast<> to use audiolayer-specific methods + * Open the audio devices only once at startup + * Refactoring of the ALSA part + * Functional plug-in manager + * Use a C++ thread to handle tones and DTMF in ALSA + * Restore IAXVoIPLink, restore Mutex + * Make the plugins registering against the plugin manager + * Migrate to 1->N relationship between voiplink and accounts + * API plugin for registration + * Use C++ thread in SIP, move everything in sipvoiplink + * Complete singleton pattern for the plugin manager + * Add -Wno-return-type compilation flag to remove warnings; Update + version number in configure.ac + * Add the dynamic loading for the plugin framework; integate unittest + + [ Yun Liu ] + * Update rpm spec file + * modify build package script and spec file for suse + + [ Alexandre Savard ] + * Add audiorecorder plugin and testaudiorecorder + * Add audio Recording class, edit global.h + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 04 Feb 2009 14:00:30 -0500 + +sflphone (0.9.2-2ubuntu7) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Update changelog to 0.9.2-6 + * Fix some dbus-glib implementation details on the client side + * Init history after dbus initialization + * Add error checking in useragent; Clean sipvoiplink + * Prevent crash when trying to call an empty number + * Set the volume of the playback stream to PA_VOLUME_NORM at startup + * Fix GTK+ generic value double initialization + * Fix jaunty control file dependency problems + * Fix jaunty control file dependency problems + + [ Yun Liu ] + * Fix bug ticket # 137 + * Tolerant to gsm library of OpenSuse 11 + + [ Sven Werlen ] + * Update german translation + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 23 Jan 2009 17:48:13 -0500 + +sflphone (0.9.2-2ubuntu6) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * Migrate STUN configuration to the main config window + * Update french translation + * Other tiny memory leaks + * Fix memory leak in sampleconverter.cpp + * Generate packages from the release branch + * update the build package script + * modify the control files with architecture=any + * Remove valgring uninitialized value + * IAX and SIP use the same global variables to set account + configuration ; fix broken code + + [ Maxime Chambreuil ] + * Update spanish translation + + [ Hussein Abdallah ] + * Update russian translation + + [ Yun Liu ] + * Update translation files + * Fix the bug when user uncheck the account which fails in the + previous registration + * Add stun error status + * Fix bug ticket #143 + * Script for auto-install dependencies + * Fix bug ticket #140 + * Fix bug ticket 141 + * Fix the reregister process when user change the details of an + account + + -- Emmanuel Milou <manu@sulfur.inside.savoirfairelinux.net> Fri, 16 Jan 2009 18:19:05 -0500 + +sflphone (0.9.2-2ubuntu5) SYSTEM; urgency=low + + * Fix memory leak in the pulseaudio callback + * Update debian package generation script + * Warnings removal in GTK+ client + * Clean adjust volume method in alsalayer + * Plug the sflphone playback volume control to the pulseaudio volume + manager + * Display the date in history according to the current locale + * Generate the changelog according to the git commit messages + * Complete header in chinese translation file + * Use the right gpg key to sign the packages + * add debian jaunty jackalope support + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 14 Jan 2009 21:17:20 -0500 + +sflphone (0.9.2-2ubuntu4) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * add german translation + + [ Yun Liu ] + * Fix GUI crash in Ubuntu8.10 64bit system + + -- Yun Liu <yun.liu@savoirfairelinux.com> Thu, 08 Jan 2009 13:08:51 -0500 + +sflphone (0.9.2-2ubuntu3) SYSTEM; urgency=low + + [ Emmanuel Milou ] + * The main thread synchronizes the ringtone thread + * disable custom ringtone for the ALSA layer + * Fix the Makefile.am in man directory, add a SEE ALSO section + + [ Yun Liu ] + * Fix daemon crash caused by the previous patch ( for bug ticket #129) + + -- Yun Liu <yun.liu@savoirfairelinux.com> Tue, 06 Jan 2009 16:18:38 -0500 + +sflphone (0.9.2-2ubuntu2) SYSTEM; urgency=low + + * Fix bug ticket #129 + + -- Yun Liu <yun.liu@savoirfairelinux.com> Wed, 5 Jan 2009 15:54:53 -0500 + +sflphone (0.9.2-2ubuntu1) SYSTEM; urgency=low + + * Migrate from eXosip library to pjsip + * Add multiple SIP accounts support + * Fix ringtones problems + * Add a pulseaudio support + * Improve audio quality with ALSA + * Add chinese translation + * Improve spanish translation + * Migrate to a maintained C++ DBus bindings + * Clean and improve the build system + * Add build-dependency on Perl because we need pod2man to generate manpages + + -- Yun Liu <yun.liu@savoirfairelinux.com> Wed, 26 Nov 2008 09:47:53 -0500 + +sflphone (0.9.1) unstable; urgency=low + * Add a search tool in the history + * Migrate some gtk_entry_new to sexy_icon_entry_new + * Bug fix (Ticket #78): The voicemail password isn't displayed anymore in + the history tab + * Add the SIP registration expire value in the user file. + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Thu, 22 May 2008 11:14:25 -0500 + +sflphone (0.9.0) unstable; urgency=low + * Add history features + * Call date + * Call duration + * Mouse events in the history tab + * Smooth switch from the history tab to the calls tab + * Remove most of GTK-Critical warnings + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 13 May 2008 16:58:25 -0500 + +sflphone (0.9-2008-06-06) unstable; urgency=low + * Audio bug correction: capture stopped after a few minutes of conversation + with USB Plantronics sound card + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Tue, 06 May 2008 16:58:25 -0500 + +sflphone (0.9-2008-05-06) unstable; urgency=low + * Bug correction: account creation with the assistant + * GTK+ warnings removal + * libnotify warnings removal + * Remove aliasing on the SFLphone logo + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Mon, 05 May 2008 16:58:25 -0500 + +sflphone (0.9) unstable; urgency=low + * Clean dependencies ( removal of libboost ) + * Several GTK improvement and updates + -account window + -configuration window + * Migrate from GtkCheckMenuItem to GtkImageMenuItem + * ALSA standard I/O transfers: MMAP instead of R/W + * Fix speex audio quality + * IAX2 protocol + -Fix hold/unhold situation + -Add on hold music + * SIP protocol + -Ringtone on incoming call + -Fix transfer situation + * Add desktop notification ( libnotify ) + * Improve the system tray icon behaviour + * Improve registration error handling + * Register/unregister from the account window takes effect without starting back SFLphone + * Compilation warnings removal + * Call history + * Add an account configuration wizard + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Wed, 30 Apr 2008 16:58:25 -0500 + +sflphone (0.8.2) unstable; urgency=low + * Internationalization of the GTK GUI + * English / French + * STUN support + * Slight modifications of the graphical interface ( tooltips, dialpad, ...) + + -- Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> Fri, 21 Mar 2008 11:37:53 -0500 diff --git a/tools/build-system/launchpad/sflphone/debian/control b/tools/build-system/launchpad/sflphone/debian/control new file mode 100644 index 0000000000..050440b8b5 --- /dev/null +++ b/tools/build-system/launchpad/sflphone/debian/control @@ -0,0 +1,12 @@ +Package: sflphone +Section: gnome +Priority: optional +Architecture: all +Depends: sflphone-gnome, sflphone-gnome +Maintainer: Savoir-faire Linux Inc <emmanuel.milou@savoirfairelinux.com> +Description: metapackage providing the GNOME client for SFLphone + Provide a GNOME client for SFLphone. + SFLphone is meant to be a robust enterprise-class desktop phone. + SFLphone is released under the GNU General Public License. + SFLphone is being developed by the global community, and maintained by + Savoir-faire Linux, a Montreal, Quebec, Canada-based Linux consulting company. diff --git a/tools/build-system/make-telify-package.sh b/tools/build-system/make-telify-package.sh new file mode 100644 index 0000000000..f7a4d3cef8 --- /dev/null +++ b/tools/build-system/make-telify-package.sh @@ -0,0 +1,45 @@ +#!/bin/bash +##################################################### +# File Name: make-telify-package.sh +# +# Purpose : +# +# Author: Julien Bonjean (julien@bonjean.info) +# +# Creation Date: 2009-12-15 +# Last Modified: 2009-12-15 18:16:47 -0500 +##################################################### + +#set -x + +. `dirname $0`/setenv.sh + +# change to working directory +cd ${LAUNCHPAD_DIR} + +if [ "$?" -ne "0" ]; then + echo " !! Cannot cd to launchpad directory" + exit -1 +fi + +cd ${REFERENCE_REPOSITORY} + +for LAUNCHPAD_DISTRIBUTION in ${LAUNCHPAD_DISTRIBUTIONS[*]} +do + LOCAL_VERSION="${SOFTWARE_VERSION}~ppa${VERSION_INDEX}~${LAUNCHPAD_DISTRIBUTION}" + + cp ${DEBIAN_DIR}/control ${DEBIAN_DIR}/control + cp ${DEBIAN_DIR}/changelog.generic ${DEBIAN_DIR}/changelog + + sed -i "s/SYSTEM/${LAUNCHPAD_DISTRIBUTION}/g" ${DEBIAN_DIR}/changelog + + cd ${LAUNCHPAD_DIR}/${LAUNCHPAD_PACKAGE} + ./autogen.sh + debuild -S -sa -kFDFE4451 + cd ${LAUNCHPAD_DIR} + + if [ ${DO_UPLOAD} ] ; then + dput -f -c ${LAUNCHPAD_DIR}/dput.conf ${LAUNCHPAD_CONF_PREFIX}-${LAUNCHPAD_DISTRIBUTION} ${LAUNCHPAD_PACKAGE}_${LOCAL_VERSION}_source.changes + fi +done + diff --git a/tools/build-system/rpm/sflphone.spec b/tools/build-system/rpm/sflphone.spec new file mode 100644 index 0000000000..69a4df1963 --- /dev/null +++ b/tools/build-system/rpm/sflphone.spec @@ -0,0 +1,372 @@ +%bcond_with video +Name: sflphone +Version: 1.4.2 +%if 0%{?nightly} +%define rel rc%{nightly} +%define tarball %{name}-%{version}-rc%{nightly} +%else +%define rel 1 +%define tarball %{name}-%{version} +%endif +Release: %{rel}%{?dist} +Summary: SIP/IAX2 compatible enterprise-class software phone +Group: Applications/Internet +License: GPLv3 +URL: http://sflphone.org/ +Source0: https://projects.savoirfairelinux.com/attachments/download/6423/%{tarball}.tar.gz +BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) +BuildRequires: gettext gnutls-devel desktop-file-utils perl libuuid-devel +BuildRequires: yaml-cpp-devel alsa-lib-devel pulseaudio-libs-devel +BuildRequires: ccrtp-devel libzrtpcpp-devel dbus-c++-devel pcre-devel +BuildRequires: gsm-devel opus-devel speex-devel expat-devel libsamplerate-devel +BuildRequires: gnome-doc-utils libtool libsexy-devel intltool yelp-tools +BuildRequires: libnotify-devel check-devel rarian-compat ilbc-devel +BuildRequires: evolution-data-server-devel gnome-common libsndfile-devel +BuildRequires: pjproject-devel libsrtp-devel +# KDE requires +BuildRequires: cmake kdepimlibs-devel +BuildRequires: perl-podlators +%if %{with video} && 0%{?fedora} < 18 +BuildRequires: libudev-devel +%endif +%if %{with video} && 0%{?fedora} >= 18 +BuildRequires: systemd-devel +%endif + +%description +SFLphone is a robust standards-compliant enterprise software phone, +for desktop and embedded systems. It is designed to handle +several hundreds of calls a day. It supports both SIP and IAX2 +protocols. + +%prep +%setup -q -n %{tarball} + +%build +# Build some dependencies with contrib since no Fedora packages exist (yet) +mkdir -p daemon/contrib/native +pushd daemon/contrib/native +../bootstrap +make .iax +make .dht +popd +# Compile the daemon +pushd daemon +./autogen.sh +%if %{with video} +%configure --enable-video +%else +%configure +%endif +make %{?_smp_mflags} +make doc +popd +pushd plugins +./autogen.sh +%configure +make %{?_smp_mflags} +popd +# Compile kde client (only without video) +pushd kde +sed -i '/^[^#]add_subdirectory.*test/s/^[^#]/#/' src/CMakeLists.txt +./config.sh --prefix=%{_prefix} +cd build +make %{?_smp_mflags} +popd +# Compile gnome client +pushd gnome +./autogen.sh +%if %{with video} +%configure --enable-video +%else +%configure +%endif +make %{?_smp_mflags} +popd + + +%if %{with video} +%package gnome-video +Summary: SIP/IAX2 compatible enterprise-class software phone +Group: Applications/Internet +Requires: %{name}-common-video +Conflicts: sflphone-gnome sflphone +BuildRequires: ffmpeg-devel clutter-gtk-devel +%description gnome-video +SFLphone is a robust standards-compliant enterprise software phone, +for desktop and embedded systems. It is designed to handle +several hundreds of calls a day. It supports both SIP and IAX2 +protocols. + +This package includes the Gnome client with videoconferencing ability + +%package common-video +Summary: SIP/IAX2 compatible enterprise-class software phone +Group: Applications/Internet +Conflicts: sflphone sflphone-daemon sflphone-common +%description common-video +SFLphone is a robust standards-compliant enterprise software phone, +for desktop and embedded systems. It is designed to handle +several hundreds of calls a day. It supports both SIP and IAX2 +protocols. + +This package includes the SFLPhone daemon with videoconferencing enabled +%else +%package common +Summary: SIP/IAX2 compatible enterprise-class software phone +Group: Applications/Internet +Conflicts: sflphone sflphone-daemon-video +%description common +SFLphone is a robust standards-compliant enterprise software phone, +for desktop and embedded systems. It is designed to handle +several hundreds of calls a day. It supports both SIP and IAX2 +protocols. + +This package includes the SFLPhone common + +%package gnome +Summary: Gnome interface for SFLphone +Group: Applications/Internet +%if %{with video} +Requires: %{name}-common-video = %{version} +%else +Requires: %{name}-common = %{version} +%endif +Obsoletes: sflphone < 1.2.2-2 +Conflicts: sflphone-video +%description gnome +SFLphone is a robust standards-compliant enterprise software phone, +for desktop and embedded systems. It is designed to handle +several hundreds of calls a day. It supports both SIP and IAX2 +protocols. + +This package includes the Gnome client + +%endif + +%package kde-video +Summary: KDE interface for SFLphone +Group: Applications/Internet +%if %{with video} +Requires: %{name}-common-video = %{version} +%else +Requires: %{name}-common = %{version} +%endif +%description kde-video +SFLphone is a robust standards-compliant enterprise software phone, +for desktop and embedded systems. It is designed to handle +several hundreds of calls a day. It supports both SIP and IAX2 +protocols. + +This package includes the KDE client + +%package plugins +Summary: Plugins (address book) for SFLphone +Group: Applications/Internet +%if %{with video} +Requires: %{name}-common-video = %{version} +%else +Requires: %{name}-common = %{version} +%endif +%description plugins +SFLphone is a robust standards-compliant enterprise software phone, +for desktop and embedded systems. It is designed to handle +several hundreds of calls a day. It supports both SIP and IAX2 +protocols. + +This package includes the address book plugin. + +%install +rm -rf %{buildroot} +pushd daemon +make install DESTDIR=$RPM_BUILD_ROOT +popd +# Gnome install +pushd gnome +make install DESTDIR=$RPM_BUILD_ROOT +# Find Lang files +popd +# Plugins install +pushd plugins +make install DESTDIR=$RPM_BUILD_ROOT +popd +%find_lang sflphone --with-gnome +# Handling desktop file +desktop-file-validate %{buildroot}%{_datadir}/applications/%{name}.desktop +# KDE install +pushd kde/build +make install DESTDIR=$RPM_BUILD_ROOT +popd +%find_lang sflphone-client-kde --with-kde -f sflphone-client-kde +%find_lang sflphone-kde --with-kde -f sflphone-kde + +%if %{with video} +%pre gnome-video +if [ "$1" -gt 1 ] ; then + glib-compile-schemas %{_datadir}/glib-2.0/schemas &>/dev/null +fi + +%post gnome-video + glib-compile-schemas %{_datadir}/glib-2.0/schemas &>/dev/null + +%preun gnome-video +if [ "$1" -eq 0 ] ; then + glib-compile-schemas %{_datadir}/glib-2.0/schemas &>/dev/null +fi +%else +%pre gnome +if [ "$1" -gt 1 ] ; then + glib-compile-schemas %{_datadir}/glib-2.0/schemas &>/dev/null +fi + +%post gnome + glib-compile-schemas %{_datadir}/glib-2.0/schemas &>/dev/null + +%preun gnome +if [ "$1" -eq 0 ] ; then + glib-compile-schemas %{_datadir}/glib-2.0/schemas &>/dev/null +fi +%endif + +%post kde-video -p /usr/sbin/ldconfig +%postun kde-video -p /usr/sbin/ldconfig + +%if %{with video} +%files common-video +%else +%files common +%endif +%defattr(-,root,root,-) +%doc daemon/AUTHORS COPYING NEWS README +%{_libdir}/%{name}/* +%{_libexecdir}/sflphoned +%{_datadir}/dbus-1/services/org.%{name}.SFLphone.service +%{_mandir}/man1/sflphoned.1.gz* +%{_datadir}/pixmaps/%{name}.svg +%{_datadir}/%{name}/* + +%if %{with video} +%files -f sflphone.lang gnome-video +%else +%files -f sflphone.lang gnome +%endif +%defattr(-,root,root,-) +%{_bindir}/sflphone +%{_bindir}/sflphone-client-gnome +%exclude %{_libdir}/libsflphone.a +%exclude %{_libdir}/libsflphone.la +%{_datadir}/glib-2.0/schemas/org.sflphone.SFLphone.gschema.xml +%{_datadir}/applications/%{name}.desktop +%{_mandir}/man1/sflphone.1.gz +%{_mandir}/man1/sflphone-client-gnome.1.gz +%{_datadir}/pixmaps/%{name}.svg +%{_datadir}/%{name}/* + +%files plugins +%{_libdir}/sflphone/plugins/libevladdrbook.so + +%files kde-video -f sflphone-kde -f sflphone-client-kde +%{_bindir}/sflphone-client-kde +%{_datadir}/kde4/apps/sflphone-client-kde +%{_datadir}/config.kcfg/sflphone-client-kde.kcfg +%{_datadir}/applications/kde4 +%doc %{_mandir}/man1/*kde* +%{_datadir}/icons/hicolor +%{_libdir}/libksflphone.so* +%{_libdir}/libqtsflphone.so* +%exclude %{_includedir}/kde4/ksflphone/*.h +%exclude %{_includedir}/qtsflphone/*.h + +%changelog +* Tue Nov 25 2014 Tristan Matthews <tristan.matthews@savoirfairelinux.com> - 1.4.2-6 +- Build dht from contrib + +* Mon Nov 24 2014 Tristan Matthews <tristan.matthews@savoirfairelinux.com> - 1.4.2-5 +- drop .h and .so that are no longer built + +* Wed Nov 19 2014 Simon Piette <simon.piette@savoirfairelinux.com> - 1.4.2-4 +- add libstrp build require + +* Fri Nov 14 2014 Simon Piette <simon.piette@savoirfairelinux.com> - 1.4.2-3 +- Changed sflphoned path + +* Tue Nov 4 2014 Tristan Matthews <tristan.matthews@savoirfairelinux.com> - 1.4.2-2 +- Use Fedora's pjproject package + +* Thu Sep 25 2014 Tristan Matthews <tristan.matthews@savoirfairelinux.com> - 1.4.2-1 +- Bump version after release + +* Fri Sep 12 2014 Tristan Matthews <tristan.matthews@savoirfairelinux.com> - 1.4.1-5 +- Enable opus + +* Thu Sep 4 2014 Tristan Matthews <tristan.matthews@savoirfairelinux.com> - 1.4.1-4 +- Depend on yaml-cpp-devel instead of libyaml-devel + +* Mon Aug 25 2014 Tristan Matthews <tristan.matthews@savoirfairelinux.com> - 1.4.1-3 +- Build iax and pjproject with contrib + +* Wed Jul 23 2014 Simon Piette <simon.piette@savoirfairelinux.com> - 1.4.1-2 +- Always build kde package + +* Tue Jul 15 2014 Tristan Matthews <tristan.matthews@savoirfairelinux.com> - 1.4.1-1 +- Start development of 1.4.1 + +* Tue Jul 15 2014 Tristan Matthews <tristan.matthews@savoirfairelinux.com> - 1.4.0-1 +- Update to 1.4.0 + +* Wed Jul 9 2014 Tristan Matthews <tristan.matthews@savoirfairelinux.com> - 1.3.0-5 +- Drop uuid dependency + +* Mon Jul 07 2014 Simon Piette <simon.piette@savoirfairelinux.com> - 1.3.0-n +- Support both nightly and release + +* Thu May 15 2014 Simon Piette <simon.piette@savoirfairelinux.com> - 1.3.0-rc%{nightly} +- Adapt for nightly builds + +* Tue Jan 21 2014 Simon Piette <simon.piette@savoirfairelinux.com> - 1.3.0-2 +- Fix "Fix KDE paths" + +* Mon Jan 13 2014 Tristan Matthews <tristan.matthews@savoirfairelinux.com> - 1.3.0-1 +- Update to 1.3.0 +- Fix KDE paths (tested on f20) +- Added libuuid dependency for pjsip + +* Wed Jun 19 2013 Simon Piette <simonp@fedoraproject.org> - 1.2.3-1 +- Update to 1.2.3 +- Enable ilbc + +* Mon Feb 18 2013 Simon Piette <simonp@fedoraproject.org> - 1.2.2-6 +- Add sflphone-plugins + +* Mon Feb 18 2013 Simon Piette <simonp@fedoraproject.org> - 1.2.2-5 +- Renamed daemon to config + +* Mon Feb 18 2013 Simon Piette <simonp@fedoraproject.org> - 1.2.2-4 +- Video variant for gnome + +* Wed Feb 13 2013 Simon Piette <simonp@fedoraproject.org> - 1.2.2-3 +- split daemon and gnome packages + +* Wed Feb 13 2013 Simon Piette <simonp@fedoraproject.org> - 1.2.2-2 +- creates a kde client package + +* Tue Jan 15 2013 Simon Piette <simonp@fedoraproject.org> - 1.2.2-1 +- upgraded to 1.2.2 +- updated BuildRequires +- disabled ilbc +- replaced gconf with gsettings + +* Tue Sep 11 2012 Simon Piette <simonp@fedoraproject.org> - 1.2.0-1 +- upgraded to 1.2.0 (tested on f16) +- updated BuildRequires + +* Wed Apr 20 2011 Prabin Kumar Datta <prabindatta@fedoraproject.org> - 0.9.13-1 +- avoiding compling with Celt codec support to resolve build problem +- removed clean section since not required +- upgraded to 0.9.13 + +* Mon Apr 18 2011 Prabin Kumar Datta <prabindatta@fedoraproject.org> - 0.9.12-2 +- Fixed schema registration problem + +* Fri Mar 25 2011 Prabin Kumar Datta <prabindatta@fedoraproject.org> - 0.9.12-1 +- Initial build diff --git a/tools/build-system/scripts/run_package_test.sh b/tools/build-system/scripts/run_package_test.sh new file mode 100755 index 0000000000..bda3f9ed4b --- /dev/null +++ b/tools/build-system/scripts/run_package_test.sh @@ -0,0 +1,291 @@ +#!/bin/bash + +# package_test.sh is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# package_test.sh is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with package_test.sh If not, see <http://www.gnu.org/licenses/>. +# +# @author: Emmanuel Lepage Vallee <elv1313@gmail.com> +# @copyright: Savoir-faire Linux 2014 +# +# Description: +# This script is used to quickly test packages from a CI system such as Jenkins +# by hand. It was developed in response to numerous issues with the sflphone +# ppa over the years and to quickly reproduce environment from similar to those +# used by users whose packages crashed and were reported to errors.ubuntu.com + +# Settings +DEBIAN_VERSION="trusty" +IMAGEPATH="/var/chroot/" +ARCH="amd64" +IMAGENAME=${DEBIAN_VERSION}_integration.img +MASTERMOUNTPOINT=/tmp/ppa_testing/master +SNAPSHOTMOUNTPOINT=/tmp/ppa_testing/mountpoint +SNAPSHOTNAME=snapshot_$(date '+%d.%m.%y') +IMAGE_SIZE=8 # In Gigabyte +PACKAGE_CACHE_SIZE=4 # In Gigabyte +MIRROR="http://ftp.ussg.iu.edu/linux/ubuntu/" +PACKAGES=() +REPOSITORIES=() +POST_COMMANDS=() +PRE_COMMANDS=() +PRE_UMOUNT_COMMANDS=() + +# Parse arguments +OLD_IFS=$IFS +IFS=`echo -en "\n\b"` +while getopts ":a:d:p:r:s:c:b:u:m:h" opt; do + case $opt in + d) + echo "Using alternate Debian derivative $OPTARG" + DEBIAN_VERSION=$OPTARG + ;; + a) + echo "Using alternate architecture $OPTARG" + ARCH=$OPTARG + ;; + r) + echo "Use alt repository (or PPA) $OPTARG" + PPA=$OPTARG + REPOSITORIES+=($OPTARG) + ;; + p) + echo "Add package $OPTARG" + PACKAGES+=($OPTARG) + ;; + p) + MIRROR=$OPTARG + ;; + c) + POST_COMMANDS+=($OPTARG) + ;; + b) + echo -e "\n\n\n\nADDING " $OPTARG + PRE_COMMANDS+=($OPTARG) + ;; + u) + PRE_UMOUNT_COMMANDS+=($OPTARG) + ;; + s) + echo "Open a shell before deleting the snapshot" + OPEN_SHELL=true + ;; + :|h) + echo "./create_jail.sh [-arch trusty] [-h] [-ppa link]" + echo "a: architechture (i386, amd64, armhf)" + echo "r: Add an apt repository to /etc/apt/sources.list (multiple allowed)" + echo "p: Package to install (multiple allowed)" + echo "d: alternative distribution (trusty, precise, wheezy, etc)" + echo "c: Bash command to execute before exiting (multiple allowed)" + echo "b: Bash command to execute before installing packages (multiple allowed)" + echo "u: LOCAL (as ROOT on your *REAL* Linux) commands to execute before"\ + "unmounting the jail (PWD in the jail /), be careful!" + echo "m: Ubuntu/Debian repository mirror" + echo "shell: Open a shell before exiting" + echo + echo "Example usage:" + echo "./create_jail.sh" + exit 0 + ;; + \?) + echo "Invalid option: -$OPTARG", use -h for helo >&2 + exit 1 + ;; + esac +done +IFS=$OLD_IFS + +# Unmount a jail +function unmountjail() { + CHROOT_PATH=`pwd` + if [ "$1" != "" ]; then + CHROOT_PATH=$1 + fi + echo Unmounting jail on $CHROOT_PATH + umount -l $CHROOT_PATH/dev 2> /dev/null + umount -l $CHROOT_PATH/dev/pts 2> /dev/null + umount -l $CHROOT_PATH/sys 2> /dev/null + umount -l $CHROOT_PATH/proc 2> /dev/null + umount -l $CHROOT_PATH/var/cache/apt/archives/ 2> /dev/null +} + +# Mount the special APT packet cache to avoid redundant downloads +function mountcache() { + MOUNT_PATH=$1 + CACHE_PATH=$IMAGEPATH/cache_${IMAGENAME} + #If the cache doesn't exist, create it + if [ ! -f $CACHE_PATH ]; then + echo "Creating a package cache, this may take a while" + dd if=/dev/zero of=$CACHE_PATH bs=1M count=${PACKAGE_CACHE_SIZE}000 + mkfs.btrfs $CACHE_PATH + fi + + # Mount the cache + mount -o loop $CACHE_PATH $MOUNT_PATH + + # Check if the cache is full, clear it + PERCENT_USE=`df /$MOUNT_PATH | egrep "([0-9.]+)%" -o | egrep "([0-9.]+)" -o` + if [ $PERCENT_USE -gt 75 ]; then + echo "The cache is full, forcing a cleanup (${PERCENT_USE} used of ${PACKAGE_CACHE_SIZE}Gb)" + rm $MOUNT_PATH/*.deb + fi +} + +# Mount a chroot jail in the current PWD or $1 +function mountjail() { + CHROOT_PATH=`pwd` + if [ "$1" != "" ]; then + CHROOT_PATH=$1 + fi + unmountjail $CHROOT_PATH + echo Mounting jail on $CHROOT_PATH + mount -o bind /dev $CHROOT_PATH/dev + mount -o bind /dev/pts $CHROOT_PATH/dev/pts + mount -o bind /sys $CHROOT_PATH/sys + mount -o bind /proc $CHROOT_PATH/proc + mountcache $CHROOT_PATH/var/cache/apt/archives/ +} + +function clearmountpoints() { + # Close the jails + unmountjail $SNAPSHOTMOUNTPOINT + + # Delete the snapshot + umount -l $SNAPSHOTMOUNTPOINT 2> /dev/null + btrfs subvolume delete ./${SNAPSHOTNAME} 2> /dev/null + + # Unmount the master + umount -l $MASTERMOUNTPOINT 2> /dev/null +} + +# Check the dependencies +if ! command -v debootstrap ; then + echo Please install debootstrap + exit 1 +fi +if ! command -v btrfs ; then + echo Please install btrfs-tools + exit 1 +fi + +# Check the script can be executed +if [ "$(whoami)" != "root" ]; then + echo This script needs to be executed as root + exit 1 +fi + +# Make sure the mount points exists +mkdir $MASTERMOUNTPOINT $SNAPSHOTMOUNTPOINT $IMAGEPATH -p + +cd $IMAGEPATH + +# Create the container image if it doesn't already exist +if [ ! -f $IMAGENAME ]; then + echo "Creating a disk image (use space now), this may take a while" + dd if=/dev/zero of=$IMAGEPATH/$IMAGENAME bs=1M count=${IMAGE_SIZE}000 + mkfs.btrfs $IMAGENAME +fi + +# Mount the image master snapshot +clearmountpoints +mount -o loop $IMAGEPATH/$IMAGENAME $MASTERMOUNTPOINT +cd $MASTERMOUNTPOINT + +# Create the chroot if empty +if [ "$(ls)" == "" ]; then + debootstrap --variant=buildd --arch $ARCH $DEBIAN_VERSION ./ $MIRROR #http://archive.ubuntu.com/ubuntu + + # We need universe packages + sed -i 's/main/main universe restricted multiverse/' ./etc/apt/sources.list + + # Add the deb-src repository + cat ./etc/apt/sources.list | sed "s/deb /deb-src /" >> ./etc/apt/sources.list +fi + + +# Apply updates +mountjail +chroot ./ apt-get update > /dev/null +chroot ./ apt-get upgrade -y --force-yes > /dev/null +unmountjail + +# Create +btrfs subvolume snapshot . ./${SNAPSHOTNAME} + +# Mount the subvolume +mount -t btrfs -o loop,subvol=${SNAPSHOTNAME} $IMAGEPATH/$IMAGENAME $SNAPSHOTMOUNTPOINT + + + +################################################### +# Begin testing # +################################################### + +mountjail $SNAPSHOTMOUNTPOINT + +# Execute all PRE commands +OLD_IFS=$IFS +IFS=`echo -en "\n\b"` +for COMMAND in ${PRE_COMMANDS[@]}; do + echo EXEC $COMMAND + chroot $SNAPSHOTMOUNTPOINT bash -c "$COMMAND" +done +IFS=$OLD_IFS + +# Add the PPA/repositories to the clear/vanilla snapshot +for REPOSITORY in ${REPOSITORIES[@]}; do + echo deb $REPOSITORY $DEBIAN_VERSION main \ + >> $SNAPSHOTMOUNTPOINT/etc/apt/sources.list + echo deb-src $REPOSITORY $DEBIAN_VERSION main \ + >> $SNAPSHOTMOUNTPOINT/etc/apt/sources.list +done + +# Fetch/Update the repositories +chroot $SNAPSHOTMOUNTPOINT apt-get update > /dev/null 2> /dev/null + +# Install each package individually +for PACKAGE in ${PACKAGES[@]}; do + echo -e "\n\n===========Installing ${PACKAGE} ==============\n" + chroot $SNAPSHOTMOUNTPOINT apt-get install $PACKAGE -y --force-yes + RET=$? + if [ "$RET" != "0" ]; then + echo -e "\n\n\nInstall PPA to vanilla Ubuntu completed with $RET \n" + clearmountpoints + exit $RET + else + echo -e "\n\n${PACKAGE} successfully installed" + chroot $SNAPSHOTMOUNTPOINT dpkg -s $PACKAGE + fi +done + +# Execute the post install bash commands +OLD_IFS=$IFS +IFS=`echo -en "\n\b"` +for COMMAND in ${POST_COMMANDS[@]}; do + chroot $SNAPSHOTMOUNTPOINT /bin/bash -c "$COMMAND" +done +IFS=$OLD_IFS + +# Execute commands on the *REAL OS*, as root +OLD_IFS=$IFS +IFS=`echo -en "\n\b"` +for COMMAND in ${PRE_UMOUNT_COMMANDS[@]}; do + /bin/bash -c "cd $SNAPSHOTMOUNTPOINT;$COMMAND" +done +IFS=$OLD_IFS + +# Open an interactive shell +if [ "$OPEN_SHELL" == "true" ]; then + chroot $SNAPSHOTMOUNTPOINT /bin/bash +fi + +clearmountpoints +echo "Completed successfully!" diff --git a/tools/build-system/scripts/sflphone_integration.sh b/tools/build-system/scripts/sflphone_integration.sh new file mode 100755 index 0000000000..14dd2422ac --- /dev/null +++ b/tools/build-system/scripts/sflphone_integration.sh @@ -0,0 +1,90 @@ +#!/bin/bash + +NIGHTLY_PPA="http://ppa.launchpad.net/savoirfairelinux/sflphone-nightly/ubuntu" +FAILED=0 + +# Print an error if a test failed +function() checkResult() { + RET=$? + if [ "$RET" != "0" ]; then + echo !! " [FAILED]" + let FAILED=$FAILED+1 + fi +} + +# +# Install the PPA packages +# + +# Install the gnome client from the PPA +sudo ./run_package_test.sh -r $NIGHTLY_PPA -p sflphone-gnome +checkResult + +# Install the KDE client from the PPA +sudo ./run_package_test.sh -r $NIGHTLY_PPA -p sflphone-kde +checkResult + +# Install both clients from the PPA +sudo ./run_package_test.sh -r $NIGHTLY_PPA -p sflphone-gnome-video sflphone-kde +checkResult + + + + +# +# Upgrade stock Ubuntu sflphone packages to our PPA +# + +# Install the stock gnome client, then upgrade to the PPA +sudo ./run_package_test.sh -r $NIGHTLY_PPA \ + -b "apt-get install sflphone-gnome" -p sflphone-gnome +checkResult + +# Install the stock KDE client, then upgrade to the PPA +sudo ./run_package_test.sh -r $NIGHTLY_PPA \ + -b "apt-get install sflphone-kde" -p sflphone-kde +checkResult + + + + +# +# Toggle the PPA gnome client video support +# + +# Upgrade from non-video Gnome client to Video Gnome client +sudo ./run_package_test.sh -r $NIGHTLY_PPA \ + -b "apt-get install sflphone-gnome" -p sflphone-gnome-video +checkResult + +# Downgrade from non-video Gnome client to Video Gnome client +sudo ./run_package_test.sh -r $NIGHTLY_PPA \ + -b "apt-get install sflphone-gnome-video" -p sflphone-gnome +checkResult + + + + +# +# List build dependencies +# + + +# List Gnome client build-dep versus PPA +sudo ./run_package_test.sh -r $NIGHTLY_PPA \ + -b "apt-get build-dep sflphone-gnome -s" -c "apt-get build-dep sflphone-gnome -s" \ + -c "debtree -b --arch=all --no-recommends --no-conflicts sflphone-gnome > /tmp/graph" \ + -p debtree -u "dotty tmp/graph" +checkResult + +# List Gnome client (+video) build-dep versus PPA +sudo ./run_package_test.sh -r $NIGHTLY_PPA \ + -b "apt-get build-dep sflphone-gnome -s" -c "apt-get build-dep sflphone-gnome-video -s" \ + -c "debtree -b --arch=all --no-recommends --no-conflicts sflphone-gnome-video > /tmp/graph"\ + -p debtree -u "dotty tmp/graph" +checkResult + +# Exit with error 1 if one or more test failed +if [ $FAILED ~= "0" ]; then + exit 1 +fi diff --git a/tools/build-system/setenv.sh b/tools/build-system/setenv.sh new file mode 100755 index 0000000000..e65c18496d --- /dev/null +++ b/tools/build-system/setenv.sh @@ -0,0 +1,19 @@ +#!/bin/bash +# Export environment variables for launch-build-machine-jenkins.sh script. + +# home directory +export ROOT_DIR=${HOME} + +# In case the script is executed manually, replace the variables set by Jenkins +export WORKSPACE=${WORKSPACE:=.} + +# gpg passphrase file +export GPG_FILE="${WORKSPACE}/.gpg-sflphone" + +export EDITOR="echo" + +export REFERENCE_REPOSITORY="${WORKSPACE}" + +export WORKING_DIR="${WORKSPACE}/tools/build-system" +export LAUNCHPAD_DIR="${WORKING_DIR}/launchpad" +export LAUNCHPAD_DISTRIBUTIONS=("trusty utopic") diff --git a/tools/build-system/sfl-git-dch-2.sh b/tools/build-system/sfl-git-dch-2.sh new file mode 100755 index 0000000000..cde69a6a2c --- /dev/null +++ b/tools/build-system/sfl-git-dch-2.sh @@ -0,0 +1,94 @@ +#!/bin/bash +##################################################### +# File Name: sfl-git-dch.sh +# +# Purpose : +# +# Author: Julien Bonjean (julien@bonjean.info) +# +# Creation Date: 2009-10-21 +# Last Modified: 2009-10-21 14:58:22 -0400 +##################################################### + +#set -x + +. $1 + +echo "********************************************************************************" +echo "Software: ${SOFTWARE}" +echo "Version: ${VERSION}" +echo "Distribution: ${DISTRIBUTION}" +echo "Generating changelog (from commit ${COMMIT_HASH_BEGIN} to ${COMMIT_HASH_END}) in file ${CHANGELOG_FILE}" +if [ ${IS_RELEASE} ] ; then + echo "Release mode" +else + echo "Snapshot mode" +fi + +cd ${WORKING_DIR} + +# use git log to retrieve changelog content +CHANGELOG_CONTENT=`git log --no-merges --pretty=format:"%s" ${COMMIT_HASH_BEGIN}..${COMMIT_HASH_END} $2 | grep -v "\[\#1262\]"` + +if [ "$?" -eq "1" ]; then + echo " !! No new commit since last release" + CHANGELOG_CONTENT="No new commit" +fi + +if [ "$?" -ne "0" ]; then + echo " !! Error when retrieving changelog content" + exit -1 +fi + +rm -f ${CHANGELOG_FILE}.dch >/dev/null 2>&1 + +IS_FIRST=1 +echo "${CHANGELOG_CONTENT}" | while read line +do + if [ ${IS_FIRST} ]; then + + yes | dch --changelog ${CHANGELOG_FILE} -b --allow-lower-version --no-auto-nmu --distribution ${DISTRIBUTION} --newversion ${VERSION} "$line" >/dev/null 2>&1 + + if [ "$?" -ne "0" ]; then + echo + echo " !! Error with new version" + exit -1 + fi + + IS_FIRST= + + else + dch --changelog ${CHANGELOG_FILE} --no-auto-nmu "$line" + if [ "$?" -ne "0" ]; then + echo + echo " !! Error when adding changelog entry" + exit -1 + fi + fi + echo -n . +done + +# add snapshot or release flag if needed +echo +if [ ${IS_RELEASE} ]; then + sed -i "3i\ ** ${VERSION} **\n" ${CHANGELOG_FILE} + if [ "$?" -ne "0" ]; then + echo " !! Error when adding snapshot flag" + exit -1 + fi +else + sed -i "3i\ ** SNAPSHOT ${VERSION} **\n" ${CHANGELOG_FILE} + if [ "$?" -ne "0" ]; then + echo " !! Error when adding snapshot flag" + exit -1 + fi +fi + +echo +echo "All done !" +echo "********************************************************************************" + +cd - + +exit 0 + diff --git a/tools/dringctrl/__init__.py b/tools/dringctrl/__init__.py new file mode 100644 index 0000000000..991f029774 --- /dev/null +++ b/tools/dringctrl/__init__.py @@ -0,0 +1,29 @@ +# +# Copyright (C) 2015 Savoir-Faire Linux Inc. +# Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Additional permission under GNU GPL version 3 section 7: +# +# If you modify this program, or any covered work, by linking or +# combining it with the OpenSSL project's OpenSSL library (or a +# modified version of that library), containing parts covered by the +# terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. +# grants you additional permission to convey the resulting work. +# Corresponding Source for a non-source form of such a combination +# shall include the source code for the parts of OpenSSL used as well +# as that of the covered work. +# diff --git a/tools/dringctrl/controler.py b/tools/dringctrl/controler.py new file mode 100644 index 0000000000..e71b2c46b6 --- /dev/null +++ b/tools/dringctrl/controler.py @@ -0,0 +1,616 @@ +# +# Copyright (C) 2015 Savoir-Faire Linux Inc. +# Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Additional permission under GNU GPL version 3 section 7: +# +# If you modify this program, or any covered work, by linking or +# combining it with the OpenSSL project's OpenSSL library (or a +# modified version of that library), containing parts covered by the +# terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. +# grants you additional permission to convey the resulting work. +# Corresponding Source for a non-source form of such a combination +# shall include the source code for the parts of OpenSSL used as well +# as that of the covered work. +# + +"""DRing controling class through DBUS""" + +import os +import random +import time +import hashlib + +from threading import Thread +from functools import partial + +from gi.repository import GObject + +from errors import * + +try: + import dbus + from dbus.mainloop.glib import DBusGMainLoop +except ImportError as e: + raise DRingCtrlError("No python3-dbus module found") + + +DBUS_DEAMON_OBJECT = 'cx.ring.Ring' +DBUS_DEAMON_PATH = '/cx/ring/Ring' + + +class DRingCtrl(Thread): + def __init__(self, name): + super().__init__() + + self.activeCalls = {} # list of active calls (known by the client) + self.activeConferences = {} # list of active conferences + self.account = None # current active account + self.name = name # client name + + self.currentCallId = "" + self.currentConfId = "" + + self.isStop = False + + # Glib MainLoop for processing callbacks + self.loop = GObject.MainLoop() + + GObject.threads_init() + + # client registered to sflphoned ? + self.registered = False + self.register() + + def __del__(self): + if self.registered: + self.unregister() + self.loop.quit() + + def stopThread(self): + self.isStop = True + + def register(self): + if self.registered: + return + + try: + # register the main loop for d-bus events + DBusGMainLoop(set_as_default=True) + bus = dbus.SessionBus() + + except dbus.DBusException as e: + raise DRingCtrlDBusError("Unable to connect DBUS session bus") + + if not bus.name_has_owner(DBUS_DEAMON_OBJECT) : + raise DRingCtrlDBusError(("Unable to find %s in DBUS." % DBUS_DEAMON_OBJECT) + + " Check if dring is running") + + try: + proxy_instance = bus.get_object(DBUS_DEAMON_OBJECT, + DBUS_DEAMON_PATH+'/Instance', introspect=False) + proxy_callmgr = bus.get_object(DBUS_DEAMON_OBJECT, + DBUS_DEAMON_PATH+'/CallManager', introspect=False) + proxy_confmgr = bus.get_object(DBUS_DEAMON_OBJECT, + DBUS_DEAMON_PATH+'/ConfigurationManager', introspect=False) + proxy_videomgr = bus.get_object(DBUS_DEAMON_OBJECT, + DBUS_DEAMON_PATH+'/VideoManager', introspect=False) + + self.instance = dbus.Interface(proxy_instance, + DBUS_DEAMON_OBJECT+'.Instance') + self.callmanager = dbus.Interface(proxy_callmgr, + DBUS_DEAMON_OBJECT+'.CallManager') + self.configurationmanager = dbus.Interface(proxy_confmgr, + DBUS_DEAMON_OBJECT+'.ConfigurationManager') + if proxy_videomgr: + self.videomanager = dbus.Interface(proxy_videomgr, + DBUS_DEAMON_OBJECT+'.VideoManager') + + except dbus.DBusException as e: + raise DRingCtrlDBusError("Unable to bind to dring DBus API") + + try: + self.instance.Register(os.getpid(), self.name) + self.registered = True + + except: + raise DRingCtrlDeamonError("Client registration failed") + + try: + proxy_callmgr.connect_to_signal('incomingCall', self.onIncomingCall) + proxy_callmgr.connect_to_signal('callStateChanged', self.onCallStateChanged) + proxy_callmgr.connect_to_signal('conferenceCreated', self.onConferenceCreated) + + except dbus.DBusException as e: + raise DRingCtrlDBusError("Unable to connect to dring DBus signals") + + + def unregister(self): + if not self.registered: + return + + try: + self.instance.Unregister(os.getpid()) + self.registered = False + + except: + raise DRingCtrlDeamonError("Client unregistration failed") + + + def isRegistered(self): + return self.registered + + # + # Signal handling + # + + def onIncomingCall_cb(self): + pass + + def onCallHangup_cb(self, callId): + pass + + def onCallRinging_cb(self): + pass + + def onCallHold_cb(self): + pass + + def onCallCurrent_cb(self): + pass + + def onCallBusy_cb(self): + pass + + def onCallFailure_cb(self): + pass + + def onIncomingCall(self, account, callid, to): + """ On incoming call event, add the call to the list of active calls """ + + self.activeCalls[callid] = {'Account': account, + 'To': to, + 'State': ''} + self.currentCallId = callid + self.onIncomingCall_cb() + + + def onCallHangUp(self, callid): + """ Remove callid from call list """ + + self.onCallHangup_cb(callid) + self.currentCallId = "" + del self.activeCalls[callid] + + + def onCallRinging(self, callid, state): + """ Update state for this call to Ringing """ + + self.activeCalls[callid]['State'] = state + self.onCallRinging_cb() + + + def onCallHold(self, callid, state): + """ Update state for this call to Hold """ + + self.activeCalls[callid]['State'] = state + self.onCallHold_cb() + + + def onCallCurrent(self, callid, state): + """ Update state for this call to current """ + + self.activeCalls[callid]['State'] = state + self.onCallCurrent_cb() + + + def onCallBusy(self, callid, state): + """ Update state for this call to busy """ + + self.activeCalls[callid]['State'] = state + self.onCallBusy_cb() + + + def onCallFailure(self, callid, state): + """ Handle call failure """ + + self.onCallFailure_cb() + del self.activeCalls[callid] + + + def onCallStateChanged(self, callid, state): + """ On call state changed event, set the values for new calls, + or delete the call from the list of active calls + """ + + print(("On call state changed " + callid + " " + state)) + + if callid not in self.activeCalls: + print("This call didn't exist!: " + callid + ". Adding it to the list.") + callDetails = self.getCallDetails(callid) + self.activeCalls[callid] = {'Account': callDetails['ACCOUNTID'], + 'To': callDetails['PEER_NUMBER'], + 'State': state } + + + self.currentCallId = callid + + if state == "HUNGUP": + self.onCallHangUp(callid) + elif state == "RINGING": + self.onCallRinging(callid, state) + elif state == "CURRENT": + self.onCallCurrent(callid, state) + elif state == "HOLD": + self.onCallHold(callid, state) + elif state == "BUSY": + self.onCallBusy(callid, state) + elif state == "FAILURE": + self.onCallFailure(callid, state) + else: + print("unknown state") + + def onConferenceCreated_cb(self): + pass + + def onConferenceCreated(self, confId): + self.currentConfId = confId + self.onConferenceCreated_cb() + + # + # Account management + # + + def _valid_account(self, account): + account = account or self.account + if account is None: + raise DRingCtrlError("No provided or current account!") + return account + + def isAccountExists(self, account): + """ Checks if the account exists""" + + return account in self.getAllAccounts() + + def isAccountEnable(self, account=None): + """Return True if the account is enabled. If no account is provided, active account is used""" + + return self.getAccountDetails(self._valid_account(account))['Account.enable'] == "true" + + def isAccountRegistered(self, account=None): + """Return True if the account is registered. If no account is provided, active account is used""" + + return self.getVolatileAccountDetails(self._valid_account(account))['Account.registrationStatus'] in ('READY', 'REGISTERED') + + def isAccountOfType(self, account_type, account=None): + """Return True if the account type is the given one. If no account is provided, active account is used""" + + return self.getAccountDetails(self._valid_account(account))['Account.type'] == account_type + + def getAllAccounts(self, account_type=None): + """Return a list with all accounts""" + + acclist = map(str, self.configurationmanager.getAccountList()) + if account_type: + acclist = filter(partial(self.isAccountOfType, account_type), acclist) + return list(acclist) + + def getAllEnabledAccounts(self): + """Return a list with all enabled-only accounts""" + + return [x for x in self.getAllAccounts() if self.isAccountEnable(x)] + + def getAllRegisteredAccounts(self): + """Return a list with all registered-only accounts""" + + return [x for x in self.getAllAccounts() if self.isAccountRegistered(x)] + + def getAccountDetails(self, account=None): + """Return a list of string. If no account is provided, active account is used""" + + account = self._valid_account(account) + if self.isAccountExists(account): + return self.configurationmanager.getAccountDetails(account) + return [] + + def getVolatileAccountDetails(self, account=None): + """Return a list of string. If no account is provided, active account is used""" + + account = self._valid_account(account) + if self.isAccountExists(account): + return self.configurationmanager.getVolatileAccountDetails(account) + return [] + + def setActiveCodecList(self, account=None, codec_list=''): + """Activate given codecs on an account. If no account is provided, active account is used""" + + account = self._valid_account(account) + if self.isAccountExists(account): + codec_list = [dbus.UInt32(x) for x in codec_list.split(',')] + self.configurationmanager.setActiveCodecList(account, codec_list) + + def addAccount(self, details=None): + """Add a new account account + + Add a new account to the SFLphone-daemon. Default parameters are \ + used for missing account configuration field. + + Required parameters are type, alias, hostname, username and password + + input details + """ + + if details is None: + raise DRingCtrlAccountError("Must specifies type, alias, hostname, \ + username and password in \ + order to create a new account") + + return self.configurationmanager.addAccount(details) + + def removeAccount(self, accountID=None): + """Remove an account from internal list""" + + if accountID is None: + raise DRingCtrlAccountError("Account ID must be specified") + + self.configurationmanager.removeAccount(accountID) + + def setAccountByAlias(self, alias): + """Define as active the first account who match with the alias""" + + for testedaccount in self.getAllAccounts(): + details = self.getAccountDetails(testedaccount) + if (details['Account.enable'] == 'true' and + details['Account.alias'] == alias): + self.account = testedaccount + return + raise DRingCtrlAccountError("No enabled account matched with alias") + + def getAccountByAlias(self, alias): + """Get account name having its alias""" + + for account in self.getAllAccounts(): + details = self.getAccountDetails(account) + if details['Account.alias'] == alias: + return account + + raise DRingCtrlAccountError("No account matched with alias") + + def setAccount(self, account): + """Define the active account + + The active account will be used when sending a new call + """ + + if account in self.getAllAccounts(): + self.account = account + else: + print(account) + raise DRingCtrlAccountError("Not a valid account") + + def setFirstRegisteredAccount(self): + """Find the first enabled account and define it as active""" + + rAccounts = self.getAllRegisteredAccounts() + if 0 == len(rAccounts): + raise DRingCtrlAccountError("No registered account !") + self.account = rAccounts[0] + + def setFirstActiveAccount(self): + """Find the first enabled account and define it as active""" + + aAccounts = self.getAllEnabledAccounts() + if 0 == len(aAccounts): + raise DRingCtrlAccountError("No active account !") + self.account = aAccounts[0] + + def getAccount(self): + """Return the active account""" + + return self.account + + def setAccountEnable(self, account=None, enable=False): + """Set account enabled""" + + account = self._valid_account(account) + if enable == True: + details = self.getAccountDetails(account) + details['Account.enable'] = "true" + self.configurationmanager.setAccountDetails(account, details) + else: + details = self.getAccountDetails(account) + details['Account.enable'] = "false" + self.configurationmanager.setAccountDetails(account, details) + + def setAccountRegistered(self, account=None, register=False): + """ Tries to register the account""" + + account = self._valid_account(account) + self.configurationmanager.sendRegister(account, register) + + # + # Codec manager + # + + def getAllCodecs(self): + """ Return all codecs""" + + return [int(x) for x in self.configurationmanager.getCodecList()] + + def getActiveCodecs(self, account=None): + """ Return all active codecs on given account""" + + account = self._valid_account(account) + return [int(x) for x in self.configurationmanager.getActiveCodecList(account)] + + # + # Call management + # + + def getAllCalls(self): + """Return all calls handled by the daemon""" + + return [str(x) for x in self.callmanager.getCallList()] + + def getCallDetails(self, callid): + """Return informations on this call if exists""" + + return self.callmanager.getCallDetails(callid) + + def printClientCallList(self): + print("Client active call list:") + print("------------------------") + for call in self.activeCalls: + print("\t" + call) + + def Call(self, dest): + """Start a call and return a CallID + + Use the current account previously set using setAccount(). + If no account specified, first registered one in account list is used. + + return callID Newly generated callidentifier for this call + """ + + if dest is None or dest == "": + raise SflPhoneError("Invalid call destination") + + # Set the account to be used for this call + if not self.account: + self.setFirstRegisteredAccount() + + if self.account is not "IP2IP" and not self.isAccountRegistered(): + raise DRingCtrlAccountError("Can't place a call without a registered account") + + # Send the request to the CallManager + callid = self.callmanager.placeCall(self.account, dest) + if callid: + # Add the call to the list of active calls and set status to SENT + self.activeCalls[callid] = {'Account': self.account, 'To': dest, 'State': 'SENT' } + + return callid + + + def HangUp(self, callid): + """End a call identified by a CallID""" + + if not self.account: + self.setFirstRegisteredAccount() + + if callid is None or callid == "": + pass # just to see + + self.callmanager.hangUp(callid) + + + def Transfer(self, callid, to): + """Transfert a call identified by a CallID""" + + if callid is None or callid == "": + raise DRingCtrlError("Invalid callID") + + self.callmanager.transfert(callid, to) + + + def Refuse(self, callid): + """Refuse an incoming call identified by a CallID""" + + print("Refuse call " + callid) + + if callid is None or callid == "": + raise DRingCtrlError("Invalid callID") + + self.callmanager.refuse(callid) + + + def Accept(self, callid): + """Accept an incoming call identified by a CallID""" + + print("Accept call " + callid) + if not self.account: + self.setFirstRegisteredAccount() + + if not self.isAccountRegistered(): + raise DRingCtrlAccountError("Can't accept a call without a registered account") + + if callid is None or callid == "": + raise DRingCtrlError("Invalid callID") + + self.callmanager.accept(callid) + + + def Hold(self, callid): + """Hold a call identified by a CallID""" + + if callid is None or callid == "": + raise DRingCtrlError("Invalid callID") + + self.callmanager.hold(callid) + + + def UnHold(self, callid): + """Unhold an incoming call identified by a CallID""" + + if callid is None or callid == "": + raise DRingCtrlError("Invalid callID") + + self.callmanager.unhold(callid) + + + def Dtmf(self, key): + """Send a DTMF""" + + self.callmanager.playDTMF(key) + + + def _GenerateCallID(self): + """Generate Call ID""" + + m = hashlib.md5() + t = int( time.time() * 1000 ) + r = int( random.random()*100000000000000000 ) + m.update(str(t) + str(r)) + callid = m.hexdigest() + return callid + + + def createConference(self, call1Id, call2Id): + """ Create a conference given the two call ids """ + + self.callmanager.joinParticipant(call1Id, call2Id) + + + def hangupConference(self, confId): + """ Hang up each call for this conference """ + + self.callmanager.hangUpConference(confId) + + + def run(self): + """Processing method for this thread""" + + context = self.loop.get_context() + + while True: + context.iteration(True) + + if self.isStop: + print("++++++++++++++++++++++++++++++++++++++++") + print("++++++++++++++++++++++++++++++++++++++++") + print("++++++++++++++++++++++++++++++++++++++++") + print("++++++++++++++++++++++++++++++++++++++++") + return diff --git a/tools/dringctrl/dring.functest.yml b/tools/dringctrl/dring.functest.yml new file mode 100644 index 0000000000..926a0506b6 --- /dev/null +++ b/tools/dringctrl/dring.functest.yml @@ -0,0 +1,208 @@ +--- +accounts: +- alias: 100 + codecs: 0/3/8/9/110/111/112/ + credential: + - Account.password: password + Account.realm: '*' + Account.username: 100 + displayName: + dtmfType: overrtp + enable: true + hostname: 127.0.0.1:5062 + id: Account:1334024061 + interface: default + mailbox: + port: 5060 + publishAddr: 0.0.0.0 + publishPort: 5060 + registrationexpire: 600 + ringtoneEnabled: true + ringtonePath: /home/alexandresavard/Development/sflphone/gnome + sameasLocal: true + serviceRoute: + srtp: + enable: false + keyExchange: + rtpFallback: false + stunEnabled: false + stunServer: stun.sflphone.org + tls: + calist: + certificate: + ciphers: + enable: false + method: TLSv1 + password: + privateKey: + requireCertif: true + server: + timeout: 2 + tlsPort: 5061 + verifyClient: true + verifyServer: true + type: SIP + updateContact: false + username: 100 + zrtp: + displaySas: true + displaySasOnce: false + helloHashEnabled: true + notSuppWarning: true +- alias: 200 + codecs: 0/3/8/9/110/111/112/ + credential: + - Account.password: password + Account.realm: '*' + Account.username: 200 + displayName: + dtmfType: overrtp + enable: true + hostname: 127.0.0.1:5062 + id: Account:1334024356 + interface: default + mailbox: + port: 5060 + publishAddr: 0.0.0.0 + publishPort: 5060 + registrationexpire: 600 + ringtoneEnabled: true + ringtonePath: /home/alexandresavard/Development/sflphone/gnome + sameasLocal: true + serviceRoute: + srtp: + enable: false + keyExchange: + rtpFallback: false + stunEnabled: false + stunServer: stun.sflphone.org + tls: + calist: + certificate: + ciphers: + enable: false + method: TLSv1 + password: + privateKey: + requireCertif: true + server: + timeout: 2 + tlsPort: 5061 + verifyClient: true + verifyServer: true + type: SIP + updateContact: false + username: 200 + zrtp: + displaySas: true + displaySasOnce: false + helloHashEnabled: true + notSuppWarning: true +- alias: + codecs: 0/3/8/9/110/111/112/ + credential: + - Account.password: + Account.realm: '*' + Account.username: + displayName: + dtmfType: overrtp + enable: true + hostname: + id: IP2IP + interface: default + mailbox: + port: 5060 + publishAddr: + publishPort: 5060 + registrationexpire: 600 + ringtoneEnabled: true + ringtonePath: /usr/share/sflphone/ringtones/konga.ul + sameasLocal: true + serviceRoute: + srtp: + enable: false + keyExchange: sdes + rtpFallback: false + stunEnabled: false + stunServer: stun.sflphone.org + tls: + calist: + certificate: + ciphers: + enable: false + method: TLSv1 + password: + privateKey: + requireCertif: true + server: + timeout: 2 + tlsPort: 5061 + verifyClient: true + verifyServer: true + type: SIP + updateContact: false + username: + zrtp: + displaySas: true + displaySasOnce: false + helloHashEnabled: true + notSuppWarning: true +preferences: + historyLimit: 30 + historyMaxCalls: 20 + md5Hash: false + notifyMails: false + order: Account:1334024356/Account:1334024061/ + portNum: 5060 + registrationexpire: 180 + searchBarDisplay: true + zeroConfenable: false + zoneToneChoice: North America +voipPreferences: + playDtmf: true + playTones: true + pulseLength: 250 + symmetric: true + zidFile: true +addressbook: + business: true + enabled: true + home: true + list: + maxResults: 25 + mobile: true + photo: true +hooks: + iax2Enabled: false + numberAddPrefix: + numberEnabled: false + sipEnabled: false + urlCommand: x-www-browser + urlSipField: X-sflphone-url +audio: + alsa: + cardIn: 0 + cardOut: 0 + cardRing: 0 + plugin: default + smplRate: 44100 + alwaysRecording: false + audioApi: pulseaudio + echoCancel: false + echoDelayLength: 0 + echoTailLength: 100 + noiseReduce: true + pulse: + devicePlayback: alsa_output.pci-0000_00_1b.0.analog-stereo + deviceRecord: alsa_input.pci-0000_00_1b.0.analog-stereo + deviceRingtone: alsa_output.pci-0000_00_1b.0.analog-stereo + recordPath: + volumeMic: 100 + volumeSpkr: 100 +shortcuts: + hangUp: + pickUp: + popupWindow: + toggleHold: + togglePickupHangup: +... diff --git a/tools/dringctrl/dringctrl.py b/tools/dringctrl/dringctrl.py new file mode 100755 index 0000000000..7e1e64f029 --- /dev/null +++ b/tools/dringctrl/dringctrl.py @@ -0,0 +1,239 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2015 Savoir-Faire Linux Inc. +# Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Additional permission under GNU GPL version 3 section 7: +# +# If you modify this program, or any covered work, by linking or +# combining it with the OpenSSL project's OpenSSL library (or a +# modified version of that library), containing parts covered by the +# terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. +# grants you additional permission to convey the resulting work. +# Corresponding Source for a non-source form of such a combination +# shall include the source code for the parts of OpenSSL used as well +# as that of the covered work. +# + +import sys +import os +import random +import time +import argparse + +from gi.repository import GObject + +from errors import * +from controler import DRingCtrl +from tester import DRingTester + +def printAccountDetails(account): + details = ctrl.getAccountDetails(account) + print(account) + for k in sorted(details.keys()): + print(" %s: %s" % (k, details[k])) + print() + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument('--gaa', help='Get all accounts (of optionaly given type)', + nargs='?', metavar='<type>', type=str, default=argparse.SUPPRESS) + parser.add_argument('--gara', help='Get all registered accounts', action='store_true') + parser.add_argument('--gaea', help='Get all enabled accounts', action='store_true') + parser.add_argument('--gaad', help='Get all account details', action='store_true') + + parser.add_argument('--gac', help='Get all codecs', action='store_true') + + parser.add_argument('--gad', help='Get account details', + metavar='<account>', type=str) + + group = parser.add_mutually_exclusive_group() + group.add_argument('--enable', help='Enable the account', + metavar='<account>', type=str) + group.add_argument('--disable', help='Disable the account', + metavar='<account>', type=str) + + group = parser.add_mutually_exclusive_group() + group.add_argument('--register', help='Register the account', + metavar='<account>', type=str) + group.add_argument('--unregister', help='Unregister the account', + metavar='<account>', type=str) + + parser.add_argument('--sac', help='Set active account', + metavar='<account>', type=str) + + parser.add_argument('--gacl', help='Get active codecs for the account', + nargs='?', metavar='<account>', type=str, default=argparse.SUPPRESS) + parser.add_argument('--sacl', help='Set active codecs for active account', + metavar='<codec list>', type=str) + + #parser.add_argument('--gcc', help='Get current callid', action='store_true') + parser.add_argument('--gcl', help='Get call list', action='store_true') + + group = parser.add_mutually_exclusive_group() + group.add_argument('--call', help='Call to number', metavar='<destination>') + #group.add_argument('--transfer', help='Transfer active call', metavar='<destination>') + + group = parser.add_mutually_exclusive_group() + group.add_argument('--accept', help='Accept the call', metavar='<account>') + group.add_argument('--hangup', help='Hangup the call', metavar='<account>') + group.add_argument('--refuse', help='Refuse the call', metavar='<account>') + + group = parser.add_mutually_exclusive_group() + group.add_argument('--hold', help='Hold the call', metavar='<call>') + group.add_argument('--unhold', help='Unhold the call', metavar='<call>') + + parser.add_argument('--dtmf', help='Send DTMF', metavar='<key>') + parser.add_argument('--toggleVideo', help='Launch toggle video tests', action='store_true') + + parser.add_argument('--test', help='Launch automatic tests', action='store_true') + + args = parser.parse_args() + + ctrl = DRingCtrl(sys.argv[0]) + + if len(sys.argv) == 1: + ctrl.run() + sys.exit(0) + + if args.gac: + print(ctrl.getAllCodecs()) + + if hasattr(args, 'gaa'): + for account in ctrl.getAllAccounts(args.gaa): + print(account) + + if args.gara: + for account in ctrl.getAllRegisteredAccounts(): + print(account) + + if args.gaea: + for account in ctrl.getAllEnabledAccounts(): + print(account) + + if args.gaad: + for account in ctrl.getAllAccounts(): + printAccountDetails(account) + + if args.sac: + ctrl.setAccount(args.sac) + + if args.gad: + printAccountDetails(args.gad) + + if hasattr(args, 'gacl'): + print(ctrl.getActiveCodecs(args.gacl)) + + if args.sacl: + ctrl.setActiveCodecList(codec_list=args.sacl) + + if args.enable: + ctrl.setAccountEnable(args.enable, True) + + if args.disable: + ctrl.setAccountEnable(args.enable, False) + + if args.register: + ctrl.setAccountRegistered(args.register, True) + + if args.unregister: + ctrl.setAccountRegistered(args.unregister, False) + + if args.gcl: + for call in ctrl.getAllCalls(): + print(call) + + if args.call: + ctrl.Call(args.call) + + if args.accept: + ctrl.Accept(args.accept) + + if args.refuse: + ctrl.Refuse(args.refuse) + + if args.hangup: + ctrl.HangUp(args.hangup) + + if args.hold: + ctrl.Hold(args.hold) + + if args.unhold: + ctrl.UnHold(args.unhold) + + if args.dtmf: + ctrl.Dtmf(args.dtmf) + + if args.test: + DRingTester().start(ctrl) + + if args.toggleVideo: + if not ctrl.videomanager: + print("Error: daemon without video support") + sys.exit(1) + import time + while True: + time.sleep(2) + ctrl.videomanager.startCamera() + time.sleep(2) + ctrl.videomanager.stopCamera() + +""" + + # Get call details + elif opt == "--gcd": + if arg == "current": arg = sflphone.getCurrentCallID() + + details = sflphone.getCallDetails(arg) + if details: + print "Call: " + arg + print "Account: " + details['ACCOUNTID'] + print "Peer: " + details['PEER_NAME'] + "<" + details['PEER_NUMBER'] + ">" + + # Transfer the current call + elif opt == "--transfer": + call = sflphone.callmanager.getCurrentCallID() + sflphone.Transfert(call, arg) + + # + # account options + # + + # Register an account + elif opt == "--register": + if not sflphone.checkAccountExists(arg): + print "Account " + arg + ": no such account." + + elif arg in sflphone.getAllRegisteredAccounts(): + print "Account " + arg + ": already registered." + + else: + sflphone.setAccountRegistered(arg, True) + print arg + ": Sent register request." + + # Unregister an account + elif opt == "--unregister": + if not sflphone.checkAccountExists(arg): + print "Account " + arg + ": no such account." + + elif arg not in sflphone.getAllRegisteredAccounts(): + print "Account " + arg + ": is not registered." + + else: + sflphone.setAccountRegistered(arg, False) + print arg + ": Sent unregister request." +""" diff --git a/tools/dringctrl/dringctrl_testdbus.py b/tools/dringctrl/dringctrl_testdbus.py new file mode 100644 index 0000000000..fc2bc66835 --- /dev/null +++ b/tools/dringctrl/dringctrl_testdbus.py @@ -0,0 +1,390 @@ +#!/usr/bin/env python +import signal + +import time +import sys + +import getopt +import gtk + +from threading import Thread +from threading import Event + +print "Import SFLphone" +from sflphonectrlsimple import SflPhoneCtrlSimple + +# Define remote IP address constant +REMOTEADDR_lo="127.0.0.1:5062" +REMOTEADDR_lo2="127.0.0.1:5064" +REMOTEADDR_lo3="127.0.0.1:5066" + +# Defines phone numbers +PHONE1="27182" +PHONE2="31416" +PHONE3="14142" + + +# Define function callback to emulate UA behavior on +# recieving a call (peer hangup)) +def acceptOnIncomingCall(sflphone): + + sflphone.Accept(sflphone.currentCallId) + + +# Define function callback to emulate UA behavior on +# receiving a call and hanging up +def acceptOnIncomingCallHangup(sflphone): + + sflphone.Accept(sflphone.currentCallId) + sflphone.HangUp(sflphone.currentCallId) + + +# Define function callback to emulate UA behavior on +# refusing a call +def refuseOnIncomingCall(sflphone): + # time.sleep(0.5) + sflphone.Refuse(sflphone.currentCallId) + + +class SflPhoneTests(): + + def __init__(self, sfl): + print "Create test instance" + self.sflphone = sfl + + def test_get_allaccounts_methods(self): + + for account in self.getAllAccounts(): + print " " + account + + for account in self.getAllRegisteredAccounts(): + print " " + account + + for account in self.getAllSipAccounts(): + print " " + account + + for account in self.getAllIaxAccounts(): + print " " + account + + def test_create_account(self): + """Create a new sip account""" + + CONFIG_ACCOUNT_TYPE = "Account.type" + CONFIG_ACCOUNT_ALIAS = "Account.alias" + HOSTNAME = "hostname" + USERNAME = "username" + PASSWORD = "password" + + accDetails = {CONFIG_ACCOUNT_TYPE:"SIP", CONFIG_ACCOUNT_ALIAS:"testsuiteaccount", + HOSTNAME:"192.168.50.79", USERNAME:"31416", + PASSWORD:"1234"} + + + accountID = self.sflphone.addAccount(accDetails) + print "New Account ID " + accountID + + return accountID + + + def test_remove_account(self, accountID): + """Remove test account""" + + self.sflphone.removeAccount(accountID) + print "Account with ID " + accountID + " removed" + + + # SCENARIO 1 Test 1 + def test_ip2ip_send_hangup(self): + """Make a call to a server (sipp) on port 5062""" + i = 0 + while(i < 500): + + callid = self.sflphone.Call("sip:test@" + REMOTEADDR_lo) + time.sleep(0.5) + + self.sflphone.HangUp(callid) + time.sleep(0.5) + + i = i+1 + + self.sflphone.unregister() + del self.sflphone + + + # SCENARIO 1 Test 2 + def test_ip2ip_send_peer_hungup(self): + """Make a call to a server (sipp) on port 5062""" + i = 0 + while(i < 10): + + callid = self.sflphone.Call("sip:test@" + REMOTEADDR_lo) + time.sleep(1.0) + + i = i+1 + + del self.sflphone + + + # SCENARIO 1 Test 3 + def test_ip2ip_recv_hangup(self): + """Wait for calls, answer then hangup""" + + # Add callback for this test + self.sflphone.onIncomingCall_cb = acceptOnIncomingCallHangup + + # Start Glib mainloop + self.sflphone.start() + + + + + # SCENARIO 1 Test 4 + def test_ip2ip_recv_peer_hungup(self): + """Wait for calls, answer, peer hangup""" + + # Add callback for this test + self.sflphone.onIncomingCall_cb = acceptOnIncomingCall + + # Start Glib mainloop + self.sflphone.start() + + + # SCENARIO 2 Test 1 + def test_account_send_hangup(self): + """Send new account call, hangup once peer answered""" + + i = 0 + while(i < 10): + + callid = self.sflphone.Call(PHONE1) + time.sleep(0.2) + + self.sflphone.HangUp(callid) + time.sleep(0.2) + + i = i+1 + + # del self.sflphone + + + # SCENARIO 2 Test 2 + def test_account_send_peer_hungup(self): + """Send new account call, hangup once peer answered""" + + i = 0 + while(i < 10): + + callid = self.sflphone.Call(PHONE1) + time.sleep(1.0) + + i = i+1 + + del self.sflphone + + + # SCENARIO 2 Test 3 + def test_account_recv_hangup(self): + """Register an account and wait for incoming calls""" + + # Add callback for this test + self.sflphone.onIncomingCall_cb = acceptOnIncomingCallHangup + + # Start Glib mainloop + self.sflphone.start() + + + # SCENARIO 2 Test 4 + def test_account_recv_peer_hungup(self): + """Register an account and wait for incoming calls""" + + # Add callback for this test + self.sflphone.onIncomingCall_cb = acceptOnIncomingCall + + # Start Glib mainloop + self.sflphone.start() + + + # SCENARIO 3 Test 1 + def test_ip2ip_send_hold_offhold(self): + """Send new call, hold this call, offhold, hangup""" + i = 0 + while(i < 10): + + callid = self.sflphone.Call("sip:test@" + REMOTEADDR_lo) + time.sleep(0.5) + + self.sflphone.Hold(callid) + time.sleep(0.5) + + self.sflphone.UnHold(callid) + time.sleep(0.5) + + self.sflphone.HangUp(callid) + time.sleep(0.5) + + i = i+1 + + del self.sflphone + + + # SCENARIO 4 Test 1 + def test_account_send_transfer(self): + """Send new calls, transfer it to a new instance""" + + i = 0 + while(i < 1): + + callid = self.sflphone.Call(PHONE1) + time.sleep(1.0) + + self.sflphone.Transfer(callid,PHONE3) + # self.sflphone.HangUp(callid) + # time.sleep(1.0) + + i = i+1 + + + # SCENARIO 5 Test 1 + def test_ip2ip_recv_refuse(self): + """Receive an incoming IP2IP call, refuse it""" + + # Add callback for this test + self.sflphone.onIncomingCall_cb = refuseOnIncomingCall + + # Start Glib mainloop + self.sflphone.start() + + + # SCENARIO 6 Test 1 + def test_mult_ip2ip_send_hangup(self): + """Make a first call to a sipp server (5062) and a second to sipp server (5064)""" + i = 0 + while(i < 500): + + callid1 = self.sflphone.Call("sip:test@" + REMOTEADDR_lo) + time.sleep(0.1) + + callid2 = self.sflphone.Call("sip:test@" + REMOTEADDR_lo2) + time.sleep(0.1) + + callid3 = self.sflphone.Call("sip:test@" + REMOTEADDR_lo3) + time.sleep(0.1) + + self.sflphone.HangUp(callid1) + time.sleep(0.1) + + self.sflphone.HangUp(callid2) + time.sleep(0.1) + + self.sflphone.HangUp(callid3) + time.sleep(0.1) + + i = i+1 + + del self.sflphone + + + # SCENARIO 6 Test 2 + def test_mult_ip2ip_send_hangup(self): + """Receive multiple calls peer hangup""" + + # Add callback for this test + self.sflphone.onIncomingCall_cb = acceptOnIncomingCall + + # Start Glib mainloop + self.sflphone.start() + + del self.sflphone + + + +# Open sflphone and connect to sflphoned through dbus +sflphone = SflPhoneCtrlSimple(True) + +# Init test suite +testsuite = SflPhoneTests(sflphone) + +# Register the first account available, should be the test account +sflphone.setFirstRegisteredAccount(); + + +# ============================ Test Suite ============================ + + + +# SCENARIO 1: IP2IP Normal flow calls + +# Test 1: - Send an IP2IP call +# - Hangup +# testsuite.test_ip2ip_send_hangup() + +# Test 2: - Send an IP2IP call +# - Peer Hangup +# testsuite.test_ip2ip_send_peer_hungup() + +# Test 3: - Receive an IP2IP call +# - Hangup +testsuite.test_ip2ip_recv_hangup() + +# Test 4: - Receive an IP2IP call +# - Peer Hangup +# testsuite.test_ip2ip_recv_peer_hungup() + + + +# SCENARIO 2: ACCOUNT Normal flow calls + +# Test 1: - Send an ACCOUNT call +# - Hangup +# testsuite.test_account_send_hangup() + +# Test 2: - Send an ACCOUNT call +# - Peer Hangup +# testsuite.test_account_send_peer_hungup() + +# Test 3: - Receive an ACCOUNT call +# - Hangup +# testsuite.test_account_recv_hangup() + +# Test 4: - Receive an ACCOUNT call +# - Peer Hangup +# testsuite.test_account_recv_peer_hungup() + + + +# SCENARIO 3: IP2IP Call, HOLD/OFFHOLD + +# Test 1: - Send an IP2IP call +# - Put this call on HOLD +# - Off HOLD this call +# - Hangup +# testsuite.test_ip2ip_send_hold_offhold() + + + +# SCENARIO 4: IP2IP Call, HOLD/OFFHOLD + +# Test 1: - Send an IP2IP call +# - Transfer this call to another sipp instance +# - Hangup +# testsuite.test_account_send_transfer() + + + +# SCENARIO 5: IP2IP Call, Refuse + +# Test 1: - Receive an incoming call +# - Hangup without answer +# testsuite.test_ip2ip_recv_refuse() + + + +# SCENARIO 6: Multiple simultaneous calls + +# Test 1: - Send multiple simultaneous IP2IP call +# - Hangup +# testsuite.test_mult_ip2ip_send_hangup() + +# Test 2: - Receive simultaneous IP2IP call +# - Hangup +# testsuite.test_mult_ip2ip_send_hangup() diff --git a/tools/dringctrl/errors.py b/tools/dringctrl/errors.py new file mode 100644 index 0000000000..27bcf5d719 --- /dev/null +++ b/tools/dringctrl/errors.py @@ -0,0 +1,50 @@ +# +# Copyright (C) 2015 Savoir-Faire Linux Inc. +# Author: Guillaume Roguez <guillaume.roguez@savoirfairelinux.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Additional permission under GNU GPL version 3 section 7: +# +# If you modify this program, or any covered work, by linking or +# combining it with the OpenSSL project's OpenSSL library (or a +# modified version of that library), containing parts covered by the +# terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. +# grants you additional permission to convey the resulting work. +# Corresponding Source for a non-source form of such a combination +# shall include the source code for the parts of OpenSSL used as well +# as that of the covered work. +# + +"""Internal exceptions""" + + +class DRingCtrlError(Exception): + """Base class for all our exceptions.""" + + def __init__(self, help=None): + self.help = str(help) + + def __str__(self): + return self.help + +class DRingCtrlDBusError(DRingCtrlError): + """General error for dbus communication""" + +class DRingCtrlDeamonError(DRingCtrlError): + """General error for daemon communication""" + +class DRingCtrlAccountError(DRingCtrlError): + """General error for account handling""" diff --git a/tools/dringctrl/sippwrap.py b/tools/dringctrl/sippwrap.py new file mode 100644 index 0000000000..0bdc073558 --- /dev/null +++ b/tools/dringctrl/sippwrap.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python +# +# Copyright (C) 2012 by the Free Software Foundation, Inc. +# +# Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +import os + +class SippWrapper: + """ Wrapper taht allow for managing sipp command line easily """ + + def __init__(self): + self.commandLine = "./sipp" + self.remoteServer = "" + self.remotePort = "" + self.localInterface = "" + self.localPort = "" + self.customScenarioFile = "" + self.isUserAgenClient = True + self.launchInBackground = False + self.numberOfCall = 0 + self.numberOfSimultaneousCall = 0 + self.enableTraceMsg = False + self.enableTraceShormsg = False + self.enableTraceScreen = False + self.enableTraceError = False + self.enableTraceStat = False + self.enableTraceCounts = False + self.enableTraceRtt = False + self.enableTraceLogs = False + + def buildCommandLine(self, port): + """ Fill the command line arguments based on specified parameters """ + + self.localPort = str(port) + + if not self.remotePort and not self.remoteServer: + self.isUserAgentClient = False + elif self.remotePort and not self.remoteServer: + print "Error cannot have remote port specified with no server" + return + + if self.remoteServer: + self.commandLine += " " + self.remoteServer + + if self.remotePort: + self.commandLine += ":" + self.remotePort + + if self.localInterface: + self.commandLine += " -i " + self.localInterface + + if self.localPort: + self.commandLine += " -p " + self.localPort + + if self.customScenarioFile: + self.commandLine += " -sf " + self.customScenarioFile + elif self.isUserAgentClient is True: + self.commandLine += " -sn uac" + elif self.isUserAgentClient is False: + self.commandLine += " -sn uas" + + if self.launchInBackground: + self.commandLine += " -bg" + + if self.numberOfCall: + self.commandLine += " -m " + str(self.numberOfCall) + + if self.numberOfSimultaneousCall: + self.commandLine += " -l " + str(self.numberOfSimultaneousCall) + + if self.enableTraceMsg: + self.commandLine += " -trace_msg" + + if self.enableTraceShormsg: + self.commandLine += " -trace_shortmsg" + + if self.enableTraceScreen: + self.commandLine += " -trace_screen" + + if self.enableTraceError: + self.commandLine += " -trace_err" + + if self.enableTraceStat: + self.commandLine += " -trace_stat" + + if self.enableTraceCounts: + self.commandLine += " -trace_counts" + + if self.enableTraceRtt: + self.commandLine += " -trace_rtt" + + if self.enableTraceLogs: + self.commandLine += " -trace_logs" + + + def launch(self): + """ Launch the sipp instance using the specified arguments """ + + print self.commandLine + return os.system(self.commandLine + " 2>&1 > /dev/null") + + +class SippScreenStatParser: + """ Class that parse statistic reported by a sipp instance + report some of the most important value """ + + def __init__(self, filename): + print "Opening " + filename + self.logfile = open(filename, "r").readlines() + print self.logfile[39] + print self.logfile[40] + + def isAnyFailedCall(self): + """ Look for any failed call + Return true if there are failed call, false elsewhere """ + + # TODO: Find a better way to determine which line to consider + if "Failed call" not in self.logfile[40]: + print "Error: Could not find 'Failed call' statistics" + # We consider this as a failure + return True + + return "1" in self.logfile[40] + + def isAnySuccessfulCall(self): + """ Look for any successful call + Return true if there are successful call, false elsewhere """ + + # TODO: Find a better way to determine which line to consider + if "Successful call" not in self.logfile[39]: + print "Error: Could not find 'Successful call' statistics" + return False + + return "1" in self.logfile[39] + + + +def test_result_parsing(): + dirlist = os.listdir("./") + + logfile = [x for x in dirlist if "screen.log" in x] + testResult = SippScreenStatParser(logfile[0]) + + assert(not testResult.isAnyFailedCall()) + + assert(testResult.isAnySuccessfulCall()) diff --git a/tools/dringctrl/test_dring_dbus_interface.py b/tools/dringctrl/test_dring_dbus_interface.py new file mode 100644 index 0000000000..34dfee8a27 --- /dev/null +++ b/tools/dringctrl/test_dring_dbus_interface.py @@ -0,0 +1,412 @@ +#!/usr/bin/env python +# +# Copyright (C) 2012 by the Free Software Foundation, Inc. +# +# Author: Alexandre Savard <alexandre.savard@savoirfairelinux.com> +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +import os +import time +import yaml +import logging +import multiprocessing +from sippwrap import SippWrapper +from sippwrap import SippScreenStatParser +from sflphonectrl import SflPhoneCtrl + +from nose.tools import nottest + +### +### function starting with 'test' are executed. +### + +SCENARIO_PATH = "../sippxml/" + +class SippCtrl: + + def __init__(self): + self.remoteServer = "127.0.0.1" + self.remotePort = str(5062) + self.localInterface = "127.0.0.1" + self.localPort = str(5060) + + def initialize_sipp_registration_instance(self, instance, xmlScenario): + instance.remoteServer = self.remoteServer + instance.remotePort = self.remotePort + instance.localInterface = self.localInterface + instance.localPort = self.localPort + instance.customScenarioFile = SCENARIO_PATH + xmlScenario + instance.numberOfCall = 1 + instance.numberOfSimultaneousCall = 1 + + def initialize_sipp_call_instance(self, instance): + instance.localInterface = self.localInterface + instance.localPort = self.localPort + instance.numberOfCall = 1 + instance.numberOfSimultaneousCall = 1 + instance.enableTraceScreen = True + + def launchSippProcess(self, sippInstance, localPort): + sippInstance.buildCommandLine(localPort) + sippInstance.launch() + + def find_sipp_pid(self): + # Retreive the PID of the last + # The /proc/PID/cmdline contain the command line from + pids = [int(x) for x in os.listdir("/proc") if x.isdigit()] + sippPid = [pid for pid in pids if "sipp" in open("/proc/" + str(pid) + "/cmdline").readline()] + + return sippPid[0] + + def clean_log_directory(self): + dirlist = os.listdir("./") + files = [x for x in dirlist if "screen.log" in x] + for f in files: + os.remove(f) + + def parse_results(self): + dirlist = os.listdir("./") + logfile = [x for x in dirlist if "screen.log" in x] + + fullpath = os.path.dirname(os.path.realpath(__file__)) + "/" + + # there should be only one screen.log file (see clean_log_directory) + resultParser = SippScreenStatParser(fullpath + logfile[0]) + + assert(not resultParser.isAnyFailedCall()) + assert(resultParser.isAnySuccessfulCall()) + +class TestSFLPhoneAccountConfig(SflPhoneCtrl): + """ The test suite for account configuration """ + + def __init__(self): + SflPhoneCtrl.__init__(self) + + self.logger = logging.getLogger("TestSFLPhoneAccountConfig") + filehdlr = logging.FileHandler("/tmp/sflphonedbustest.log") + formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s") + filehdlr.setFormatter(formatter) + self.logger.addHandler(filehdlr) + self.logger.setLevel(logging.INFO) + + + def get_config(self): + """ Parsse configuration file and return a dictionary """ + config = {} + with open("sflphoned.functest.yml","r") as stream: + config = yaml.load(stream) + + return config + + + def get_account_list_from_config(self): + """ Get the accout list from config and add IP2IP """ + + config = self.get_config() + + accounts = config["preferences"]["order"] + accountList = accounts.split('/') + del accountList[len(accountList)-1] + accountList.append("IP2IP") + + return accountList + + + def test_get_account_list(self): + self.logger.info("Test get account list") + + accountList = self.get_account_list_from_config() + + # make sure that the intersection between the list is of same size + accList = self.getAllAccounts() + listIntersection = set(accList) & set(accountList) + assert len(listIntersection) == len(accountList) + + + def test_account_registration(self): + self.logger.info("Test account registration") + accList = [x for x in self.getAllAccounts() if x != "IP2IP"] + for acc in accList: + self.logger.info("Registering account " + acc) + + if self.isAccountEnable(acc): + self.setAccountEnable(acc, False) + time.sleep(2) + + # Account should not be registered + assert self.isAccountRegistered(acc) + + self.setAccountEnable(acc, True) + time.sleep(2) + + assert self.isAccountRegistered(acc) + + + @nottest + def test_get_account_details(self): + self.logger.info("Test account details") + + accList = [x for x in self.getAllAccounts() if x != "IP2IP"] + + config = self.get_config() + + accountDetails = {} + for acc in accList: + accountDetails[acc] = self.getAccountDetails(acc) + + accountConfDetails = {} + for accConf in config["accounts"]: + accountConfDetails[accConf["id"]] = accConf + + + @nottest + def test_add_remove_account(self): + self.logger.info("Test add/remove account") + accountDetails = {} + newAccList = [] + + # consider only true accounts + accList = [x for x in self.getAllAccounts() if x != "IP2IP"] + + # Store the account details localy + for acc in accList: + accountDetails[acc] = self.getAccountDetails(acc) + + # Remove all accounts from sflphone + for acc in accountDetails: + self.removeAccount(acc) + + # Recreate all accounts + for acc in accountDetails: + newAccList.append(self.addAccount(accountDetails[acc])) + + # New accounts should be automatically registered + for acc in newAccList: + assert self.isAccountRegistered(acc) + + + +class TestSFLPhoneRegisteredCalls(SflPhoneCtrl, SippCtrl): + """ The test suite for call interaction """ + + def __init__(self): + SflPhoneCtrl.__init__(self) + SippCtrl.__init__(self) + + self.logger = logging.getLogger("TestSFLPhoneRegisteredCalls") + filehdlr = logging.FileHandler("/tmp/sfltestregisteredcall.log") + formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s") + filehdlr.setFormatter(formatter) + self.logger.addHandler(filehdlr) + self.logger.setLevel(logging.INFO) + self.sippRegistrationInstance = SippWrapper() + self.sippCallInstance = SippWrapper() + + # Make sure the test directory is populated with most recent log files + self.clean_log_directory() + + + def onCallCurrent_cb(self): + """ On incoming call, answer the callm, then hangup """ + + print "Hangup Call with id " + self.currentCallId + self.HangUp(self.currentCallId) + + print "Stopping Thread" + self.stopThread() + + + def onCallRinging_cb(self): + """ Display messages when call is ringing """ + + print "The call is ringing" + + + def onCallFailure_cb(self): + """ If a failure occurs duing the call, just leave the running thread """ + + print "Stopping Thread" + self.stopThread() + + + def test_registered_call(self): + self.logger.info("Test Registered Call") + + # Launch a sipp instance for account registration on asterisk + # this account will then be used to receive call from sflphone + self.initialize_sipp_registration_instance(self.sippRegistrationInstance, "uac_register_no_cvs_300.xml") + regd = multiprocessing.Process(name='sipp1register', target=self.launchSippProcess, + args=(self.sippRegistrationInstance, 5064,)) + regd.start() + + # wait for the registration to complete + regd.join() + + # Launch a sipp instance waiting for a call from previously registered account + self.initialize_sipp_call_instance(self.sippCallInstance) + calld = multiprocessing.Process(name='sipp1call', target=self.launchSippProcess, + args=(self.sippCallInstance, 5064,)) + calld.start() + + # Make sure every account are enabled + accList = [x for x in self.getAllAccounts() if x != "IP2IP"] + for acc in accList: + if not self.isAccountRegistered(acc): + self.setAccountEnable(acc, True) + + # Make a call to the SIPP instance + self.Call("300") + + # Start the threaded loop to handle GLIB cllbacks + self.start() + + # Wait for the sipp instance to dump log files + calld.join() + + self.stopThread() + self.parse_results() + + +class TestSFLPhoneConferenceCalls(SflPhoneCtrl, SippCtrl): + """ Test Conference calls """ + + def __init__(self): + SflPhoneCtrl.__init__(self) + SippCtrl.__init__(self) + + self.logger = logging.getLogger("TestSFLPhoneRegisteredCalls") + filehdlr = logging.FileHandler("/tmp/sfltestregisteredcall.log") + formatter = logging.Formatter("%(asctime)s %(levelname)s %(message)s") + filehdlr.setFormatter(formatter) + self.logger.addHandler(filehdlr) + self.logger.setLevel(logging.INFO) + self.sippRegistrationInstanceA = SippWrapper() + self.sippRegistrationInstanceB = SippWrapper() + self.sippCallInstanceA = SippWrapper() + self.sippCallInstanceB = SippWrapper() + self.localPortCallA = str(5064) + self.localPortCallB = str(5066) + self.callCount = 0 + self.accountCalls = [] + + # Make sure the test directory is populated with most recent log files + # self.clean_log_directory() + + + def onCallCurrent_cb(self): + """ On incoming call, answer the call, then hangup """ + + self.callCount += 1 + + self.accountCalls.append(self.currentCallId) + print "Account List: ", str(self.accountCalls) + + if self.callCount == 2: + self.createConference(self.accountCalls[0], self.accountCalls[1]) + + + def onCallRinging_cb(self): + """ Display messages when call is ringing """ + + print "The call is ringing" + + + def onCallHangup_cb(self, callId): + """ Exit thread when all call are finished """ + + if callId in self.accountCalls: + self.accountCalls.remove(callId) + + self.callCount -= 1 + if self.callCount == 0: + self.stopThread() + + + def onCallFailure_cb(self): + """ If a failure occurs duing the call, just leave the running thread """ + + print "Stopping Thread" + self.stopThread() + + + def onConferenceCreated_cb(self): + """ Called once the conference is created """ + + print "Conference Created ", self.currentConfId + print "Conference Hangup ", self.currentConfId + + self.hangupConference(self.currentConfId) + + + def test_conference_call(self): + self.logger.info("Test Registered Call") + + # launch the sipp instance to register the first participant to astersik + self.initialize_sipp_registration_instance(self.sippRegistrationInstanceA, "uac_register_no_cvs_300.xml") + regd = multiprocessing.Process(name='sipp1register', target=self.launchSippProcess, + args=(self.sippRegistrationInstanceA, 5064,)) + regd.start() + regd.join() + + # launch the sipp instance to register the second participant to asterisk + self.initialize_sipp_registration_instance(self.sippRegistrationInstanceB, "uac_register_no_cvs_400.xml") + regd = multiprocessing.Process(name='sipp2register', target=self.launchSippProcess, + args=(self.sippRegistrationInstanceB, 5066,)) + regd.start() + regd.join() + + # launch the sipp instance waining for call as the first participant + self.initialize_sipp_call_instance(self.sippCallInstanceA) + calldA = multiprocessing.Process(name='sipp1call', target=self.launchSippProcess, + args=(self.sippCallInstanceA, 5064,)) + calldA.start() + + + # launch the sipp instance waiting for call as the second particpant + self.initialize_sipp_call_instance(self.sippCallInstanceB) + calldB = multiprocessing.Process(name='sipp2call', target=self.launchSippProcess, + args=(self.sippCallInstanceB, 5066,)) + calldB.start() + + # make sure every account are enabled + accList = [x for x in self.getAllAccounts() if x != "IP2IP"] + for acc in accList: + if not self.isAccountRegistered(acc): + self.setAccountEnable(acc, True) + + # make a call to the SIPP instance + self.Call("300") + self.Call("400") + + # start the main loop for processing glib callbacks + self.start() + + print "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-" + calldA.join() + print "+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+" + calldB.join() + + print "=====================================================" + + self.stopThread() + self.parse_results() + + +# callInstance = TestSFLPhoneRegisteredCalls() +# callInstance.test_registered_call() + +confInstance = TestSFLPhoneConferenceCalls() +confInstance.test_conference_call() diff --git a/tools/dringctrl/tester.py b/tools/dringctrl/tester.py new file mode 100644 index 0000000000..f1c747569e --- /dev/null +++ b/tools/dringctrl/tester.py @@ -0,0 +1,187 @@ +# +# Copyright (C) 2015 Savoir-Faire Linux Inc. +# Author: Eloi Bail <eloi.bail@savoirfairelinux.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Additional permission under GNU GPL version 3 section 7: +# +# If you modify this program, or any covered work, by linking or +# combining it with the OpenSSL project's OpenSSL library (or a +# modified version of that library), containing parts covered by the +# terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. +# grants you additional permission to convey the resulting work. +# Corresponding Source for a non-source form of such a combination +# shall include the source code for the parts of OpenSSL used as well +# as that of the covered work. +# + +import sys +import os +import time +from threading import Thread +from random import shuffle +from errors import * + + +# Dht Client +SIP_TEST_ACCOUNT = 'sf1' +# Dht Client +DHT_TEST_ACCOUNT = '6c8d591e7c55b348338209bb6f9f638688d50034' +# Ring Client +RING_TEST_ACCOUNT = '192.168.48.125' +# RING_TEST_ACCOUNT = '192.168.48.158' +# Polycom Client +POLYCOM_TEST_ACCOUNT = '192.168.40.38' + +WITH_HOLD = True +WITHOUT_HOLD = False + + +class DRingTester(): + def testConfig(self, ctrl): + print("**[BEGIN] test config") + allCodecs = ctrl.getAllCodecs() + if len(allCodecs) == 0: + print("error no codec on the system") + return 0 + print("**[END] test config") + return 1 + + def checkIP2IPAccount(self, ctrl): + ipAccount = ctrl.getAllAccounts() + print(ipAccount) + if len(ipAccount) == 0: + print("no IP2IP account") + return 0 + return 1 + + def registerAccount(self, ctrl, account): + print("registering:" + account) + ctrl.setAccountRegistered(account, True) + + def setRandomActiveCodecs(self, ctrl, account): + codecList = ctrl.getAllCodecs() + shuffle(codecList) + ctrl.setActiveCodecList(account, str(codecList)[1:-1]) + print("New active list for " + account) + print(ctrl.getActiveCodecs(account)) + + def holdToggle(self, ctrl, callId, delay): + time.sleep(delay) + print("Holding: " + callId) + ctrl.Hold(callId) + time.sleep(delay) + print("UnHolding: " + callId) + ctrl.UnHold(callId) + + def startCallTests(self, ctrl, localAccount, destAccount, nbIteration, + delay, withHold): + print("**[BEGIN] Call Test for account:" + localAccount) + if localAccount == 'IP2IP': + ctrl.setAccount(localAccount) + + count = 0 + while count < nbIteration: + print("[%s/%s]" % (count, nbIteration)) + self.setRandomActiveCodecs(ctrl, localAccount) + + ctrl.Call(destAccount) + callId = ctrl.getAllCalls()[0] + + if withHold: + delayHold = 5 + nbHold = delay / (delayHold * 2) + countHold = 0 + + while countHold < nbHold: + self.holdToggle(ctrl, callId, delayHold) + countHold += 1 + else: + time.sleep(delay) + + ctrl.HangUp(callId) + count += 1 + + print("**[END] Call Test for account:" + localAccount) + + def startSimultaneousCallTests(self, ctrl, localAccount, destAccount, + nbIteration, delay, withHold, nbCalls): + print("**[BEGIN] Call Test for account:" + localAccount) + if localAccount == 'IP2IP': + ctrl.setAccount(localAccount) + + count = 0 + while count < nbIteration: + print("[%s/%s]" % (count, nbIteration)) + self.setRandomActiveCodecs(ctrl, localAccount) + + # do all the calls + currCall = 0 + while currCall < nbCalls: + ctrl.Call(destAccount) + time.sleep(1) + currCall = currCall + 1 + + # hold or wait for each call + if withHold: + delayHold = 5 + nbHold = delay / (delayHold * 2) + countHold = 0 + + while countHold < nbHold: + for callId in ctrl.getAllCalls(): + self.holdToggle(ctrl, callId, delayHold) + countHold = countHold + 1 + + else: + time.sleep(delay) + + # hangup each call + for callId in ctrl.getAllCalls(): + ctrl.HangUp(callId) + + count += 1 + + print("**[END] Call Test for account:" + localAccount) + + def start(self, ctrl): + # testConfig + if not (self.testConfig(ctrl)): + print(("testConfig [KO]")) + return + else: + print(("testConfig [OK]")) + + # RING IP2IP tests + self.startCallTests(ctrl, 'IP2IP', RING_TEST_ACCOUNT, 1000, 20, WITHOUT_HOLD) + self.startCallTests(ctrl, 'IP2IP', RING_TEST_ACCOUNT, 1000, 20, WITH_HOLD) + + # Polycom IP2IP tests + self.startCallTests(ctrl, 'IP2IP', POLYCOM_TEST_ACCOUNT, 1000, 20, WITHOUT_HOLD) + self.startCallTests(ctrl, 'IP2IP', POLYCOM_TEST_ACCOUNT, 1000, 20, WITH_HOLD) + + # SIP tests + sipAccount = ctrl.getAllAccounts('SIP')[0] + self.registerAccount(ctrl, sipAccount) + # self.startSimultaneousCallTests(ctrl, sipAccount, SIP_TEST_ACCOUNT, 10, 40, WITHOUT_HOLD,10) + self.startCallTests(ctrl, sipAccount, SIP_TEST_ACCOUNT, 1000, 20, WITH_HOLD) + self.startCallTests(ctrl, sipAccount, SIP_TEST_ACCOUNT, 1000, 20, WITHOUT_HOLD) + + # DHT tests + dhtAccount = ctrl.getAllAccounts('RING')[0] + self.registerAccount(ctrl, dhtAccount) + self.startCallTests(ctrl, dhtAccount, DHT_TEST_ACCOUNT, 10, 60, WITHOUT_HOLD) + self.startCallTests(ctrl, dhtAccount, DHT_TEST_ACCOUNT, 10, 60, WITH_HOLD) diff --git a/tools/dringctrl/toggle_video_preview.py b/tools/dringctrl/toggle_video_preview.py new file mode 100755 index 0000000000..08ee58e7b7 --- /dev/null +++ b/tools/dringctrl/toggle_video_preview.py @@ -0,0 +1,50 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2015 Savoir-Faire Linux Inc. +# Author: Eloi Bail <eloi.bail@savoirfairelinux.com> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Additional permission under GNU GPL version 3 section 7: +# +# If you modify this program, or any covered work, by linking or +# combining it with the OpenSSL project's OpenSSL library (or a +# modified version of that library), containing parts covered by the +# terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. +# grants you additional permission to convey the resulting work. +# Corresponding Source for a non-source form of such a combination +# shall include the source code for the parts of OpenSSL used as well +# as that of the covered work. +# + +import dbus +import time +import sys +import os +from random import randint + + +class DRingToggleVideo(): + def start(self): + bus = dbus.SessionBus() + + videoControlBus = bus.get_object('org.sflphone.SFLphone', '/org/sflphone/SFLphone/VideoControls') + videoControl = dbus.Interface(videoControlBus, dbus_interface='org.sflphone.SFLphone.VideoControls') + + while True: + time.sleep(2) + videoControl.startCamera() + time.sleep(2) + videoControl.stopCamera() diff --git a/tools/git-gerrit b/tools/git-gerrit new file mode 100755 index 0000000000..846f63331f --- /dev/null +++ b/tools/git-gerrit @@ -0,0 +1,82 @@ +#!/bin/sh +# +# Copyright 2014 Savoir-Faire Linux Inc. +# Author: Vivien Didelot <vivien.didelot@savoirfairelinux.com> +# Licensed under the terms of the GNU GPL v3, or at your option, any later version. +# +# You can install this script as a Git subcommand with one of the 2 methods below: +# +# 1) git config alias.gerrit '!tools/git-gerrit' +# 2) or put this script in your $PATH + +usage () { + echo "Usage:" + echo " $0 <url|open|fetch> [<git-rev>]" + echo " $0 help" + echo + echo "Examples:" + echo " git gerrit open" + echo " git gerrit url HEAD~2" + echo " git gerrit fetch ; git diff FETCH_HEAD" +} + +test $# -ge 1 -a $# -le 2 || { + echo "Invalid syntax." + usage + exit 1 +} + +test "$1" = "help" && { + usage + exit +} + +GERRIT_USER=`git config gerrit.user` +GERRIT_HOST=`git config gerrit.host` +GERRIT_PORT=`git config gerrit.port` + +test -n "$GERRIT_USER" -a -n "$GERRIT_HOST" -a -n "$GERRIT_PORT" || { + echo "You must configure your Gerrit host, e.g.:" + echo + echo " git config gerrit.user vivien" + echo " git config gerrit.host gerrit-ring.savoirfairelinux.com" + echo " git config gerrit.port 29420" + echo + exit 1 +} + +alias _gerrit="ssh -p $GERRIT_PORT $GERRIT_HOST gerrit query" + +CHANGE_ID=`git show --summary --format=%b $2 | perl -n -e '/^Change-Id: (I[0-9a-f]+)$/ && print $1'` + +test -n "$CHANGE_ID" || { + echo "no Change ID!" + exit 1 +} + +test "$1" = "fetch" && { + GERRIT_REMOTE=`git config gerrit.remote` + + test -n "$GERRIT_REMOTE" || { + echo "You must specify the Git remote pointing to Gerrit, e.g.:" + echo + echo " git config gerrit.remote origin" + echo + exit 1 + } + + _gerrit --current-patch-set $CHANGE_ID | awk '/ref:/ { print $2 }' | while read ref ; do + git fetch $GERRIT_REMOTE $ref:$ref + done + exit +} + +URL=`_gerrit $CHANGE_ID | awk '/url:/ { print $2 }'` + +case $1 in + url) echo $URL ;; + open) xdg-open $URL ;; + *) echo "Oops, bad command" ; usage ; exit 1 ;; +esac + +exit diff --git a/tools/git-redmine b/tools/git-redmine new file mode 100755 index 0000000000..d2ca906e0f --- /dev/null +++ b/tools/git-redmine @@ -0,0 +1,58 @@ +#!/bin/sh +# +# Copyright 2014 Savoir-Faire Linux Inc. +# Author: Vivien Didelot <vivien.didelot@savoirfairelinux.com> +# Licensed under the terms of the GNU GPL v3, or at your option, any later version. +# +# You can install this script as a Git subcommand with one of the 2 methods below: +# +# 1) git config alias.redmine '!tools/git-redmine' +# 2) or put this script in your $PATH + +usage () { + echo "Usage:" + echo " $0 <url|open> [<git-rev>]" + echo " $0 help" + echo + echo "Examples:" + echo " git redmine open # open the Redmine ticket related to the current commit" + echo " git redmine url HEAD~2" +} + +test $# -ge 1 -a $# -le 2 || { + echo "Invalid syntax." + usage + exit 1 +} + +test "$1" = "help" && { + usage + exit +} + +REDMINE_URL=`git config redmine.url` + +test -n "$REDMINE_URL" || { + echo "You must configure your Redmine URL, e.g.:" + echo + echo " git config redmine.url https://projects.savoirfairelinux.com" + echo + exit 1 +} + +ISSUE=`git show --summary --format=%b $2 | perl -n -e '/^(Refs|Issue:) #(\d+)$/ && print $2'` + +test -n "$ISSUE" || { + echo "no issue ID!" + exit 1 +} + +URL=$REDMINE_URL/issues/$ISSUE + +case $1 in + url) echo $URL ;; + open) xdg-open $URL ;; + *) echo "Oops, bad command" ; usage ; exit 1 ;; +esac + +exit diff --git a/tools/sflphone-callto b/tools/sflphone-callto new file mode 100755 index 0000000000..c652a776f0 --- /dev/null +++ b/tools/sflphone-callto @@ -0,0 +1,57 @@ +#!/bin/sh +# +# This script can be used as a callto: (or other) protocol handler in +# Mozilla Firefox-based browser. +# In Firefox use Preferences > Applications and set the callto handler +# to this script. + +# The sflphone daemon config file +RESFILE=~/.config/sflphone/sflphoned.yml + +# Parse sflphonedrc and get default account id string +if [ -f "$RESFILE" ]; then + +# Test if a SFLphone client is already open, if not open a new one +# Opening a new client will start sflphoned if not already running +SFLPHONEC=`ps -A | grep ring-client` +if [ "$SFLPHONEC" = "" ]; then + ring-client-gnome& +fi + +# FIXME: this doesn't check if account is enabled, and is unreadable/unmaintainable. +# D-Bus API should be fixed so that we can simply dial and let the daemon worry +# about which account (default account? most recently used?) should place the +# call. +# +# Use first ID +ACCOUNTID=`grep order $RESFILE | sed -e 's/order: IP2IP\///' -e 's/\/.*//' | tr -d ' '` + +else + echo Fatal: Can't find sflphoned.yml config file. + exit 1 +fi + +# Check 1st argument (phone number) +if [ -z $1 ]; then + echo "Error: argument 1 (phone number) not provided." + exit 1 +fi + +# Cleanup destination, keeping numbers only +TO="`echo $1 | sed -e 's/[^0123456789]//g'`" + +# Generate call id. +CALLID=${RANDOM}$$ + +dbus-send \ + --type="method_call" \ + --dest="org.sflphone.SFLphone" \ + "/org/sflphone/SFLphone/CallManager" \ + "org.sflphone.SFLphone.CallManager.placeCall" \ + string:"$ACCOUNTID" \ + string:"$CALLID" \ + string:"$TO" + +exit 0 + +# EOF diff --git a/tools/sippxml/account_uac_send_hangup.xml b/tools/sippxml/account_uac_send_hangup.xml new file mode 100644 index 0000000000..8971769c59 --- /dev/null +++ b/tools/sippxml/account_uac_send_hangup.xml @@ -0,0 +1,147 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> +<!DOCTYPE scenario SYSTEM "sipp.dtd"> + +<!-- This program is free software; you can redistribute it and/or --> +<!-- modify it under the terms of the GNU General Public License as --> +<!-- published by the Free Software Foundation; either version 2 of the --> +<!-- License, or (at your option) any later version. --> +<!-- --> +<!-- This program is distributed in the hope that it will be useful, --> +<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of --> +<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --> +<!-- GNU General Public License for more details. --> +<!-- --> +<!-- You should have received a copy of the GNU General Public License --> +<!-- along with this program; if not, write to the --> +<!-- Free Software Foundation, Inc., --> +<!-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA --> + + +<scenario name="accountcall_client"> + + <pause milliseconds="200"/> + + <send retrans="500"> + <![CDATA[ + + INVITE sip:2000@[remote_ip] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];rport;branch=[branch] + From: <sip:27182@[remote_ip]:[remote_port]>;tag=[call_number] + To: <sip:2000@[remote_ip]:[remote_port]> + Call-ID: [call_id] + CSeq: 3 INVITE + Contact: sip:27182@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Functional Test + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv response="401" auth="true"> + </recv> + + <pause milliseconds="200"/> + + <send> + <![CDATA[ + + ACK sip:27182@[remote_ip] SIP/2.0 + Max-Forwards: 70 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: <sip:27182@[remote_ip]:[remote_port]>;tag=[call_number] + To: <sip:2000@[remote_ip]:[remote_port]> + Call-ID: [call_id] + CSeq: 4 ACK + Subject: Functional Test + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + INVITE sip:2000@[remote_ip] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];rport;branch=[branch] + From: <sip:27182@:[remote_ip]:[remote_port]>;tag=[call_number] + To: <sip:2000@[remote_ip]:[remote_port]> + Call-ID: [call_id] + CSeq: 5 INVITE + Contact: sip:27182@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Functional Test + Content-Type: application/sdp + Content-Length: [len] + [authentication username=27182 password=1234] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv response="100"> + </recv> + + <recv response="180"> + </recv> + + + <recv response="200"> + </recv> + + <send> + <![CDATA[ + + ACK sip:2000@192.168.50.79 SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: <sip:27182@[local_ip]:[local_port]>;tag=[call_number] + To: <sip:2000@192.168.50.79:[remote_port]> + Call-ID: [call_id] + CSeq: 5 ACK + Contact: sip:27182@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Functional Test + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + BYE sip:2000@192.168.50.79 SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: <sip:27182@[local_ip]:[local_port]>;tag=[call_number] + To: <sip:2000@192.168.50.79:[remote_port]> + Call-ID: [call_id] + CSeq: 6 BYE + Contact: sip:27182@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Functional Test + Content-Length: 0 + + ]]> + </send> + + <recv response="200"> + </recv> + + <pause milliseconds="1000"/> + +</scenario> diff --git a/tools/sippxml/account_uac_send_peer_hungup.xml b/tools/sippxml/account_uac_send_peer_hungup.xml new file mode 100644 index 0000000000..716da9dd6d --- /dev/null +++ b/tools/sippxml/account_uac_send_peer_hungup.xml @@ -0,0 +1,155 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> +<!DOCTYPE scenario SYSTEM "sipp.dtd"> + +<!-- This program is free software; you can redistribute it and/or --> +<!-- modify it under the terms of the GNU General Public License as --> +<!-- published by the Free Software Foundation; either version 2 of the --> +<!-- License, or (at your option) any later version. --> +<!-- --> +<!-- This program is distributed in the hope that it will be useful, --> +<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of --> +<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --> +<!-- GNU General Public License for more details. --> +<!-- --> +<!-- You should have received a copy of the GNU General Public License --> +<!-- along with this program; if not, write to the --> +<!-- Free Software Foundation, Inc., --> +<!-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA --> + + +<scenario name="accountcall_client"> + + <pause milliseconds="200"/> + + <send retrans="500"> + <![CDATA[ + + INVITE sip:2000@[remote_ip] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];rport;branch=[branch] + From: <sip:27182@[remote_ip]>;tag=[call_number] + To: <sip:2000@[remote_ip]> + Call-ID: [call_id] + CSeq: 3 INVITE + Contact: sip:27182@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Functional Test + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv response="401" auth="true"> + </recv> + + <pause milliseconds="200"/> + + <send> + <![CDATA[ + + ACK sip:27182@[remote_ip] SIP/2.0 + Max-Forwards: 70 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: <sip:27182@[remote_ip]>;tag=[call_number] + To: <sip:2000@[remote_ip]> + Call-ID: [call_id] + CSeq: 4 ACK + Subject: Functional Test + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + INVITE sip:2000@[remote_ip] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];rport;branch=[branch] + From: <sip:27182@:[remote_ip]>;tag=[call_number] + To: <sip:2000@[remote_ip]> + Call-ID: [call_id] + CSeq: 5 INVITE + Contact: sip:27182@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Functional Test + Content-Type: application/sdp + Content-Length: [len] + [authentication username=27182 password=1234] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv response="100"> + </recv> + + <recv response="180"> + </recv> + + + <recv response="200"> + </recv> + + <send> + <![CDATA[ + + ACK sip:2000@192.168.50.79 SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: <sip:27182@[remote_ip]>;tag=[call_number] + To: <sip:2000@[remote_ip]> + Call-ID: [call_id] + CSeq: 5 ACK + Contact: sip:27182@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Functional Test + Content-Length: 0 + + ]]> + </send> + + <recv request="BYE"> + </recv> + + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <pause milliseconds="1000"/> + +</scenario> diff --git a/tools/sippxml/account_uas_receive_transfer.xml b/tools/sippxml/account_uas_receive_transfer.xml new file mode 100644 index 0000000000..80a875fbf7 --- /dev/null +++ b/tools/sippxml/account_uas_receive_transfer.xml @@ -0,0 +1,101 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> +<!DOCTYPE scenario SYSTEM "sipp.dtd"> + +<!-- This program is free software; you can redistribute it and/or --> +<!-- modify it under the terms of the GNU General Public License as --> +<!-- published by the Free Software Foundation; either version 2 of the --> +<!-- License, or (at your option) any later version. --> +<!-- --> +<!-- This program is distributed in the hope that it will be useful, --> +<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of --> +<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --> +<!-- GNU General Public License for more details. --> +<!-- --> +<!-- You should have received a copy of the GNU General Public License --> +<!-- along with this program; if not, write to the --> +<!-- Free Software Foundation, Inc., --> +<!-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA --> + + +<scenario name="accountcall_client"> + + <recv request="INVITE"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 180 Ringing + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv request="ACK"> + </recv> + + <recv request="INVITE"> + </recv> + + <pause milliseconds="200"> + </recv> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv request="ACK"> + </recv> + + +</scenario> diff --git a/tools/sippxml/account_uas_recv_hangup.xml b/tools/sippxml/account_uas_recv_hangup.xml new file mode 100644 index 0000000000..94bea47042 --- /dev/null +++ b/tools/sippxml/account_uas_recv_hangup.xml @@ -0,0 +1,119 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> +<!DOCTYPE scenario SYSTEM "sipp.dtd"> + +<!-- This program is free software; you can redistribute it and/or --> +<!-- modify it under the terms of the GNU General Public License as --> +<!-- published by the Free Software Foundation; either version 2 of the --> +<!-- License, or (at your option) any later version. --> +<!-- --> +<!-- This program is distributed in the hope that it will be useful, --> +<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of --> +<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --> +<!-- GNU General Public License for more details. --> +<!-- --> +<!-- You should have received a copy of the GNU General Public License --> +<!-- along with this program; if not, write to the --> +<!-- Free Software Foundation, Inc., --> +<!-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA --> + + +<scenario name="accountcall_client"> + + <recv request="INVITE"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 180 Ringing + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <pause milliseconds="200"/> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv request="ACK"> + </recv> + + <recv request="INVITE"> + </recv> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv request="ACK"> + </recv> + + <send retrans="500"> + <![CDATA[ + + BYE sip:2000@192.168.50.79 SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: <sip:27182@[local_ip]:[local_port]>;tag=[call_number] + To: <sip:2000@192.168.50.79:[remote_port]> + Call-ID: [call_id] + CSeq: 6 BYE + Contact: sip:27182@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Functional Test + Content-Length: 0 + + ]]> + </send> + + <recv response="200"> + </recv> + +</scenario> diff --git a/tools/sippxml/account_uas_recv_peer_hungup.xml b/tools/sippxml/account_uas_recv_peer_hungup.xml new file mode 100644 index 0000000000..c91230c7fc --- /dev/null +++ b/tools/sippxml/account_uas_recv_peer_hungup.xml @@ -0,0 +1,127 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> +<!DOCTYPE scenario SYSTEM "sipp.dtd"> + +<!-- This program is free software; you can redistribute it and/or --> +<!-- modify it under the terms of the GNU General Public License as --> +<!-- published by the Free Software Foundation; either version 2 of the --> +<!-- License, or (at your option) any later version. --> +<!-- --> +<!-- This program is distributed in the hope that it will be useful, --> +<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of --> +<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --> +<!-- GNU General Public License for more details. --> +<!-- --> +<!-- You should have received a copy of the GNU General Public License --> +<!-- along with this program; if not, write to the --> +<!-- Free Software Foundation, Inc., --> +<!-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA --> + + +<scenario name="accountcall_client"> + + <recv request="INVITE"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 180 Ringing + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv request="ACK"> + </recv> + + <recv request="INVITE"> + </recv> + + <pause milliseconds="200"> + </recv> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv request="ACK"> + </recv> + + <recv request="BYE"> + </recv> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + +</scenario> diff --git a/tools/sippxml/account_uas_recv_transfered.xml b/tools/sippxml/account_uas_recv_transfered.xml new file mode 100644 index 0000000000..8e67f3cec2 --- /dev/null +++ b/tools/sippxml/account_uas_recv_transfered.xml @@ -0,0 +1,132 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> +<!DOCTYPE scenario SYSTEM "sipp.dtd"> + +<!-- This program is free software; you can redistribute it and/or --> +<!-- modify it under the terms of the GNU General Public License as --> +<!-- published by the Free Software Foundation; either version 2 of the --> +<!-- License, or (at your option) any later version. --> +<!-- --> +<!-- This program is distributed in the hope that it will be useful, --> +<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of --> +<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --> +<!-- GNU General Public License for more details. --> +<!-- --> +<!-- You should have received a copy of the GNU General Public License --> +<!-- along with this program; if not, write to the --> +<!-- Free Software Foundation, Inc., --> +<!-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA --> + + +<scenario name="accountcall_client"> + + <recv request="INVITE"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 180 Ringing + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv request="ACK"> + </recv> + + <recv request="INVITE"> + </recv> + + <pause milliseconds="200"> + </recv> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv request="ACK"> + </recv> + + + <!-- + + <recv request="BYE"> + </recv> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + --> + +</scenario> diff --git a/tools/sippxml/account_uas_register.xml b/tools/sippxml/account_uas_register.xml new file mode 100644 index 0000000000..e4ebb81227 --- /dev/null +++ b/tools/sippxml/account_uas_register.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> +<!DOCTYPE scenario SYSTEM "sipp.dtd"> + +<!-- This program is free software; you can redistribute it and/or --> +<!-- modify it under the terms of the GNU General Public License as --> +<!-- published by the Free Software Foundation; either version 2 of the --> +<!-- License, or (at your option) any later version. --> +<!-- --> +<!-- This program is distributed in the hope that it will be useful, --> +<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of --> +<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --> +<!-- GNU General Public License for more details. --> +<!-- --> +<!-- You should have received a copy of the GNU General Public License --> +<!-- along with this program; if not, write to the --> +<!-- Free Software Foundation, Inc., --> +<!-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA --> + + +<scenario name="accountcall_client"> + + <send retrans="500"> + <![CDATA[ + + REGISTER sip:[remote_ip] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];rport;branch=[branch] + Max-Forwards: 70 + From: <sip:27182@[remote_ip]>;tag=[call_number] + To: <sip:27182@[remote_ip]> + Call-ID: REG///[call_id] + CSeq: 1 REGISTER + Contact: <sip:27182@[local_ip]:[local_port]> + Content-Length: 0 + Expires: 300 + + ]]> + </send> + + <recv response="401" auth="true"> + </recv> + + <send retrans="500"> + <![CDATA[ + + REGISTER sip:[remote_ip] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];rport;branch=[branch] + Max-Forwards: 70 + From: <sip:27182@[remote_ip]>;tag=[call_number] + To: <sip:27182@[remote_ip]> + Call-ID: REG///[call_id] + CSeq: 2 REGISTER + Contact: <sip:27182@[local_ip]:[local_port]> + Content-Length: 0 + Expires: 300 + [authentication username=27182 password=1234] + + ]]> + </send> + + <recv response="200"> + </recv> + +</scenario> diff --git a/tools/sippxml/accountcalluac.xml b/tools/sippxml/accountcalluac.xml new file mode 100644 index 0000000000..a5ba5ac283 --- /dev/null +++ b/tools/sippxml/accountcalluac.xml @@ -0,0 +1,186 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> +<!DOCTYPE scenario SYSTEM "sipp.dtd"> + +<!-- This program is free software; you can redistribute it and/or --> +<!-- modify it under the terms of the GNU General Public License as --> +<!-- published by the Free Software Foundation; either version 2 of the --> +<!-- License, or (at your option) any later version. --> +<!-- --> +<!-- This program is distributed in the hope that it will be useful, --> +<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of --> +<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --> +<!-- GNU General Public License for more details. --> +<!-- --> +<!-- You should have received a copy of the GNU General Public License --> +<!-- along with this program; if not, write to the --> +<!-- Free Software Foundation, Inc., --> +<!-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA --> + + +<scenario name="accountcall_client"> + + <send retrans="500"> + <![CDATA[ + + REGISTER sip:[remote_ip] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];rport;branch=[branch] + Max-Forward: 70 + From: <sip:27182@[remote_ip]:[remote_port]>;tag=[call_number] + To: <sip:27182@[remote_ip]:[remote_port]> + Call-ID: REG///[call_id] + CSeq: 1 REGISTER + Contact: <sip:27182@[remote_ip]:[remote_port]> + Content-Length: 0 + Expires: 300 + + ]]> + </send> + + <recv response="401" auth="true"> + </recv> + + <send retrans="500"> + <![CDATA[ + + REGISTER sip:[remote_ip] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];rport;branch=[branch] + Max-Forwards: 70 + From: <sip:27182@[remote_ip]:[remote_port]>;tag=[call_number] + To: <sip:27182@[remote_ip]:[remote_port]> + Call-ID: REG///[call_id] + CSeq: 2 REGISTER + Contact: <sip:27182@[remote_ip]:[remote_port]> + Content-Length: 0 + Expires: 300 + [authentication username=27182 password=1234] + + ]]> + </send> + + <recv response="200"> + </recv> + + <pause milliseconds="200"/> + + <send retrans="500"> + <![CDATA[ + + INVITE sip:2000@[remote_ip] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];rport;branch=[branch] + From: <sip:27182@[remote_ip]:[remote_port]>;tag=[call_number] + To: <sip:2000@[remote_ip]:[remote_port]> + Call-ID: [call_id] + CSeq: 3 INVITE + Contact: sip:27182@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Functional Test + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv response="401" auth="true"> + </recv> + + <pause milliseconds="200"/> + + <send> + <![CDATA[ + + ACK sip:27182@[remote_ip] SIP/2.0 + Max-Forwards: 70 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: <sip:27182@[remote_ip]:[remote_port]>;tag=[call_number] + To: <sip:2000@[remote_ip]:[remote_port]> + Call-ID: [call_id] + CSeq: 4 ACK + Subject: Functional Test + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + INVITE sip:2000@[remote_ip] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];rport;branch=[branch] + From: <sip:27182@:[remote_ip]:[remote_port]>;tag=[call_number] + To: <sip:2000@[remote_ip]:[remote_port]> + Call-ID: [call_id] + CSeq: 5 INVITE + Contact: sip:27182@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Functional Test + Content-Type: application/sdp + Content-Length: [len] + [authentication username=27182 password=1234] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv response="100"> + </recv> + + <recv response="180"> + </recv> + + + <recv response="200"> + </recv> + + <send> + <![CDATA[ + + ACK sip:2000@192.168.50.79 SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: <sip:27182@[local_ip]:[local_port]>;tag=[call_number] + To: <sip:2000@192.168.50.79:[remote_port]> + Call-ID: [call_id] + CSeq: 5 ACK + Contact: sip:27182@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Functional Test + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + BYE sip:2000@192.168.50.79 SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: <sip:27182@[local_ip]:[local_port]>;tag=[call_number] + To: <sip:2000@192.168.50.79:[remote_port]> + Call-ID: [call_id] + CSeq: 6 BYE + Contact: sip:27182@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Functional Test + Content-Length: 0 + + ]]> + </send> + + + <pause milliseconds="1000"/> + +</scenario> diff --git a/tools/sippxml/accountcalluas.xml b/tools/sippxml/accountcalluas.xml new file mode 100644 index 0000000000..82158ef0a5 --- /dev/null +++ b/tools/sippxml/accountcalluas.xml @@ -0,0 +1,159 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> +<!DOCTYPE scenario SYSTEM "sipp.dtd"> + +<!-- This program is free software; you can redistribute it and/or --> +<!-- modify it under the terms of the GNU General Public License as --> +<!-- published by the Free Software Foundation; either version 2 of the --> +<!-- License, or (at your option) any later version. --> +<!-- --> +<!-- This program is distributed in the hope that it will be useful, --> +<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of --> +<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --> +<!-- GNU General Public License for more details. --> +<!-- --> +<!-- You should have received a copy of the GNU General Public License --> +<!-- along with this program; if not, write to the --> +<!-- Free Software Foundation, Inc., --> +<!-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA --> + + +<scenario name="branch_server"> + <recv request="REGISTER"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + Expires: 300 + + ]]> + </send> + + <!-- Set variable 3 if the ua is of the form ua2... --> + <recv request="INVITE" crlf="true"> + <action> + <ereg regexp="ua2" + search_in="hdr" + header="From: " + assign_to="3"/> + </action> + </recv> + + <!-- send 180 then trying if variable 3 is set --> + <send next="1" test="3"> + <![CDATA[ + + SIP/2.0 180 Ringing + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <!-- if not, send a 403 error then skip to wait for a BYE --> + <send next="2"> + <![CDATA[ + + SIP/2.0 403 Error + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <label id="1"/> + + <send> + <![CDATA[ + + SIP/2.0 100 Trying + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: 136 + + v=0 + o=user1 53655765 2353687637 IN IP4 127.0.0.1 + s=- + t=0 0 + c=IN IP4 [media_ip] + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv request="ACK" + optional="true" + rtd="true" + crlf="true"> + </recv> + + <label id="2"/> + + <recv request="BYE"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <!-- Keep the call open for a while in case the 200 is lost to be --> + <!-- able to retransmit it if we receive the BYE again. --> + <pause milliseconds="4000"/> + + <!-- Definition of the response time repartition table (unit is ms) --> + <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/> + + <!-- Definition of the call length repartition table (unit is ms) --> + <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/> + +</scenario> diff --git a/tools/sippxml/g711a.pcap b/tools/sippxml/g711a.pcap new file mode 100644 index 0000000000000000000000000000000000000000..bafea386f396eacbd14871f0156aed76df0166ee GIT binary patch literal 73184 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 T6i`DDw0|NmEeSMNm{tS;yh}BP literal 0 HcmV?d00001 diff --git a/tools/sippxml/ip2ip_uac_send_hangup.xml b/tools/sippxml/ip2ip_uac_send_hangup.xml new file mode 100644 index 0000000000..5c9673644d --- /dev/null +++ b/tools/sippxml/ip2ip_uac_send_hangup.xml @@ -0,0 +1,98 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> +<!DOCTYPE scenario SYSTEM "sipp.dtd"> + +<!-- This program is free software; you can redistribute it and/or --> +<!-- modify it under the terms of the GNU General Public License as --> +<!-- published by the Free Software Foundation; either version 2 of the --> +<!-- License, or (at your option) any later version. --> +<!-- --> +<!-- This program is distributed in the hope that it will be useful, --> +<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of --> +<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --> +<!-- GNU General Public License for more details. --> +<!-- --> +<!-- You should have received a copy of the GNU General Public License --> +<!-- along with this program; if not, write to the --> +<!-- Free Software Foundation, Inc., --> +<!-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA --> + + +<scenario name="accountcall_client"> + + <send retrans="500"> + <![CDATA[ + + INVITE sip:[remote_ip] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];rport;branch=[branch] + From: <sip:[remote_ip]:[remote_port]>;tag=[call_number] + To: <sip:[remote_ip]:[remote_port]> + Call-ID: [call_id] + CSeq: 1 INVITE + Contact: sip:[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Functional Test + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv response="100" optional="true"> + </recv> + + <recv response="180"> + </recv> + + + <recv response="200"> + <action> + <ereg regexp="tag=.*" search_in="hdr" header="To:" check_it="true" assign_to="1" /> + </action> + </recv> + + <send> + <![CDATA[ + + ACK sip:[remote_ip] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: <sip:[local_ip]:[local_port]>;tag=[call_number] + To: <sip:[remote_ip]:[remote_port]>;tag=[$1] + Call-ID: [call_id] + CSeq: 2 ACK + Contact: sip:[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Functional Test + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + BYE sip:[remote_ip] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: <sip:[local_ip]:[local_port]>;tag=[call_number] + To: <sip:[remote_ip]:[remote_port]>;tag=[$1] + Call-ID: [call_id] + CSeq: 3 BYE + Contact: sip:[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Functional Test + Content-Length: 0 + + ]]> + </send> + + <recv response="200"> + </recv> + +</scenario> diff --git a/tools/sippxml/ip2ip_uac_send_peer_hungup.xml b/tools/sippxml/ip2ip_uac_send_peer_hungup.xml new file mode 100644 index 0000000000..048b0d8a06 --- /dev/null +++ b/tools/sippxml/ip2ip_uac_send_peer_hungup.xml @@ -0,0 +1,92 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> +<!DOCTYPE scenario SYSTEM "sipp.dtd"> + +<!-- This program is free software; you can redistribute it and/or --> +<!-- modify it under the terms of the GNU General Public License as --> +<!-- published by the Free Software Foundation; either version 2 of the --> +<!-- License, or (at your option) any later version. --> +<!-- --> +<!-- This program is distributed in the hope that it will be useful, --> +<!-- but WITHOUT ANY WARRANTY; without even the implied warranty of --> +<!-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the --> +<!-- GNU General Public License for more details. --> +<!-- --> +<!-- You should have received a copy of the GNU General Public License --> +<!-- along with this program; if not, write to the --> +<!-- Free Software Foundation, Inc., --> +<!-- 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA --> + + +<scenario name="accountcall_client"> + + <send retrans="1000"> + <![CDATA[ + + INVITE sip:[remote_ip] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];rport;branch=[branch] + From: <sip:[remote_ip]:[remote_port]>;tag=[call_number] + To: <sip:[remote_ip]:[remote_port]> + Call-ID: [call_id] + CSeq: 1 INVITE + Contact: sip:[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Functional Test + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv response="100" optional="true"> + </recv> + + <recv response="180"> + </recv> + + <recv response="200"> + </recv> + + <send> + <![CDATA[ + + ACK sip:192.168.50.79 SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: <sip:[local_ip]:[local_port]>;tag=[call_number] + To: <sip:[remote_ip]:[remote_port]> + Call-ID: [call_id] + CSeq: 2 ACK + Contact: sip:[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Functional Test + Content-Length: 0 + + ]]> + </send> + + <recv request="BYE"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + +</scenario> diff --git a/tools/sippxml/ip2ip_uas_recv_hangup.xml b/tools/sippxml/ip2ip_uas_recv_hangup.xml new file mode 100644 index 0000000000..45023d08fa --- /dev/null +++ b/tools/sippxml/ip2ip_uas_recv_hangup.xml @@ -0,0 +1,87 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE scenario SYSTEM "sipp.dtd"> + + +<scenario name="Basic UAS responder"> + + <recv request="INVITE" crlf="true"> + </recv> + + + <send> + <![CDATA[ + + SIP/2.0 180 Ringing + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <!-- tag from From: header is required to build the To: header in + -- Bye request. --> + + <recv request="ACK"> + <action> + <ereg regexp="tag=.*" search_in="hdr" header="From:" check_it="true" assign_to="1" /> + </action> + </recv> + + <pause milliseconds="500"/> + + <send retrans="500"> + <![CDATA[ + + BYE sip:[service] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];rport;branch=[branch] + From: <sip:[local_ip]:[local_port]>;tag=[call_number] + To: <sip:[remote_ip]:[remote_port]>;[$1] + [last_Call-ID:] + CSeq: [cseq] BYE + Contact: <sip:test@[local_ip]:[local_port]> + Max-Forwards: 70 + Subject: Functional Test + Content-Length: 0 + + ]]> + </send> + + <recv response="200"> + </recv> + + <pause milliseconds="1000"/> + + + <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/> + + <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/> + +</scenario> diff --git a/tools/sippxml/ip2ip_uas_recv_hold_offhold.xml b/tools/sippxml/ip2ip_uas_recv_hold_offhold.xml new file mode 100644 index 0000000000..bae7eaa2bc --- /dev/null +++ b/tools/sippxml/ip2ip_uas_recv_hold_offhold.xml @@ -0,0 +1,187 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE scenario SYSTEM "sipp.dtd"> + + +<scenario name="UAS HOLD/OFFHOLD"> + + <!-- Receive a new call --> + + <recv request="INVITE" crlf="true"> + <action> + <ereg regexp="sendrecv" search_in="body" check_it="true" assign_to="1"/> + <log message="Media is [$1]"/> + </action> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 180 Ringing + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> +</send> + + <recv request="ACK" optional="true" rtd="true" crlf="true"> + </recv> + + <!-- This call is now on HOLD: sendonly tell to PBX to send music on hold--> + + <recv request="INVITE" crlf="true"> + <action> + <ereg regexp="sendonly" search_in="body" check_it="true" assign_to="2"/> + <log message="Media is [$2]"/> + </action> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 180 Ringing + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv request="ACK" optional="true" rtd="true" crlf="true"> + </recv> + + <!-- OFFHOLD this call --> + + <recv request="INVITE" crlf="true"> + <action> + <ereg regexp="sendrecv" search_in="body" check_it="true" assign_to="3"/> + <log message="Media is [$3]"/> + </action> + </recv> + + + <send> + <![CDATA[ + + SIP/2.0 180 Ringing + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv request="ACK" optional="true" rtd="true" crlf="true"> + </recv> + + <!-- Hangup this call --> + + <recv request="BYE"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <pause milliseconds="4000"/> + + + <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/> + + <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/> + +</scenario> diff --git a/tools/sippxml/ip2ip_uas_recv_peer_hungup.xml b/tools/sippxml/ip2ip_uas_recv_peer_hungup.xml new file mode 100644 index 0000000000..0d9da874ba --- /dev/null +++ b/tools/sippxml/ip2ip_uas_recv_peer_hungup.xml @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE scenario SYSTEM "sipp.dtd"> + + +<scenario name="Basic UAS responder"> + + <recv request="INVITE" crlf="true"> + </recv> + + + <send> + <![CDATA[ + + SIP/2.0 180 Ringing + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> +</send> + + <recv request="ACK" optional="true" rtd="true" crlf="true"> + </recv> + + <recv request="INVITE" optional="true"> + </recv> + + <recv request="BYE"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + + <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/> + + <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/> + +</scenario> diff --git a/tools/sippxml/ip2ipcalluac.xml b/tools/sippxml/ip2ipcalluac.xml new file mode 100644 index 0000000000..7d146cec2b --- /dev/null +++ b/tools/sippxml/ip2ipcalluac.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE scenario SYSTEM "sipp.dtd"> + + +<scenario name="Basic Sipstone UAC"> + + <send retrans="500"> + <![CDATA[ + + INVITE sip:[service]@[remote_ip]:[remote_port] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[call_number] + To: sut <sip:[service]@[remote_ip]:[remote_port]> + Call-ID: [call_id] + CSeq: 1 INVITE + Contact: sip:sipp@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Performance Test + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv response="100" + optional="true"> + </recv> + + <recv response="180" optional="true"> + </recv> + + <recv response="200" rtd="true"> + </recv> + + <send> + <![CDATA[ + + ACK sip:[service]@[remote_ip]:[remote_port] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[call_number] + To: sut <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param] + Call-ID: [call_id] + CSeq: 1 ACK + Contact: sip:sipp@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Performance Test + Content-Length: 0 + + ]]> + </send> + + <pause/> + + <send retrans="500"> + <![CDATA[ + + BYE sip:[service]@[remote_ip]:[remote_port] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[call_number] + To: sut <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param] + Call-ID: [call_id] + CSeq: 2 BYE + Contact: sip:sipp@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Performance Test + Content-Length: 0 + + ]]> + </send> + + <recv response="200" crlf="true"> + </recv> + + <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/> + + <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/> + +</scenario> diff --git a/tools/sippxml/ip2ipcalluas.xml b/tools/sippxml/ip2ipcalluas.xml new file mode 100644 index 0000000000..08d17440ea --- /dev/null +++ b/tools/sippxml/ip2ipcalluas.xml @@ -0,0 +1,80 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE scenario SYSTEM "sipp.dtd"> + + +<scenario name="Basic UAS responder"> + + <recv request="INVITE" crlf="true"> + </recv> + + + <send> + <![CDATA[ + + SIP/2.0 180 Ringing + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv request="ACK" + optional="true" + rtd="true" + crlf="true"> + </recv> + + <recv request="BYE"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <pause milliseconds="4000"/> + + + <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/> + + <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/> + +</scenario> diff --git a/tools/sippxml/simpleServiceRoute.xml b/tools/sippxml/simpleServiceRoute.xml new file mode 100644 index 0000000000..c200717631 --- /dev/null +++ b/tools/sippxml/simpleServiceRoute.xml @@ -0,0 +1,85 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE scenario SYSTEM "sipp.dtd"> + +<scenario name="Basic Sipstone UAC"> + + <send retrans="500"> + <![CDATA[ + + INVITE sip:[service]@[remote_ip]:[remote_port] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[call_number] + To: sut <sip:[service]@[remote_ip]:[remote_port]> + Call-ID: [call_id] + CSeq: 1 INVITE + Contact: sip:sipp@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Performance Test + Content-Type: application/sdp + Content-Length: [len] + Route: <sip:sipp@[local_ip]:[local_port]> + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv response="100" + optional="true"> + </recv> + + <recv response="183" optional="true"> + </recv> + + <recv response="200" rtd="true"> + </recv> + + <send> + <![CDATA[ + + ACK sip:[service]@[remote_ip]:[remote_port] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[call_number] + To: sut <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param] + Call-ID: [call_id] + CSeq: 1 ACK + Contact: sip:sipp@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Performance Test + Content-Length: 0 + + ]]> + </send> + + <pause/> + + <send retrans="500"> + <![CDATA[ + + BYE sip:[service]@[remote_ip]:[remote_port] SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: sipp <sip:sipp@[local_ip]:[local_port]>;tag=[call_number] + To: sut <sip:[service]@[remote_ip]:[remote_port]>[peer_tag_param] + Call-ID: [call_id] + CSeq: 2 BYE + Contact: sip:sipp@[local_ip]:[local_port] + Max-Forwards: 70 + Subject: Performance Test + Content-Length: 0 + + ]]> + </send> + + <recv response="200" crlf="true"> + </recv> + + <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/> + + <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/> + +</scenario> diff --git a/tools/sippxml/sippusage.txt b/tools/sippxml/sippusage.txt new file mode 100644 index 0000000000..f1a5190882 --- /dev/null +++ b/tools/sippxml/sippusage.txt @@ -0,0 +1,460 @@ +Usage: + + sipp remote_host[:remote_port] [options] + + Available options: + + -v : Display version and copyright information. + + -aa : Enable automatic 200 OK answer for INFO, UPDATE and + NOTIFY messages. + + -auth_uri : Force the value of the URI for authentication. + By default, the URI is composed of + remote_ip:remote_port. + + -base_cseq : Start value of [cseq] for each call. + + -bg : Launch SIPp in background mode. + + -bind_local : Bind socket to local IP address, i.e. the local IP + address is used as the source IP address. If SIPp runs + in server mode it will only listen on the local IP + address instead of all IP addresses. + + -buff_size : Set the send and receive buffer size. + + -calldebug_file : Set the name of the call debug file. + + -calldebug_overwrite: Overwrite the call debug file (default true). + + -cid_str : Call ID string (default %u-%p@%s). %u=call_number, + %s=ip_address, %p=process_number, %%=% (in any order). + + -ci : Set the local control IP address + + -cp : Set the local control port number. Default is 8888. + + -d : Controls the length of calls. More precisely, this + controls the duration of 'pause' instructions in the + scenario, if they do not have a 'milliseconds' section. + Default value is 0 and default unit is milliseconds. + + -deadcall_wait : How long the Call-ID and final status of calls should be + kept to improve message and error logs (default unit is + ms). + + -default_behaviors: Set the default behaviors that SIPp will use. Possbile + values are: + - all Use all default behaviors + - none Use no default behaviors + - bye Send byes for aborted calls + - abortunexp Abort calls on unexpected messages + - pingreply Reply to ping requests + If a behavior is prefaced with a -, then it is turned + off. Example: all,-bye + + + -error_file : Set the name of the error log file. + + -error_overwrite : Overwrite the error log file (default true). + + -f : Set the statistics report frequency on screen. Default is + 1 and default unit is seconds. + + -fd : Set the statistics dump log report frequency. Default is + 60 and default unit is seconds. + + -i : Set the local IP address for 'Contact:','Via:', and + 'From:' headers. Default is primary host IP address. + + + -inf : Inject values from an external CSV file during calls into + the scenarios. + First line of this file say whether the data is to be + read in sequence (SEQUENTIAL), random (RANDOM), or user + (USER) order. + Each line corresponds to one call and has one or more + ';' delimited data fields. Those fields can be referred + as [field0], [field1], ... in the xml scenario file. + Several CSV files can be used simultaneously (syntax: + -inf f1.csv -inf f2.csv ...) + + -infindex : file field + Create an index of file using field. For example -inf + users.csv -infindex users.csv 0 creates an index on the + first key. + + -ip_field : Set which field from the injection file contains the IP + address from which the client will send its messages. + If this option is omitted and the '-t ui' option is + present, then field 0 is assumed. + Use this option together with '-t ui' + + -l : Set the maximum number of simultaneous calls. Once this + limit is reached, traffic is decreased until the number + of open calls goes down. Default: + (3 * call_duration (s) * rate). + + -log_file : Set the name of the log actions log file. + + -log_overwrite : Overwrite the log actions log file (default true). + + -lost : Set the number of packets to lose by default (scenario + specifications override this value). + + -rtcheck : Select the retransmisison detection method: full + (default) or loose. + + -m : Stop the test and exit when 'calls' calls are processed + + -mi : Set the local media IP address (default: local primary + host IP address) + + -master : 3pcc extended mode: indicates the master number + + -max_recv_loops : Set the maximum number of messages received read per + cycle. Increase this value for high traffic level. The + default value is 1000. + + -max_sched_loops : Set the maximum number of calsl run per event loop. + Increase this value for high traffic level. The default + value is 1000. + + -max_reconnect : Set the the maximum number of reconnection. + + -max_retrans : Maximum number of UDP retransmissions before call ends on + timeout. Default is 5 for INVITE transactions and 7 for + others. + + -max_invite_retrans: Maximum number of UDP retransmissions for invite + transactions before call ends on timeout. + + -max_non_invite_retrans: Maximum number of UDP retransmissions for non-invite + transactions before call ends on timeout. + + -max_log_size : What is the limit for error and message log file sizes. + + -max_socket : Set the max number of sockets to open simultaneously. + This option is significant if you use one socket per + call. Once this limit is reached, traffic is distributed + over the sockets already opened. Default value is 50000 + + -mb : Set the RTP echo buffer size (default: 2048). + + -message_file : Set the name of the message log file. + + -message_overwrite: Overwrite the message log file (default true). + + -mp : Set the local RTP echo port number. Default is 6000. + + -nd : No Default. Disable all default behavior of SIPp which + are the following: + - On UDP retransmission timeout, abort the call by + sending a BYE or a CANCEL + - On receive timeout with no ontimeout attribute, abort + the call by sending a BYE or a CANCEL + - On unexpected BYE send a 200 OK and close the call + - On unexpected CANCEL send a 200 OK and close the call + - On unexpected PING send a 200 OK and continue the call + - On any other unexpected message, abort the call by + sending a BYE or a CANCEL + + + -nr : Disable retransmission in UDP mode. + + -nostdin : Disable stdin. + + + -p : Set the local port number. Default is a random free port + chosen by the system. + + -pause_msg_ign : Ignore the messages received during a pause defined in + the scenario + + -periodic_rtd : Reset response time partition counters each logging + interval. + + -plugin : Load a plugin. + + -r : Set the call rate (in calls per seconds). This value can + bechanged during test by pressing '+','_','*' or '/'. + Default is 10. + pressing '+' key to increase call rate by 1 * + rate_scale, + pressing '-' key to decrease call rate by 1 * + rate_scale, + pressing '*' key to increase call rate by 10 * + rate_scale, + pressing '/' key to decrease call rate by 10 * + rate_scale. + If the -rp option is used, the call rate is calculated + with the period in ms given by the user. + + -rp : Specify the rate period for the call rate. Default is 1 + second and default unit is milliseconds. This allows + you to have n calls every m milliseconds (by using -r n + -rp m). + Example: -r 7 -rp 2000 ==> 7 calls every 2 seconds. + -r 10 -rp 5s => 10 calls every 5 seconds. + + -rate_scale : Control the units for the '+', '-', '*', and '/' keys. + + -rate_increase : Specify the rate increase every -fd units (default is + seconds). This allows you to increase the load for each + independent logging period. + Example: -rate_increase 10 -fd 10s + ==> increase calls by 10 every 10 seconds. + + -rate_max : If -rate_increase is set, then quit after the rate + reaches this value. + Example: -rate_increase 10 -rate_max 100 + ==> increase calls by 10 until 100 cps is hit. + + -no_rate_quit : If -rate_increase is set, do not quit after the rate + reaches -rate_max. + + -recv_timeout : Global receive timeout. Default unit is milliseconds. If + the expected message is not received, the call times out + and is aborted. + + -send_timeout : Global send timeout. Default unit is milliseconds. If a + message is not sent (due to congestion), the call times + out and is aborted. + + -sleep : How long to sleep for at startup. Default unit is + seconds. + + -reconnect_close : Should calls be closed on reconnect? + + -reconnect_sleep : How long (in milliseconds) to sleep between the close and + reconnect? + + -ringbuffer_files: How many error/message files should be kept after + rotation? + + -ringbuffer_size : How large should error/message files be before they get + rotated? + + -rsa : Set the remote sending address to host:port for sending + the messages. + + -rtp_echo : Enable RTP echo. RTP/UDP packets received on port defined + by -mp are echoed to their sender. + RTP/UDP packets coming on this port + 2 are also echoed + to their sender (used for sound and video echo). + + -rtt_freq : freq is mandatory. Dump response times every freq calls + in the log file defined by -trace_rtt. Default value is + 200. + + -s : Set the username part of the resquest URI. Default is + 'service'. + + -sd : Dumps a default scenario (embeded in the sipp executable) + + -sf : Loads an alternate xml scenario file. To learn more + about XML scenario syntax, use the -sd option to dump + embedded scenarios. They contain all the necessary help. + + -shortmessage_file: Set the name of the short message log file. + + -shortmessage_overwrite: Overwrite the short message log file (default true). + + -oocsf : Load out-of-call scenario. + + -oocsn : Load out-of-call scenario. + + -skip_rlimit : Do not perform rlimit tuning of file descriptor limits. + Default: false. + + -slave : 3pcc extended mode: indicates the slave number + + -slave_cfg : 3pcc extended mode: indicates the file where the master + and slave addresses are stored + + -sn : Use a default scenario (embedded in the sipp executable). + If this option is omitted, the Standard SipStone UAC + scenario is loaded. + Available values in this version: + + - 'uac' : Standard SipStone UAC (default). + - 'uas' : Simple UAS responder. + - 'regexp' : Standard SipStone UAC - with regexp and + variables. + - 'branchc' : Branching and conditional branching in + scenarios - client. + - 'branchs' : Branching and conditional branching in + scenarios - server. + + Default 3pcc scenarios (see -3pcc option): + + - '3pcc-C-A' : Controller A side (must be started after + all other 3pcc scenarios) + - '3pcc-C-B' : Controller B side. + - '3pcc-A' : A side. + - '3pcc-B' : B side. + + + -stat_delimiter : Set the delimiter for the statistics file + + -stf : Set the file name to use to dump statistics + + -t : Set the transport mode: + - u1: UDP with one socket (default), + - un: UDP with one socket per call, + - ui: UDP with one socket per IP address The IP + addresses must be defined in the injection file. + - t1: TCP with one socket, + - tn: TCP with one socket per call, + - l1: TLS with one socket, + - ln: TLS with one socket per call, + - c1: u1 + compression (only if compression plugin + loaded), + - cn: un + compression (only if compression plugin + loaded). This plugin is not provided with sipp. + + + -timeout : Global timeout. Default unit is seconds. If this option + is set, SIPp quits after nb units (-timeout 20s quits + after 20 seconds). + + -timeout_error : SIPp fails if the global timeout is reached is set + (-timeout option required). + + -timer_resol : Set the timer resolution. Default unit is milliseconds. + This option has an impact on timers precision.Small + values allow more precise scheduling but impacts CPU + usage.If the compression is on, the value is set to + 50ms. The default value is 10ms. + + -sendbuffer_warn : Produce warnings instead of errors on SendBuffer + failures. + + -trace_msg : Displays sent and received SIP messages in <scenario file + name>_<pid>_messages.log + + -trace_shortmsg : Displays sent and received SIP messages as CSV in + <scenario file name>_<pid>_shortmessages.log + + -trace_screen : Dump statistic screens in the + <scenario_name>_<pid>_ s.log file when quitting + SIPp. Useful to get a final status report in background + mode (-bg option). + + -trace_err : Trace all unexpected messages in <scenario file + name>_<pid>_errors.log. + + -trace_calldebug : Dumps debugging information about aborted calls to + <scenario_name>_<pid>_calldebug.log file. + + -trace_stat : Dumps all statistics in <scenario_name>_<pid>.csv file. + Use the '-h stat' option for a detailed description of + the statistics file content. + + -trace_counts : Dumps individual message counts in a CSV file. + + -trace_rtt : Allow tracing of all response times in <scenario file + name>_<pid>_rtt.csv. + + -trace_logs : Allow tracing of <log> actions in <scenario file + name>_<pid>_logs.log. + + -users : Instead of starting calls at a fixed rate, begin 'users' + calls at startup, and keep the number of calls constant. + + -watchdog_interval: Set gap between watchdog timer firings. Default is 400. + + -watchdog_reset : If the watchdog timer has not fired in more than this + time period, then reset the max triggers counters. + Default is 10 minutes. + + -watchdog_minor_threshold: If it has been longer than this period between watchdog + executions count a minor trip. Default is 500. + + -watchdog_major_threshold: If it has been longer than this period between watchdog + executions count a major trip. Default is 3000. + + -watchdog_major_maxtriggers: How many times the major watchdog timer can be tripped + before the test is terminated. Default is 10. + + -watchdog_minor_maxtriggers: How many times the minor watchdog timer can be tripped + before the test is terminated. Default is 120. + + -ap : Set the password for authentication challenges. Default + is 'password + + -tls_cert : Set the name for TLS Certificate file. Default is + 'cacert.pem + + -tls_key : Set the name for TLS Private Key file. Default is + 'cakey.pem' + + -tls_crl : Set the name for Certificate Revocation List file. If not + specified, X509 CRL is not activated. + + -3pcc : Launch the tool in 3pcc mode ("Third Party call + control"). The passed ip address is depending on the + 3PCC role. + - When the first twin command is 'sendCmd' then this is + the address of the remote twin socket. SIPp will try to + connect to this address:port to send the twin command + (This instance must be started after all other 3PCC + scenarii). + Example: 3PCC-C-A scenario. + - When the first twin command is 'recvCmd' then this is + the address of the local twin socket. SIPp will open + this address:port to listen for twin command. + Example: 3PCC-C-B scenario. + + -tdmmap : Generate and handle a table of TDM circuits. + A circuit must be available for the call to be placed. + Format: -tdmmap {0-3}{99}{5-8}{1-31} + + -key : keyword value + Set the generic parameter named "keyword" to "value". + + -set : variable value + Set the global variable parameter named "variable" to + "value". + + -dynamicStart : variable value + Set the start offset of dynamic_id varaiable + + -dynamicMax : variable value + Set the maximum of dynamic_id variable + + -dynamicStep : variable value + Set the increment of dynamic_id variable + +Signal handling: + + SIPp can be controlled using posix signals. The following signals + are handled: + USR1: Similar to press 'q' keyboard key. It triggers a soft exit + of SIPp. No more new calls are placed and all ongoing calls + are finished before SIPp exits. + Example: kill -SIGUSR1 732 + USR2: Triggers a dump of all statistics screens in + <scenario_name>_<pid>_screens.log file. Especially useful + in background mode to know what the current status is. + Example: kill -SIGUSR2 732 + +Exit code: + + Upon exit (on fatal error or when the number of asked calls (-m + option) is reached, sipp exits with one of the following exit + code: + 0: All calls were successful + 1: At least one call failed + 97: exit on internal command. Calls may have been processed + 99: Normal exit without calls processed + -1: Fatal error + + +Example: + + Run sipp with embedded server (uas) scenario: + ./sipp -sn uas + On the same host, run sipp with embedded client (uac) scenario + ./sipp -sn uac 127.0.0.1 diff --git a/tools/sippxml/tempscript.sh b/tools/sippxml/tempscript.sh new file mode 100644 index 0000000000..090a058ed8 --- /dev/null +++ b/tools/sippxml/tempscript.sh @@ -0,0 +1,5 @@ +SERVERPORT=5064 + +sipp -sf account_uas_register_bis.xml 192.168.50.79 -i 192.168.50.182 -p ${SERVERPORT} -l 1 -m 1 + +sipp -sf account_uas_receive_transfer.xml 192.168.50.79 -i 192.168.50.182 -p ${SERVERPORT} -l 1 \ No newline at end of file diff --git a/tools/sippxml/testEarlyMedia.xml b/tools/sippxml/testEarlyMedia.xml new file mode 100644 index 0000000000..7909bebecd --- /dev/null +++ b/tools/sippxml/testEarlyMedia.xml @@ -0,0 +1,107 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> <!DOCTYPE scenario SYSTEM "sipp.dtd"> + +<!-- sudo sipp -sf testEarlyMedia.xml 127.0.0.1 -i 127.0.0.1 -p 5062 -l 1 -m 1 -mp 6000 --> + +<scenario name="Basic UAS responder"> + + <recv request="INVITE" crlf="true"> + </recv> + + + <send> + <![CDATA[ + + SIP/2.0 100 Ringing + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <send> + <![CDATA[ + + SIP/2.0 183 Trying + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <!-- Play a pre-recorded PCAP file (RTP stream) --> + <nop> + <action> + <exec play_pcap_audio="g711a.pcap"/> + </action> + </nop> + + <!-- Pause 8 seconds, which is approximately the duration of the --> + <!-- PCAP file --> + <pause milliseconds="8000"/> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: [len] + + ]]> + </send> + + <recv request="ACK" + optional="true" + rtd="true" + crlf="true"> + </recv> + + <recv request="BYE"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <pause milliseconds="4000"/> + + + <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/> + + <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/> + +</scenario> diff --git a/tools/sippxml/testsuiteuac.sh b/tools/sippxml/testsuiteuac.sh new file mode 100644 index 0000000000..5635003582 --- /dev/null +++ b/tools/sippxml/testsuiteuac.sh @@ -0,0 +1,383 @@ +#!/bin/bash + + +LOCALPORT=5062 +LOCALIP_lo=127.0.0.1 +LOCALIP_eth0=192.168.50.182 + +REMOTEADDR_lo=127.0.0.1:5060 +REMOTEADDR_ast=192.168.50.79 + +# SCENARIO 1 Test 1 +function test_ip2ip_send_hangup { + + # start sflphoned + # /usr/lib/sflphone/sflphoned& + + # start sipp server to receive calls from sflphone + sipp -sf ip2ip_uas_recv_peer_hungup.xml ${REMOTEADDR_lo} -i ${LOCALIP_lo} -p ${LOCALPORT} + + # wait some time to make sure sflphoned is started + # sleep 1; + + # run python client and script to make calls + # python ../tools/pysflphone/pysflphone_testdbus.py & + + # kill every one + # bashtrap +} + +# SCENARIO 1 Test 2 +function test_ip2ip_send_peer_hungup { + + # start sflphoned + # /usr/lib/sflphone/sflphoned& + + # start sipp server to receive calls from sflphone and then hangup + sipp -sf ip2ip_uas_recv_hangup.xml ${REMOTEADDR_lo} -s ${REMOTEADDR_lo} -i ${LOCALIP_lo} -p ${LOCALPORT} + + # wait some time to make sure sflphoned is started + # sleep 1; + + # run python client and script to make calls + # python ../tools/pysflphone/pysflphone_testdbus.py & + + # kill every one + bashtrap +} + + +# SCENARIO 1 Test 3 +function test_ip2ip_recv_hangup { + + # start sflphoned + # /usr/lib/sflphone/sflphoned& + + # wait some time to make sure sflphoned is started + # sleep 1; + + # python ../tools/pysflphone/pysflphone_testdbus.py & + + # wait some time to make sure client is bound + # sleep 1; + + # start sipp client and send calls + sipp -sf ip2ip_uac_send_peer_hungup.xml ${REMOTEADDR_lo} -i ${LOCALIP_lo} -p ${LOCALPORT} -l 1 -m 10 + + # kill every one + # bashtrap +} + + +# SCENARIO 1 Test 4 +function test_ip2ip_recv_peer_hungup { + + # start sflphoned + # /usr/lib/sflphone/sflphoned& + + # wait some time to make sure sflphoned is started + # sleep 1; + + # python ../tools/pysflphone/pysflphone_testdbus.py & + + # wait some time to make sure client is bound + # sleep 1; + + # start sipp client and send calls + sipp -sf ip2ip_uac_send_hangup.xml ${REMOTEADDR_lo} -i ${LOCALIP_lo} -p ${LOCALPORT} -l 1 -m 10 + + # kill every one + # bashtrap +} + + +# SCENARIO 2 Test 1 +function test_account_send_hangup { + + # start sflphoned + # /usr/lib/sflphone/sflphoned& + + # wait some time to make sure sflphoned is started + # sleep 1; + + # python ../tools/pysflphone/pysflphone_testdbus.py & + + # wait some time to make sure client is bound + # sleep 1; + + # process only one registration + sipp -sf account_uas_register.xml ${REMOTEADDR_ast} -i ${LOCALIP_eth0} -p ${LOCALPORT} -l 1 -m 1 + + # start sipp client and send calls + sipp -sf account_uas_recv_peer_hungup.xml ${REMOTEADDR_ast} -i ${LOCALIP_eth0} -p ${LOCALPORT} -l 1 + + # kill every one + # bashtrap +} + +# SCENARIO 2 Test 2 +function test_account_send_peer_hungup { + + # start sflphoned + # /usr/lib/sflphone/sflphoned& + + # wait some time to make sure sflphoned is started + # sleep 1; + + # python ../tools/pysflphone/pysflphone_testdbus.py & + + # wait some time to make sure client is bound + # sleep 1; + + # process only one registration + sipp -sf account_uas_register.xml ${REMOTEADDR_ast} -i ${LOCALIP_eth0} -p ${LOCALPORT} -l 1 -m 1 + + # start sipp client and send calls + sipp -sf account_uas_recv_hangup.xml ${REMOTEADDR_ast} -i ${LOCALIP_eth0} -p ${LOCALPORT} -l 1 + + # kill every one + # bashtrap +} + +# SCENARIO 2 Test 3 +function test_account_recv_hangup { + + # start sflphoned + # /usr/lib/sflphone/sflphoned& + + # wait some time to make sure sflphoned is started + # sleep 1; + + # python ../tools/pysflphone/pysflphone_testdbus.py & + + # wait some time to make sure client is bound + # sleep 1; + + # process only one registration + sipp -sf account_uas_register.xml ${REMOTEADDR_ast} -i ${LOCALIP_eth0} -p ${LOCALPORT} -l 1 -m 1 + + # start sipp client and send calls + sipp -sf account_uac_send_peer_hungup.xml ${REMOTEADDR_ast} -i ${LOCALIP_eth0} -p ${LOCALPORT} -l 1 -m 10 + # kill every one + # bashtrap +} + +# SCENARIO 2 Test 4 +function test_account_recv_peer_hungup { + + # start sflphoned + # /usr/lib/sflphone/sflphoned& + + # wait some time to make sure sflphoned is started + # sleep 1; + + # python ../tools/pysflphone/pysflphone_testdbus.py & + + # wait some time to make sure client is bound + # sleep 1; + + # process only one registration + sipp -sf account_uas_register.xml ${REMOTEADDR_ast} -i ${LOCALIP_eth0} -p ${LOCALPORT} -l 1 -m 1 + + # start sipp client and send calls + sipp -sf account_uac_send_hangup.xml ${REMOTEADDR_ast} -i ${LOCALIP_eth0} -p ${LOCALPORT} -l 1 -m 10 + + # kill every one + # bashtrap +} + +# SCENARIO 3 Test 1 +function test_ip2ip_send_hold_offhold { + + # start sflphoned + # /usr/lib/sflphone/sflphoned& + + # wait some time to make sure sflphoned is started + # sleep 1; + + # python ../tools/pysflphone/pysflphone_testdbus.py & + + # wait some time to make sure client is bound + # sleep 1; + + # start sipp client and send calls + sipp -sf ip2ip_uas_recv_hold_offhold.xml ${REMOTEADDR_lo} -i ${LOCALIP_lo} -p ${LOCALPORT} + # kill every one + # bashtrap +} + +# SCENARIO 4 Test 1 +function test_account_send_transfer { + + # start sflphoned + # /usr/lib/sflphone/sflphoned& + + # wait some time to make sure sflphoned is started + # sleep 1; + + # python ../tools/pysflphone/pysflphone_testdbus.py & + + # wait some time to make sure client is bound + # sleep 1; + + # process only one registration + sipp -sf account_uas_register.xml ${REMOTEADDR_ast} -i ${LOCALIP_eth0} -p ${LOCALPORT} -l 1 -m 1 + + # start sipp client and send calls + sipp -sf account_uas_recv_transfered.xml ${REMOTEADDR_ast} -i ${LOCALIP_eth0} -p ${LOCALPORT} -l 1 + + # kill every one + # bashtrap +} + + +# SCENARIO 5 Test 1 +function test_ip2ip_send_refused { + + # start sflphoned + # /usr/lib/sflphone/sflphoned& + + # start sipp server to receive calls from sflphone and then hangup + sipp -sf ip2ip_uac_send_refused.xml ${REMOTEADDR_lo} -s ${REMOTEADDR_lo} -i ${LOCALIP_lo} -p ${LOCALPORT} -l 1 + + # wait some time to make sure sflphoned is started + # sleep 1; + + # run python client and script to make calls + # python ../tools/pysflphone/pysflphone_testdbus.py & + + # kill every one + bashtrap +} + + +# SCENARIO 6 Test 1 +function test_mult_ip2ip_send_hangup { + + # start sflphoned + # /usr/lib/sflphone/sflphoned& + + # start sipp server to receive calls from sflphone + sipp -sf ip2ip_uac_send_hangup.xml 127.0.0.1:5060 -i 127.0.0.1 -p 5062 -l 1 -m 10 + sipp -sf ip2ip_uac_send_hangup.xml 127.0.0.1:5060 -i 127.0.0.1 -p 5064 -l 1 -m 10 + sipp -sf ip2ip_uac_send_hangup.xml 127.0.0.1:5060 -i 127.0.0.1 -p 5066 -l 1 -m 10 + # sipp -sf ip2ip_uac_send_hangup.xml ${REMOTEADDR_lo} -i ${LOCALIP_lo} -p ${LOCALPORT} -l 1 -m 10 + + # wait some time to make sure sflphoned is started + # sleep 1; + + # run python client and script to make calls + # python ../tools/pysflphone/pysflphone_testdbus.py & + + # kill every one + # bashtrap +} + + +# SCENARIO 6 Test 2 +function test_mult_ip2ip_recv_peer_hangup { + + # start sflphoned + # /usr/lib/sflphone/sflphoned& + + # start sipp server to receive calls from sflphone + sipp -sf ip2ip_uac_send_hangup.xml 127.0.0.1 -i 127.0.0.1:5060 -p 5062 + sipp -sf ip2ip_uac_send_hangup.xml 127.0.0.1 -i 127.0.0.1:5060 -p 5064 + sipp -sf ip2ip_uac_send_hangup.xml 127.0.0.1 -i 127.0.0.1:5060 -p 5066 + # sipp -sf ip2ip_uas_recv_peer_hungup.xml ${REMOTEADDR_lo} -i ${LOCALIP_lo} -p ${LOCALPORT} + + # wait some time to make sure sflphoned is started + # sleep 1; + + # run python client and script to make calls + # python ../tools/pysflphone/pysflphone_testdbus.py & + + # kill every one + # bashtrap +} + + +# function called if CTRL-C detected +bashtrap() +{ + killall sipp + killall sflphoned +} + + +# ============================ Test Suite ============================ + + + +# SCENARIO 1: (IP2IP) Normal flow calls + +# Test 1: - Send an IP2IP call +# - Hangup +test_ip2ip_send_hangup + +# Test 2: - Send an IP2IP call +# - Peer Hangup +# test_ip2ip_send_peer_hungup + +# Test 3: - Receive an IP2IP call +# - Hangup +# test_ip2ip_recv_hangup + +# Test 4: - Receive an IP2IP call +# - Peer Hangup +# test_ip2ip_recv_peer_hungup + + + +# SCENARIO 2: (ACCOUNT) Normal flow calls + +# Test 1: - Send an ACCOUNT call +# - Hangup +# test_account_send_hangup + +# Test 2: - Send an ACCOUNT call +# - Peer Hangup +# test_account_send_peer_hungup + +# Test 3: - Receive an ACCOUNT call +# - Hangup +# test_account_recv_hangup + +# Test 4: - Receive an ACCOUNT call +# - Peer Hangup +# test_account_recv_peer_hungup + + + +# SCENARIO 3: Hold/offHold calls (Account) + +# Test 1: - Send an IP2IP call +# - Put this call on HOLD +# - Off HOLD this call +# - Hangup +# test_ip2ip_send_hold_offhold + + + +# SCENARIO 4: Transfer calls (Account) + +# Test 1: - Send an IP2IP call +# - Transfer this call to another sipp instance +# - Hangup +# test_account_send_transfer + + +#SCENARIO 5: Refuse call (IP2IP) + +# Test 1: - Receive a call +# - Refuse (hangup without answer) +# test_ip2ip_send_refused + + +#SCENARIO 6: Multiple simultaneous Call + +# Test 1: - +# test_mult_ip2ip_send_hangup + +# Test 2: - +# test_mult_ip2ip_recv_peer_hangup \ No newline at end of file diff --git a/tools/sippxml/uac_register_diff_contact.xml b/tools/sippxml/uac_register_diff_contact.xml new file mode 100644 index 0000000000..c07d8a05f8 --- /dev/null +++ b/tools/sippxml/uac_register_diff_contact.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="ISO-8859-2" ?> + +<!-- Use with CSV file struct like: 3000;192.168.1.106;[authentication username=3000 password=3000]; + (user part of uri, server address, auth tag in each line) +--> + +<scenario name="register_client"> + <send retrans="500"> + <![CDATA[ + + REGISTER sip:127.0.0.1 SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: <sip:[field0]@[field1]>;tag=[call_number] + To: <sip:[field0]@[field1]> + Call-ID: [call_id] + CSeq: [cseq] REGISTER + Contact: sip:[field0]@[local_ip]:[local_port] + Max-Forwards: 10 + Expires: 120 + User-Agent: SIPp/Win32 + Content-Length: 0 + + ]]> + </send> + + <!-- asterisk --> + <recv response="200"> + <action> + <ereg regexp=".*" search_in="hdr" header="Contact:" check_it="true" assign_to="1" /> + </action> + </recv> + + <send retrans="500"> + <![CDATA[ + + REGISTER sip:127.0.0.1 SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: <sip:[field0]@[field1]>;tag=[call_number] + To: <sip:[field0]@[field1]> + Call-ID: [call_id] + CSeq: [cseq] REGISTER + Contact: [$1] + Max-Forwards: 10 + Expires: 120 + User-Agent: SIPp/Win32 + Content-Length: 0 + + ]]> + </send> + + <recv response="200"> + </recv> + + +</scenario> diff --git a/tools/sippxml/uac_register_no_cvs_300.xml b/tools/sippxml/uac_register_no_cvs_300.xml new file mode 100644 index 0000000000..f22ae377a8 --- /dev/null +++ b/tools/sippxml/uac_register_no_cvs_300.xml @@ -0,0 +1,110 @@ +<?xml version="1.0" encoding="ISO-8859-2" ?> + +<!-- Use with CSV file struct like: 3000;192.168.1.106;[authentication username=3000 password=3000]; + (user part of uri, server address, auth tag in each line) +--> + +<scenario name="register_client"> + <send retrans="500"> + <![CDATA[ + + REGISTER sip:127.0.0.1 SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: <sip:300@127.0.0.1>;tag=[call_number] + To: <sip:300@127.0.0.1> + Call-ID: [call_id] + CSeq: [cseq] REGISTER + Contact: sip:300@[local_ip]:[local_port] + Max-Forwards: 10 + Expires: 120 + User-Agent: SIPp/Win32 + Content-Length: 0 + + ]]> + </send> + + <!-- asterisk --> + <recv response="200"> + <!-- + <action> + <ereg regexp=".*" search_in="hdr" header="Contact:" check_it="true" assign_to="1" /> + </action> + --> + </recv> + + <!-- + <recv request="INVITE" crlf="true"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 180 Ringing + [last_Via:] + [last_From:] + [last_To:];tag=[pid]SIPpTag01[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[pid]SIPpTag01[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv request="ACK" + optional="true" + rtd="true" + crlf="true"> + </recv> + + <recv request="BYE"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <timewait milliseconds="4000"/> + + + <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/> + + <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/> + --> + +</scenario> diff --git a/tools/sippxml/uac_register_no_cvs_400.xml b/tools/sippxml/uac_register_no_cvs_400.xml new file mode 100644 index 0000000000..ccceacdf77 --- /dev/null +++ b/tools/sippxml/uac_register_no_cvs_400.xml @@ -0,0 +1,110 @@ +<?xml version="1.0" encoding="ISO-8859-2" ?> + +<!-- Use with CSV file struct like: 3000;192.168.1.106;[authentication username=3000 password=3000]; + (user part of uri, server address, auth tag in each line) +--> + +<scenario name="register_client"> + <send retrans="500"> + <![CDATA[ + + REGISTER sip:127.0.0.1 SIP/2.0 + Via: SIP/2.0/[transport] [local_ip]:[local_port];branch=[branch] + From: <sip:300@127.0.0.1>;tag=[call_number] + To: <sip:400@127.0.0.1> + Call-ID: [call_id] + CSeq: [cseq] REGISTER + Contact: sip:300@[local_ip]:[local_port] + Max-Forwards: 10 + Expires: 120 + User-Agent: SIPp/Win32 + Content-Length: 0 + + ]]> + </send> + + <!-- asterisk --> + <recv response="200"> + <!-- + <action> + <ereg regexp=".*" search_in="hdr" header="Contact:" check_it="true" assign_to="1" /> + </action> + --> + </recv> + + <!-- + <recv request="INVITE" crlf="true"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 180 Ringing + [last_Via:] + [last_From:] + [last_To:];tag=[pid]SIPpTag01[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <send retrans="500"> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[pid]SIPpTag01[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Type: application/sdp + Content-Length: [len] + + v=0 + o=user1 53655765 2353687637 IN IP[local_ip_type] [local_ip] + s=- + c=IN IP[media_ip_type] [media_ip] + t=0 0 + m=audio [media_port] RTP/AVP 0 + a=rtpmap:0 PCMU/8000 + + ]]> + </send> + + <recv request="ACK" + optional="true" + rtd="true" + crlf="true"> + </recv> + + <recv request="BYE"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:[local_ip]:[local_port];transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <timewait milliseconds="4000"/> + + + <ResponseTimeRepartition value="10, 20, 30, 40, 50, 100, 150, 200"/> + + <CallLengthRepartition value="10, 50, 100, 500, 1000, 5000, 10000"/> + --> + +</scenario> diff --git a/tools/sippxml/uas_register_diff_contact.xml b/tools/sippxml/uas_register_diff_contact.xml new file mode 100644 index 0000000000..3e74a7ba4c --- /dev/null +++ b/tools/sippxml/uas_register_diff_contact.xml @@ -0,0 +1,55 @@ +<?xml version="1.0" encoding="ISO-8859-2" ?> +<!DOCTYPE scenario SYSTEM "sipp.dtd"> + +<scenario name="UAS REGISTER + SUBSCRIBE/noresource"> + <!-- By adding rrs="true" (Record Route Sets), the route sets --> + <!-- are saved and used for following messages sent. Useful to test --> + <!-- against stateful SIP proxies/B2BUAs. --> + <recv request="REGISTER" crlf="true"> + </recv> + + <!-- The '[last_*]' keyword is replaced automatically by the --> + <!-- specified header if it was present in the last message received --> + <!-- (except if it was a retransmission). If the header was not --> + <!-- present or if no message has been received, the '[last_*]' --> + <!-- keyword is discarded, and all bytes until the end of the line --> + <!-- are also discarded. --> + <!-- --> + <!-- If the specified header was present several times in the --> + <!-- message, all occurences are concatenated (CRLF seperated) --> + <!-- to be used in place of the '[last_*]' keyword. --> + + <send> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:127.0.0.50:5060;transport=[transport]> + Content-Length: 0 + + ]]> + </send> + + <recv request="REGISTER" crl="true"> + </recv> + + <send> + <![CDATA[ + + SIP/2.0 200 OK + [last_Via:] + [last_From:] + [last_To:];tag=[call_number] + [last_Call-ID:] + [last_CSeq:] + Contact: <sip:sip:127.0.0.50:5060;transport=[transport]> + Content-Length: 0 + ]]> + </send> + +</scenario> + diff --git a/tools/sippxml/voice b/tools/sippxml/voice new file mode 100644 index 0000000000000000000000000000000000000000..c881cf2d1b97c326d0f5d78ad4beebde51bd4b15 GIT binary patch literal 252 zcmca|c+)~A1{MYwxWmf8z{tSBu=mF!?{oH?3|AN!z?gwW+4blJiIuF12W&YQTp1XS zF)%nVI0*h(&ebW9!qq9kb?K329K$9%=LQA^2bbET4YjQQCVY8R_TXvkquejAK33kn zT=?jB&Yk}+Ui?1){_@SMrT4NDvLC*>Q(Kgp9u*Xtlb-nWb<*7@QSW}ddwJ*S?R$5g zzbbn9@aoOVA9Xiw-gtlSPFmj2wD%7#yuN$!$E*7f?_RzC_vz(l*Dt^R@-?eGH~Gh{ z{M>@Niu%|0s;*x+f9cizcQ>A0zkcre)w55o-hBVPIw>!{A~XBrqq@7#&KEws`Ytas F7yv}Kj_v>e literal 0 HcmV?d00001 diff --git a/tools/translations/sflphone-translation-push-template b/tools/translations/sflphone-translation-push-template new file mode 100755 index 0000000000..e2f52903dd --- /dev/null +++ b/tools/translations/sflphone-translation-push-template @@ -0,0 +1,38 @@ +#!/bin/bash +# +# Script to push up-to-date template file to Launchpad. +# +# Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + +set -x + +# This is an environment variable provided by Jenkins. It points to the repository's root +cd ${WORKSPACE} + +WORKING_DIR=${WORKSPACE}/gnome/po +BZR_DIR=${WORKING_DIR}/tmp +BZR=bzr +BZR_BRANCH="lp:sflphone" + +# Clone Bazaar branch that contains Launchpad translations +cd ${WORKING_DIR} +if [ -e $BZR_DIR ] ; then + # Remove the directory first + rm -rf $BZR_DIR +fi +$BZR branch $BZR_BRANCH $BZR_DIR + +# Update the template file with the latest strings +cd ${WORKSPACE}/gnome +./autogen.sh +./configure --prefix=/usr +cd ${WORKING_DIR} +make sflphone.pot + +# Add the new template file to the Bazaar repository +cp sflphone.pot $BZR_DIR/sflphone +cd $BZR_DIR/sflphone +$BZR commit sflphone.pot --message "[Translations] Update POT file" +$BZR push $BZR_BRANCH + +exit 0; diff --git a/tools/translations/sflphone-translation-update b/tools/translations/sflphone-translation-update new file mode 100755 index 0000000000..bcde044e2b --- /dev/null +++ b/tools/translations/sflphone-translation-update @@ -0,0 +1,55 @@ +#!/bin/bash +# +# Script to update translations from Launchpad, create a patch and send by email +# @See git-format-patch +# @See git-send-email +# +# Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> + +set -x + +# This is an environment variable provided by Jenkins. It points to the repository's root +cd ${WORKSPACE} + +WORKING_DIR=${WORKSPACE}/gnome/po +BZR_DIR=${WORKING_DIR}/tmp +BZR=bzr +MAKE=make +BZR_BRANCH="lp:sflphone" +EMAIL_TO="sflphone-dev@lists.savoirfairelinux.net" +COMMIT_MSG="intl: automatic translations update" +PATCH="0001-intl-automatic-translations-update.patch" + +# Clone Bazaar branch that contains Launchpad translations +cd ${WORKING_DIR} +if [ -e $BZR_DIR ] ; then + # Remove the directory first + rm -rf $BZR_DIR +fi +$BZR branch $BZR_BRANCH $BZR_DIR + +# Update the po files with the latest translations +cd ${WORKSPACE}/gnome +./autogen.sh +./configure --prefix=/usr +cd ${WORKING_DIR} +cp $BZR_DIR/sflphone/*.po ${WORKING_DIR} +# Compile the po files +$MAKE + +# Build the patch +git status +if git diff-index HEAD . ; then + git add *.po + git commit -a -m "$COMMIT_MSG" + git format-patch --to $EMAIL_TO HEAD~..HEAD + # Send the patch + git send-email \ + --8bit-encoding "utf-8" \ + --from "Jenkins CI <jenkins@savoirfairelinux.com>" \ + --to "<emmanuel.milou@savoirfairelinux.com>" \ + --confirm=never \ + $PATCH +fi + +exit 0 diff --git a/tools/update-copyright b/tools/update-copyright new file mode 100755 index 0000000000..5092cc0da7 --- /dev/null +++ b/tools/update-copyright @@ -0,0 +1,42 @@ +#!/bin/bash + +# Copyright (C) 2004-2015 Savoir-Faire Linux Inc. +# Author: Emmanuel Milou <emmanuel.milou@savoirfairelinux.com> +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# +# Additional permission under GNU GPL version 3 section 7: +# +# If you modify this program, or any covered work, by linking or +# combining it with the OpenSSL project's OpenSSL library (or a +# modified version of that library), containing parts covered by the +# terms of the OpenSSL or SSLeay licenses, Savoir-Faire Linux Inc. +# grants you additional permission to convey the resulting work. +# Corresponding Source for a non-source form of such a combination +# shall include the source code for the parts of OpenSSL used as well +# as that of the covered work. +# + +# List all files in current directory +FILES=`find -name "*.h" -o -name "*.c" -o -name "*.cpp" -o -name "*.i" -o -name "README" -o -name "*.sh" -o -name "*.py"` +year=`date +%Y` + +# Loop and replace +for f in $FILES +do + sed -i "/$year/b; s/\-[0-9]\+ Savoir-Faire Linux Inc/-$year Savoir-Faire Linux Inc/g" $f + sed -i "/$year/b; s/ \([0-9]\+\) Savoir-Faire Linux Inc/ \1-$year Savoir-Faire Linux Inc/g" $f +done + +exit 0; -- GitLab