diff --git a/.gitignore b/.gitignore index b8bd026..4286771 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,9 @@ *.exe *.out *.app + +build/ + +# IntelliJ +*.iml +.idea/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..c48446c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,59 @@ +cmake_minimum_required(VERSION 3.1.1) +project(libmumble) + +list(APPEND CMAKE_C_FLAGS " -std=c99 -g -Wall -pedantic ${CMAKE_C_FLAGS}") +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 unit_test_framework program_options filesystem 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) + +file(GLOB ProtoFiles "mumble.proto") + +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/include/mumlib.hpp b/include/mumlib.hpp new file mode 100644 index 0000000..a615e91 --- /dev/null +++ b/include/mumlib.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include "mumlib/Callback.hpp" + +#include +#include + +#include +#include + +namespace mumlib { + + using namespace std; + using namespace boost::asio; + + class MumlibException : public runtime_error { + public: + MumlibException(string message) : runtime_error(message) { } + }; + + struct _Mumlib_Private; + + + class Mumlib : boost::noncopyable { + public: + Mumlib(); + + Mumlib(io_service &ioService); + + ~Mumlib(); + + void setCallback(Callback &callback); + + void connect(string host, int port, string user, string password); + + void disconnect(); + + void run(); + + ConnectionState getConnectionState(); + + void sendAudioData(int16_t *pcmData, int pcmLength); + + private: + _Mumlib_Private *impl; + }; +} \ No newline at end of file diff --git a/include/mumlib/Audio.hpp b/include/mumlib/Audio.hpp new file mode 100644 index 0000000..a71d73f --- /dev/null +++ b/include/mumlib/Audio.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include "Transport.hpp" + +#include + +namespace mumlib { + + constexpr int SAMPLE_RATE = 48000; + + class AudioException : public MumlibException { + public: + AudioException(string message) : MumlibException(message) { } + }; + + class Audio : boost::noncopyable { + public: + Audio(); + + ~Audio(); + + + int decodeAudioPacket(AudioPacketType type, 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); + + private: + log4cpp::Category &logger; + + OpusDecoder *opusDecoder; + OpusEncoder *opusEncoder; + + int64_t outgoingSequenceNumber; + }; +} \ No newline at end of file diff --git a/include/mumlib/Callback.hpp b/include/mumlib/Callback.hpp new file mode 100644 index 0000000..896f5b1 --- /dev/null +++ b/include/mumlib/Callback.hpp @@ -0,0 +1,318 @@ +#pragma once + +#include +#include +#include + +namespace mumlib { + + using namespace std; + + class Callback { + public: + virtual void version_callback( + uint16_t major, + uint8_t minor, + uint8_t patch, + string release, + string os, + string os_version) { }; + + virtual void audio_callback( + uint8_t *pcm_data, + uint32_t pcm_data_size) { }; + + virtual void unsupported_audio_callback( + uint8_t *encoded_audio_data, + uint32_t encoded_audio_data_size) { }; + + virtual void serversync_callback( + string welcome_text, + int32_t session, + int32_t max_bandwidth, + int64_t permissions) { }; + + virtual void channelremove_callback(uint32_t channel_id) { }; + + virtual void channelstate_callback( + 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_callback( + uint32_t session, + int32_t actor, + string reason, + bool ban) { }; + + virtual void userstate_callback( + 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_callback( + 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_callback( + uint32_t actor, + uint32_t n_session, + uint32_t *session, + uint32_t n_channel_id, + uint32_t *channel_id, + uint32_t n_tree_id, + uint32_t *tree_id, + string message) { }; + + virtual void permissiondenied_callback( + int32_t permission, + int32_t channel_id, + int32_t session, + string reason, + int32_t deny_type, + string name) { }; + + virtual void acl_callback() { }; + + virtual void queryusers_callback( + uint32_t n_ids, + uint32_t *ids, + uint32_t n_names, + string *names) { }; + + virtual void cryptsetup_callback( + uint32_t key_size, + uint8_t *key, + uint32_t client_nonce_size, + uint8_t *client_nonce, + uint32_t server_nonce_size, + uint8_t *server_nonce) { }; + + virtual void contextactionmodify_callback( + string action, + string text, + uint32_t m_context, + uint32_t operation) { }; + + virtual void contextaction_callback( + int32_t session, + int32_t channel_id, + string action) { }; + + virtual void userlist_callback( + uint32_t user_id, + string name, + string last_seen, + int32_t last_channel) { }; + + virtual void voicetarget_callback() { }; + + virtual void permissionquery_callback( + int32_t channel_id, + uint32_t permissions, + int32_t flush) { }; + + virtual void codecversion_callback( + int32_t alpha, + int32_t beta, + uint32_t prefer_alpha, + int32_t opus) { }; + + virtual void userstats_callback() { }; + + virtual void requestblob_callback() { }; + + virtual void serverconfig_callback( + uint32_t max_bandwidth, + string welcome_text, + uint32_t allow_html, + uint32_t message_length, + uint32_t image_message_length) { }; + + virtual void suggestconfig_callback( + uint32_t version, + uint32_t positional, + uint32_t push_to_talk) { }; + + }; + + class _BasicCallback_Private; + + class BasicCallback : public Callback { + public: + BasicCallback(); + + ~BasicCallback(); + + virtual void version_callback( + uint16_t major, + uint8_t minor, + uint8_t patch, + string release, + string os, + string os_version); + + virtual void audio_callback( + uint8_t *pcm_data, + uint32_t pcm_data_size); + + virtual void unsupported_audio_callback( + uint8_t *encoded_audio_data, + uint32_t encoded_audio_data_size); + + virtual void serversync_callback( + string welcome_text, + int32_t session, + int32_t max_bandwidth, + int64_t permissions); + + virtual void channelremove_callback(uint32_t channel_id); + + virtual void channelstate_callback( + 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_callback( + uint32_t session, + int32_t actor, + string reason, + bool ban); + + virtual void userstate_callback( + 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_callback( + 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_callback( + uint32_t actor, + uint32_t n_session, + uint32_t *session, + uint32_t n_channel_id, + uint32_t *channel_id, + uint32_t n_tree_id, + uint32_t *tree_id, + string message); + + virtual void permissiondenied_callback( + int32_t permission, + int32_t channel_id, + int32_t session, + string reason, + int32_t deny_type, + string name); + + virtual void acl_callback(); + + virtual void queryusers_callback( + uint32_t n_ids, + uint32_t *ids, + uint32_t n_names, + string *names); + + virtual void cryptsetup_callback( + uint32_t key_size, + uint8_t *key, + uint32_t client_nonce_size, + uint8_t *client_nonce, + uint32_t server_nonce_size, + uint8_t *server_nonce); + + virtual void contextactionmodify_callback( + string action, + string text, + uint32_t m_context, + uint32_t operation); + + virtual void contextaction_callback( + int32_t session, + int32_t channel_id, + string action); + + virtual void userlist_callback( + uint32_t user_id, + string name, + string last_seen, + int32_t last_channel); + + virtual void voicetarget_callback(); + + virtual void permissionquery_callback( + int32_t channel_id, + uint32_t permissions, + int32_t flush); + + virtual void codecversion_callback( + int32_t alpha, + int32_t beta, + uint32_t prefer_alpha, + int32_t opus); + + virtual void userstats_callback(); + + virtual void requestblob_callback(); + + virtual void serverconfig_callback( + uint32_t max_bandwidth, + string welcome_text, + uint32_t allow_html, + uint32_t message_length, + uint32_t image_message_length); + + virtual void suggestconfig_callback( + uint32_t version, + uint32_t positional, + uint32_t push_to_talk); + + private: + _BasicCallback_Private *impl; + }; +} \ No newline at end of file diff --git a/include/mumlib/CryptState.hpp b/include/mumlib/CryptState.hpp new file mode 100644 index 0000000..cf5b61f --- /dev/null +++ b/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/include/mumlib/Transport.hpp b/include/mumlib/Transport.hpp new file mode 100644 index 0000000..4f3679f --- /dev/null +++ b/include/mumlib/Transport.hpp @@ -0,0 +1,128 @@ +#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 = 2048; + + 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); + + 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[MAX_TCP_LENGTH]; + + + 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/include/mumlib/VarInt.hpp b/include/mumlib/VarInt.hpp new file mode 100644 index 0000000..451c61e --- /dev/null +++ b/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/include/mumlib/enums.hpp b/include/mumlib/enums.hpp new file mode 100644 index 0000000..b4a9b43 --- /dev/null +++ b/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/mumble.proto b/mumble.proto new file mode 100644 index 0000000..ad4f90e --- /dev/null +++ b/mumble.proto @@ -0,0 +1,295 @@ +package MumbleProto; + +option optimize_for = SPEED; + +message Version { + optional uint32 version = 1; + optional string release = 2; + optional string os = 3; + optional string os_version = 4; +} + +message UDPTunnel { + required bytes packet = 1; +} + +message Authenticate { + optional string username = 1; + optional string password = 2; + repeated string tokens = 3; + repeated int32 celt_versions = 4; + optional bool opus = 5 [default = false]; +} + +message Ping { + optional uint64 timestamp = 1; + optional uint32 good = 2; + optional uint32 late = 3; + optional uint32 lost = 4; + optional uint32 resync = 5; + optional uint32 udp_packets = 6; + optional uint32 tcp_packets = 7; + optional float udp_ping_avg = 8; + optional float udp_ping_var = 9; + optional float tcp_ping_avg = 10; + optional float tcp_ping_var = 11; +} + +message Reject { + enum RejectType { + None = 0; + WrongVersion = 1; + InvalidUsername = 2; + WrongUserPW = 3; + WrongServerPW = 4; + UsernameInUse = 5; + ServerFull = 6; + NoCertificate = 7; + AuthenticatorFail = 8; + } + optional RejectType type = 1; + optional string reason = 2; +} + +message ServerSync { + optional uint32 session = 1; + optional uint32 max_bandwidth = 2; + optional string welcome_text = 3; + optional uint64 permissions = 4; +} + +message ChannelRemove { + required uint32 channel_id = 1; +} + +message ChannelState { + optional uint32 channel_id = 1; + optional uint32 parent = 2; + optional string name = 3; + repeated uint32 links = 4; + optional string description = 5; + repeated uint32 links_add = 6; + repeated uint32 links_remove = 7; + optional bool temporary = 8 [default = false]; + optional int32 position = 9 [default = 0]; + optional bytes description_hash = 10; +} + +message UserRemove { + required uint32 session = 1; + optional uint32 actor = 2; + optional string reason = 3; + optional bool ban = 4; +} + +message UserState { + optional uint32 session = 1; + optional uint32 actor = 2; + optional string name = 3; + optional uint32 user_id = 4; + optional uint32 channel_id = 5; + optional bool mute = 6; + optional bool deaf = 7; + optional bool suppress = 8; + optional bool self_mute = 9; + optional bool self_deaf = 10; + optional bytes texture = 11; + optional bytes plugin_context = 12; + optional string plugin_identity = 13; + optional string comment = 14; + optional string hash = 15; + optional bytes comment_hash = 16; + optional bytes texture_hash = 17; + optional bool priority_speaker = 18; + optional bool recording = 19; +} + +message BanList { + message BanEntry { + required bytes address = 1; + required uint32 mask = 2; + optional string name = 3; + optional string hash = 4; + optional string reason = 5; + optional string start = 6; + optional uint32 duration = 7; + } + repeated BanEntry bans = 1; + optional bool query = 2 [default = false]; +} + +message TextMessage { + optional uint32 actor = 1; + repeated uint32 session = 2; + repeated uint32 channel_id = 3; + repeated uint32 tree_id = 4; + required string message = 5; +} + +message PermissionDenied { + enum DenyType { + Text = 0; + Permission = 1; + SuperUser = 2; + ChannelName = 3; + TextTooLong = 4; + H9K = 5; + TemporaryChannel = 6; + MissingCertificate = 7; + UserName = 8; + ChannelFull = 9; + NestingLimit = 10; + } + optional uint32 permission = 1; + optional uint32 channel_id = 2; + optional uint32 session = 3; + optional string reason = 4; + optional DenyType type = 5; + optional string name = 6; +} + +message ACL { + message ChanGroup { + required string name = 1; + optional bool inherited = 2 [default = true]; + optional bool inherit = 3 [default = true]; + optional bool inheritable = 4 [default = true]; + repeated uint32 add = 5; + repeated uint32 remove = 6; + repeated uint32 inherited_members = 7; + } + message ChanACL { + optional bool apply_here = 1 [default = true]; + optional bool apply_subs = 2 [default = true]; + optional bool inherited = 3 [default = true]; + optional uint32 user_id = 4; + optional string group = 5; + optional uint32 grant = 6; + optional uint32 deny = 7; + } + required uint32 channel_id = 1; + optional bool inherit_acls = 2 [default = true]; + repeated ChanGroup groups = 3; + repeated ChanACL acls = 4; + optional bool query = 5 [default = false]; +} + +message QueryUsers { + repeated uint32 ids = 1; + repeated string names = 2; +} + +message CryptSetup { + optional bytes key = 1; + optional bytes client_nonce = 2; + optional bytes server_nonce = 3; +} + +message ContextActionModify { + enum Context { + Server = 0x01; + Channel = 0x02; + User = 0x04; + } + enum Operation { + Add = 0; + Remove = 1; + } + required string action = 1; + optional string text = 2; + optional uint32 context = 3; + optional Operation operation = 4; +} + +message ContextAction { + optional uint32 session = 1; + optional uint32 channel_id = 2; + required string action = 3; +} + +message UserList { + message User { + required uint32 user_id = 1; + optional string name = 2; + optional string last_seen = 3; + optional uint32 last_channel = 4; + } + repeated User users = 1; +} + +message VoiceTarget { + message Target { + repeated uint32 session = 1; + optional uint32 channel_id = 2; + optional string group = 3; + optional bool links = 4 [default = false]; + optional bool children = 5 [default = false]; + } + optional uint32 id = 1; + repeated Target targets = 2; +} + +message PermissionQuery { + optional uint32 channel_id = 1; + optional uint32 permissions = 2; + optional bool flush = 3 [default = false]; +} + +message CodecVersion { + required int32 alpha = 1; + required int32 beta = 2; + required bool prefer_alpha = 3 [default = true]; + optional bool opus = 4 [default = false]; +} + +message UserStats { + message Stats { + optional uint32 good = 1; + optional uint32 late = 2; + optional uint32 lost = 3; + optional uint32 resync = 4; + } + + optional uint32 session = 1; + optional bool stats_only = 2 [default = false]; + repeated bytes certificates = 3; + optional Stats from_client = 4; + optional Stats from_server = 5; + + optional uint32 udp_packets = 6; + optional uint32 tcp_packets = 7; + optional float udp_ping_avg = 8; + optional float udp_ping_var = 9; + optional float tcp_ping_avg = 10; + optional float tcp_ping_var = 11; + + optional Version version = 12; + repeated int32 celt_versions = 13; + optional bytes address = 14; + optional uint32 bandwidth = 15; + optional uint32 onlinesecs = 16; + optional uint32 idlesecs = 17; + optional bool strong_certificate = 18 [default = false]; + optional bool opus = 19 [default = false]; +} + +message RequestBlob { + repeated uint32 session_texture = 1; + repeated uint32 session_comment = 2; + repeated uint32 channel_description = 3; +} + +message ServerConfig { + optional uint32 max_bandwidth = 1; + optional string welcome_text = 2; + optional bool allow_html = 3; + optional uint32 message_length = 4; + optional uint32 image_message_length = 5; +} + +message SuggestConfig { + optional uint32 version = 1; + optional bool positional = 2; + optional bool push_to_talk = 3; +} + diff --git a/mumlib_example.cpp b/mumlib_example.cpp new file mode 100644 index 0000000..86c162f --- /dev/null +++ b/mumlib_example.cpp @@ -0,0 +1,54 @@ +#include "mumlib.hpp" + +#include "log4cpp/Category.hh" +#include "log4cpp/FileAppender.hh" +#include "log4cpp/OstreamAppender.hh" + +#include +#include +#include + +void audioSenderThreadFunction(mumlib::Mumlib *mum) { + while (mum->getConnectionState() != mumlib::ConnectionState::FAILED) { + if (mum->getConnectionState() == mumlib::ConnectionState::CONNECTED) { + constexpr double FREQUENCY = 1000; // Hz + constexpr int BUFF_SIZE = mumlib::SAMPLE_RATE / 100; // 10 ms + int16_t buff[BUFF_SIZE]; + + for (int i = 0; i < BUFF_SIZE; ++i) { + buff[i] = 10000 * std::sin(2.0 * M_PI * FREQUENCY * ((double) i) / ((double) mumlib::SAMPLE_RATE)); + } + + mum->sendAudioData(buff, BUFF_SIZE); + } + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } +} + +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::WARN); + logger.addAppender(appender1); + + if (argc < 3) { + logger.crit("Usage: %s {server} {password}", argv[0]); + return 1; + } + + mumlib::Mumlib mum; + + mumlib::BasicCallback callback; + mum.setCallback(callback); + + mum.connect(argv[1], 64738, "mumlib_example", argv[2]); + + std::thread audioSenderThread(audioSenderThreadFunction, &mum); + + mum.run(); + + return 0; +} \ No newline at end of file diff --git a/src/Audio.cpp b/src/Audio.cpp new file mode 100644 index 0000000..7b54f9a --- /dev/null +++ b/src/Audio.cpp @@ -0,0 +1,124 @@ +#include "mumlib/Audio.hpp" + +#include + +mumlib::Audio::Audio() + : + logger(log4cpp::Category::getInstance("Mumlib.Audio")), + opusDecoder(nullptr), + opusEncoder(nullptr), + outgoingSequenceNumber(1) { + + 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)); +} + +mumlib::Audio::~Audio() { + if (opusDecoder) { + opus_decoder_destroy(opusDecoder); + } + + if (opusEncoder) { + opus_encoder_destroy(opusEncoder); + } +} + +int mumlib::Audio::decodeAudioPacket(AudioPacketType type, + uint8_t *inputBuffer, + int inputLength, + int16_t *pcmBuffer, + int pcmBufferSize) { + + if (type != AudioPacketType::OPUS) { + throw AudioException("codecs other than OPUS are not supported"); + } + + int target = inputBuffer[0] & 0x1F; + + int64_t sessionId; + int64_t sequenceNumber; + int64_t opusDataLength; + + std::array varInts = {&sessionId, &sequenceNumber, &opusDataLength}; + + int dataPointer = 1; + for (int64_t *val : varInts) { + VarInt varInt(&inputBuffer[dataPointer]); + *val = varInt.getValue(); + dataPointer += varInt.getEncoded().size(); + } + + bool lastPacket = (opusDataLength & 0x2000) != 0; + opusDataLength = opusDataLength & 0x1fff; + + 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( + "Received %d B of OPUS data, decoded to %d B (target: %d, sessionID: %ld, seq num: %ld, last: %d).", + opusDataLength, + outputSize, + target, + sessionId, + sequenceNumber, + lastPacket); + + + return outputSize; +} + +int mumlib::Audio::encodeAudioPacket(int target, int16_t *inputPcmBuffer, int inputLength, uint8_t *outputBuffer, + int outputBufferSize) { + //if (!bPreviousVoice) + // opus_encoder_ctl(opusState, OPUS_RESET_STATE, NULL); //todo do something with it + + 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); + + outgoingSequenceNumber += 2; + + return outputSize + header.size(); +} diff --git a/src/Callback.cpp b/src/Callback.cpp new file mode 100644 index 0000000..b330231 --- /dev/null +++ b/src/Callback.cpp @@ -0,0 +1,182 @@ +#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("BasicCallback")) { } + + log4cpp::Category &logger; + }; +} + +mumlib::BasicCallback::BasicCallback() { + impl = new _BasicCallback_Private(); +} + +mumlib::BasicCallback::~BasicCallback() { + delete impl; +} + +void mumlib::BasicCallback::version_callback( + uint16_t major, + uint8_t minor, + uint8_t patch, + string release, + string os, + string os_version) { + impl->logger.debug("Version Callback: 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_callback( + uint8_t *pcm_data, + uint32_t pcm_data_size) { + impl->logger.debug("Received %d bytes of raw PCM data.", pcm_data_size); +} + +void mumlib::BasicCallback::unsupported_audio_callback( + uint8_t *encoded_audio_data, + uint32_t encoded_audio_data_size) { + impl->logger.debug("Received %d bytes of encoded audio data.", encoded_audio_data_size); +} + +void mumlib::BasicCallback::serversync_callback( + string welcome_text, + int32_t session, + int32_t max_bandwidth, + int64_t permissions) { + impl->logger.debug("Text: %s, session: %d, max bandwidth: %d, permissions: %d", welcome_text.c_str(), session, + max_bandwidth, permissions); +} + +void mumlib::BasicCallback::channelremove_callback(uint32_t channel_id) { } + +void mumlib::BasicCallback::channelstate_callback( + 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("Obtained channel state %d: %s, %s", channel_id, name.c_str(), description.c_str()); +} + +void mumlib::BasicCallback::userremove_callback( + uint32_t session, + int32_t actor, + string reason, + bool ban) { } + +void mumlib::BasicCallback::userstate_callback( + 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) { } + +void mumlib::BasicCallback::banlist_callback( + uint8_t *ip_data, + uint32_t ip_data_size, + uint32_t mask, + string name, + string hash, + string reason, + string start, + int32_t duration) { } + +void mumlib::BasicCallback::textmessage_callback( + uint32_t actor, + uint32_t n_session, + uint32_t *session, + uint32_t n_channel_id, + uint32_t *channel_id, + uint32_t n_tree_id, + uint32_t *tree_id, + string message) { } + +void mumlib::BasicCallback::permissiondenied_callback( + int32_t permission, + int32_t channel_id, + int32_t session, + string reason, + int32_t deny_type, + string name) { } + +void mumlib::BasicCallback::acl_callback() { } + +void mumlib::BasicCallback::queryusers_callback( + uint32_t n_ids, + uint32_t *ids, + uint32_t n_names, + string *names) { } + +void mumlib::BasicCallback::cryptsetup_callback( + uint32_t key_size, + uint8_t *key, + uint32_t client_nonce_size, + uint8_t *client_nonce, + uint32_t server_nonce_size, + uint8_t *server_nonce) { } + +void mumlib::BasicCallback::contextactionmodify_callback( + string action, + string text, + uint32_t m_context, + uint32_t operation) { } + +void mumlib::BasicCallback::contextaction_callback( + int32_t session, + int32_t channel_id, + string action) { } + +void mumlib::BasicCallback::userlist_callback( + uint32_t user_id, + string name, + string last_seen, + int32_t last_channel) { } + +void mumlib::BasicCallback::voicetarget_callback() { } + +void mumlib::BasicCallback::permissionquery_callback( + int32_t channel_id, + uint32_t permissions, + int32_t flush) { } + +void mumlib::BasicCallback::codecversion_callback( + int32_t alpha, + int32_t beta, + uint32_t prefer_alpha, + int32_t opus) { } + +void mumlib::BasicCallback::userstats_callback() { } + +void mumlib::BasicCallback::requestblob_callback() { } + +void mumlib::BasicCallback::serverconfig_callback( + uint32_t max_bandwidth, + string welcome_text, + uint32_t allow_html, + uint32_t message_length, + uint32_t image_message_length) { } + +void mumlib::BasicCallback::suggestconfig_callback( + uint32_t version, + uint32_t positional, + uint32_t push_to_talk) { } diff --git a/src/CryptState.cpp b/src/CryptState.cpp new file mode 100644 index 0000000..0760374 --- /dev/null +++ b/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) ({register uint64_t __out, __in = (x); __asm__("bswap %q0" : "=r"(__out) : "0"(__in)); __out;}) +#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/src/Transport.cpp b/src/Transport.cpp new file mode 100644 index 0000000..db23927 --- /dev/null +++ b/src/Transport.cpp @@ -0,0 +1,520 @@ +#include "mumlib/Transport.hpp" + +#include "mumble.pb.h" + +#include + +using namespace std; + +static boost::posix_time::seconds PING_INTERVAL(5); + +const long CLIENT_VERSION = 0x010203; +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)) { + + pingTimer.async_wait(boost::bind(&Transport::pingTimerTick, this, _1)); +} + +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() { + + state = ConnectionState::NOT_CONNECTED; + + sslSocket.shutdown(); + sslSocket.lowest_layer().shutdown(tcp::socket::shutdown_both); + + udpSocket.shutdown(udp::socket::shutdown_both); +} + + +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() { + 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.info("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 { + 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() { + 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)); + size_t remaining = payloadSize + 6 - bytesTransferred; + remaining = max(remaining, (size_t) 0); + + 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 { + 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; + case MessageType::VOICETARGET: { + MumbleProto::VoiceTarget voiceTarget; + voiceTarget.ParseFromArray(buffer, length); + logger.warn("VoiceTarget Message: I don't think the server ever sends this structure...."); + } + break; + default: { + logger.debug("Calling external ProcessControlMessageFunction."); + processMessageFunction(messageType, buffer, length); + } + break; + } +} + +void mumlib::Transport::sendUdpPing() { + 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); + + write(sslSocket, boost::asio::buffer(buff, length)); +} + +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("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/src/VarInt.cpp b/src/VarInt.cpp new file mode 100644 index 0000000..2eeebf8 --- /dev/null +++ b/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/src/mumlib.cpp b/src/mumlib.cpp new file mode 100644 index 0000000..57ae2cf --- /dev/null +++ b/src/mumlib.cpp @@ -0,0 +1,244 @@ +#include "mumlib.hpp" + +#include "mumlib/CryptState.hpp" +#include "mumlib/VarInt.hpp" +#include "mumlib/enums.hpp" +#include "mumlib/Transport.hpp" +#include "mumlib/Audio.hpp" + +#include +#include +#include + +#include + +using namespace std; +using namespace boost::asio; + +using namespace mumlib; + +namespace mumlib { + struct _Mumlib_Private : boost::noncopyable { + bool externalIoService; + io_service *ioService; + + Callback *callback; + + Transport *transport; + + Audio *audio; + + log4cpp::Category *logger; + + bool processIncomingTcpMessage(MessageType messageType, uint8_t *buffer, int length) { + switch (messageType) { + case MessageType::VERSION: { + MumbleProto::Version version; + version.ParseFromArray(buffer, length); + callback->version_callback( + 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); + callback->serversync_callback( + serverSync.welcome_text(), + serverSync.session(), + serverSync.max_bandwidth(), + serverSync.permissions() + ); + } + break; + case MessageType::CHANNELREMOVE: { + MumbleProto::ChannelRemove channelRemove; + channelRemove.ParseFromArray(buffer, length); + callback->channelremove_callback(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; + std::copy(channelState.links().begin(), channelState.links().end(), links.begin()); + + vector links_add; + std::copy(channelState.links_add().begin(), channelState.links_add().end(), links_add.begin()); + + vector links_remove; + std::copy(channelState.links_remove().begin(), channelState.links_remove().end(), + links_remove.begin()); + + callback->channelstate_callback( + 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_callback( + user_remove.session(), + actor, + user_remove.reason(), + ban + ); + } + break; + case MessageType::USERSTATE: // 9 +// return MessageType::private_process_userstate(context, message, message_size); + + break; + case MessageType::BANLIST: // 10 +// return MessageType::private_process_banlist(context, message, message_size); + + break; + case MessageType::TEXTMESSAGE: // 11 +// return MessageType::private_process_textmessage(context, message, message_size); + + break; + case MessageType::PERMISSIONDENIED: // 12 +// return MessageType::private_process_permissiondenied(context, message, message_size); + + break; + case MessageType::ACL: // 13 +// return MessageType::private_process_acl(context, message, message_size); + + break; + case MessageType::QUERYUSERS: // 14 +// return MessageType::private_process_queryusers(context, message, message_size); + + break; + case MessageType::CONTEXTACTIONMODIFY: // 16 +// return MessageType::private_process_contextactionmodify(context, message, message_size); + + break; + case MessageType::CONTEXTACTION: // 17 +// return MessageType::private_process_contextaction(context, message, message_size); + + break; + case MessageType::USERLIST: // 18 +// return MessageType::private_process_userlist(context, message, message_size); + + break; + case MessageType::PERMISSIONQUERY: // 20 +// return MessageType::private_process_permission_query(context, message, message_size); + + break; + case MessageType::CODECVERSION: // 21 +// return MessageType::private_process_codecversion(context, message, message_size); + + break; + case MessageType::USERSTATS: // 22 +// return MessageType::private_process_userstats(context, message, message_size); + + break; + case MessageType::REQUESTBLOB: // 23 +// return MessageType::private_process_requestblob(context, message, message_size); + + break; + case MessageType::SERVERCONFIG: // 24 +// return MessageType::private_process_serverconfig(context, message, message_size); + + break; + case MessageType::SUGGESTCONFIG: // 25 +// return MessageType::private_process_suggestconfig(context, message, message_size); + break; + default: + throw MumlibException("unknown message type: " + to_string(static_cast(messageType))); + } + return true; + } + + bool processAudioPacket(AudioPacketType type, uint8_t *buffer, int length) { + logger->info("Got %d B of encoded audio data.", length); + int16_t pcmData[5000]; + audio->decodeAudioPacket(type, buffer, length, pcmData, 5000); + } + + }; + + + ConnectionState Mumlib::getConnectionState() { + return impl->transport->getConnectionState(); + } +} + +mumlib::Mumlib::Mumlib() : impl(new _Mumlib_Private) { + impl->logger = &(log4cpp::Category::getInstance("Mumlib.Mumlib")); + impl->externalIoService = false; + impl->ioService = new io_service(); + impl->audio = new Audio(); + impl->transport = new Transport( + *(impl->ioService), + boost::bind(&_Mumlib_Private::processIncomingTcpMessage, impl, _1, _2, _3), + boost::bind(&_Mumlib_Private::processAudioPacket, impl, _1, _2, _3) + ); +} + +mumlib::Mumlib::Mumlib(io_service &ioService) : impl(new _Mumlib_Private) { + //todo do this constructor + throw mumlib::MumlibException("not implented yet"); +} + +mumlib::Mumlib::~Mumlib() { + + if (not impl->externalIoService) { + delete impl->ioService; + } + + delete impl; +} + +void mumlib::Mumlib::setCallback(Callback &callback) { + impl->callback = &callback; +} + +void mumlib::Mumlib::connect(string host, int port, string user, string password) { + impl->transport->connect(host, port, user, password); +} + +void mumlib::Mumlib::disconnect() { + impl->transport->disconnect(); +} + +void mumlib::Mumlib::run() { + if (impl->externalIoService) { + throw MumlibException("can't call run() when using external io_service"); + } + + impl->ioService->run(); +} + +void mumlib::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); +} \ No newline at end of file