diff --git a/modules/SCsub b/modules/SCsub index d1c0cdc05cb2..5523451351f4 100644 --- a/modules/SCsub +++ b/modules/SCsub @@ -20,4 +20,4 @@ for x in env.module_list: lib = env_modules.Library("modules", env.modules_sources) -env.Prepend(LIBS=[lib]) +env.Prepend(LIBS=[lib,'boost_system', 'protobuf', 'log4cpp']) diff --git a/modules/mumble/.gitignore b/modules/mumble/.gitignore new file mode 100644 index 000000000000..06833525e1e2 --- /dev/null +++ b/modules/mumble/.gitignore @@ -0,0 +1,4 @@ +*.backup +*.back +*.pyc +*.o diff --git a/modules/mumble/SCsub b/modules/mumble/SCsub new file mode 100644 index 000000000000..5d9df14e80bc --- /dev/null +++ b/modules/mumble/SCsub @@ -0,0 +1,263 @@ +# SCsub + +Import('env') + + +mumble_env = env.Clone() +if (env['builtin_opus'] != 'no'): + thirdparty_dir = "#thirdparty/opus/" + + thirdparty_sources = [ + "silk/tables_other.c", + "silk/sum_sqr_shift.c", + "silk/PLC.c", + "silk/dec_API.c", + "silk/decode_pulses.c", + "silk/inner_prod_aligned.c", + "silk/init_encoder.c", + "silk/interpolate.c", + "silk/stereo_encode_pred.c", + "silk/decode_frame.c", + "silk/NLSF_del_dec_quant.c", + "silk/VAD.c", + "silk/resampler_private_AR2.c", + "silk/NLSF_unpack.c", + "silk/resampler_down2.c", + "silk/sort.c", + "silk/resampler_private_IIR_FIR.c", + "silk/resampler_down2_3.c", + "silk/resampler_private_up2_HQ.c", + "silk/tables_gain.c", + "silk/stereo_find_predictor.c", + "silk/stereo_quant_pred.c", + "silk/NLSF_stabilize.c", + "silk/ana_filt_bank_1.c", + "silk/check_control_input.c", + "silk/bwexpander.c", + "silk/A2NLSF.c", + "silk/LPC_inv_pred_gain.c", + "silk/log2lin.c", + "silk/process_NLSFs.c", + "silk/sigm_Q15.c", + "silk/VQ_WMat_EC.c", + "silk/quant_LTP_gains.c", + "silk/resampler_private_down_FIR.c", + "silk/NLSF_decode.c", + "silk/control_codec.c", + "silk/NLSF_VQ_weights_laroia.c", + "silk/decode_pitch.c", + "silk/stereo_decode_pred.c", + "silk/tables_pulses_per_block.c", + "silk/init_decoder.c", + "silk/table_LSF_cos.c", + "silk/decode_core.c", + "silk/code_signs.c", + "silk/enc_API.c", + "silk/tables_LTP.c", + "silk/pitch_est_tables.c", + "silk/biquad_alt.c", + "silk/encode_indices.c", + "silk/tables_NLSF_CB_WB.c", + "silk/debug.c", + "silk/decode_parameters.c", + "silk/tables_pitch_lag.c", + "silk/NLSF2A.c", + "silk/resampler.c", + "silk/decode_indices.c", + "silk/NLSF_VQ.c", + "silk/bwexpander_32.c", + "silk/tables_NLSF_CB_NB_MB.c", + "silk/encode_pulses.c", + "silk/NSQ_del_dec.c", + "silk/control_SNR.c", + "silk/shell_coder.c", + "silk/NLSF_encode.c", + "silk/stereo_MS_to_LR.c", + "silk/stereo_LR_to_MS.c", + "silk/HP_variable_cutoff.c", + "silk/LPC_analysis_filter.c", + "silk/CNG.c", + "silk/decoder_set_fs.c", + "silk/resampler_rom.c", + "silk/control_audio_bandwidth.c", + "silk/lin2log.c", + "silk/LP_variable_cutoff.c", + "silk/NSQ.c", + "silk/gain_quant.c", + "celt/laplace.c", + "celt/vq.c", + "celt/quant_bands.c", + "celt/kiss_fft.c", + "celt/entcode.c", + "celt/entenc.c", + "celt/celt_lpc.c", + "celt/pitch.c", + "celt/rate.c", + "celt/mathops.c", + #"celt/arm/armcpu.c", + #"celt/arm/celt_neon_intr.c", + #"celt/arm/celt_ne10_mdct.c", + #"celt/arm/celt_ne10_fft.c", + #"celt/arm/arm_celt_map.c", + "celt/celt_encoder.c", + "celt/celt.c", + "celt/bands.c", + "celt/cwrs.c", + "celt/entdec.c", + "celt/celt_decoder.c", + "celt/mdct.c", + "celt/modes.c", + "repacketizer.c", + "mlp_data.c", + "opus_multistream.c", + "opusfile.c", + "opus_encoder.c", + "analysis.c", + "mlp.c", + "info.c", + "stream.c", + "opus_decoder.c", + "internal.c", + "wincerts.c", + "opus.c", + "opus_multistream_encoder.c", + "http.c", + "opus_multistream_decoder.c" + ] + + opus_sources_silk = [] + + if("opus_fixed_point" in env and env.opus_fixed_point == "yes"): + mumble_env.Append(CFLAGS=["-DFIXED_POINT"]) + opus_sources_silk = [ + "silk/fixed/schur64_FIX.c", + "silk/fixed/residual_energy16_FIX.c", + "silk/fixed/encode_frame_FIX.c", + "silk/fixed/regularize_correlations_FIX.c", + "silk/fixed/apply_sine_window_FIX.c", + "silk/fixed/solve_LS_FIX.c", + "silk/fixed/schur_FIX.c", + "silk/fixed/pitch_analysis_core_FIX.c", + "silk/fixed/noise_shape_analysis_FIX.c", + "silk/fixed/find_LTP_FIX.c", + "silk/fixed/vector_ops_FIX.c", + "silk/fixed/autocorr_FIX.c", + "silk/fixed/warped_autocorrelation_FIX.c", + "silk/fixed/find_pitch_lags_FIX.c", + "silk/fixed/k2a_Q16_FIX.c", + "silk/fixed/LTP_scale_ctrl_FIX.c", + "silk/fixed/corrMatrix_FIX.c", + "silk/fixed/prefilter_FIX.c", + "silk/fixed/find_LPC_FIX.c", + "silk/fixed/residual_energy_FIX.c", + "silk/fixed/process_gains_FIX.c", + "silk/fixed/LTP_analysis_filter_FIX.c", + "silk/fixed/k2a_FIX.c", + "silk/fixed/burg_modified_FIX.c", + "silk/fixed/find_pred_coefs_FIX.c" + ] + else: + opus_sources_silk = [ + "silk/float/LTP_scale_ctrl_FLP.c", + "silk/float/regularize_correlations_FLP.c", + "silk/float/corrMatrix_FLP.c", + "silk/float/LPC_analysis_filter_FLP.c", + "silk/float/levinsondurbin_FLP.c", + "silk/float/schur_FLP.c", + "silk/float/scale_vector_FLP.c", + "silk/float/apply_sine_window_FLP.c", + "silk/float/pitch_analysis_core_FLP.c", + "silk/float/wrappers_FLP.c", + "silk/float/bwexpander_FLP.c", + "silk/float/warped_autocorrelation_FLP.c", + "silk/float/solve_LS_FLP.c", + "silk/float/find_LPC_FLP.c", + "silk/float/autocorrelation_FLP.c", + "silk/float/find_pred_coefs_FLP.c", + "silk/float/find_pitch_lags_FLP.c", + "silk/float/burg_modified_FLP.c", + "silk/float/find_LTP_FLP.c", + "silk/float/energy_FLP.c", + "silk/float/sort_FLP.c", + "silk/float/LPC_inv_pred_gain_FLP.c", + "silk/float/k2a_FLP.c", + "silk/float/noise_shape_analysis_FLP.c", + "silk/float/inner_product_FLP.c", + "silk/float/process_gains_FLP.c", + "silk/float/encode_frame_FLP.c", + "silk/float/scale_copy_vector_FLP.c", + "silk/float/residual_energy_FLP.c", + "silk/float/LTP_analysis_filter_FLP.c", + "silk/float/prefilter_FLP.c" + ] + + thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources + opus_sources_silk] + + mumble_env.add_source_files(env.modules_sources, thirdparty_sources) + mumble_env.Append(CFLAGS=["-DHAVE_CONFIG_H"]) + + thirdparty_include_paths = [ + "", + "celt", + "opus", + "silk", + "silk/fixed", + "silk/float", + ] + mumble_env.Append(CPPPATH=[thirdparty_dir + "/" + dir for dir in thirdparty_include_paths]) + + # also requires libogg + if (env['builtin_libogg'] != 'no'): + mumble_env.Append(CPPPATH=["#thirdparty/libogg"]) + + +sources = [ + "register_types.cpp", + "mumble.cpp" + "callback.cpp" + "utils.cpp" +] + + +thirdparty_dir = "#thirdparty" + +third_party_includes = [ + "mumlib/include", + "mumlib/build" +] +third_party_sources = [ + "mumlib/src/Audio.cpp", + "mumlib/src/Callback.cpp", + "mumlib/src/CryptState.cpp", + "mumlib/src/Transport.cpp", + "mumlib/src/VarInt.cpp", + "mumlib/src/mumlib.cpp", + "mumlib/build/Mumble.pb.cc" +] +third_party_system_lib = [ + "/usr/lib64", + "/usr/lib" +] +third_party_system_include = [ + "/usr/include", + "/usr/include/boost" +] +build_mumlib_proto = "pushd ../../thirdparty/mumlib; mkdir build;" + \ +"protoc --proto_path=. --cpp_out=build/ Mumble.proto; popd;" + +import subprocess +def subprocess_cmd(command): + process = subprocess.Popen(command,stdout=subprocess.PIPE, shell=True, executable='/bin/bash') + proc_stdout = process.communicate()[0].strip() + +## build .pb.h files +subprocess_cmd(build_mumlib_proto) + +#mumble_env.Append(LIBPATH=third_party_system_lib) +#mumble_env.Append(CPPPATH=third_party_system_include) +#mumble_env.Append(LIBS=['protobuf', 'log4cpp']) + +mumble_env.Append(CPPPATH=[ thirdparty_dir + "/" + dir for dir in third_party_includes]) +mumble_env.add_source_files(env.modules_sources, [ thirdparty_dir + '/' + dir for dir in third_party_sources ]) +mumble_env.add_source_files(env.modules_sources,"*.cpp") +mumble_env.Append(CXXFLAGS=['-O2', '-std=c++11']) diff --git a/modules/mumble/callback.cpp b/modules/mumble/callback.cpp new file mode 100644 index 000000000000..e9c7fa8bf3f1 --- /dev/null +++ b/modules/mumble/callback.cpp @@ -0,0 +1,67 @@ +#include "callback.h" +#include "utils.h" +#include "variant.h" +#include "print_string.h" +#include "scene/resources/audio_stream_sample.h" + +SimpleCallback::SimpleCallback() : _callback(*this) { } +SimpleCallback::~SimpleCallback() { } +mumlib::Callback *SimpleCallback::get_callback(){ + return &_callback; +} +void SimpleCallback::MyCallBack::audio( int target, + int sessionId, + int sequenceNumber, + int16_t *pcm_data, + uint32_t pcm_data_size){ + if(!_cb._audio_handler.is_null()){ + Variant sid(sessionId); + Variant snum(sequenceNumber); + Ref sam = Ref(utils::pcm2Sample(pcm_data, pcm_data_size)); + Variant pcm(sam); + + Variant::CallError err; + const Variant *args[3] = { &sid, &snum, &pcm}; + Variant result = _cb._audio_handler->call_func( args, 3, err); + } +} + + +void SimpleCallback::MyCallBack::textMessage( + uint32_t actor, + std::vector session, + std::vector channel_id, + std::vector tree_id, + std::string message) { + if(!_cb._text_handler.is_null()){ + Variant s = utils::cpp_uint32vec2Variant(session); + Variant c = utils::cpp_uint32vec2Variant(channel_id); + Variant t = utils::cpp_uint32vec2Variant(tree_id); + Variant a(actor); + Variant m(String(message.c_str())); + Variant::CallError err; + const Variant *args[5] = {&a, &s, &c, &t, &m}; + Variant result = _cb._text_handler->call_func( args, 5, err ); + } + +} +void SimpleCallback::MyCallBack::version( + uint16_t major, + uint8_t minor, + uint8_t patch, + std::string release, + std::string os, + std::string os_version){ } + +void SimpleCallback::_bind_methods(){ + ClassDB::bind_method(D_METHOD("setAudioHandler", "handler"), &SimpleCallback::setAudioHandler); + ClassDB::bind_method(D_METHOD("setTextHandler", "handler"), &SimpleCallback::setTextHandler); + +} + +void SimpleCallback::setAudioHandler( Ref handler){ + this->_audio_handler = handler; +} +void SimpleCallback::setTextHandler( Ref handler){ + this->_text_handler = handler; +} diff --git a/modules/mumble/callback.h b/modules/mumble/callback.h new file mode 100644 index 000000000000..b4e76ff24bc0 --- /dev/null +++ b/modules/mumble/callback.h @@ -0,0 +1,69 @@ +/* callback.h */ +#ifndef SimpleCALLBACK_H +#define SimpleCALLBACK_H + +#include "reference.h" +#include "ustring.h" +#include "func_ref.h" +#include +#include +#include +#include "scene/resources/audio_stream_sample.h" + +class SimpleCallback : public Reference { + GDCLASS(SimpleCallback,Reference); + + +protected: +// bool _set(const StringName &p_name, const Variant &p_value); +// bool _get(const StringName &p_name, Variant &r_ret) const; +// void _get_property_list(List *p_list) const; + static void _bind_methods(); + +private: + Ref _audio_handler; + Ref _text_handler; + + class MyCallBack : public mumlib::Callback{ + protected: + static void _bind_methods(); + + public: + MyCallBack( SimpleCallback &o ): _cb(o){ + }; + virtual void audio(int target, + int sessionId, + int sequenceNumber, + int16_t *pcm_data, + uint32_t pcm_data_size) ; + + virtual void textMessage( + uint32_t actor, + std::vector session, + std::vector channel_id, + std::vector tree_id, + std::string message); + + virtual void version( + uint16_t major, + uint8_t minor, + uint8_t patch, + std::string release, + std::string os, + std::string os_version); + private: + SimpleCallback &_cb; + }; + MyCallBack _callback; +public: + SimpleCallback(); + ~SimpleCallback(); + mumlib::Callback * get_callback(); + + void setAudioHandler(Ref handler); + void setTextHandler(Ref handler); + + +}; + +#endif diff --git a/modules/mumble/config.py b/modules/mumble/config.py new file mode 100644 index 000000000000..b39f1d00103b --- /dev/null +++ b/modules/mumble/config.py @@ -0,0 +1,7 @@ +# config.py + +def can_build(platform): + return True + +def configure(env): + pass diff --git a/modules/mumble/mumble.cpp b/modules/mumble/mumble.cpp new file mode 100644 index 000000000000..109e6444645c --- /dev/null +++ b/modules/mumble/mumble.cpp @@ -0,0 +1,109 @@ +/* mumble.cpp */ + +#include "mumble.h" +#include "callback.h" +#include "utils.h" +#include "os/thread.h" +#include "print_string.h" +#include +#include +#include "scene/main/timer.h" + +Mumble::_PrivateMumble::_PrivateMumble( mumlib::Callback & c) : _mum(c) { +} +void Mumble::_PrivateMumble::engage(String host, int port, String user, String password){ + std::string h = utils::gstr2cpp_str(host); + std::string u = utils::gstr2cpp_str(user); + std::string p = utils::gstr2cpp_str(password); + while(true){ + try{ + this->_mum.connect(h, port, u, p); + print_line( "Mumble: connecting to " + host ); + this->_mum.run(); + }catch (mumlib::TransportException &exp) { + print_line( "Mumble: error " + utils::cpp_str2gstr(exp.what())); + print_line( "Mumble: attempting to reconnect in 5s. "); + Timer * sleep = memnew(Timer); + sleep -> set_wait_time(5.0); + sleep -> start(); + memdelete(sleep); + + } + } +} + +void Mumble::_PrivateMumble::sendText(const String text){ + _mum.sendTextMessage( utils::gstr2cpp_str(text) ); +} + +void Mumble::_PrivateMumble::send16bAudio(const PoolByteArray & sample){ + uint32_t pcm_len = sample.size()/2; + int16_t pcm[50000]; + for(int i = 0; i < pcm_len; i++){ + uint16_t low = (uint16_t) sample[2*i]; + uint16_t hi = (uint16_t) sample[2*i+1]; + pcm[i] = ( low | ( hi << 8)); + } + _mum.sendAudioData(pcm, pcm_len); +} + +void Mumble::_bind_methods() { + ClassDB::bind_method(D_METHOD("add", "value"), &Mumble::add); + ClassDB::bind_method(D_METHOD("reset"), &Mumble::reset); + ClassDB::bind_method(D_METHOD("get_total"), &Mumble::get_total); + + ClassDB::bind_method(D_METHOD("engage", "host", "port", "user", "password"), &Mumble::engage); + ClassDB::bind_method(D_METHOD("setCallback", "callback"), &Mumble::setCallback); + ClassDB::bind_method(D_METHOD("sendText", "text"), &Mumble::sendText); + ClassDB::bind_method(D_METHOD("sendAudio", "sample"), &Mumble::sendAudio); + +} +void Mumble::engage(String host, int port, String user, String password) { + _pMumble -> engage( host, port, user, password); +} + +void Mumble::setCallback( Ref cb){ + + _pMumble = Ref<_PrivateMumble>(memnew(_PrivateMumble(*(cb->get_callback())))); +} +void Mumble::sendText(const String text){ + _pMumble -> sendText( text ); +} + +void Mumble::sendAudio(Ref sample){ + + const PoolByteArray data = sample->get_data(); + + + if(data.size() > 0){ + switch(sample->get_format()){ + case AudioStreamSample::FORMAT_16_BITS: + _pMumble->send16bAudio(data); + default: + return; + } + } + +} + + + +Mumble::Mumble() { + count=0; +} + +void Mumble::add(int value) { + + count+=value; +} + +void Mumble::reset() { + + count=0; +} + + +int Mumble::get_total() const { + + return count; +} diff --git a/modules/mumble/mumble.h b/modules/mumble/mumble.h new file mode 100644 index 000000000000..1527072d9a23 --- /dev/null +++ b/modules/mumble/mumble.h @@ -0,0 +1,45 @@ +/* mumble.h */ +#ifndef MUMBLE_H +#define MUMBLE_H + +#include "reference.h" +#include +#include "scene/resources/audio_stream_sample.h" +#include "variant.h" +#include "callback.h" + +class Mumble : public Reference { + GDCLASS(Mumble,Reference); + int count; +private: + + + class _PrivateMumble: public Reference { + GDCLASS(_PrivateMumble,Reference); + private: + mumlib::Mumlib _mum; + public: + _PrivateMumble(mumlib::Callback & c); + void engage(String host, int port, String user, String password); + void sendText(const String text); + void send16bAudio(const PoolByteArray & arr); + }; + Ref<_PrivateMumble> _pMumble; + +protected: + static void _bind_methods(); + +public: + void add(int value); + void reset(); + int get_total() const; + void engage(String host, int port, String user, String password); + void setCallback( Ref c ); + void sendText(const String message); + void sendAudio(Ref sample); +// void sendAudio(PoolByteArray sample); + Mumble(); +// ~Mumble(); +}; + +#endif diff --git a/modules/mumble/register_types.cpp b/modules/mumble/register_types.cpp new file mode 100644 index 000000000000..0df991f8f9fd --- /dev/null +++ b/modules/mumble/register_types.cpp @@ -0,0 +1,17 @@ +/* register_types.cpp */ + +#include "register_types.h" +#include "class_db.h" +#include "mumble.h" +#include "callback.h" + +void register_mumble_types() { + + ClassDB::register_class(); + ClassDB::register_class(); +} + +void unregister_mumble_types() { + //nothing to do here +} + diff --git a/modules/mumble/register_types.h b/modules/mumble/register_types.h new file mode 100644 index 000000000000..2febdf657abc --- /dev/null +++ b/modules/mumble/register_types.h @@ -0,0 +1,6 @@ +/* register_types.h */ + +void register_mumble_types(); +void unregister_mumble_types(); +/* yes, the word in the middle must be the same as the module folder name */ + diff --git a/modules/mumble/utils.cpp b/modules/mumble/utils.cpp new file mode 100644 index 000000000000..05b055e15001 --- /dev/null +++ b/modules/mumble/utils.cpp @@ -0,0 +1,40 @@ +#include "utils.h" +#include "variant.h" +#include "vector.h" + + + +std::string utils::gstr2cpp_str(String s){ + std::wstring ws(s.c_str()); + std::string str(ws.begin(), ws.end()); + return str; +}; +String utils::cpp_str2gstr(std::string s){ + return String(s.c_str()); +} +Variant utils::cpp_uint32vec2Variant( const std::vector &v ){ + Vector ret; + ret.resize(v.size()); + for( int i = 0; i < v.size(); i++){ + ret[i] = (Variant( (uint64_t)v[i])); + } + return ret; +} + + +AudioStreamSample *utils::pcm2Sample( const int16_t * pcm_data, uint32_t size){ + PoolByteArray d; + d.resize(size * 2); + AudioStreamSample *sam = memnew(AudioStreamSample); + sam->set_format(AudioStreamSample::FORMAT_16_BITS); + sam->set_loop_mode(AudioStreamSample::LOOP_DISABLED); + for( int i=0; i < size; i++){ + uint16_t sh = (uint16_t) pcm_data[i]; + d.set( i*2 , (uint8_t) (sh & 0xFF) ); + d.set( i*2+1 , (uint8_t) (sh >> 8)); + } + + + sam->set_data(d); + return sam; +} diff --git a/modules/mumble/utils.h b/modules/mumble/utils.h new file mode 100644 index 000000000000..6626a4337c2c --- /dev/null +++ b/modules/mumble/utils.h @@ -0,0 +1,13 @@ +#include "ustring.h" +#include "array.h" +#include +#include +#include "scene/resources/audio_stream_sample.h" +#include "variant.h" + +namespace utils{ + std::string gstr2cpp_str(String s); + String cpp_str2gstr(std::string s); + Variant cpp_uint32vec2Variant( const std::vector &v ); + AudioStreamSample *pcm2Sample( const int16_t * pcm_data, uint32_t size); +} diff --git a/pkgs_install b/pkgs_install new file mode 100644 index 000000000000..ecc00217600a --- /dev/null +++ b/pkgs_install @@ -0,0 +1,11 @@ +#!/usr/bin/env bash + +PKGS="git" + +GODOT_PKGS="build-essential scons pkg-config libx11-dev libxcursor-dev libxinerama-dev \ + libgl1-mesa-dev libglu-dev libasound2-dev libpulse-dev libfreetype6-dev libssl-dev libudev-dev \ + libxrandr-dev" + +MUMBLE_PKGS="liblog4cpp5-dev libboost-dev libprotobuf-dev protobuf-compiler libboost-system-dev" + +apt-get install -y $PKGS $GODOT_PKGS $MUMBLE_PKGS diff --git a/scene/resources/audio_stream_sample.cpp b/scene/resources/audio_stream_sample.cpp index 1fd84a860e17..fdc3b79db652 100644 --- a/scene/resources/audio_stream_sample.cpp +++ b/scene/resources/audio_stream_sample.cpp @@ -486,7 +486,8 @@ PoolVector AudioStreamSample::get_data() const { { PoolVector::Write w = pv.write(); - copymem(w.ptr(), data, data_bytes); + uint8_t *dataptr = (uint8_t *)data; + copymem(w.ptr(), dataptr + DATA_PAD, data_bytes); } } diff --git a/thirdparty/mumlib/.gitignore b/thirdparty/mumlib/.gitignore new file mode 100644 index 000000000000..4286771d360d --- /dev/null +++ b/thirdparty/mumlib/.gitignore @@ -0,0 +1,34 @@ +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +build/ + +# IntelliJ +*.iml +.idea/ diff --git a/thirdparty/mumlib/CMakeLists.txt b/thirdparty/mumlib/CMakeLists.txt new file mode 100644 index 000000000000..9fbbab39783f --- /dev/null +++ b/thirdparty/mumlib/CMakeLists.txt @@ -0,0 +1,56 @@ +cmake_minimum_required(VERSION 2.8.0) +project(mumlib) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11") +add_definitions(-DOPT_TLS_GNUTLS -D_POSIX_C_SOURCE=200112L) + +INCLUDE(FindPkgConfig) +find_package(PkgConfig REQUIRED) +find_package(Boost COMPONENTS system REQUIRED) +find_package(OpenSSL REQUIRED) +find_package(Protobuf REQUIRED) + +pkg_check_modules(LOG4CPP "log4cpp") +pkg_check_modules(OPUS "opus") + + +INCLUDE_DIRECTORIES(${Boost_INCLUDE_DIR}) +include_directories(${OPENSSL_INCLUDE_DIR}) +include_directories(${PROTOBUF_INCLUDE_DIRS}) +include_directories(${OPUS_INCLUDE_DIRS}) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +include_directories(${LOG4CPP_INCLUDE_DIRS}) +include_directories(include) + +set(MUMLIB_PUBLIC_HEADERS include/mumlib.hpp include/mumlib/VarInt.hpp) + +set(MUMLIB_PRIVATE_HEADERS + include/mumlib/Callback.hpp + include/mumlib/CryptState.hpp + include/mumlib/Transport.hpp + include/mumlib/Audio.hpp + include/mumlib/enums.hpp + ) + +set(MUMLIB_SRC + src/mumlib.cpp + src/Callback.cpp + src/CryptState.cpp + src/VarInt.cpp + src/Transport.cpp + src/Audio.cpp + ) + +PROTOBUF_GENERATE_CPP(PROTO_SRCS PROTO_HDRS Mumble.proto) + +add_library(mumlib SHARED ${MUMLIB_SRC} ${MUMLIB_PUBLIC_HEADERS} ${MUMLIB_PRIVATE_HEADERS} ${PROTO_SRCS} ${PROTO_HDRS}) +target_link_libraries(mumlib ${PROTOBUF_LIBRARIES}) +target_link_libraries(mumlib ${Boost_LIBRARIES}) +target_link_libraries(mumlib ${OPENSSL_LIBRARIES}) +target_link_libraries(mumlib ${LOG4CPP_LIBRARIES}) +target_link_libraries(mumlib ${OPUS_LIBRARIES}) + + +add_executable(mumlib_example mumlib_example.cpp) +target_link_libraries(mumlib_example mumlib) diff --git a/thirdparty/mumlib/LICENSE b/thirdparty/mumlib/LICENSE new file mode 100644 index 000000000000..341c30bda445 --- /dev/null +++ b/thirdparty/mumlib/LICENSE @@ -0,0 +1,166 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser 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 +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. + diff --git a/thirdparty/mumlib/Mumble.proto b/thirdparty/mumlib/Mumble.proto new file mode 100644 index 000000000000..92b873775f4c --- /dev/null +++ b/thirdparty/mumlib/Mumble.proto @@ -0,0 +1,544 @@ +package MumbleProto; + +option optimize_for = SPEED; + +message Version { + // 2-byte Major, 1-byte Minor and 1-byte Patch version number. + optional uint32 version = 1; + // Client release name. + optional string release = 2; + // Client OS name. + optional string os = 3; + // Client OS version. + optional string os_version = 4; +} + +// Not used. Not even for tunneling UDP through TCP. +message UDPTunnel { + // Not used. + required bytes packet = 1; +} + +// Used by the client to send the authentication credentials to the server. +message Authenticate { + // UTF-8 encoded username. + optional string username = 1; + // Server or user password. + optional string password = 2; + // Additional access tokens for server ACL groups. + repeated string tokens = 3; + // A list of CELT bitstream version constants supported by the client. + repeated int32 celt_versions = 4; + optional bool opus = 5 [default = false]; +} + +// Sent by the client to notify the server that the client is still alive. +// Server must reply to the packet with the same timestamp and its own +// good/late/lost/resync numbers. None of the fields is strictly required. +message Ping { + // Client timestamp. Server should not attempt to decode. + optional uint64 timestamp = 1; + // The amount of good packets received. + optional uint32 good = 2; + // The amount of late packets received. + optional uint32 late = 3; + // The amount of packets never received. + optional uint32 lost = 4; + // The amount of nonce resyncs. + optional uint32 resync = 5; + // The total amount of UDP packets received. + optional uint32 udp_packets = 6; + // The total amount of TCP packets received. + optional uint32 tcp_packets = 7; + // UDP ping average. + optional float udp_ping_avg = 8; + // UDP ping variance. + optional float udp_ping_var = 9; + // TCP ping average. + optional float tcp_ping_avg = 10; + // TCP ping variance. + optional float tcp_ping_var = 11; +} + +// Sent by the server when it rejects the user connection. +message Reject { + enum RejectType { + // TODO ?? + None = 0; + // The client attempted to connect with an incompatible version. + WrongVersion = 1; + // The user name supplied by the client was invalid. + InvalidUsername = 2; + // The client attempted to authenticate as a user with a password but it + // was wrong. + WrongUserPW = 3; + // The client attempted to connect to a passworded server but the password + // was wrong. + WrongServerPW = 4; + // Supplied username is already in use. + UsernameInUse = 5; + // Server is currently full and cannot accept more users. + ServerFull = 6; + // The user did not provide a certificate but one is required. + NoCertificate = 7; + AuthenticatorFail = 8; + } + // Rejection type. + optional RejectType type = 1; + // Human readable rejection reason. + optional string reason = 2; +} + +// ServerSync message is sent by the server when it has authenticated the user +// and finished synchronizing the server state. +message ServerSync { + // The session of the current user. + optional uint32 session = 1; + // Maximum bandwidth that the user should use. + optional uint32 max_bandwidth = 2; + // Server welcome text. + optional string welcome_text = 3; + // Current user permissions TODO: Confirm?? + optional uint64 permissions = 4; +} + +// Sent by the client when it wants a channel removed. Sent by the server when +// a channel has been removed and clients should be notified. +message ChannelRemove { + required uint32 channel_id = 1; +} + +// Used to communicate channel properties between the client and the server. +// Sent by the server during the login process or when channel properties are +// updated. Client may use this message to update said channel properties. +message ChannelState { + // Unique ID for the channel within the server. + optional uint32 channel_id = 1; + // channel_id of the parent channel. + optional uint32 parent = 2; + // UTF-8 encoded channel name. + optional string name = 3; + // A collection of channel id values of the linked channels. Absent during + // the first channel listing. + repeated uint32 links = 4; + // UTF-8 encoded channel description. Only if the description is less than + // 128 bytes + optional string description = 5; + // A collection of channel_id values that should be added to links. + repeated uint32 links_add = 6; + // A collection of channel_id values that should be removed from links. + repeated uint32 links_remove = 7; + // True if the channel is temporary. + optional bool temporary = 8 [default = false]; + // Position weight to tweak the channel position in the channel list. + optional int32 position = 9 [default = 0]; + // SHA1 hash of the description if the description is 128 bytes or more. + optional bytes description_hash = 10; +} + +// Used to communicate user leaving or being kicked. May be sent by the client +// when it attempts to kick a user. Sent by the server when it informs the +// clients that a user is not present anymore. +message UserRemove { + // The user who is being kicked, identified by their session, not present + // when no one is being kicked. + required uint32 session = 1; + // The user who initiated the removal. Either the user who performs the kick + // or the user who is currently leaving. + optional uint32 actor = 2; + // Reason for the kick, stored as the ban reason if the user is banned. + optional string reason = 3; + // True if the kick should result in a ban. + optional bool ban = 4; +} + +// Sent by the server when it communicates new and changed users to client. +// First seen during login procedure. May be sent by the client when it wishes +// to alter its state. +message UserState { + // Unique user session ID of the user whose state this is, may change on + // reconnect. + optional uint32 session = 1; + // The session of the user who is updating this user. + optional uint32 actor = 2; + // User name, UTF-8 encoded. + optional string name = 3; + // Registered user ID if the user is registered. + optional uint32 user_id = 4; + // Channel on which the user is. + optional uint32 channel_id = 5; + // True if the user is muted by admin. + optional bool mute = 6; + // True if the user is deafened by admin. + optional bool deaf = 7; + // True if the user has been suppressed from talking by a reason other than + // being muted. + optional bool suppress = 8; + // True if the user has muted self. + optional bool self_mute = 9; + // True if the user has deafened self. + optional bool self_deaf = 10; + // User image if it is less than 128 bytes. + optional bytes texture = 11; + // TODO ?? + optional bytes plugin_context = 12; + // TODO ?? + optional string plugin_identity = 13; + // User comment if it is less than 128 bytes. + optional string comment = 14; + // The hash of the user certificate. + optional string hash = 15; + // SHA1 hash of the user comment if it 128 bytes or more. + optional bytes comment_hash = 16; + // SHA1 hash of the user picture if it 128 bytes or more. + optional bytes texture_hash = 17; + // True if the user is a priority speaker. + optional bool priority_speaker = 18; + // True if the user is currently recording. + optional bool recording = 19; +} + +// Relays information on the bans. The client may send the BanList message to +// either modify the list of bans or query them from the server. The server +// sends this list only after a client queries for it. +message BanList { + message BanEntry { + // Banned IP address. + required bytes address = 1; + // The length of the subnet mask for the ban. + required uint32 mask = 2; + // User name for identification purposes (does not affect the ban). + optional string name = 3; + // TODO ?? + optional string hash = 4; + // Reason for the ban (does not affect the ban). + optional string reason = 5; + // Ban start time. + optional string start = 6; + // Ban duration in seconds. + optional uint32 duration = 7; + } + // List of ban entries currently in place. + repeated BanEntry bans = 1; + // True if the server should return the list, false if it should replace old + // ban list with the one provided. + optional bool query = 2 [default = false]; +} + +// Used to send and broadcast text messages. +message TextMessage { + // The message sender, identified by its session. + optional uint32 actor = 1; + // Target users for the message, identified by their session. + repeated uint32 session = 2; + // The channels to which the message is sent, identified by their + // channel_ids. + repeated uint32 channel_id = 3; + // The root channels when sending message recursively to several channels, + // identified by their channel_ids. + repeated uint32 tree_id = 4; + // The UTF-8 encoded message. May be HTML if the server allows. + required string message = 5; +} + +message PermissionDenied { + enum DenyType { + // Operation denied for other reason, see reason field. + Text = 0; + // Permissions were denied. + Permission = 1; + // Cannot modify SuperUser. + SuperUser = 2; + // Invalid channel name. + ChannelName = 3; + // Text message too long. + TextTooLong = 4; + // The flux capacitor was spelled wrong. + H9K = 5; + // Operation not permitted in temporary channel. + TemporaryChannel = 6; + // Operation requires certificate. + MissingCertificate = 7; + // Invalid username. + UserName = 8; + // Channel is full. + ChannelFull = 9; + NestingLimit = 10; + } + // The denied permission when type is Permission. + optional uint32 permission = 1; + // channel_id for the channel where the permission was denied when type is + // Permission. + optional uint32 channel_id = 2; + // The user who was denied permissions, identified by session. + optional uint32 session = 3; + // Textual reason for the denial. + optional string reason = 4; + // Type of the denial. + optional DenyType type = 5; + // The name that is invalid when type is UserName. + optional string name = 6; +} + +message ACL { + message ChanGroup { + // Name of the channel group, UTF-8 encoded. + required string name = 1; + // True if the group has been inherited from the parent (Read only). + optional bool inherited = 2 [default = true]; + // True if the group members are inherited. + optional bool inherit = 3 [default = true]; + // True if the group can be inherited by sub channels. + optional bool inheritable = 4 [default = true]; + // Users explicitly included in this group, identified by user_id. + repeated uint32 add = 5; + // Users explicitly removed from this group in this channel if the group + // has been inherited, identified by user_id. + repeated uint32 remove = 6; + // Users inherited, identified by user_id. + repeated uint32 inherited_members = 7; + } + message ChanACL { + // True if this ACL applies to the current channel. + optional bool apply_here = 1 [default = true]; + // True if this ACL applies to the sub channels. + optional bool apply_subs = 2 [default = true]; + // True if the ACL has been inherited from the parent. + optional bool inherited = 3 [default = true]; + // ID of the user that is affected by this ACL. + optional uint32 user_id = 4; + // ID of the group that is affected by this ACL. + optional string group = 5; + // Bit flag field of the permissions granted by this ACL. + optional uint32 grant = 6; + // Bit flag field of the permissions denied by this ACL. + optional uint32 deny = 7; + } + // Channel ID of the channel this message affects. + required uint32 channel_id = 1; + // True if the channel inherits its parent's ACLs. + optional bool inherit_acls = 2 [default = true]; + // User group specifications. + repeated ChanGroup groups = 3; + // ACL specifications. + repeated ChanACL acls = 4; + // True if the message is a query for ACLs instead of setting them. + optional bool query = 5 [default = false]; +} + +// Client may use this message to refresh its registered user information. The +// client should fill the IDs or Names of the users it wants to refresh. The +// server fills the missing parts and sends the message back. +message QueryUsers { + // user_ids. + repeated uint32 ids = 1; + // User names in the same order as ids. + repeated string names = 2; +} + +// Used to initialize and resync the UDP encryption. Either side may request a +// resync by sending the message without any values filled. The resync is +// performed by sending the message with only the client or server nonce +// filled. +message CryptSetup { + // Encryption key. + optional bytes key = 1; + // Client nonce. + optional bytes client_nonce = 2; + // Server nonce. + optional bytes server_nonce = 3; +} + +message ContextActionModify { + enum Context { + // Action is applicable to the server. + Server = 0x01; + // Action can target a Channel. + Channel = 0x02; + // Action can target a User. + User = 0x04; + } + enum Operation { + Add = 0; + Remove = 1; + } + // The action name. + required string action = 1; + // The display name of the action. + optional string text = 2; + // Context bit flags defining where the action should be displayed. + optional uint32 context = 3; + optional Operation operation = 4; +} + +// Sent by the client when it wants to initiate a Context action. +message ContextAction { + // The target User for the action, identified by session. + optional uint32 session = 1; + // The target Channel for the action, identified by channel_id. + optional uint32 channel_id = 2; + // The action that should be executed. + required string action = 3; +} + +// Lists the registered users. +message UserList { + message User { + // Registered user ID. + required uint32 user_id = 1; + // Registered user name. + optional string name = 2; + optional string last_seen = 3; + optional uint32 last_channel = 4; + } + // A list of registered users. + repeated User users = 1; +} + +// Sent by the client when it wants to register or clear whisper targets. +// +// Note: The first available target ID is 1 as 0 is reserved for normal +// talking. Maximum target ID is 30. +message VoiceTarget { + message Target { + // Users that are included as targets. + repeated uint32 session = 1; + // Channels that are included as targets. + optional uint32 channel_id = 2; + // TODO ?? + optional string group = 3; + // True if the voice should follow links from the specified channel. + optional bool links = 4 [default = false]; + // True if the voice should also be sent to children of the specific + // channel. + optional bool children = 5 [default = false]; + } + // Voice target ID. + optional uint32 id = 1; + // The receivers that this voice target includes. + repeated Target targets = 2; +} + +// Sent by the client when it wants permissions for a certain channel. Sent by +// the server when it replies to the query or wants the user to resync all +// channel permissions. +message PermissionQuery { + // channel_id of the channel for which the permissions are queried. + optional uint32 channel_id = 1; + // Channel permissions. + optional uint32 permissions = 2; + // True if the client should drop its current permission information for all + // channels. + optional bool flush = 3 [default = false]; +} + +// Sent by the server to notify the users of the version of the CELT codec they +// should use. This may change during the connection when new users join. +message CodecVersion { + // The version of the CELT Alpha codec. + required int32 alpha = 1; + // The version of the CELT Beta codec. + required int32 beta = 2; + // True if the user should prefer Alpha over Beta. + required bool prefer_alpha = 3 [default = true]; + optional bool opus = 4 [default = false]; +} + +// Used to communicate user stats between the server and clients. +message UserStats { + message Stats { + // The amount of good packets received. + optional uint32 good = 1; + // The amount of late packets received. + optional uint32 late = 2; + // The amount of packets never received. + optional uint32 lost = 3; + // The amount of nonce resyncs. + optional uint32 resync = 4; + } + + // User whose stats these are. + optional uint32 session = 1; + // True if the message contains only mutable stats (packets, ping). + optional bool stats_only = 2 [default = false]; + // Full user certificate chain of the user certificate in DER format. + repeated bytes certificates = 3; + // Packet statistics for packets received from the client. + optional Stats from_client = 4; + // Packet statistics for packets sent by the server. + optional Stats from_server = 5; + + // Amount of UDP packets sent. + optional uint32 udp_packets = 6; + // Amount of TCP packets sent. + optional uint32 tcp_packets = 7; + // UDP ping average. + optional float udp_ping_avg = 8; + // UDP ping variance. + optional float udp_ping_var = 9; + // TCP ping average. + optional float tcp_ping_avg = 10; + // TCP ping variance. + optional float tcp_ping_var = 11; + + // Client version. + optional Version version = 12; + // A list of CELT bitstream version constants supported by the client of this + // user. + repeated int32 celt_versions = 13; + // Client IP address. + optional bytes address = 14; + // Bandwith used by this client. + optional uint32 bandwidth = 15; + // Connection duration. + optional uint32 onlinesecs = 16; + // Duration since last activity. + optional uint32 idlesecs = 17; + // True if the user has a strong certificate. + optional bool strong_certificate = 18 [default = false]; + optional bool opus = 19 [default = false]; +} + +// Used by the client to request binary data from the server. By default large +// comments or textures are not sent within standard messages but instead the +// hash is. If the client does not recognize the hash it may request the +// resource when it needs it. The client does so by sending a RequestBlob +// message with the correct fields filled with the user sessions or channel_ids +// it wants to receive. The server replies to this by sending a new +// UserState/ChannelState message with the resources filled even if they would +// normally be transmitted as hashes. +message RequestBlob { + // sessions of the requested UserState textures. + repeated uint32 session_texture = 1; + // sessions of the requested UserState comments. + repeated uint32 session_comment = 2; + // channel_ids of the requested ChannelState descriptions. + repeated uint32 channel_description = 3; +} + +// Sent by the server when it informs the clients on server configuration +// details. +message ServerConfig { + // The maximum bandwidth the clients should use. + optional uint32 max_bandwidth = 1; + // Server welcome text. + optional string welcome_text = 2; + // True if the server allows HTML. + optional bool allow_html = 3; + // Maximum text message length. + optional uint32 message_length = 4; + // Maximum image message length. + optional uint32 image_message_length = 5; +} + +// Sent by the server to inform the clients of suggested client configuration +// specified by the server administrator. +message SuggestConfig { + // Suggested client version. + optional uint32 version = 1; + // True if the administrator suggests positional audio to be used on this + // server. + optional bool positional = 2; + // True if the administrator suggests push to talk to be used on this server. + optional bool push_to_talk = 3; +} diff --git a/thirdparty/mumlib/README.md b/thirdparty/mumlib/README.md new file mode 100644 index 000000000000..efae7540380d --- /dev/null +++ b/thirdparty/mumlib/README.md @@ -0,0 +1,45 @@ +# mumlib - simple Mumble client library + +Fairy simple Mumble library written in C++, using *boost::asio* asynchronous networking framework. Library supports: + +* audio streaming through TCP and UDP channel +* text messaging + +Todo: + +* channel support +* user information +* remaining server messages (ACL, user stats etc) + +## Dependencies + +* Boost libraries +* OpenSSL +* *log4cpp* +* Opus library +* Google Protobuf: libraries and compiler +* CMake + +## Build + +The library uses CMake build system: + +``` +mkdir build && cd build +cmake .. +make +``` + +## Usage + +Sample usage is covered in *mumlib_example.cpp* file. Basically, you should extend *mumlib::Callback* class +to implement your own handlers. + +## Credits + +2015 Michał Słomkowski. The code is published under the terms of Lesser General Public License Version 3. + +The library contains code from following 3rd party projects: + +* official Mumble Client: https://github.com/mumble-voip/mumble +* *libmumble*: https://github.com/cornejo/libmumble \ No newline at end of file diff --git a/thirdparty/mumlib/include/mumlib.hpp b/thirdparty/mumlib/include/mumlib.hpp new file mode 100644 index 000000000000..95afe0229e4b --- /dev/null +++ b/thirdparty/mumlib/include/mumlib.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "mumlib/Callback.hpp" + +#include +#include + +#include +#include + +namespace mumlib { + + constexpr int DEFAULT_OPUS_ENCODER_BITRATE = 16000; + + using namespace std; + using namespace boost::asio; + + class MumlibException : public runtime_error { + public: + MumlibException(string message) : runtime_error(message) { } + }; + + struct MumlibConfiguration { + int opusEncoderBitrate = DEFAULT_OPUS_ENCODER_BITRATE; + // additional fields will be added in the future + }; + + struct _Mumlib_Private; + + + class Mumlib : boost::noncopyable { + public: + Mumlib(Callback &callback); + + Mumlib(Callback &callback, io_service &ioService); + + Mumlib(Callback &callback, MumlibConfiguration &configuration); + + Mumlib(Callback &callback, io_service &ioService, MumlibConfiguration &configuration); + + virtual ~Mumlib(); + + void connect(string host, int port, string user, string password); + + void disconnect(); + + void run(); + + ConnectionState getConnectionState(); + + void sendAudioData(int16_t *pcmData, int pcmLength); + + void sendTextMessage(std::string message); + + void joinChannel(int channelId); + + private: + _Mumlib_Private *impl; + }; +} diff --git a/thirdparty/mumlib/include/mumlib/Audio.hpp b/thirdparty/mumlib/include/mumlib/Audio.hpp new file mode 100644 index 000000000000..d98f41a1551f --- /dev/null +++ b/thirdparty/mumlib/include/mumlib/Audio.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include "Transport.hpp" + +#include + +#include + +namespace mumlib { + + constexpr int SAMPLE_RATE = 48000; + + class MumlibException; + + class AudioException : public MumlibException { + public: + AudioException(string message) : MumlibException(message) { } + }; + + struct IncomingAudioPacket { + AudioPacketType type; + int target; + int64_t sessionId; + int64_t sequenceNumber; + uint8_t *audioPayload; + int audioPayloadLength; + }; + + class Audio : boost::noncopyable { + public: + Audio(int opusEncoderBitrate = DEFAULT_OPUS_ENCODER_BITRATE); + + virtual ~Audio(); + + IncomingAudioPacket decodeIncomingAudioPacket(uint8_t *inputBuffer, int inputBufferLength); + + std::pair decodeOpusPayload(uint8_t *inputBuffer, + int inputLength, + int16_t *pcmBuffer, + int pcmBufferSize); + + int encodeAudioPacket( + int target, + int16_t *inputPcmBuffer, + int inputLength, + uint8_t *outputBuffer, + int outputBufferSize = MAX_UDP_LENGTH); + + void setOpusEncoderBitrate(int bitrate); + + int getOpusEncoderBitrate(); + + void resetEncoder(); + + private: + log4cpp::Category &logger; + + OpusDecoder *opusDecoder; + OpusEncoder *opusEncoder; + + int64_t outgoingSequenceNumber; + + std::chrono::time_point lastEncodedAudioPacketTimestamp; + }; +} \ No newline at end of file diff --git a/thirdparty/mumlib/include/mumlib/Callback.hpp b/thirdparty/mumlib/include/mumlib/Callback.hpp new file mode 100644 index 000000000000..a11b7b4a89d1 --- /dev/null +++ b/thirdparty/mumlib/include/mumlib/Callback.hpp @@ -0,0 +1,292 @@ +#pragma once + +#include +#include +#include + +namespace mumlib { + + using namespace std; + + class Callback { + public: + virtual void version( + uint16_t major, + uint8_t minor, + uint8_t patch, + string release, + string os, + string os_version) { }; + + virtual void audio( + int target, + int sessionId, + int sequenceNumber, + int16_t *pcm_data, + uint32_t pcm_data_size) { }; + + virtual void unsupportedAudio( + int target, + int sessionId, + int sequenceNumber, + uint8_t *encoded_audio_data, + uint32_t encoded_audio_data_size) { }; + + virtual void serverSync( + string welcome_text, + int32_t session, + int32_t max_bandwidth, + int64_t permissions) { }; + + virtual void channelRemove(uint32_t channel_id) { }; + + virtual void channelState( + string name, + int32_t channel_id, + int32_t parent, + string description, + vector links, + vector inks_add, + vector links_remove, + bool temporary, + int32_t position) { }; + + virtual void userRemove( + uint32_t session, + int32_t actor, + string reason, + bool ban) { }; + + virtual void userState( + int32_t session, + int32_t actor, + string name, + int32_t user_id, + int32_t channel_id, + int32_t mute, + int32_t deaf, + int32_t suppress, + int32_t self_mute, + int32_t self_deaf, + string comment, + int32_t priority_speaker, + int32_t recording) { }; + + virtual void banList( + const uint8_t *ip_data, + uint32_t ip_data_size, + uint32_t mask, + string name, + string hash, + string reason, + string start, + int32_t duration) { }; + + virtual void textMessage( + uint32_t actor, + std::vector session, + std::vector channel_id, + std::vector tree_id, + string message) { }; + + virtual void permissionDenied( + int32_t permission, + int32_t channel_id, + int32_t session, + string reason, + int32_t deny_type, + string name) { }; + + virtual void queryUsers( + uint32_t n_ids, + uint32_t *ids, + uint32_t n_names, + string *names) { }; + + virtual void contextActionModify( + string action, + string text, + uint32_t m_context, + uint32_t operation) { }; + + virtual void contextAction( + int32_t session, + int32_t channel_id, + string action) { }; + + virtual void userList( + uint32_t user_id, + string name, + string last_seen, + int32_t last_channel) { }; + + virtual void permissionQuery( + int32_t channel_id, + uint32_t permissions, + int32_t flush) { }; + + virtual void codecVersion( + int32_t alpha, + int32_t beta, + uint32_t prefer_alpha, + int32_t opus) { }; + + virtual void serverConfig( + uint32_t max_bandwidth, + string welcome_text, + uint32_t allow_html, + uint32_t message_length, + uint32_t image_message_length) { }; + + virtual void suggestConfig( + uint32_t version, + uint32_t positional, + uint32_t push_to_talk) { }; + + }; + + class _BasicCallback_Private; + + class BasicCallback : public Callback { + public: + BasicCallback(); + + ~BasicCallback(); + + virtual void version( + uint16_t major, + uint8_t minor, + uint8_t patch, + string release, + string os, + string os_version) override; + + virtual void audio( + int target, + int sessionId, + int sequenceNumber, + int16_t *pcm_data, + uint32_t pcm_data_size) override; + + virtual void unsupportedAudio( + int target, + int sessionId, + int sequenceNumber, + uint8_t *encoded_audio_data, + uint32_t encoded_audio_data_size) override; + + virtual void serverSync( + string welcome_text, + int32_t session, + int32_t max_bandwidth, + int64_t permissions) override; + + virtual void channelRemove(uint32_t channel_id) override; + + virtual void channelState( + string name, + int32_t channel_id, + int32_t parent, + string description, + vector links, + vector inks_add, + vector links_remove, + bool temporary, + int32_t position) override; + + virtual void userRemove( + uint32_t session, + int32_t actor, + string reason, + bool ban) override; + + virtual void userState( + int32_t session, + int32_t actor, + string name, + int32_t user_id, + int32_t channel_id, + int32_t mute, + int32_t deaf, + int32_t suppress, + int32_t self_mute, + int32_t self_deaf, + string comment, + int32_t priority_speaker, + int32_t recording) override; + + virtual void banList( + const uint8_t *ip_data, + uint32_t ip_data_size, + uint32_t mask, + string name, + string hash, + string reason, + string start, + int32_t duration) override; + + virtual void textMessage( + uint32_t actor, + std::vector session, + std::vector channel_id, + std::vector tree_id, + string message) override; + + virtual void permissionDenied( + int32_t permission, + int32_t channel_id, + int32_t session, + string reason, + int32_t deny_type, + string name) override; + + virtual void queryUsers( + uint32_t n_ids, + uint32_t *ids, + uint32_t n_names, + string *names) override; + + virtual void contextActionModify( + string action, + string text, + uint32_t m_context, + uint32_t operation) override; + + virtual void contextAction( + int32_t session, + int32_t channel_id, + string action) override; + + virtual void userList( + uint32_t user_id, + string name, + string last_seen, + int32_t last_channel) override; + + virtual void permissionQuery( + int32_t channel_id, + uint32_t permissions, + int32_t flush) override; + + virtual void codecVersion( + int32_t alpha, + int32_t beta, + uint32_t prefer_alpha, + int32_t opus) override; + + virtual void serverConfig( + uint32_t max_bandwidth, + string welcome_text, + uint32_t allow_html, + uint32_t message_length, + uint32_t image_message_length) override; + + virtual void suggestConfig( + uint32_t version, + uint32_t positional, + uint32_t push_to_talk) override; + + private: + _BasicCallback_Private *impl; + }; +} \ No newline at end of file diff --git a/thirdparty/mumlib/include/mumlib/CryptState.hpp b/thirdparty/mumlib/include/mumlib/CryptState.hpp new file mode 100644 index 000000000000..cf5b61f2f63e --- /dev/null +++ b/thirdparty/mumlib/include/mumlib/CryptState.hpp @@ -0,0 +1,80 @@ +/* Copyright (C) 2005-2011, Thorvald Natvig + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + - Neither the name of the Mumble Developers nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#pragma once + +#include +#include + +namespace mumlib { + + class CryptState : boost::noncopyable { + public: + unsigned char raw_key[AES_BLOCK_SIZE]; + unsigned char encrypt_iv[AES_BLOCK_SIZE]; + unsigned char decrypt_iv[AES_BLOCK_SIZE]; + unsigned char decrypt_history[0x100]; + + unsigned int uiGood; + unsigned int uiLate; + unsigned int uiLost; + unsigned int uiResync; + + unsigned int uiRemoteGood; + unsigned int uiRemoteLate; + unsigned int uiRemoteLost; + unsigned int uiRemoteResync; + + AES_KEY encrypt_key; + AES_KEY decrypt_key; + bool bInit; + + CryptState(); + + bool isValid() const; + + void setKey(const unsigned char *rkey, const unsigned char *eiv, const unsigned char *div); + + void setDecryptIV(const unsigned char *iv); + + void ocb_encrypt(const unsigned char *plain, unsigned char *encrypted, unsigned int len, + const unsigned char *nonce, + unsigned char *tag); + + void ocb_decrypt(const unsigned char *encrypted, unsigned char *plain, unsigned int len, + const unsigned char *nonce, + unsigned char *tag); + + bool decrypt(const unsigned char *source, unsigned char *dst, unsigned int crypted_length); + + void encrypt(const unsigned char *source, unsigned char *dst, unsigned int plain_length); + }; + +}; diff --git a/thirdparty/mumlib/include/mumlib/Transport.hpp b/thirdparty/mumlib/include/mumlib/Transport.hpp new file mode 100644 index 000000000000..ccf94a72ce6d --- /dev/null +++ b/thirdparty/mumlib/include/mumlib/Transport.hpp @@ -0,0 +1,129 @@ +#pragma once + +#include "mumlib/CryptState.hpp" +#include "mumlib/VarInt.hpp" +#include "enums.hpp" + +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace mumlib { + + constexpr int MAX_UDP_LENGTH = 1024; + constexpr int MAX_TCP_LENGTH = 129 * 1024; // 128 kB + some reserve + + using namespace std; + using namespace boost::asio; + using namespace boost::asio::ip; + + typedef function ProcessControlMessageFunction; + + typedef function ProcessEncodedAudioPacketFunction; + + class TransportException : public MumlibException { + public: + TransportException(string message) : MumlibException(message) { } + }; + + class Transport : boost::noncopyable { + public: + Transport(io_service &ioService, + ProcessControlMessageFunction processControlMessageFunc, + ProcessEncodedAudioPacketFunction processEncodedAudioPacketFunction, + bool noUdp = false); + + ~Transport(); + + void connect(string host, + int port, + string user, + string password); + + void disconnect(); + + ConnectionState getConnectionState() { + return state; + } + + bool isUdpActive(); + + void sendControlMessage(MessageType type, google::protobuf::Message &message); + + void sendEncodedAudioPacket(uint8_t *buffer, int length); + + private: + log4cpp::Category &logger; + + io_service &ioService; + + pair connectionParams; + + pair credentials; + + ProcessControlMessageFunction processMessageFunction; + + ProcessEncodedAudioPacketFunction processEncodedAudioPacketFunction; + + const bool noUdp; + + volatile bool udpActive; + + ConnectionState state; + + udp::socket udpSocket; + ip::udp::endpoint udpReceiverEndpoint; + uint8_t udpIncomingBuffer[MAX_UDP_LENGTH]; + CryptState cryptState; + + ssl::context sslContext; + ssl::stream sslSocket; + uint8_t *sslIncomingBuffer; + + deadline_timer pingTimer; + std::chrono::time_point lastReceivedUdpPacketTimestamp; + + boost::pool<> asyncBufferPool; + + void pingTimerTick(const boost::system::error_code &e); + + void sslConnectHandler(const boost::system::error_code &error); + + void sslHandshakeHandler(const boost::system::error_code &error); + + void doReceiveSsl(); + + void sendSsl(uint8_t *buff, int length); + + void sendSslAsync(uint8_t *buff, int length); + + void sendControlMessagePrivate(MessageType type, google::protobuf::Message &message); + + void sendSslPing(); + + void sendVersion(); + + void sendAuthentication(); + + void processMessageInternal(MessageType messageType, uint8_t *buffer, int length); + + void doReceiveUdp(); + + void sendUdpAsync(uint8_t *buff, int length); + + void sendUdpPing(); + + void throwTransportException(string message); + + void processAudioPacket(uint8_t *buff, int length); + }; + + +} diff --git a/thirdparty/mumlib/include/mumlib/VarInt.hpp b/thirdparty/mumlib/include/mumlib/VarInt.hpp new file mode 100644 index 000000000000..451c61e9b9b0 --- /dev/null +++ b/thirdparty/mumlib/include/mumlib/VarInt.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include +#include +#include + +namespace mumlib { + class VarIntException : public MumlibException { + public: + VarIntException(std::string message) : MumlibException(message) { } + }; + + class VarInt { + public: + VarInt(uint8_t *encoded); + + VarInt(std::vector encoded); + + VarInt(int64_t value); + + int64_t getValue() const { + return this->value; + } + + std::vector getEncoded() const; + + private: + const int64_t value; + + int64_t parseVariant(uint8_t *buffer); + }; +} \ No newline at end of file diff --git a/thirdparty/mumlib/include/mumlib/enums.hpp b/thirdparty/mumlib/include/mumlib/enums.hpp new file mode 100644 index 000000000000..b4a9b436552d --- /dev/null +++ b/thirdparty/mumlib/include/mumlib/enums.hpp @@ -0,0 +1,48 @@ +#pragma once + +namespace mumlib { + enum class MessageType { + VERSION = 0, + UDPTUNNEL = 1, + AUTHENTICATE = 2, + PING = 3, + REJECT = 4, + SERVERSYNC = 5, + CHANNELREMOVE = 6, + CHANNELSTATE = 7, + USERREMOVE = 8, + USERSTATE = 9, + BANLIST = 10, + TEXTMESSAGE = 11, + PERMISSIONDENIED = 12, + ACL = 13, + QUERYUSERS = 14, + CRYPTSETUP = 15, + CONTEXTACTIONMODIFY = 16, + CONTEXTACTION = 17, + USERLIST = 18, + VOICETARGET = 19, + PERMISSIONQUERY = 20, + CODECVERSION = 21, + USERSTATS = 22, + REQUESTBLOB = 23, + SERVERCONFIG = 24, + SUGGESTCONFIG = 25 + }; + + enum class ConnectionState { + NOT_CONNECTED, + IN_PROGRESS, + CONNECTED, + FAILED + }; + + enum class AudioPacketType { + CELT_Alpha, + Ping, + Speex, + CELT_Beta, + OPUS + }; + +} \ No newline at end of file diff --git a/thirdparty/mumlib/mumlib_example.cpp b/thirdparty/mumlib/mumlib_example.cpp new file mode 100644 index 000000000000..2e874d6249a0 --- /dev/null +++ b/thirdparty/mumlib/mumlib_example.cpp @@ -0,0 +1,64 @@ +#include "mumlib.hpp" + +#include "log4cpp/Category.hh" +#include "log4cpp/FileAppender.hh" +#include "log4cpp/OstreamAppender.hh" + +#include +#include +#include + +class MyCallback : public mumlib::BasicCallback { +public: + mumlib::Mumlib *mum; + + virtual void audio(int target, + int sessionId, + int sequenceNumber, + int16_t *pcm_data, + uint32_t pcm_data_size) override { + mum->sendAudioData(pcm_data, pcm_data_size); + } + + virtual void textMessage( + uint32_t actor, + std::vector session, + std::vector channel_id, + std::vector tree_id, + std::string message) override { + mumlib::BasicCallback::textMessage(actor, session, channel_id, tree_id, message); + mum->sendTextMessage("someone said: " + message); + } +}; + +int main(int argc, char *argv[]) { + + log4cpp::Appender *appender1 = new log4cpp::OstreamAppender("console", &std::cout); + appender1->setLayout(new log4cpp::BasicLayout()); + log4cpp::Category &logger = log4cpp::Category::getRoot(); + logger.setPriority(log4cpp::Priority::NOTICE); + logger.addAppender(appender1); + + if (argc < 3) { + logger.crit("Usage: %s {server} {password}", argv[0]); + return 1; + } + + MyCallback myCallback; + + while (true) { + try { + mumlib::MumlibConfiguration conf; + conf.opusEncoderBitrate = 32000; + mumlib::Mumlib mum(myCallback, conf); + myCallback.mum = &mum; + mum.connect(argv[1], 64738, "mumlib_example", argv[2]); + mum.run(); + } catch (mumlib::TransportException &exp) { + logger.error("TransportException: %s.", exp.what()); + + logger.notice("Attempting to reconnect in 5 s."); + std::this_thread::sleep_for(std::chrono::seconds(5)); + } + } +} \ No newline at end of file diff --git a/thirdparty/mumlib/src/Audio.cpp b/thirdparty/mumlib/src/Audio.cpp new file mode 100644 index 000000000000..3ce75944b03e --- /dev/null +++ b/thirdparty/mumlib/src/Audio.cpp @@ -0,0 +1,189 @@ +#include "mumlib/Audio.hpp" + +#include + +static boost::posix_time::seconds RESET_SEQUENCE_NUMBER_INTERVAL(5); + +mumlib::Audio::Audio(int opusEncoderBitrate) + : logger(log4cpp::Category::getInstance("mumlib.Audio")), + opusDecoder(nullptr), + opusEncoder(nullptr), + outgoingSequenceNumber(0) { + + int error; + + opusDecoder = opus_decoder_create(SAMPLE_RATE, 1, &error); + if (error != OPUS_OK) { + throw AudioException((boost::format("failed to initialize OPUS decoder: %s") % opus_strerror(error)).str()); + } + + opusEncoder = opus_encoder_create(SAMPLE_RATE, 1, OPUS_APPLICATION_VOIP, &error); + if (error != OPUS_OK) { + throw AudioException((boost::format("failed to initialize OPUS encoder: %s") % opus_strerror(error)).str()); + } + + opus_encoder_ctl(opusEncoder, OPUS_SET_VBR(0)); + + setOpusEncoderBitrate(opusEncoderBitrate); + + resetEncoder(); +} + +mumlib::Audio::~Audio() { + if (opusDecoder) { + opus_decoder_destroy(opusDecoder); + } + + if (opusEncoder) { + opus_encoder_destroy(opusEncoder); + } +} + +void mumlib::Audio::setOpusEncoderBitrate(int bitrate) { + int error = opus_encoder_ctl(opusEncoder, OPUS_SET_BITRATE(bitrate)); + if (error != OPUS_OK) { + throw AudioException((boost::format("failed to initialize transmission bitrate to %d B/s: %s") + % bitrate % opus_strerror(error)).str()); + } +} + +int mumlib::Audio::getOpusEncoderBitrate() { + opus_int32 bitrate; + int error = opus_encoder_ctl(opusEncoder, OPUS_GET_BITRATE(&bitrate)); + if (error != OPUS_OK) { + throw AudioException((boost::format("failed to read Opus bitrate: %s") % opus_strerror(error)).str()); + } + return bitrate; +} + +std::pair mumlib::Audio::decodeOpusPayload(uint8_t *inputBuffer, + int inputLength, + int16_t *pcmBuffer, + int pcmBufferSize) { + int64_t opusDataLength; + + int dataPointer = 0; + VarInt varInt(inputBuffer); + opusDataLength = varInt.getValue(); + dataPointer += varInt.getEncoded().size(); + + bool lastPacket = (opusDataLength & 0x2000) != 0; + opusDataLength = opusDataLength & 0x1fff; + + if (inputLength < opusDataLength + dataPointer) { + throw AudioException((boost::format("invalid Opus payload (%d B): header %d B, expected Opus data length %d B") + % inputLength % dataPointer % opusDataLength).str()); + } + + int outputSize = opus_decode(opusDecoder, + reinterpret_cast(&inputBuffer[dataPointer]), + opusDataLength, + pcmBuffer, + pcmBufferSize, + 0); + + if (outputSize <= 0) { + throw AudioException((boost::format("failed to decode %d B of OPUS data: %s") % inputLength % + opus_strerror(outputSize)).str()); + } + + logger.debug("%d B of Opus data decoded to %d PCM samples, last packet: %d.", + opusDataLength, outputSize, lastPacket); + + return std::make_pair(outputSize, lastPacket); +} + +int mumlib::Audio::encodeAudioPacket(int target, int16_t *inputPcmBuffer, int inputLength, uint8_t *outputBuffer, + int outputBufferSize) { + + using namespace std::chrono; + + const int lastAudioPacketSentInterval = duration_cast( + system_clock::now() - lastEncodedAudioPacketTimestamp).count(); + + if (lastAudioPacketSentInterval > RESET_SEQUENCE_NUMBER_INTERVAL.total_milliseconds() + 1000) { + logger.debug("Last audio packet was sent %d ms ago, resetting encoder.", lastAudioPacketSentInterval); + resetEncoder(); + } + + std::vector header; + + header.push_back(0x80 | target); + + auto sequenceNumberEnc = VarInt(outgoingSequenceNumber).getEncoded(); + header.insert(header.end(), sequenceNumberEnc.begin(), sequenceNumberEnc.end()); + + uint8_t tmpOpusBuffer[1024]; + const int outputSize = opus_encode(opusEncoder, + inputPcmBuffer, + inputLength, + tmpOpusBuffer, + min(outputBufferSize, 1024) + ); + + if (outputSize <= 0) { + throw AudioException((boost::format("failed to encode %d B of PCM data: %s") % inputLength % + opus_strerror(outputSize)).str()); + } + + auto outputSizeEnc = VarInt(outputSize).getEncoded(); + header.insert(header.end(), outputSizeEnc.begin(), outputSizeEnc.end()); + + memcpy(outputBuffer, &header[0], header.size()); + memcpy(outputBuffer + header.size(), tmpOpusBuffer, outputSize); + + int incrementNumber = 100 * inputLength / SAMPLE_RATE; + + outgoingSequenceNumber += incrementNumber; + + lastEncodedAudioPacketTimestamp = std::chrono::system_clock::now(); + + return outputSize + header.size(); +} + +void mumlib::Audio::resetEncoder() { + int status = opus_encoder_ctl(opusEncoder, OPUS_RESET_STATE, nullptr); + + if (status != OPUS_OK) { + throw AudioException((boost::format("failed to reset encoder: %s") % opus_strerror(status)).str()); + } + + outgoingSequenceNumber = 0; +} + +mumlib::IncomingAudioPacket mumlib::Audio::decodeIncomingAudioPacket(uint8_t *inputBuffer, int inputBufferLength) { + mumlib::IncomingAudioPacket incomingAudioPacket; + + incomingAudioPacket.type = static_cast((inputBuffer[0] & 0xE0) >> 5); + incomingAudioPacket.target = inputBuffer[0] & 0x1F; + + std::array varInts = {&incomingAudioPacket.sessionId, &incomingAudioPacket.sequenceNumber}; + + int dataPointer = 1; + for (int64_t *val : varInts) { + VarInt varInt(&inputBuffer[dataPointer]); + *val = varInt.getValue(); + dataPointer += varInt.getEncoded().size(); + } + + incomingAudioPacket.audioPayload = &inputBuffer[dataPointer]; + incomingAudioPacket.audioPayloadLength = inputBufferLength - dataPointer; + + if (dataPointer >= inputBufferLength) { + throw AudioException((boost::format("invalid incoming audio packet (%d B): header %d B") % inputBufferLength % + dataPointer).str()); + } + + logger.debug( + "Received %d B of audio packet, %d B header, %d B payload (target: %d, sessionID: %ld, seq num: %ld).", + inputBufferLength, + dataPointer, + incomingAudioPacket.audioPayloadLength, + incomingAudioPacket.target, + incomingAudioPacket.sessionId, + incomingAudioPacket.sequenceNumber); + + return incomingAudioPacket; +} + + diff --git a/thirdparty/mumlib/src/Callback.cpp b/thirdparty/mumlib/src/Callback.cpp new file mode 100644 index 000000000000..ea8fb804ddff --- /dev/null +++ b/thirdparty/mumlib/src/Callback.cpp @@ -0,0 +1,135 @@ +#include "mumlib/Callback.hpp" + +#include +#include + +using namespace std; +using namespace mumlib; + +namespace mumlib { + struct _BasicCallback_Private : boost::noncopyable { + public: + _BasicCallback_Private() : logger(log4cpp::Category::getInstance("mumlib.BasicCallback")) { } + + log4cpp::Category &logger; + }; + +} + +mumlib::BasicCallback::BasicCallback() { + impl = new _BasicCallback_Private(); +} + +mumlib::BasicCallback::~BasicCallback() { + delete impl; +} + +void mumlib::BasicCallback::version( + uint16_t major, + uint8_t minor, + uint8_t patch, + string release, + string os, + string os_version) { + impl->logger.debug("version: v%d.%d.%d. %s/%s/%s\n", major, minor, patch, release.c_str(), os.c_str(), + os_version.c_str()); +} + +void mumlib::BasicCallback::audio( + int target, + int sessionId, + int sequenceNumber, + int16_t *pcmData, + uint32_t pcm_data_size) { + impl->logger.debug("audio: %d bytes of raw PCM data, target: %d, session: %d, seq: %d.", + pcm_data_size, target, sessionId, sequenceNumber); +} + +void BasicCallback::unsupportedAudio( + int target, + int sessionId, + int sequenceNumber, + uint8_t *encoded_audio_data, + uint32_t encoded_audio_data_size) { + impl->logger.debug("unsupportedAudio: received %d bytes of encoded data, target: %d, session: %d, seq: %d.", + encoded_audio_data_size, target, sessionId, sequenceNumber); +} + +void BasicCallback::serverSync(string welcome_text, int32_t session, int32_t max_bandwidth, int64_t permissions) { + impl->logger.debug("serverSync: text: %s, session: %d, max bandwidth: %d, permissions: %d", welcome_text.c_str(), + session, + max_bandwidth, permissions); +} + +void BasicCallback::channelRemove(uint32_t channel_id) { + impl->logger.debug("channelRemove: %d", channel_id); +} + +void BasicCallback::channelState(string name, int32_t channel_id, int32_t parent, string description, + vector links, vector inks_add, vector links_remove, + bool temporary, int32_t position) { + impl->logger.debug("channelState: %d: %s, %s", channel_id, name.c_str(), description.c_str()); +} + +void BasicCallback::userRemove(uint32_t session, int32_t actor, string reason, bool ban) { + impl->logger.debug("userRemove: session: %d, actor: %d, reason: %s, ban: %d.", session, actor, reason.c_str(), ban); +} + +void BasicCallback::userState(int32_t session, int32_t actor, string name, int32_t user_id, int32_t channel_id, + int32_t mute, int32_t deaf, int32_t suppress, int32_t self_mute, int32_t self_deaf, + string comment, int32_t priority_speaker, int32_t recording) { + impl->logger.debug("userState: %s: mute: %d, deaf: %d, suppress: %d, self mute: %d, self deaf: %d", + name.c_str(), mute, deaf, suppress, self_mute, self_deaf); +} + +void BasicCallback::banList(const uint8_t *ip_data, uint32_t ip_data_size, uint32_t mask, string name, string hash, + string reason, string start, int32_t duration) { + impl->logger.debug("banList: %s, hash: %s, reason: %s", name.c_str(), hash.c_str(), reason.c_str()); +} + +void BasicCallback::textMessage( + uint32_t actor, + std::vector session, + std::vector channel_id, + std::vector tree_id, + string message) { + impl->logger.debug("textMessage: %d: %s", actor, message.c_str()); +} + +void BasicCallback::permissionDenied(int32_t permission, int32_t channel_id, int32_t session, string reason, + int32_t deny_type, string name) { + impl->logger.debug("permissionDenied: %s %s", name.c_str(), reason.c_str()); +} + +void BasicCallback::queryUsers(uint32_t n_ids, uint32_t *ids, uint32_t n_names, string *names) { + impl->logger.debug("queryUsers: %d users", n_names); //todo make it more high-level +} + +void BasicCallback::contextActionModify(string action, string text, uint32_t m_context, uint32_t operation) { + impl->logger.debug("contextActionModify: "); +} + +void BasicCallback::contextAction(int32_t session, int32_t channel_id, string action) { + impl->logger.debug("contextAction."); +} + +void BasicCallback::userList(uint32_t user_id, string name, string last_seen, int32_t last_channel) { + impl->logger.debug("userList."); +} + +void BasicCallback::permissionQuery(int32_t channel_id, uint32_t permissions, int32_t flush) { + impl->logger.debug("permissionQuery."); +} + +void BasicCallback::codecVersion(int32_t alpha, int32_t beta, uint32_t prefer_alpha, int32_t opus) { + impl->logger.debug("codecVersion."); +} + +void BasicCallback::serverConfig(uint32_t max_bandwidth, string welcome_text, uint32_t allow_html, + uint32_t message_length, uint32_t image_message_length) { + impl->logger.debug("serverConfig: %s", welcome_text.c_str()); +} + +void BasicCallback::suggestConfig(uint32_t version, uint32_t positional, uint32_t push_to_talk) { + impl->logger.debug("suggestConfig."); +} diff --git a/thirdparty/mumlib/src/CryptState.cpp b/thirdparty/mumlib/src/CryptState.cpp new file mode 100644 index 000000000000..9c5937a4b6d3 --- /dev/null +++ b/thirdparty/mumlib/src/CryptState.cpp @@ -0,0 +1,286 @@ +/* Copyright (C) 2005-2011, Thorvald Natvig + + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + + - Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + - Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + - Neither the name of the Mumble Developers nor the names of its + contributors may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR + CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* + * This code implements OCB-AES128. + * In the US, OCB is covered by patents. The inventor has given a license + * to all programs distributed under the GPL. + * Mumble is BSD (revised) licensed, meaning you can use the code in a + * closed-source program. If you do, you'll have to either replace + * OCB with something else or get yourself a license. + */ + + +#include "mumlib/CryptState.hpp" + +#include +#include + +using namespace std; + +mumlib::CryptState::CryptState() { + for (int i = 0; i < 0x100; i++) + decrypt_history[i] = 0; + bInit = false; + uiGood = uiLate = uiLost = uiResync = 0; + uiRemoteGood = uiRemoteLate = uiRemoteLost = uiRemoteResync = 0; +} + +bool mumlib::CryptState::isValid() const { + return bInit; +} + +void mumlib::CryptState::setKey(const unsigned char *rkey, const unsigned char *eiv, const unsigned char *div) { + memcpy(raw_key, rkey, AES_BLOCK_SIZE); + memcpy(encrypt_iv, eiv, AES_BLOCK_SIZE); + memcpy(decrypt_iv, div, AES_BLOCK_SIZE); + AES_set_encrypt_key(raw_key, 128, &encrypt_key); + AES_set_decrypt_key(raw_key, 128, &decrypt_key); + bInit = true; +} + +void mumlib::CryptState::setDecryptIV(const unsigned char *iv) { + memcpy(decrypt_iv, iv, AES_BLOCK_SIZE); +} + +void mumlib::CryptState::encrypt(const unsigned char *source, unsigned char *dst, unsigned int plain_length) { + unsigned char tag[AES_BLOCK_SIZE]; + + // First, increase our IV. + for (int i = 0; i < AES_BLOCK_SIZE; i++) + if (++encrypt_iv[i]) + break; + + ocb_encrypt(source, dst + 4, plain_length, encrypt_iv, tag); + + dst[0] = encrypt_iv[0]; + dst[1] = tag[0]; + dst[2] = tag[1]; + dst[3] = tag[2]; +} + +bool mumlib::CryptState::decrypt(const unsigned char *source, unsigned char *dst, unsigned int crypted_length) { + if (crypted_length < 4) + return false; + + unsigned int plain_length = crypted_length - 4; + + unsigned char saveiv[AES_BLOCK_SIZE]; + unsigned char ivbyte = source[0]; + bool restore = false; + unsigned char tag[AES_BLOCK_SIZE]; + + int lost = 0; + int late = 0; + + memcpy(saveiv, decrypt_iv, AES_BLOCK_SIZE); + + if (((decrypt_iv[0] + 1) & 0xFF) == ivbyte) { + // In order as expected. + if (ivbyte > decrypt_iv[0]) { + decrypt_iv[0] = ivbyte; + } else if (ivbyte < decrypt_iv[0]) { + decrypt_iv[0] = ivbyte; + for (int i = 1; i < AES_BLOCK_SIZE; i++) + if (++decrypt_iv[i]) + break; + } else { + return false; + } + } else { + // This is either out of order or a repeat. + + int diff = ivbyte - decrypt_iv[0]; + if (diff > 128) + diff = diff - 256; + else if (diff < -128) + diff = diff + 256; + + if ((ivbyte < decrypt_iv[0]) && (diff > -30) && (diff < 0)) { + // Late packet, but no wraparound. + late = 1; + lost = -1; + decrypt_iv[0] = ivbyte; + restore = true; + } else if ((ivbyte > decrypt_iv[0]) && (diff > -30) && (diff < 0)) { + // Last was 0x02, here comes 0xff from last round + late = 1; + lost = -1; + decrypt_iv[0] = ivbyte; + for (int i = 1; i < AES_BLOCK_SIZE; i++) + if (decrypt_iv[i]--) + break; + restore = true; + } else if ((ivbyte > decrypt_iv[0]) && (diff > 0)) { + // Lost a few packets, but beyond that we're good. + lost = ivbyte - decrypt_iv[0] - 1; + decrypt_iv[0] = ivbyte; + } else if ((ivbyte < decrypt_iv[0]) && (diff > 0)) { + // Lost a few packets, and wrapped around + lost = 256 - decrypt_iv[0] + ivbyte - 1; + decrypt_iv[0] = ivbyte; + for (int i = 1; i < AES_BLOCK_SIZE; i++) + if (++decrypt_iv[i]) + break; + } else { + return false; + } + + if (decrypt_history[decrypt_iv[0]] == decrypt_iv[1]) { + memcpy(decrypt_iv, saveiv, AES_BLOCK_SIZE); + return false; + } + } + + ocb_decrypt(source + 4, dst, plain_length, decrypt_iv, tag); + + if (memcmp(tag, source + 1, 3) != 0) { + memcpy(decrypt_iv, saveiv, AES_BLOCK_SIZE); + return false; + } + decrypt_history[decrypt_iv[0]] = decrypt_iv[1]; + + if (restore) + memcpy(decrypt_iv, saveiv, AES_BLOCK_SIZE); + + uiGood++; + uiLate += late; + uiLost += lost; + + return true; +} + +#define BLOCKSIZE 2 +#define SHIFTBITS 63 +typedef uint64_t subblock; + +#define SWAP64(x) (__builtin_bswap64(x)) +#define SWAPPED(x) SWAP64(x) + +typedef subblock keyblock[BLOCKSIZE]; + +static void inline XOR(subblock *dst, const subblock *a, const subblock *b) { + for (int i = 0; i < BLOCKSIZE; i++) { + dst[i] = a[i] ^ b[i]; + } +} + +static void inline S2(subblock *block) { + subblock carry = SWAPPED(block[0]) >> SHIFTBITS; + for (int i = 0; i < BLOCKSIZE - 1; i++) + block[i] = SWAPPED((SWAPPED(block[i]) << 1) | (SWAPPED(block[i + 1]) >> SHIFTBITS)); + block[BLOCKSIZE - 1] = SWAPPED((SWAPPED(block[BLOCKSIZE - 1]) << 1) ^ (carry * 0x87)); +} + +static void inline S3(subblock *block) { + subblock carry = SWAPPED(block[0]) >> SHIFTBITS; + for (int i = 0; i < BLOCKSIZE - 1; i++) + block[i] ^= SWAPPED((SWAPPED(block[i]) << 1) | (SWAPPED(block[i + 1]) >> SHIFTBITS)); + block[BLOCKSIZE - 1] ^= SWAPPED((SWAPPED(block[BLOCKSIZE - 1]) << 1) ^ (carry * 0x87)); +} + +static void inline ZERO(keyblock &block) { + for (int i = 0; i < BLOCKSIZE; i++) + block[i] = 0; +} + +#define AESencrypt(src, dst, key) AES_encrypt(reinterpret_cast(src),reinterpret_cast(dst), key); +#define AESdecrypt(src, dst, key) AES_decrypt(reinterpret_cast(src),reinterpret_cast(dst), key); + +void mumlib::CryptState::ocb_encrypt(const unsigned char *plain, unsigned char *encrypted, unsigned int len, + const unsigned char *nonce, unsigned char *tag) { + keyblock checksum, delta, tmp, pad; + + // Initialize + AESencrypt(nonce, delta, &encrypt_key); + ZERO(checksum); + + while (len > AES_BLOCK_SIZE) { + S2(delta); + XOR(tmp, delta, reinterpret_cast(plain)); + AESencrypt(tmp, tmp, &encrypt_key); + XOR(reinterpret_cast(encrypted), delta, tmp); + XOR(checksum, checksum, reinterpret_cast(plain)); + len -= AES_BLOCK_SIZE; + plain += AES_BLOCK_SIZE; + encrypted += AES_BLOCK_SIZE; + } + + S2(delta); + ZERO(tmp); + tmp[BLOCKSIZE - 1] = SWAPPED(len * 8); + XOR(tmp, tmp, delta); + AESencrypt(tmp, pad, &encrypt_key); + memcpy(tmp, plain, len); + memcpy(reinterpret_cast(tmp) + len, reinterpret_cast(pad) + len, + AES_BLOCK_SIZE - len); + XOR(checksum, checksum, tmp); + XOR(tmp, pad, tmp); + memcpy(encrypted, tmp, len); + + S3(delta); + XOR(tmp, delta, checksum); + AESencrypt(tmp, tag, &encrypt_key); +} + +void mumlib::CryptState::ocb_decrypt(const unsigned char *encrypted, unsigned char *plain, unsigned int len, + const unsigned char *nonce, unsigned char *tag) { + keyblock checksum, delta, tmp, pad; + + // Initialize + AESencrypt(nonce, delta, &encrypt_key); + ZERO(checksum); + + while (len > AES_BLOCK_SIZE) { + S2(delta); + XOR(tmp, delta, reinterpret_cast(encrypted)); + AESdecrypt(tmp, tmp, &decrypt_key); + XOR(reinterpret_cast(plain), delta, tmp); + XOR(checksum, checksum, reinterpret_cast(plain)); + len -= AES_BLOCK_SIZE; + plain += AES_BLOCK_SIZE; + encrypted += AES_BLOCK_SIZE; + } + + S2(delta); + ZERO(tmp); + tmp[BLOCKSIZE - 1] = SWAPPED(len * 8); + XOR(tmp, tmp, delta); + AESencrypt(tmp, pad, &encrypt_key); + memset(tmp, 0, AES_BLOCK_SIZE); + memcpy(tmp, encrypted, len); + XOR(tmp, tmp, pad); + XOR(checksum, checksum, tmp); + memcpy(plain, tmp, len); + + S3(delta); + XOR(tmp, delta, checksum); + AESencrypt(tmp, tag, &encrypt_key); +} diff --git a/thirdparty/mumlib/src/Transport.cpp b/thirdparty/mumlib/src/Transport.cpp new file mode 100644 index 000000000000..f13451275954 --- /dev/null +++ b/thirdparty/mumlib/src/Transport.cpp @@ -0,0 +1,566 @@ +#include "mumlib/Transport.hpp" + +#include "Mumble.pb.h" + +#include + +using namespace std; + +static boost::posix_time::seconds PING_INTERVAL(5); + +const long CLIENT_VERSION = 0x01020A; +const string CLIENT_RELEASE("Mumlib"); +const string CLIENT_OS("OS Unknown"); +const string CLIENT_OS_VERSION("1"); + +static map rejectMessages = { + {MumbleProto::Reject_RejectType_None, "no reason provided"}, + {MumbleProto::Reject_RejectType_WrongVersion, "wrong version"}, + {MumbleProto::Reject_RejectType_InvalidUsername, "invalid username"}, + {MumbleProto::Reject_RejectType_WrongUserPW, "wrong user password"}, + {MumbleProto::Reject_RejectType_WrongServerPW, "wrong server password"}, + {MumbleProto::Reject_RejectType_UsernameInUse, "username in use"}, + {MumbleProto::Reject_RejectType_ServerFull, "server full"}, + {MumbleProto::Reject_RejectType_NoCertificate, "no certificate provided"}, + {MumbleProto::Reject_RejectType_AuthenticatorFail, "authenticator fail"} +}; + +mumlib::Transport::Transport( + io_service &ioService, + mumlib::ProcessControlMessageFunction processMessageFunc, + ProcessEncodedAudioPacketFunction processEncodedAudioPacketFunction, + bool noUdp) : + logger(log4cpp::Category::getInstance("mumlib.Transport")), + ioService(ioService), + processMessageFunction(processMessageFunc), + processEncodedAudioPacketFunction(processEncodedAudioPacketFunction), + noUdp(noUdp), + state(ConnectionState::NOT_CONNECTED), + udpSocket(ioService), + sslContext(ssl::context::sslv23), + sslSocket(ioService, sslContext), + pingTimer(ioService, PING_INTERVAL), + asyncBufferPool(max(MAX_UDP_LENGTH, MAX_TCP_LENGTH)) { + + sslIncomingBuffer = new uint8_t[MAX_TCP_LENGTH]; + + pingTimer.async_wait(boost::bind(&Transport::pingTimerTick, this, _1)); +} + +mumlib::Transport::~Transport() { + disconnect(); + delete[] sslIncomingBuffer; +} + +void mumlib::Transport::connect( + std::string host, + int port, + std::string user, + std::string password) { + + state = ConnectionState::IN_PROGRESS; + + connectionParams = make_pair(host, port); + credentials = make_pair(user, password); + + udpActive = false; + + sslSocket.set_verify_mode(boost::asio::ssl::verify_peer); + + //todo for now it accepts every certificate, move it to callback + sslSocket.set_verify_callback([](bool preverified, boost::asio::ssl::verify_context &ctx) { + return true; + }); + + try { + if (not noUdp) { + ip::udp::resolver resolverUdp(ioService); + ip::udp::resolver::query queryUdp(ip::udp::v4(), host, to_string(port)); + udpReceiverEndpoint = *resolverUdp.resolve(queryUdp); + udpSocket.open(ip::udp::v4()); + + doReceiveUdp(); + } + + ip::tcp::resolver resolverTcp(ioService); + ip::tcp::resolver::query queryTcp(host, to_string(port)); + + async_connect(sslSocket.lowest_layer(), resolverTcp.resolve(queryTcp), + bind(&Transport::sslConnectHandler, this, boost::asio::placeholders::error)); + } catch (runtime_error &exp) { + throwTransportException(string("failed to establish connection: ") + exp.what()); + } +} + +void mumlib::Transport::disconnect() { + + if (state != ConnectionState::NOT_CONNECTED) { + boost::system::error_code errorCode; + + // todo perform different operations for each ConnectionState + + sslSocket.shutdown(errorCode); + if (errorCode) { + logger.warn("SSL socket shutdown returned an error: %s.", errorCode.message().c_str()); + } + + sslSocket.lowest_layer().shutdown(tcp::socket::shutdown_both, errorCode); + if (errorCode) { + logger.warn("SSL socket lowest layer shutdown returned an error: %s.", errorCode.message().c_str()); + } + + udpSocket.close(errorCode); + if (errorCode) { + logger.warn("UDP socket close returned error: %s.", errorCode.message().c_str()); + } + + state = ConnectionState::NOT_CONNECTED; + } +} + + +void mumlib::Transport::sendVersion() { + MumbleProto::Version version; + + version.set_version(CLIENT_VERSION); + version.set_os(CLIENT_OS); + version.set_release(CLIENT_RELEASE); + version.set_os_version(CLIENT_OS_VERSION); + + logger.info("Sending version information."); + + sendControlMessagePrivate(MessageType::VERSION, version); +} + +void mumlib::Transport::sendAuthentication() { + string user, password; + tie(user, password) = credentials; + + MumbleProto::Authenticate authenticate; + authenticate.set_username(user); + authenticate.set_password(password); + authenticate.clear_celt_versions(); + authenticate.clear_tokens(); + authenticate.set_opus(true); + + logger.info("Sending authententication."); + + sendControlMessagePrivate(MessageType::AUTHENTICATE, authenticate); +} + +void mumlib::Transport::sendSslPing() { + MumbleProto::Ping ping; + ping.set_timestamp(std::time(nullptr)); + + logger.debug("Sending SSL ping."); + + sendControlMessagePrivate(MessageType::PING, ping); +} + + +bool mumlib::Transport::isUdpActive() { + return udpActive; +} + +void mumlib::Transport::doReceiveUdp() { + if (state == ConnectionState::NOT_CONNECTED) { + return; + } + + udpSocket.async_receive_from( + buffer(udpIncomingBuffer, MAX_UDP_LENGTH), + udpReceiverEndpoint, + [this](const boost::system::error_code &ec, size_t bytesTransferred) { + if (!ec and bytesTransferred > 0) { + logger.debug("Received UDP packet of %d B.", bytesTransferred); + + if (not cryptState.isValid()) { + throwTransportException("received UDP packet before CRYPT SETUP message"); + } else { + lastReceivedUdpPacketTimestamp = std::chrono::system_clock::now(); + + if (udpActive == false) { + udpActive = true; + logger.notice("UDP is up."); + } + + uint8_t plainBuffer[1024]; + const int plainBufferLength = bytesTransferred - 4; + + bool success = cryptState.decrypt( + udpIncomingBuffer, plainBuffer, bytesTransferred); + + if (not success) { + throwTransportException("UDP packet decryption failed"); + } + + processAudioPacket(plainBuffer, plainBufferLength); + } + + doReceiveUdp(); + } else if (ec == boost::asio::error::operation_aborted) { + logger.debug("UDP receive function cancelled."); + } else { + throwTransportException("UDP receive failed: " + ec.message()); + } + }); +} + +void mumlib::Transport::sslConnectHandler(const boost::system::error_code &error) { + if (!error) { + sslSocket.async_handshake(ssl::stream_base::client, + boost::bind(&Transport::sslHandshakeHandler, this, + boost::asio::placeholders::error)); + } + else { + throwTransportException((boost::format("Connect failed: %s.") % error.message()).str()); + } +} + +void mumlib::Transport::sslHandshakeHandler(const boost::system::error_code &error) { + if (!error) { + doReceiveSsl(); + + sendVersion(); + sendAuthentication(); + } + else { + throwTransportException((boost::format("Handshake failed: %s.") % error.message()).str()); + } +} + +void mumlib::Transport::pingTimerTick(const boost::system::error_code &e) { + if (state == ConnectionState::CONNECTED) { + + sendSslPing(); + + if (not noUdp) { + using namespace std::chrono; + + sendUdpPing(); + + if (udpActive) { + const int lastUdpReceivedMilliseconds = duration_cast( + system_clock::now() - lastReceivedUdpPacketTimestamp).count(); + + if (lastUdpReceivedMilliseconds > PING_INTERVAL.total_milliseconds() + 1000) { + udpActive = false; + logger.warn("Didn't receive UDP ping in %d ms, falling back to TCP.", lastUdpReceivedMilliseconds); + } + } + } + } + + pingTimer.expires_at(pingTimer.expires_at() + PING_INTERVAL); + pingTimer.async_wait(boost::bind(&Transport::pingTimerTick, this, _1)); +} + +void mumlib::Transport::sendUdpAsync(uint8_t *buff, int length) { + if (length > MAX_UDP_LENGTH - 4) { + throwTransportException("maximum allowed data length is %d" + to_string(MAX_UDP_LENGTH - 4)); + } + + auto *encryptedMsgBuff = asyncBufferPool.malloc(); + const int encryptedMsgLength = length + 4; + + cryptState.encrypt(buff, reinterpret_cast(encryptedMsgBuff), length); + + logger.debug("Sending %d B of data UDP asynchronously.", encryptedMsgLength); + + udpSocket.async_send_to( + boost::asio::buffer(encryptedMsgBuff, length + 4), + udpReceiverEndpoint, + [this, encryptedMsgBuff](const boost::system::error_code &ec, size_t bytesTransferred) { + asyncBufferPool.free(encryptedMsgBuff); + if (!ec and bytesTransferred > 0) { + logger.debug("Sent %d B via UDP.", bytesTransferred); + } else { + throwTransportException("UDP send failed: " + ec.message()); + } + }); +} + +void mumlib::Transport::doReceiveSsl() { + if (state == ConnectionState::NOT_CONNECTED) { + return; + } + + async_read( + sslSocket, + boost::asio::buffer(sslIncomingBuffer, MAX_TCP_LENGTH), + [this](const boost::system::error_code &error, size_t bytesTransferred) -> size_t { + if (bytesTransferred < 6) { + // we need the message header to determine the payload length + return 6 - bytesTransferred; + } + + const int payloadSize = ntohl(*reinterpret_cast(sslIncomingBuffer + 2)); + const int wholeMessageLength = payloadSize + 6; + size_t remaining = wholeMessageLength - bytesTransferred; + remaining = max(remaining, (size_t) 0); + + if (wholeMessageLength > MAX_TCP_LENGTH) { + throwTransportException( + (boost::format("message bigger (%d B) than max allowed size (%d B)") + % wholeMessageLength % MAX_TCP_LENGTH).str()); + } + + return remaining; + }, + [this](const boost::system::error_code &ec, size_t bytesTransferred) { + if (!ec and bytesTransferred > 0) { + + int messageType = ntohs(*reinterpret_cast(sslIncomingBuffer)); + + logger.debug("Received %d B of data (%d B payload, type %d).", bytesTransferred, + bytesTransferred - 6, messageType); + + processMessageInternal( + static_cast(messageType), + &sslIncomingBuffer[6], + bytesTransferred - 6); + + doReceiveSsl(); + } else { + logger.error("SSL receiver error: %s. Bytes transferred: %d.", + ec.message().c_str(), bytesTransferred); + //todo temporarily disable exception throwing until issue #6 is solved + //throwTransportException("receive failed: " + ec.message()); + } + }); +} + +void mumlib::Transport::processMessageInternal(MessageType messageType, uint8_t *buffer, int length) { + switch (messageType) { + + case MessageType::UDPTUNNEL: { + logger.debug("Received %d B of encoded audio data via TCP.", length); + processAudioPacket(buffer, length); + } + break; + case MessageType::AUTHENTICATE: { + logger.warn("Authenticate message received after authenticated."); + } + break; + case MessageType::PING: { + MumbleProto::Ping ping; + ping.ParseFromArray(buffer, length); + stringstream log; + log << "Received ping."; + if (ping.has_good()) { + log << " good: " << ping.good(); + } + if (ping.has_late()) { + log << " late: " << ping.late(); + } + if (ping.has_lost()) { + log << " lost: " << ping.lost(); + } + if (ping.has_tcp_ping_avg()) { + log << " TCP avg: " << ping.tcp_ping_avg() << " ms"; + } + if (ping.has_udp_ping_avg()) { + log << " UDP avg: " << ping.udp_ping_avg() << " ms"; + } + logger.debug(log.str()); + } + break; + case MessageType::REJECT: { + MumbleProto::Reject reject; + reject.ParseFromArray(buffer, length); + + stringstream errorMesg; + errorMesg << "failed to authenticate"; + + if (reject.has_type()) { + errorMesg << ": " << rejectMessages.at(reject.type()); + } + + if (reject.has_reason()) { + errorMesg << ", reason: " << reject.reason(); + } + + throwTransportException(errorMesg.str()); + } + break; + case MessageType::SERVERSYNC: { + state = ConnectionState::CONNECTED; + + logger.debug("SERVERSYNC. Calling external ProcessControlMessageFunction."); + processMessageFunction(messageType, buffer, length); + } + break; + case MessageType::CRYPTSETUP: { + if (not noUdp) { + MumbleProto::CryptSetup cryptsetup; + cryptsetup.ParseFromArray(buffer, length); + + if (cryptsetup.client_nonce().length() != AES_BLOCK_SIZE + or cryptsetup.server_nonce().length() != AES_BLOCK_SIZE + or cryptsetup.key().length() != AES_BLOCK_SIZE) { + throwTransportException("one of cryptographic parameters has invalid length"); + } + + cryptState.setKey( + reinterpret_cast(cryptsetup.key().c_str()), + reinterpret_cast(cryptsetup.client_nonce().c_str()), + reinterpret_cast(cryptsetup.server_nonce().c_str())); + + if (not cryptState.isValid()) { + throwTransportException("crypt setup data not valid"); + } + + logger.info("Set up cryptography for UDP transport. Sending UDP ping."); + + sendUdpPing(); + + } else { + logger.info("Ignoring crypt setup message, because UDP is disabled."); + } + } + break; + default: { + logger.debug("Calling external ProcessControlMessageFunction."); + processMessageFunction(messageType, buffer, length); + } + break; + } +} + +void mumlib::Transport::sendUdpPing() { + if (state == ConnectionState::NOT_CONNECTED) { + logger.debug("State changed to NOT_CONNECTED, skipping UDP ping."); + return; + } + + logger.debug("Sending UDP ping."); + + vector message; + message.push_back(0x20); + + auto timestampVarint = VarInt(time(nullptr)).getEncoded(); + message.insert(message.end(), timestampVarint.begin(), timestampVarint.end()); + + sendUdpAsync(&message[0], message.size()); +} + +void mumlib::Transport::sendSsl(uint8_t *buff, int length) { + if (length > MAX_TCP_LENGTH) { + logger.warn("Sending %d B of data via SSL. Maximal allowed data length to receive is %d B.", length, + MAX_TCP_LENGTH); + } + + logger.debug("Sending %d bytes of data.", length); + + try { + write(sslSocket, boost::asio::buffer(buff, length)); + } catch (boost::system::system_error &err) { + throwTransportException(std::string("SSL send failed: ") + err.what()); + } +} + +void mumlib::Transport::sendSslAsync(uint8_t *buff, int length) { + if (length > MAX_TCP_LENGTH) { + logger.warn("Sending %d B of data via SSL. Maximal allowed data length to receive is %d B.", length, + MAX_TCP_LENGTH); + } + + auto *asyncBuff = asyncBufferPool.malloc(); + + memcpy(asyncBuff, buff, length); + + logger.debug("Sending %d B of data asynchronously.", length); + + async_write( + sslSocket, + boost::asio::buffer(asyncBuff, length), + [this, asyncBuff](const boost::system::error_code &ec, size_t bytesTransferred) { + asyncBufferPool.free(asyncBuff); + logger.debug("Sent %d B.", bytesTransferred); + if (!ec and bytesTransferred > 0) { + + } else { + throwTransportException("async SSL send failed: " + ec.message()); + } + }); +} + +void mumlib::Transport::sendControlMessage(MessageType type, google::protobuf::Message &message) { + if (state != ConnectionState::CONNECTED) { + logger.warn("Connection not established."); + return; + } + sendControlMessagePrivate(type, message); +} + +void mumlib::Transport::sendControlMessagePrivate(MessageType type, google::protobuf::Message &message) { + + + const uint16_t type_network = htons(static_cast(type)); + + const int size = message.ByteSize(); + const uint32_t size_network = htonl(size); + + const int length = sizeof(type_network) + sizeof(size_network) + size; + + uint8_t buff[MAX_TCP_LENGTH]; + + memcpy(buff, &type_network, sizeof(type_network)); + + memcpy(buff + sizeof(type_network), &size_network, sizeof(size_network)); + + message.SerializeToArray(buff + sizeof(type_network) + sizeof(size_network), size); + + sendSsl(buff, length); +} + +void mumlib::Transport::throwTransportException(string message) { + state = ConnectionState::FAILED; + + throw TransportException(message); +} + +void mumlib::Transport::sendEncodedAudioPacket(uint8_t *buffer, int length) { + if (state != ConnectionState::CONNECTED) { + logger.warn("Connection not established."); + return; + } + + if (udpActive) { + logger.info("Sending %d B of audio data via UDP.", length); + sendUdpAsync(buffer, length); + } else { + logger.info("Sending %d B of audio data via TCP.", length); + + const uint16_t netUdptunnelType = htons(static_cast(MessageType::UDPTUNNEL)); + + const uint32_t netLength = htonl(length); + + const int packet = sizeof(netUdptunnelType) + sizeof(netLength) + length; + + uint8_t packetBuff[MAX_TCP_LENGTH]; + + memcpy(packetBuff, &netUdptunnelType, sizeof(netUdptunnelType)); + memcpy(packetBuff + sizeof(netUdptunnelType), &netLength, sizeof(netLength)); + memcpy(packetBuff + sizeof(netUdptunnelType) + sizeof(netLength), buffer, length); + + sendSslAsync(packetBuff, length + sizeof(netUdptunnelType) + sizeof(netLength)); + } +} + +void mumlib::Transport::processAudioPacket(uint8_t *buff, int length) { + AudioPacketType type = static_cast((buff[0] & 0xE0) >> 5); + switch (type) { + case AudioPacketType::CELT_Alpha: + case AudioPacketType::Speex: + case AudioPacketType::CELT_Beta: + case AudioPacketType::OPUS: + processEncodedAudioPacketFunction(type, buff, length); + break; + case AudioPacketType::Ping: + break; + default: + logger.error("Not recognized audio type: %xd.", buff[0]); + } +} + diff --git a/thirdparty/mumlib/src/VarInt.cpp b/thirdparty/mumlib/src/VarInt.cpp new file mode 100644 index 000000000000..2eeebf87438e --- /dev/null +++ b/thirdparty/mumlib/src/VarInt.cpp @@ -0,0 +1,83 @@ +#include "mumlib/VarInt.hpp" + +#include + +mumlib::VarInt::VarInt(int64_t value) : value(value) { } + +mumlib::VarInt::VarInt(uint8_t *encoded) : value(parseVariant(encoded)) { } + +mumlib::VarInt::VarInt(std::vector encoded) : value(parseVariant(&encoded[0])) { } + +/* + * This code was taken from Mumble source code + * https://github.com/mumble-voip/mumble/blob/master/src/PacketDataStream.h + */ +int64_t mumlib::VarInt::parseVariant(uint8_t *buffer) { + int64_t v = buffer[0]; + if ((v & 0x80) == 0x00) { + return (v & 0x7F); + } else if ((v & 0xC0) == 0x80) { + return (v & 0x3F) << 8 | buffer[1]; + } else if ((v & 0xF0) == 0xF0) { + switch (v & 0xFC) { + case 0xF0: + return buffer[1] << 24 | buffer[2] << 16 | buffer[3] << 8 | buffer[4]; + case 0xF4: + throw VarIntException("currently unsupported 8-byte varint size"); + case 0xF8: + case 0xFC: + throw VarIntException("currently negative varints aren't supported"); + default: + break; + } + } else if ((v & 0xF0) == 0xE0) { + return (v & 0x0F) << 24 | buffer[1] << 16 | buffer[2] << 8 | buffer[3]; + } else if ((v & 0xE0) == 0xC0) { + return (v & 0x1F) << 16 | buffer[1] << 8 | buffer[2]; + } + + throw VarIntException("invalid varint"); +} + +std::vector mumlib::VarInt::getEncoded() const { + std::vector encoded; + int64_t i = this->value; + + if ((i & 0x8000000000000000LL) && (~i < 0x100000000LL)) { + i = ~i; + if (i <= 0x3) { + encoded.push_back(0xFC | i); + return encoded; + } else { + encoded.push_back(0xF8); + } + } + + if (i < 0x80) { + encoded.push_back(i); + } else if (i < 0x4000) { + encoded.push_back(0x80 | (i >> 8)); + encoded.push_back(i & 0xFF); + } else if (i < 0x200000) { + encoded.push_back(0xC0 | (i >> 16)); + encoded.push_back((i >> 8) & 0xFF); + encoded.push_back(i & 0xFF); + } else if (i < 0x10000000) { + encoded.push_back(0xE0 | (i >> 24)); + encoded.push_back((i >> 16) & 0xFF); + encoded.push_back((i >> 8) & 0xFF); + encoded.push_back(i & 0xFF); + } else { + encoded.push_back(0xF4); + encoded.push_back((i >> 56) & 0xFF); + encoded.push_back((i >> 48) & 0xFF); + encoded.push_back((i >> 40) & 0xFF); + encoded.push_back((i >> 32) & 0xFF); + encoded.push_back((i >> 24) & 0xFF); + encoded.push_back((i >> 16) & 0xFF); + encoded.push_back((i >> 8) & 0xFF); + encoded.push_back(i & 0xFF); + } + + return encoded; +} diff --git a/thirdparty/mumlib/src/mumlib.cpp b/thirdparty/mumlib/src/mumlib.cpp new file mode 100644 index 000000000000..08197d1a39d4 --- /dev/null +++ b/thirdparty/mumlib/src/mumlib.cpp @@ -0,0 +1,407 @@ +#include "mumlib/CryptState.hpp" +#include "mumlib/VarInt.hpp" +#include "mumlib/enums.hpp" +#include "mumlib/Transport.hpp" +#include "mumlib/Audio.hpp" + +#include "mumlib.hpp" + +#include +#include +#include + +#include + +using namespace std; +using namespace boost::asio; + +using namespace mumlib; + +namespace mumlib { + struct _Mumlib_Private : boost::noncopyable { + log4cpp::Category &logger = log4cpp::Category::getInstance("mumlib.Mumlib"); + + bool externalIoService; + io_service &ioService; + + Callback &callback; + + Transport transport; + + Audio audio; + + int sessionId = 0; + int channelId = 0; + + _Mumlib_Private(Callback &callback, MumlibConfiguration &configuration) + : _Mumlib_Private(callback, *(new io_service()), configuration) { + externalIoService = false; + } + + _Mumlib_Private(Callback &callback, io_service &ioService, MumlibConfiguration &configuration) + : callback(callback), + ioService(ioService), + externalIoService(true), + transport(ioService, boost::bind(&_Mumlib_Private::processIncomingTcpMessage, this, _1, _2, _3), + boost::bind(&_Mumlib_Private::processAudioPacket, this, _1, _2, _3)) { + + audio.setOpusEncoderBitrate(configuration.opusEncoderBitrate); + } + + virtual ~_Mumlib_Private() { + if (not externalIoService) { + delete &ioService; + } + } + + bool processAudioPacket(AudioPacketType type, uint8_t *buffer, int length) { + logger.info("Got %d B of encoded audio data.", length); + try { + auto incomingAudioPacket = audio.decodeIncomingAudioPacket(buffer, length); + + if (type == AudioPacketType::OPUS) { + int16_t pcmData[5000]; + auto status = audio.decodeOpusPayload(incomingAudioPacket.audioPayload, + incomingAudioPacket.audioPayloadLength, + pcmData, + 5000); + + callback.audio(incomingAudioPacket.target, + incomingAudioPacket.sessionId, + incomingAudioPacket.sequenceNumber, + pcmData, + status.first); + } else { + logger.warn("Incoming audio packet doesn't contain Opus data, calling unsupportedAudio callback."); + callback.unsupportedAudio(incomingAudioPacket.target, + incomingAudioPacket.sessionId, + incomingAudioPacket.sequenceNumber, + incomingAudioPacket.audioPayload, + incomingAudioPacket.audioPayloadLength); + } + + } catch (mumlib::AudioException &exp) { + logger.error("Audio decode error: %s.", exp.what()); + } + + return true; + } + + private: + + bool processIncomingTcpMessage(MessageType messageType, uint8_t *buffer, int length) { + logger.debug("Process incoming message: type %d, length: %d.", messageType, length); + + switch (messageType) { + case MessageType::VERSION: { + MumbleProto::Version version; + version.ParseFromArray(buffer, length); + callback.version( + version.version() >> 16, + version.version() >> 8 & 0xff, + version.version() & 0xff, + version.release(), + version.os(), + version.os_version()); + } + break; + case MessageType::SERVERSYNC: { + MumbleProto::ServerSync serverSync; + serverSync.ParseFromArray(buffer, length); + + sessionId = serverSync.session(); + + callback.serverSync( + serverSync.welcome_text(), + serverSync.session(), + serverSync.max_bandwidth(), + serverSync.permissions() + ); + } + break; + case MessageType::CHANNELREMOVE: { + MumbleProto::ChannelRemove channelRemove; + channelRemove.ParseFromArray(buffer, length); + callback.channelRemove(channelRemove.channel_id()); + } + break; + case MessageType::CHANNELSTATE: { + MumbleProto::ChannelState channelState; + channelState.ParseFromArray(buffer, length); + + int32_t channel_id = channelState.has_channel_id() ? channelState.channel_id() : -1; + int32_t parent = channelState.has_parent() ? channelState.parent() : -1; + + + bool temporary = channelState.has_temporary() ? channelState.temporary() + : false; //todo make sure it's correct to assume it's false + int position = channelState.has_position() ? channelState.position() : 0; + + vector links; + for (int i = 0; i < channelState.links_size(); ++i) { + links.push_back(channelState.links(i)); + } + + vector links_add; + for (int i = 0; i < channelState.links_add_size(); ++i) { + links_add.push_back(channelState.links_add(i)); + } + + vector links_remove; + for (int i = 0; i < channelState.links_remove_size(); ++i) { + links_remove.push_back(channelState.links_remove(i)); + } + + this->channelId = channel_id; + + callback.channelState( + channelState.name(), + channel_id, + parent, + channelState.description(), + links, + links_add, + links_remove, + temporary, + position + ); + } + break; + case MessageType::USERREMOVE: { + MumbleProto::UserRemove user_remove; + user_remove.ParseFromArray(buffer, length); + + int32_t actor = user_remove.has_actor() ? user_remove.actor() : -1; + bool ban = user_remove.has_ban() ? user_remove.ban() + : false; //todo make sure it's correct to assume it's false + + callback.userRemove( + user_remove.session(), + actor, + user_remove.reason(), + ban + ); + } + break; + case MessageType::USERSTATE: { + MumbleProto::UserState userState; + userState.ParseFromArray(buffer, length); + + // There are far too many things in this structure. Culling to the ones that are probably important + int32_t session = userState.has_session() ? userState.session() : -1; + int32_t actor = userState.has_actor() ? userState.actor() : -1; + int32_t user_id = userState.has_user_id() ? userState.user_id() : -1; + int32_t channel_id = userState.has_channel_id() ? userState.channel_id() : -1; + int32_t mute = userState.has_mute() ? userState.mute() : -1; + int32_t deaf = userState.has_deaf() ? userState.deaf() : -1; + int32_t suppress = userState.has_suppress() ? userState.suppress() : -1; + int32_t self_mute = userState.has_self_mute() ? userState.self_mute() : -1; + int32_t self_deaf = userState.has_self_deaf() ? userState.self_deaf() : -1; + int32_t priority_speaker = userState.has_priority_speaker() ? userState.priority_speaker() : -1; + int32_t recording = userState.has_recording() ? userState.recording() : -1; + + callback.userState(session, + actor, + userState.name(), + user_id, + channel_id, + mute, + deaf, + suppress, + self_mute, + self_deaf, + userState.comment(), + priority_speaker, + recording); + } + break; + case MessageType::BANLIST: { + MumbleProto::BanList ban_list; + ban_list.ParseFromArray(buffer, length); + for (int i = 0; i < ban_list.bans_size(); i++) { + auto ban = ban_list.bans(i); + + const uint8_t *ip_data = reinterpret_cast(ban.address().c_str()); + uint32_t ip_data_size = ban.address().size(); + int32_t duration = ban.has_duration() ? ban.duration() : -1; + + callback.banList( + ip_data, + ip_data_size, + ban.mask(), + ban.name(), + ban.hash(), + ban.reason(), + ban.start(), + duration); + } + } + break; + case MessageType::TEXTMESSAGE: { + MumbleProto::TextMessage text_message; + text_message.ParseFromArray(buffer, length); + + int32_t actor = text_message.has_actor() ? text_message.actor() : -1; + + vector sessions; + for (int i = 0; i < text_message.session_size(); ++i) { + sessions.push_back(text_message.session(i)); + } + + vector channel_ids; + for (int i = 0; i < text_message.channel_id_size(); ++i) { + channel_ids.push_back(text_message.channel_id(i)); + } + + vector tree_ids; + for (int i = 0; i < text_message.tree_id_size(); ++i) { + tree_ids.push_back(text_message.tree_id(i)); + } + + callback.textMessage(actor, sessions, channel_ids, tree_ids, text_message.message()); + } + break; + case MessageType::PERMISSIONDENIED: // 12 + logger.warn("PermissionDenied Message: support not implemented yet"); + break; + case MessageType::ACL: // 13 + logger.warn("ACL Message: support not implemented yet."); + break; + case MessageType::QUERYUSERS: // 14 + logger.warn("QueryUsers Message: support not implemented yet"); + break; + case MessageType::CONTEXTACTIONMODIFY: // 16 + logger.warn("ContextActionModify Message: support not implemented yet"); + break; + case MessageType::CONTEXTACTION: // 17 + logger.warn("ContextAction Message: support not implemented yet"); + break; + case MessageType::USERLIST: // 18 + logger.warn("UserList Message: support not implemented yet"); + break; + case MessageType::VOICETARGET: + logger.warn("VoiceTarget Message: I don't think the server ever sends this structure."); + break; + case MessageType::PERMISSIONQUERY: { + MumbleProto::PermissionQuery permissionQuery; + permissionQuery.ParseFromArray(buffer, length); + + int32_t channel_id = permissionQuery.has_channel_id() ? permissionQuery.channel_id() : -1; + uint32_t permissions = permissionQuery.has_permissions() ? permissionQuery.permissions() : 0; + uint32_t flush = permissionQuery.has_flush() ? permissionQuery.flush() : -1; + + callback.permissionQuery(channel_id, permissions, flush); + } + break; + case MessageType::CODECVERSION: { + MumbleProto::CodecVersion codecVersion; + codecVersion.ParseFromArray(buffer, length); + + int32_t alpha = codecVersion.alpha(); + int32_t beta = codecVersion.beta(); + uint32_t prefer_alpha = codecVersion.prefer_alpha(); + int32_t opus = codecVersion.has_opus() ? codecVersion.opus() : 0; + + callback.codecVersion(alpha, beta, prefer_alpha, opus); + } + break; + case MessageType::USERSTATS: + logger.warn("UserStats Message: support not implemented yet"); + break; + case MessageType::REQUESTBLOB: // 23 + logger.warn("RequestBlob Message: I don't think this is sent by the server."); + break; + case MessageType::SERVERCONFIG: { + MumbleProto::ServerConfig serverConfig; + serverConfig.ParseFromArray(buffer, length); + + uint32_t max_bandwidth = serverConfig.has_max_bandwidth() ? serverConfig.max_bandwidth() : 0; + uint32_t allow_html = serverConfig.has_allow_html() ? serverConfig.allow_html() : 0; + uint32_t message_length = serverConfig.has_message_length() ? serverConfig.message_length() : 0; + uint32_t image_message_length = serverConfig.has_image_message_length() + ? serverConfig.image_message_length() : 0; + + callback.serverConfig(max_bandwidth, serverConfig.welcome_text(), allow_html, message_length, + image_message_length); + } + break; + case MessageType::SUGGESTCONFIG: // 25 + logger.warn("SuggestConfig Message: support not implemented yet"); + break; + default: + throw MumlibException("unknown message type: " + to_string(static_cast(messageType))); + } + return true; + } + + + }; + + Mumlib::Mumlib(Callback &callback) { + MumlibConfiguration conf; + impl = new _Mumlib_Private(callback, conf); + } + + Mumlib::Mumlib(Callback &callback, io_service &ioService) { + MumlibConfiguration conf; + impl = new _Mumlib_Private(callback, ioService, conf); + } + + Mumlib::Mumlib(Callback &callback, MumlibConfiguration &configuration) + : impl(new _Mumlib_Private(callback, configuration)) { } + + Mumlib::Mumlib(Callback &callback, io_service &ioService, MumlibConfiguration &configuration) + : impl(new _Mumlib_Private(callback, ioService, configuration)) { } + + Mumlib::~Mumlib() { + disconnect(); + + delete impl; + } + + ConnectionState Mumlib::getConnectionState() { + return impl->transport.getConnectionState(); + } + + void Mumlib::connect(string host, int port, string user, string password) { + impl->transport.connect(host, port, user, password); + } + + void Mumlib::disconnect() { + if (not impl->externalIoService) { + impl->ioService.reset(); + } + if (impl->transport.getConnectionState() != ConnectionState::NOT_CONNECTED) { + impl->transport.disconnect(); + } + } + + void Mumlib::run() { + if (impl->externalIoService) { + throw MumlibException("can't call run() when using external io_service"); + } + + impl->ioService.run(); + } + + void Mumlib::sendAudioData(int16_t *pcmData, int pcmLength) { + uint8_t encodedData[5000]; + int length = impl->audio.encodeAudioPacket(0, pcmData, pcmLength, encodedData, 5000); + impl->transport.sendEncodedAudioPacket(encodedData, length); + } + + void Mumlib::sendTextMessage(string message) { + MumbleProto::TextMessage textMessage; + textMessage.set_actor(impl->sessionId); + textMessage.add_channel_id(impl->channelId); + textMessage.set_message(message); + impl->transport.sendControlMessage(MessageType::TEXTMESSAGE, textMessage); + } + + void Mumlib::joinChannel(int channelId) { + MumbleProto::UserState userState; + userState.set_channel_id(channelId); + impl->transport.sendControlMessage(MessageType::USERSTATE, userState); + impl->channelId = channelId; + } +}