Skip to content

Commit

Permalink
binary frame parse
Browse files Browse the repository at this point in the history
  • Loading branch information
braindigitalis committed Sep 25, 2024
1 parent de5ce18 commit 27ae746
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 13 deletions.
22 changes: 20 additions & 2 deletions include/dpp/discordvoiceclient.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ enum voice_websocket_opcode_t : uint8_t {
voice_opcode_multiple_clients_connect = 11,
voice_opcode_client_connect = 12,
voice_opcode_client_disconnect = 13,
voice_opcode_media_sink = 15,
voice_client_flags = 18,
voice_client_platform = 20,
voice_client_dave_prepare_transition = 21,
Expand All @@ -146,6 +147,22 @@ enum voice_websocket_opcode_t : uint8_t {
voice_client_dave_mls_invalid_commit_welcome = 31,
};

/**
* @brief DAVE E2EE Binary frame header
*/
#pragma pack(push, 1)
struct dave_binary_header_t {
/**
* @brief Sequence number
*/
uint16_t seq;
/**
* @brief Opcode type
*/
uint8_t opcode;
};
#pragma pack(pop)

/** @brief Implements a discord voice connection.
* Each discord_voice_client connects to one voice channel and derives from a websocket client.
*/
Expand Down Expand Up @@ -456,9 +473,10 @@ class DPP_EXPORT discord_voice_client : public websocket_client
/**
* @brief DAVE - Discord Audio Visual Encryption
* Used for E2EE encryption. dave_protocol_none is
* the default before negotiation.
* the default right now.
* @warning DAVE E2EE is an EXPERIMENTAL feature!
*/
dave_version_t dave_version{dave_version_none};
dave_version_t dave_version;

/**
* @brief Send data to UDP socket immediately.
Expand Down
9 changes: 5 additions & 4 deletions src/davetest/dave.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@
int main() {
using namespace std::chrono_literals;
char* t = getenv("DPP_UNIT_TEST_TOKEN");
dpp::snowflake TEST_GUILD_ID(std::string(getenv("TEST_GUILD_ID")));
dpp::snowflake TEST_VC_ID(std::string(getenv("TEST_VC_ID")));
if (t) {
if (t != nullptr) {
dpp::snowflake TEST_GUILD_ID(std::string(getenv("TEST_GUILD_ID")));
dpp::snowflake TEST_VC_ID(std::string(getenv("TEST_VC_ID")));
std::cout << "Test Guild ID: " << TEST_GUILD_ID << " Test VC ID: " << TEST_VC_ID << "\n\n";
dpp::cluster dave_test(t, dpp::i_default_intents | dpp::i_guild_members);
dave_test.set_websocket_protocol(dpp::ws_etf);
dave_test.on_log(dpp::utility::cout_logger());
Expand All @@ -40,6 +41,6 @@ int main() {
s->connect_voice(TEST_GUILD_ID, TEST_VC_ID, false, false, true);
}
});
dave_test.start(dpp::st_wait);
dave_test.start(false);
}
}
2 changes: 1 addition & 1 deletion src/dpp/discordclient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -713,7 +713,7 @@ voiceconn& voiceconn::connect(snowflake guild_id) {
auto t = std::thread([guild_id, this]() {
try {
this->creator->log(ll_debug, "Connecting voice for guild " + std::to_string(guild_id) + " channel " + std::to_string(this->channel_id));
this->voiceclient = new discord_voice_client(creator->creator, this->channel_id, guild_id, this->token, this->session_id, this->websocket_hostname);
this->voiceclient = new discord_voice_client(creator->creator, this->channel_id, guild_id, this->token, this->session_id, this->websocket_hostname, this->dave);
/* Note: Spawns thread! */
this->voiceclient->run();
}
Expand Down
61 changes: 56 additions & 5 deletions src/dpp/discordvoiceclient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ void discord_voice_client::voice_courier_loop(discord_voice_client& client, cour
}

discord_voice_client::discord_voice_client(dpp::cluster* _cluster, snowflake _channel_id, snowflake _server_id, const std::string &_token, const std::string &_session_id, const std::string &_host, bool enable_dave)
: websocket_client(_host.substr(0, _host.find(":")), _host.substr(_host.find(":") + 1, _host.length()), "/?v=" + std::to_string(voice_protocol_version), OP_TEXT),
: websocket_client(_host.substr(0, _host.find(':')), _host.substr(_host.find(':') + 1, _host.length()), "/?v=" + std::to_string(voice_protocol_version), OP_TEXT),
runner(nullptr),
connect_time(0),
mixer(std::make_unique<audio_mixer>()),
Expand Down Expand Up @@ -490,6 +490,44 @@ bool discord_voice_client::handle_frame(const std::string &data)
{
log(dpp::ll_trace, std::string("R: ") + data);
json j;

/**
* Because all discord JSON must be valid UTF-8, if we see a packet with the 2nd character
* being less than 32 (' '), then we know it is a binary MLS frame, as all the binary frame
* opcodes are purposefully less than 32. We then try and parse it as MLS binary.
*/
if (data.size() >= sizeof(dave_binary_header_t) && data[2] <= voice_client_dave_mls_invalid_commit_welcome) {

/* Debug, remove once this is working */
std::cout << dpp::utility::debug_dump((uint8_t*)(data.data()), data.length()) << "\n";

dave_binary_header_t dave_header{};
std::memcpy(&dave_header, data.data(), sizeof(dave_binary_header_t));

switch (dave_header.opcode) {
case voice_client_dave_mls_external_sender: {
log(ll_debug, "voice_client_dave_mls_external_sender");
}
break;
case voice_client_dave_mls_proposals: {
log(ll_debug, "voice_client_dave_mls_proposals");
}
break;
case voice_client_dave_announce_commit_transaction: {
log(ll_debug, "voice_client_dave_announce_commit_transaction");
}
break;
case voice_client_dave_mls_welcome: {
log(ll_debug, "voice_client_dave_mls_welcome");
}
break;
default:
log(ll_debug, "Unexpected DAVE frame opcode");
break;
}

return true;
}

try {
j = json::parse(data);
Expand All @@ -507,6 +545,7 @@ bool discord_voice_client::handle_frame(const std::string &data)
case voice_opcode_connection_heartbeat_ack:
/* These opcodes do not require a response or further action */
break;
case voice_opcode_media_sink:
case voice_client_flags: {
}
break;
Expand Down Expand Up @@ -622,8 +661,16 @@ bool discord_voice_client::handle_frame(const std::string &data)
}
}

/* This is needed to start voice receiving and make sure that the start of sending isn't cut off */
send_silence(20);
if (dave_version != dave_version_none) {
if (j["d"]["dave_protocol_version"] != static_cast<uint32_t>(dave_version)) {
log(ll_error, "We requested DAVE E2EE but didn't receive it from the server, downgrading...");
dave_version = dave_version_none;
send_silence(20);
}
} else {
/* This is needed to start voice receiving and make sure that the start of sending isn't cut off */
send_silence(20);
}

/* Fire on_voice_ready */
if (!creator->on_voice_ready.empty()) {
Expand Down Expand Up @@ -1166,8 +1213,12 @@ void discord_voice_client::one_second_timer()
if (time(nullptr) > last_heartbeat + ((heartbeat_interval / 1000.0) * 0.75)) {
queue_message(json({
{"op", voice_opcode_connection_heartbeat},
{"d", rand()},
{"seq_ack", sequence}
{
"d", {
{"t", rand()},
{"seq_ack", receive_sequence},
}
},
}).dump(-1, ' ', false, json::error_handler_t::replace), true);
last_heartbeat = time(nullptr);
}
Expand Down
2 changes: 1 addition & 1 deletion src/dpp/utility.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ std::string debug_dump(uint8_t* data, size_t length) {
ascii.clear();
}
ascii.push_back(*ptr >= ' ' && *ptr <= '~' ? *ptr : '.');
out << to_hex(*ptr);
out << to_hex(*ptr) << " ";
}
out << " " << ascii;
out << "\n";
Expand Down

0 comments on commit 27ae746

Please sign in to comment.