Add source files from old repository.

This commit is contained in:
Michał Słomkowski 2015-10-25 14:40:16 +01:00
parent 261df114d2
commit 6b846aff05
17 changed files with 2549 additions and 0 deletions

6
.gitignore vendored
View File

@ -26,3 +26,9 @@
*.exe *.exe
*.out *.out
*.app *.app
build/
# IntelliJ
*.iml
.idea/

59
CMakeLists.txt Normal file
View File

@ -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)

47
include/mumlib.hpp Normal file
View File

@ -0,0 +1,47 @@
#pragma once
#include "mumlib/Callback.hpp"
#include <boost/asio.hpp>
#include <boost/noncopyable.hpp>
#include <string>
#include <mumlib/enums.hpp>
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;
};
}

41
include/mumlib/Audio.hpp Normal file
View File

@ -0,0 +1,41 @@
#pragma once
#include "Transport.hpp"
#include <opus.h>
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;
};
}

318
include/mumlib/Callback.hpp Normal file
View File

@ -0,0 +1,318 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
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<uint32_t> links,
vector<uint32_t> inks_add,
vector<uint32_t> 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<uint32_t> links,
vector<uint32_t> inks_add,
vector<uint32_t> 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;
};
}

View File

@ -0,0 +1,80 @@
/* Copyright (C) 2005-2011, Thorvald Natvig <thorvald@natvig.com>
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 <openssl/aes.h>
#include <boost/noncopyable.hpp>
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);
};
};

View File

@ -0,0 +1,128 @@
#pragma once
#include "mumlib/CryptState.hpp"
#include "mumlib/VarInt.hpp"
#include "enums.hpp"
#include <boost/noncopyable.hpp>
#include <boost/asio/ssl.hpp>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/pool/pool.hpp>
#include <log4cpp/Category.hh>
#include <google/protobuf/message.h>
#include <chrono>
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<bool(MessageType, uint8_t *, int)> ProcessControlMessageFunction;
typedef function<bool(AudioPacketType, uint8_t *, int)> 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<string, int> connectionParams;
pair<string, string> 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<tcp::socket> sslSocket;
uint8_t sslIncomingBuffer[MAX_TCP_LENGTH];
deadline_timer pingTimer;
std::chrono::time_point<std::chrono::system_clock> 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);
};
}

34
include/mumlib/VarInt.hpp Normal file
View File

@ -0,0 +1,34 @@
#pragma once
#include <mumlib.hpp>
#include <stdint.h>
#include <vector>
#include <string>
namespace mumlib {
class VarIntException : public MumlibException {
public:
VarIntException(std::string message) : MumlibException(message) { }
};
class VarInt {
public:
VarInt(uint8_t *encoded);
VarInt(std::vector<uint8_t> encoded);
VarInt(int64_t value);
int64_t getValue() const {
return this->value;
}
std::vector<uint8_t> getEncoded() const;
private:
const int64_t value;
int64_t parseVariant(uint8_t *buffer);
};
}

48
include/mumlib/enums.hpp Normal file
View File

@ -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
};
}

295
mumble.proto Normal file
View File

@ -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;
}

54
mumlib_example.cpp Normal file
View File

@ -0,0 +1,54 @@
#include "mumlib.hpp"
#include "log4cpp/Category.hh"
#include "log4cpp/FileAppender.hh"
#include "log4cpp/OstreamAppender.hh"
#include <thread>
#include <cmath>
#include <mumlib/Audio.hpp>
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;
}

124
src/Audio.cpp Normal file
View File

@ -0,0 +1,124 @@
#include "mumlib/Audio.hpp"
#include <boost/format.hpp>
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<int64_t *, 3> 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<const unsigned char *>(&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<uint8_t> 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();
}

182
src/Callback.cpp Normal file
View File

@ -0,0 +1,182 @@
#include "mumlib/Callback.hpp"
#include <boost/core/noncopyable.hpp>
#include <log4cpp/Category.hh>
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<uint32_t> links,
vector<uint32_t> inks_add,
vector<uint32_t> 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) { }

286
src/CryptState.cpp Normal file
View File

@ -0,0 +1,286 @@
/* Copyright (C) 2005-2011, Thorvald Natvig <thorvald@natvig.com>
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 <cstring>
#include <cstdint>
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<const unsigned char *>(src),reinterpret_cast<unsigned char *>(dst), key);
#define AESdecrypt(src, dst, key) AES_decrypt(reinterpret_cast<const unsigned char *>(src),reinterpret_cast<unsigned char *>(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<const subblock *>(plain));
AESencrypt(tmp, tmp, &encrypt_key);
XOR(reinterpret_cast<subblock *>(encrypted), delta, tmp);
XOR(checksum, checksum, reinterpret_cast<const subblock *>(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<unsigned char *>(tmp) + len, reinterpret_cast<const unsigned char *>(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<const subblock *>(encrypted));
AESdecrypt(tmp, tmp, &decrypt_key);
XOR(reinterpret_cast<subblock *>(plain), delta, tmp);
XOR(checksum, checksum, reinterpret_cast<const subblock *>(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);
}

520
src/Transport.cpp Normal file
View File

@ -0,0 +1,520 @@
#include "mumlib/Transport.hpp"
#include "mumble.pb.h"
#include <boost/format.hpp>
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<MumbleProto::Reject_RejectType, string> 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<milliseconds>(
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<uint8_t *>(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<uint32_t *>(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<uint16_t *>(sslIncomingBuffer));
logger.debug("Received %d B of data (%d B payload, type %d).", bytesTransferred,
bytesTransferred - 6, messageType);
processMessageInternal(
static_cast<MessageType>(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<const unsigned char *>(cryptsetup.key().c_str()),
reinterpret_cast<const unsigned char *>(cryptsetup.client_nonce().c_str()),
reinterpret_cast<const unsigned char *>(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<uint8_t> 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<uint16_t>(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<uint16_t>(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<AudioPacketType >((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]);
}
}

83
src/VarInt.cpp Normal file
View File

@ -0,0 +1,83 @@
#include "mumlib/VarInt.hpp"
#include <boost/format.hpp>
mumlib::VarInt::VarInt(int64_t value) : value(value) { }
mumlib::VarInt::VarInt(uint8_t *encoded) : value(parseVariant(encoded)) { }
mumlib::VarInt::VarInt(std::vector<uint8_t> 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<uint8_t> mumlib::VarInt::getEncoded() const {
std::vector<uint8_t> 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;
}

244
src/mumlib.cpp Normal file
View File

@ -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 <boost/asio/ssl.hpp>
#include <boost/bind.hpp>
#include <log4cpp/Category.hh>
#include <mumble.pb.h>
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<uint32_t> links;
std::copy(channelState.links().begin(), channelState.links().end(), links.begin());
vector<uint32_t> links_add;
std::copy(channelState.links_add().begin(), channelState.links_add().end(), links_add.begin());
vector<uint32_t> 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<int>(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);
}