From f0b094d7e4c64b849a76a6df65c118cf6175365d Mon Sep 17 00:00:00 2001 From: WolverinDEV Date: Sat, 28 Nov 2020 11:09:25 +0100 Subject: [PATCH] Supporting voice whisper again --- git-teaspeak | 2 +- server/CMakeLists.txt | 1 + server/src/client/ConnectedClient.h | 5 + server/src/client/SpeakingClient.cpp | 335 ++-------------- server/src/client/SpeakingClient.h | 15 +- server/src/client/music/MusicClientPlayer.cpp | 6 +- server/src/client/shared/WhisperHandler.cpp | 364 ++++++++++++++++++ server/src/client/shared/WhisperHandler.h | 76 ++++ server/src/client/voice/VoiceClient.cpp | 104 ++++- server/src/client/voice/VoiceClient.h | 10 +- .../src/client/voice/VoiceClientConnection.h | 33 +- .../VoiceClientConnectionPacketHandler.cpp | 20 +- server/src/client/web/WebClient.cpp | 17 +- server/src/client/web/WebClient.h | 5 +- server/src/rtc/imports.h | 13 +- server/src/rtc/lib.cpp | 92 ++--- server/src/rtc/lib.h | 10 +- shared | 2 +- 18 files changed, 689 insertions(+), 421 deletions(-) create mode 100644 server/src/client/shared/WhisperHandler.cpp create mode 100644 server/src/client/shared/WhisperHandler.h diff --git a/git-teaspeak b/git-teaspeak index be995e9..c089229 160000 --- a/git-teaspeak +++ b/git-teaspeak @@ -1 +1 @@ -Subproject commit be995e99c45ff7e5ba4adb36529a53558e06fa61 +Subproject commit c08922955d079e74910e7a755d757b50fda3065f diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index 95005e2..5bb98ae 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -154,6 +154,7 @@ set(SERVER_SOURCE_FILES src/client/voice/ServerCommandExecutor.cpp src/client/voice/PingHandler.cpp src/client/voice/CryptSetupHandler.cpp + src/client/shared/WhisperHandler.cpp src/terminal/PipedTerminal.cpp diff --git a/server/src/client/ConnectedClient.h b/server/src/client/ConnectedClient.h index d355e01..62c9021 100644 --- a/server/src/client/ConnectedClient.h +++ b/server/src/client/ConnectedClient.h @@ -267,6 +267,11 @@ namespace ts { inline std::shared_ptr ref() { return _this.lock(); } std::shared_mutex& get_channel_lock() { return this->channel_lock; } + + /* Attention: Ensure that channel_lock has been locked */ + [[nodiscard]] inline std::vector& current_server_groups() { return this->cached_server_groups; } + [[nodiscard]] inline GroupId& current_channel_group() { return this->cached_channel_group; } + /* * permission stuff */ diff --git a/server/src/client/SpeakingClient.cpp b/server/src/client/SpeakingClient.cpp index d454c4e..8e744e2 100644 --- a/server/src/client/SpeakingClient.cpp +++ b/server/src/client/SpeakingClient.cpp @@ -14,6 +14,7 @@ #include "StringVariable.h" #include "misc/timer.h" #include "../manager/ActionLogger.h" +#include "./voice/VoiceClient.h" using namespace std::chrono; using namespace ts; @@ -26,7 +27,7 @@ constexpr static auto kMaxWhisperClientNameLength{30}; constexpr static auto kWhisperClientUniqueIdLength{28}; /* base64 encoded SHA1 hash */ constexpr static auto kWhisperMaxHeaderLength{2 + 2 + 1 + 2 + kWhisperClientUniqueIdLength + 1 + kMaxWhisperClientNameLength}; -SpeakingClient::SpeakingClient(sql::SqlManager *a, const std::shared_ptr &b) : ConnectedClient(a, b) { +SpeakingClient::SpeakingClient(sql::SqlManager *a, const std::shared_ptr &b) : ConnectedClient(a, b), whisper_handler_{this} { speak_begin = std::chrono::system_clock::now(); speak_last_packet = std::chrono::system_clock::now(); }; @@ -68,29 +69,6 @@ bool SpeakingClient::should_handle_voice_packet(size_t) { return true; } -//2 + 2 + 8 -#define OUT_WHISPER_PKT_OFFSET 5 -//#define PKT_LOG_WHISPER - -enum WhisperType { - SERVER_GROUP = 0, - CHANNEL_GROUP = 1, - CHANNEL_COMMANDER = 2, - ALL = 3, - - ECHO = 0x10, -}; - -enum WhisperTarget { - CHANNEL_ALL = 0, - CHANNEL_CURRENT = 1, - CHANNEL_PARENT = 2, - CHANNEL_ALL_PARENT = 3, - CHANNEL_FAMILY = 4, - CHANNEL_COMPLETE_FAMILY = 5, - CHANNEL_SUBCHANNELS = 6 -}; - inline bool update_whisper_error(std::chrono::system_clock::time_point& last) { auto now = std::chrono::system_clock::now(); if(last + std::chrono::milliseconds{500} < now) { @@ -100,277 +78,6 @@ inline bool update_whisper_error(std::chrono::system_clock::time_point& last) { return false; } -//All clients => type := SERVER_GROUP and target_id := 0 -//Server group => type := SERVER_GROUP and target_id := -//Channel group => type := CHANNEL_GROUP and target_id := -//Channel commander => type := CHANNEL_COMMANDER and target_id := 0 -void SpeakingClient::handlePacketVoiceWhisper(const pipes::buffer_view& payload, bool new_packet, bool head) { - if(payload.length() < 5) { - this->disconnect("Invalid packet (Voice whisper)"); - logMessage(this->getServerId(), "{} Tried to send a too short whisper packet. Length: {}", CLIENT_STR_LOG_PREFIX, payload.length()); - return; - } - - uint16_t payload_offset{0}; - auto voice_packet_id = be2le16((char*) payload.data_ptr(), payload_offset, &payload_offset); - auto voice_codec = (uint8_t) payload[payload_offset++]; - - std::deque> target_clients; - - if(new_packet) { - if(payload.length() < 7) { - this->disconnect("Invalid packet (Voice whisper | New)"); - logMessage(this->getServerId(), "{} Tried to send a too short whisper packet. Length: {}", CLIENT_STR_LOG_PREFIX, payload.length()); - return; - } - - auto type = (WhisperType) payload[payload_offset++]; - auto target = (WhisperTarget) payload[payload_offset++]; - auto type_id = be2le64((char*) payload.data_ptr(), payload_offset, &payload_offset); - -#ifdef PKT_LOG_WHISPER - logTrace(this->getServerId(), "{} Whisper data length: {}. Type: {}. Target: {}. Target ID: {}.", CLIENT_STR_LOG_PREFIX, data_length, type, target, type_id); -#endif - if(type == WhisperType::ECHO) { - target_clients.push_back(dynamic_pointer_cast(this->ref())); - } else { - for(const auto& client : this->server->getClients()) { - auto speakingClient = dynamic_pointer_cast(client); - if(!speakingClient || client == this) continue; - if(!speakingClient->currentChannel) continue; - - if(type == WhisperType::ALL) { - target_clients.push_back(speakingClient); - } else if(type == WhisperType::SERVER_GROUP) { - if(type_id == 0) - target_clients.push_back(speakingClient); - else { - shared_lock client_lock(this->channel_lock); - for(const auto& id : client->cached_server_groups) { - if(id == type_id) { - target_clients.push_back(speakingClient); - break; - } - } - } - } else if(type == WhisperType::CHANNEL_GROUP) { - if(client->cached_channel_group == type_id) - target_clients.push_back(speakingClient); - } else if(type == WhisperType::CHANNEL_COMMANDER) { - if(client->properties()[property::CLIENT_IS_CHANNEL_COMMANDER].as()) - target_clients.push_back(speakingClient); - } - } - - if(target == WhisperTarget::CHANNEL_CURRENT) { - target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr& target) { - return target->currentChannel != this->currentChannel; - }), target_clients.end()); - } else if(target == WhisperTarget::CHANNEL_PARENT) { - auto current_parent = this->currentChannel->parent(); - if(!current_parent) return; - - target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr& target) { - return target->currentChannel != current_parent; - }), target_clients.end()); - } else if(target == WhisperTarget::CHANNEL_ALL_PARENT) { - shared_ptr current_parent; - { - current_parent = this->currentChannel->parent(); - if(!current_parent) return; - } - target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr& target) { - auto tmp_parent = current_parent; - while(tmp_parent && tmp_parent != target->currentChannel) - tmp_parent = tmp_parent->parent(); - return target->currentChannel != tmp_parent; - }), target_clients.end()); - } else if(target == WhisperTarget::CHANNEL_FAMILY) { - shared_ptr current = this->currentChannel; - target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr& target) { - auto tmp_current = target->currentChannel; - while(tmp_current && tmp_current != current) - tmp_current = tmp_current->parent(); - return current != tmp_current; - }), target_clients.end()); - } else if(target == WhisperTarget::CHANNEL_COMPLETE_FAMILY) { - shared_ptr current = this->currentChannel; - while(current && current->parent()) current = current->parent(); - target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr& target) { - auto tmp_current = target->currentChannel; - while(tmp_current && tmp_current != current) - tmp_current = tmp_current->parent(); - return current != tmp_current; - }), target_clients.end()); - } else if(target == WhisperTarget::CHANNEL_SUBCHANNELS) { - shared_ptr current = this->currentChannel; - target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr& target) { - return target->currentChannel->parent() != current; - }), target_clients.end()); - } - - auto self_lock = this->_this.lock(); - target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const std::shared_ptr& cl) { - auto speakingClient = dynamic_pointer_cast(cl); - return !speakingClient->shouldReceiveVoiceWhisper(self_lock); - }), target_clients.end()); - - if(target_clients.empty()) { - if(update_whisper_error(this->speak_last_no_whisper_target)) { - command_result result{error::whisper_no_targets}; - this->notifyError(result); - } - return; - } - - if(target_clients.size() > this->server->properties()[property::VIRTUALSERVER_MIN_CLIENTS_IN_CHANNEL_BEFORE_FORCED_SILENCE].as_save()) { - if(update_whisper_error(this->speak_last_too_many_whisper_targets)) { - command_result result{error::whisper_too_many_targets}; - this->notifyError(result); - } - return; - } - } - } else { - auto channelCount = (uint8_t) payload[payload_offset++]; - auto clientCount = (uint8_t) payload[payload_offset++]; - if(payload.length() < 5 + clientCount * 2 + channelCount * 8) { - logMessage(this->getServerId(), "{} Tried to send a too short whisper packet. Length: {} Required: {}", CLIENT_STR_LOG_PREFIX, payload.length(), to_string(5 + channelCount * 2 + clientCount * 8)); - return; - } - - ChannelId channelIds[channelCount]; - ClientId clientIds[clientCount]; - - for(uint8_t index = 0; index < channelCount; index++) { - channelIds[index] = be2le64((char*) payload.data_ptr(), payload_offset, &payload_offset); - } - - for(uint8_t index = 0; index < clientCount; index++) { - clientIds[index] = be2le16((char*) payload.data_ptr(), payload_offset, &payload_offset); - } - -#ifdef PKT_LOG_WHISPER - logTrace(this->getServerId(), "{} Whisper data length: {}. Client count: {}. Channel count: {}.", CLIENT_STR_LOG_PREFIX, dataLength, clientCount, channelCount); -#endif - - for(const auto& client : this->server->getClients()) { - auto speaking_client = dynamic_pointer_cast(client); - if(!speaking_client || client == this || !speaking_client->currentChannel) - continue; - - auto clientChannelId = speaking_client->getChannelId(); - auto clientId = speaking_client->getClientId(); - - for(uint8_t index = 0; index < channelCount; index++) { - if(channelIds[index] == clientChannelId) { - goto add_client; - } - } - - for(uint8_t index = 0; index < clientCount; index++) { - if(clientIds[index] == clientId) { - goto add_client; - } - } - - continue; - - add_client: - if(!speaking_client->shouldReceiveVoiceWhisper(this->ref())) { - continue; - } - - target_clients.push_back(speaking_client); - } - } - - if(target_clients.empty()) { - if(update_whisper_error(this->speak_last_no_whisper_target)) { - command_result result{error::whisper_no_targets}; - this->notifyError(result); - } - return; - } - - if(target_clients.size() > this->server->properties()[property::VIRTUALSERVER_MIN_CLIENTS_IN_CHANNEL_BEFORE_FORCED_SILENCE].as_save()) { - if(update_whisper_error(this->speak_last_too_many_whisper_targets)) { - command_result result{error::whisper_too_many_targets}; - this->notifyError(result); - } - return; - } - - /* send the packet */ - { - size_t voice_payload_length = payload.length() - payload_offset; - - //Create the packet data - char whisper_packet_buffer[kWhisperMaxHeaderLength + voice_payload_length]; - size_t whisper_packet_offset{0}; - size_t whisper_packet_teamspeak_offset{0}; - - /* writing the teaspeak header */ - if(head) { - auto uniqueId = this->getUid(); - auto nickname = this->getDisplayName(); - - if(uniqueId.length() > kWhisperClientUniqueIdLength) { - logCritical(LOG_GENERAL, "Clients unique id is longer than the expected max length of {}. Unique length: {}", kWhisperClientUniqueIdLength, uniqueId.length()); - return; - } - - if(nickname.length() > kMaxWhisperClientNameLength) { - logCritical(LOG_GENERAL, "Clients name is longer than the expected max length of {}. Name length: {}", kMaxWhisperClientNameLength, nickname.length()); - return; - } - - memset(whisper_packet_buffer + whisper_packet_offset, 0, kWhisperClientUniqueIdLength); - memcpy(whisper_packet_buffer + whisper_packet_offset, uniqueId.data(), uniqueId.length()); - whisper_packet_offset += kWhisperClientUniqueIdLength; - - whisper_packet_buffer[whisper_packet_offset++] = nickname.length(); - memcpy(whisper_packet_buffer + whisper_packet_offset, nickname.data(), nickname.length()); - whisper_packet_offset += nickname.length(); - } - - /* writing the "normal" header and payload */ - { - whisper_packet_teamspeak_offset = whisper_packet_offset; - - *(uint16_t*) &whisper_packet_buffer[whisper_packet_offset] = htons(voice_packet_id); - whisper_packet_offset += 2; - - *(uint16_t*) &whisper_packet_buffer[whisper_packet_offset] = htons(this->getClientId()); - whisper_packet_offset += 2; - - whisper_packet_buffer[whisper_packet_offset++] = voice_codec; - - if(voice_payload_length > 0) { - memcpy(&whisper_packet_buffer[whisper_packet_offset], &payload[payload_offset], voice_payload_length); - whisper_packet_offset += voice_payload_length; - } - } - - VoicePacketFlags flags{}; - flags.head = head; - - pipes::buffer_view teaspeak_packet{}, teamspeak_packet{}; - teaspeak_packet = pipes::buffer_view{whisper_packet_buffer, whisper_packet_offset}; - teamspeak_packet = pipes::buffer_view{whisper_packet_buffer + whisper_packet_teamspeak_offset, whisper_packet_offset - whisper_packet_teamspeak_offset}; - - auto self_ref = this->ref(); - for(const auto& cl : target_clients) { - if(cl == self_ref || cl->shouldReceiveVoiceWhisper(self_ref)) { - cl->send_voice_whisper_packet(teamspeak_packet, teaspeak_packet, flags); - } - } - } - - this->resetIdleTime(); - this->updateSpeak(false, std::chrono::system_clock::now()); -} - #define TEST_PARM(type) \ do {\ if(!cmd[0][key].castable())\ @@ -704,26 +411,28 @@ void SpeakingClient::processJoin() { TIMING_STEP(timings, "setup "); ref_server->registerClient(_this.lock()); - if(this->rtc_client_id) { - /* in case of client reconnect */ - this->server->rtc_server().destroy_client(this->rtc_client_id); - } - if(this->getType() == ClientType::CLIENT_TEAMSPEAK) { - this->rtc_client_id = this->server->rtc_server().create_native_client(dynamic_pointer_cast(this->ref())); - } else if(this->getType() == ClientType::CLIENT_TEASPEAK) { - /* TODO: Will be a RTP client later on, just without audio */ - this->rtc_client_id = this->server->rtc_server().create_native_client(dynamic_pointer_cast(this->ref())); - } else if(this->getType() == ClientType::CLIENT_WEB) { - std::string error; - auto result = this->server->rtc_server().create_rtp_client(dynamic_pointer_cast(this->ref()), error); - if(result > 0) { - this->rtc_client_id = result; - } else { - this->rtc_client_id = 0; - logCritical(this->getServerId(), "{} Failed to configure RTC session: {}", CLIENT_STR_LOG_PREFIX, error); + { + if(this->rtc_client_id) { + /* in case of client reconnect */ + this->server->rtc_server().destroy_client(this->rtc_client_id); + } + + std::string error{}; + this->rtc_client_id = this->server->rtc_server().create_client(dynamic_pointer_cast(this->ref())); + + if(auto voice_client{dynamic_cast(this)}; voice_client) { + if(!this->server->rtc_server().initialize_native_connection(error, this->rtc_client_id)) { + logCritical(this->getServerId(), "{} Native connection setup failed: {}", CLIENT_STR_LOG_PREFIX, error); + } + } + if(this->getType() == ClientType::CLIENT_WEB || this->getType() == ClientType::CLIENT_TEASPEAK) { + if(!this->server->rtc_server().initialize_rtc_connection(error, this->rtc_client_id)) { + logCritical(this->getServerId(), "{} RTC connection setup failed: {}", CLIENT_STR_LOG_PREFIX, error); + } else { + this->rtc_session_pending_describe = true; + } } } - this->rtc_session_pending_describe = true; TIMING_STEP(timings, "server reg "); ref_server->getGroupManager()->cleanupAssignments(this->getClientDatabaseId()); diff --git a/server/src/client/SpeakingClient.h b/server/src/client/SpeakingClient.h index 289a6b4..97cf7b4 100644 --- a/server/src/client/SpeakingClient.h +++ b/server/src/client/SpeakingClient.h @@ -1,6 +1,7 @@ #pragma once #include "ConnectedClient.h" +#include "./shared/WhisperHandler.h" #include #include @@ -35,19 +36,20 @@ namespace ts::server { SpeakingClient(sql::SqlManager* a, const std::shared_ptr& b); ~SpeakingClient() override; - //Voice + /* TODO: Remove this method. Currently only the music but uses that to broadcast his audio */ virtual void send_voice_packet(const pipes::buffer_view& /* voice packet data */, const VoicePacketFlags& /* flags */) = 0; - virtual bool shouldReceiveVoice(const std::shared_ptr &sender); + bool should_handle_voice_packet(size_t /* size */); - //Whisper + virtual bool shouldReceiveVoice(const std::shared_ptr &sender); bool shouldReceiveVoiceWhisper(const std::shared_ptr &sender); - virtual void send_voice_whisper_packet(const pipes::buffer_view& /* teamspeak payload */, const pipes::buffer_view& /* teaspeak payload */, const VoicePacketFlags& /* flags */) = 0; inline std::chrono::milliseconds takeSpokenTime() { auto time = this->speak_time; this->speak_time = std::chrono::milliseconds(0); return time; } + + [[nodiscard]] inline whisper::WhisperHandler& whisper_handler() { return this->whisper_handler_; } protected: void tick(const std::chrono::system_clock::time_point &time) override; @@ -58,9 +60,6 @@ namespace ts::server { command_result handleCommand(Command &command) override; public: - bool should_handle_voice_packet(size_t /* size */); - virtual void handlePacketVoiceWhisper(const pipes::buffer_view&, bool /* new */, bool /* head */); - virtual void processJoin(); void processLeave(); @@ -97,6 +96,8 @@ namespace ts::server { std::shared_ptr identityData; } handshake; + whisper::WhisperHandler whisper_handler_; + bool rtc_session_pending_describe{false}; rtc::RTCClientId rtc_client_id{0}; }; diff --git a/server/src/client/music/MusicClientPlayer.cpp b/server/src/client/music/MusicClientPlayer.cpp index ad9e47a..b96768b 100644 --- a/server/src/client/music/MusicClientPlayer.cpp +++ b/server/src/client/music/MusicClientPlayer.cpp @@ -383,9 +383,11 @@ void MusicClient::broadcast_music_stop() { SpeakingClient::VoicePacketFlags flags{}; - for(const auto& cl : this->server->getClientsByChannel(this->currentChannel)) - if(cl->shouldReceiveVoice(_this.lock())) + for(const auto& cl : this->server->getClientsByChannel(this->currentChannel)) { + if(cl->shouldReceiveVoice(_this.lock())) { cl->send_voice_packet(pipes::buffer_view{voice_buffer, voice_header_length}, flags); + } + } } void MusicClient::execute_music_tick(const shared_ptr& song) { diff --git a/server/src/client/shared/WhisperHandler.cpp b/server/src/client/shared/WhisperHandler.cpp new file mode 100644 index 0000000..1e5641e --- /dev/null +++ b/server/src/client/shared/WhisperHandler.cpp @@ -0,0 +1,364 @@ +// +// Created by WolverinDEV on 26/11/2020. +// + +#include "WhisperHandler.h" +#include "src/client/voice/VoiceClientConnection.h" +#include + +using namespace ts::server::whisper; + +constexpr static auto kMaxWhisperTargets{1024}; + +WhisperHandler::WhisperHandler(SpeakingClient* handle) : handle{handle} {}; +WhisperHandler::~WhisperHandler() { + if(this->whisper_head_ptr) { + ::free(this->whisper_head_ptr); + } +} + +bool WhisperHandler::validate_whisper_packet(const protocol::ClientPacketParser &packet, bool& match_last_header, void *&payload_ptr, size_t &payload_length) { + size_t head_length; + if(packet.flags() & protocol::PacketFlag::NewProtocol) { + if(packet.payload_length() < 3 + 10) { + /* packet too short */ + return false; + } + + head_length = 10; + } else { + if(packet.payload_length() < 3 + 2) { + /* packet too short */ + return false; + } + + auto channel_count = (uint8_t) packet.payload()[3]; + auto client_count = (uint8_t) packet.payload()[4]; + head_length = 2 + channel_count * 8 + client_count * 2; + + if(packet.payload_length() < 3 + head_length) { + /* packet is too short */ + return false; + } + } + + auto head_ptr = packet.payload().data_ptr() + 3; + match_last_header = this->whisper_head_length == head_length && memcmp(this->whisper_head_ptr, head_ptr, head_length) == 0; + if(!match_last_header) { + if(this->whisper_head_capacity < head_length) { + if(this->whisper_head_ptr) { + ::free(this->whisper_head_ptr); + } + + this->whisper_head_ptr = malloc(head_length); + this->whisper_head_capacity = head_length; + } + + this->whisper_head_length = head_length; + memcpy(this->whisper_head_ptr, head_ptr, head_length); + } + + assert(packet.payload_length() >= head_length + 3); + payload_ptr = (void*) (head_ptr + head_length); + payload_length = packet.payload_length() - head_length - 3; + + return true; +} + +bool WhisperHandler::process_packet(const protocol::ClientPacketParser &packet, void *&payload_ptr, size_t &payload_length) { + bool match_last_header; + if(!this->validate_whisper_packet(packet, match_last_header, payload_ptr, payload_length)) { + return false; + } + + auto current_timestamp = std::chrono::system_clock::now(); + switch (this->session_state) { + case SessionState::Uninitialized: + /* Definitively initialize a new session */ + break; + + case SessionState::InitializeFailed: { + if(!match_last_header) { + /* Last header does not matches the current header, we need to reinitialize the session */ + break; + } + + if(current_timestamp - std::chrono::milliseconds{500} < this->session_timestamp) { + /* Last initialize failed and is less than 500ms ago */ + return false; + } + + /* Lets try to initialize the whisper session again */ + break; + } + + case SessionState::Initialized: + if(!match_last_header) { + /* Last header does not matches the current header, we need to reinitialize the session */ + break; + } + + if(current_timestamp - std::chrono::seconds{5} > this->session_timestamp) { + /* Last session member update is 5 seconds ago. Updating the session again */ + break; + } + + /* We've nothing to change and everything is good */ + return true; + } + + this->session_timestamp = current_timestamp; + + auto head_ptr = packet.payload().data_ptr(); + size_t head_offset{3}; /* the first three bytes contain the voice sequence id and codec */ + + if(packet.flags() & protocol::PacketFlag::NewProtocol) { + auto type = head_ptr[head_offset++]; + auto target = head_ptr[head_offset++]; + auto type_id = be2le64(head_ptr, head_offset, &head_offset); + + auto result = this->initialize_session_new(2, type, target, type_id); + if(result.has_error()) { + this->session_state = SessionState::InitializeFailed; + this->handle->notifyError(result); + } else { + this->session_state = SessionState::Initialized; + } + result.release_data(); + } else { + auto channel_count = (uint8_t) head_ptr[head_offset++]; + auto client_count = (uint8_t) head_ptr[head_offset++]; + + ChannelId channel_ids[channel_count]; + ClientId client_ids[client_count]; + + for(uint8_t index = 0; index < channel_count; index++) { + channel_ids[index] = be2le64(head_ptr, head_offset, &head_offset); + } + + for(uint8_t index = 0; index < client_count; index++) { + client_ids[index] = be2le16(head_ptr, head_offset, &head_offset); + } + + auto result = this->initialize_session_old(2, client_ids, client_count, channel_ids, channel_count); + if(result.has_error()) { + this->session_state = SessionState::InitializeFailed; + this->handle->notifyError(result); + } else { + this->session_state = SessionState::Initialized; + } + result.release_data(); + } + + return this->session_state == SessionState::Initialized; +} + +void WhisperHandler::handle_session_reset() { + this->session_state = SessionState::Uninitialized; + if(this->whisper_head_ptr) { + ::free(this->whisper_head_ptr); + this->whisper_head_capacity = 0; + this->whisper_head_length = 0; + } +} + +size_t WhisperHandler::max_whisper_targets() { + auto server = this->handle->getServer(); + if(!server) { + return false; + } + + auto result = server->properties()[property::VIRTUALSERVER_MIN_CLIENTS_IN_CHANNEL_BEFORE_FORCED_SILENCE].as_save([]{ return kMaxWhisperTargets; }); + if(result > kMaxWhisperTargets) { + result = kMaxWhisperTargets; + } + return result; +} + +ts::command_result WhisperHandler::initialize_session_old(uint32_t stream_id, const uint16_t *client_ids, size_t client_count, const uint64_t *channel_ids, size_t channel_count) { + auto server = this->handle->getServer(); + if(!server) { + return ts::command_result{error::vs_critical, "missing server"}; + } + + std::vector> target_clients{}; + auto connected_clients = server->getClients(); + target_clients.reserve(connected_clients.size()); + + for(const auto& connected_client : connected_clients) { + auto speaking_client = dynamic_pointer_cast(connected_client); + if(!speaking_client || speaking_client == this->handle || !speaking_client->rtc_client_id) { + continue; + } + + auto client_channel_id = speaking_client->getChannelId(); + auto client_id = speaking_client->getClientId(); + + for(size_t index = 0; index < channel_count; index++) { + if(channel_ids[index] == client_channel_id) { + goto add_client; + } + } + + for(size_t index = 0; index < client_count; index++) { + if(client_ids[index] == client_id) { + goto add_client; + } + } + + continue; + + add_client: + target_clients.push_back(speaking_client); + } + + return this->configure_rtc_clients(stream_id, target_clients); +} + +ts::command_result WhisperHandler::initialize_session_new(uint32_t stream_id, uint8_t type_u8, uint8_t target_u8, uint64_t type_id) { + auto type = (WhisperType) type_u8; + auto target = (WhisperTarget) target_u8; + + auto server = this->handle->getServer(); + if(!server) { + return ts::command_result{error::vs_critical, "missing server"}; + } + +#ifdef PKT_LOG_WHISPER + logTrace(this->getServerId(), "{} Whisper data length: {}. Type: {}. Target: {}. Target ID: {}.", CLIENT_STR_LOG_PREFIX, data_length, type, target, type_id); +#endif + std::vector> target_clients{}; + if(type == WhisperType::ECHO) { + target_clients.push_back(dynamic_pointer_cast(this->handle->ref())); + } else { + auto connected_clients = server->getClients(); + target_clients.reserve(connected_clients.size()); + for (const auto &available_client : connected_clients) { + auto speaking_client = dynamic_pointer_cast(available_client); + if (!speaking_client || this->handle == speaking_client || !speaking_client->rtc_client_id) { + continue; + } + + if (type == WhisperType::ALL) { + target_clients.push_back(speaking_client); + } else if (type == WhisperType::SERVER_GROUP) { + if (type_id == 0) { + target_clients.push_back(speaking_client); + } else { + std::shared_lock client_view_lock(speaking_client->get_channel_lock()); + for (const auto &id : speaking_client->current_server_groups()) { + if (id == type_id) { + target_clients.push_back(speaking_client); + break; + } + } + } + } else if (type == WhisperType::CHANNEL_GROUP) { + if (speaking_client->current_channel_group() == type_id) { + target_clients.push_back(speaking_client); + } + } else if (type == WhisperType::CHANNEL_COMMANDER) { + if (speaking_client->properties()[property::CLIENT_IS_CHANNEL_COMMANDER].as_save([] { return false; })) { + target_clients.push_back(speaking_client); + } + } else { + return ts::command_result{error::parameter_invalid, "type"}; + } + } + + if (target == WhisperTarget::CHANNEL_CURRENT) { + target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr &target) { + return target->getChannel() != this->handle->getChannel(); + }), + target_clients.end()); + } else if (target == WhisperTarget::CHANNEL_PARENT) { + auto current_parent = this->handle->getChannel(); + if (current_parent && (current_parent = current_parent->parent())) { + target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr &target) { + return target->getChannel() != current_parent; + }), + target_clients.end()); + } else { + target_clients.clear(); + } + } else if (target == WhisperTarget::CHANNEL_ALL_PARENT) { + auto current_parent = this->handle->getChannel(); + if (current_parent && (current_parent = current_parent->parent())) { + target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr &target) { + auto tmp_parent = current_parent; + while (tmp_parent && tmp_parent != target->getChannel()) + tmp_parent = tmp_parent->parent(); + return target->getChannel() != tmp_parent; + }), + target_clients.end()); + } else { + target_clients.clear(); + } + } else if (target == WhisperTarget::CHANNEL_FAMILY) { + auto current_channel = this->handle->getChannel(); + target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr &target) { + auto tmp_current = target->getChannel(); + while (tmp_current && tmp_current != current_channel) { + tmp_current = tmp_current->parent(); + } + return current_channel != tmp_current; + }), + target_clients.end()); + } else if (target == WhisperTarget::CHANNEL_COMPLETE_FAMILY) { + shared_ptr current = this->handle->getChannel(); + while (current && current->parent()) current = current->parent(); + target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr &target) { + auto tmp_current = target->getChannel(); + while (tmp_current && tmp_current != current) + tmp_current = tmp_current->parent(); + return current != tmp_current; + }), + target_clients.end()); + } else if (target == WhisperTarget::CHANNEL_SUBCHANNELS) { + shared_ptr current = this->handle->getChannel(); + target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr &target) { + return target->getChannel()->parent() != current; + }), + target_clients.end()); + } else if(target == WhisperTarget::CHANNEL_ALL) { + /* nothing to filter out */ + } else { + return ts::command_result{error::parameter_invalid, "target"}; + } + } + + return this->configure_rtc_clients(stream_id, target_clients); +} + +ts::command_result WhisperHandler::configure_rtc_clients(uint32_t stream_id, const std::vector>& target_clients) { + auto max_clients = this->max_whisper_targets(); + assert(max_clients <= kMaxWhisperTargets); + + if(target_clients.size() >= max_clients) { + return ts::command_result{error::whisper_too_many_targets}; + } + + if(target_clients.empty()) { + return ts::command_result{error::whisper_no_targets}; + } + + auto server = this->handle->getServer(); + if(!server) { + return ts::command_result{error::vs_critical, "missing server"}; + } + + uint32_t target_client_ids[kMaxWhisperTargets]; + size_t target_client_count{0}; + for(const auto& target_client : target_clients) { + target_client_ids[target_client_count++] = target_client->rtc_client_id; + } + + std::string error; + if(!server->rtc_server().configure_whisper_session(error, this->handle->rtc_client_id, stream_id, target_client_ids, target_client_count)) { + logCritical(server->getServerId(), "{} Failed to configure whisper session with {} participants.", CLIENT_STR_LOG_PREFIX_(this->handle), target_client_count); + return ts::command_result{error::vs_critical, error}; + } + + logTrace(server->getServerId(), "{} Configured whisper session with {} participants.", CLIENT_STR_LOG_PREFIX_(this->handle), target_client_count); + return ts::command_result{error::ok}; +} \ No newline at end of file diff --git a/server/src/client/shared/WhisperHandler.h b/server/src/client/shared/WhisperHandler.h new file mode 100644 index 0000000..5e4eaf8 --- /dev/null +++ b/server/src/client/shared/WhisperHandler.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include + +namespace ts::connection { + class VoiceClientConnection; +} + +namespace ts::server { + class VoiceClient; + class SpeakingClient; +} + +namespace ts::server::whisper { + enum struct WhisperType { + SERVER_GROUP = 0, + CHANNEL_GROUP = 1, + CHANNEL_COMMANDER = 2, + ALL = 3, + + ECHO = 0x10, + }; + + enum struct WhisperTarget { + CHANNEL_ALL = 0, + CHANNEL_CURRENT = 1, + CHANNEL_PARENT = 2, + CHANNEL_ALL_PARENT = 3, + CHANNEL_FAMILY = 4, + CHANNEL_COMPLETE_FAMILY = 5, + CHANNEL_SUBCHANNELS = 6 + }; + + class WhisperHandler { + public: + explicit WhisperHandler(SpeakingClient* /* handle */); + ~WhisperHandler(); + + /* + * Preprocess a whisper packet. + * If the result is false the packet should not be send into the rtc source pipe. + */ + bool process_packet(const protocol::ClientPacketParser& /* packet */, void*& /* payload ptr */, size_t& /* payload length */); + + ts::command_result initialize_session_new(uint32_t /* stream id */, uint8_t /* type */, uint8_t /* target */, uint64_t /* type id */); + ts::command_result initialize_session_old(uint32_t /* stream id */, const uint16_t* /* client ids */, size_t /* client count */, const uint64_t* /* channel ids */, size_t /* channel count */); + + void handle_session_reset(); + + private: + enum struct SessionState { + Uninitialized, + InitializeFailed, + Initialized + }; + SpeakingClient* handle; + + SessionState session_state{SessionState::Uninitialized}; + std::chrono::system_clock::time_point session_timestamp{}; + + void* whisper_head_ptr{nullptr}; + size_t whisper_head_length{0}; + size_t whisper_head_capacity{0}; + + /** + * This also updates the last head ptr. + * @return True if the packet is a valid whisper packet. The payload ptr and payload length variables will be set + */ + bool validate_whisper_packet(const protocol::ClientPacketParser& /* packet */, bool& /* matches last header */, void*& /* payload ptr */, size_t& /* payload length */); + ts::command_result configure_rtc_clients(uint32_t /* stream id */, const std::vector>& /* clients */); + + [[nodiscard]] size_t max_whisper_targets(); + }; +} \ No newline at end of file diff --git a/server/src/client/voice/VoiceClient.cpp b/server/src/client/voice/VoiceClient.cpp index d8add2e..f493e45 100644 --- a/server/src/client/voice/VoiceClient.cpp +++ b/server/src/client/voice/VoiceClient.cpp @@ -16,6 +16,10 @@ using namespace std::chrono; using namespace ts::server; using namespace ts::protocol; +constexpr static auto kMaxWhisperClientNameLength{30}; +constexpr static auto kWhisperClientUniqueIdLength{28}; /* base64 encoded SHA1 hash */ +constexpr static auto kWhisperMaxHeaderLength{2 + 2 + 1 + 2 + kWhisperClientUniqueIdLength + 1 + kMaxWhisperClientNameLength}; + VoiceClient::VoiceClient(const std::shared_ptr& server, const sockaddr_storage* address) : SpeakingClient(server->server->sql, server->server), voice_server(server) { assert(address); memtrack::allocated(this); @@ -255,18 +259,84 @@ void VoiceClient::send_voice_packet(const pipes::buffer_view &voice_buffer, cons this->connection->send_packet(PacketType::VOICE, packet_flags, voice_buffer.data_ptr(), voice_buffer.length()); } -void VoiceClient::send_voice_whisper_packet(const pipes::buffer_view &teamspeak_packet, const pipes::buffer_view &teaspeak_packet, const SpeakingClient::VoicePacketFlags &flags) { - PacketFlag::PacketFlags packet_flags{PacketFlag::None}; - packet_flags |= flags.encrypted ? 0U : PacketFlag::Unencrypted; - packet_flags |= flags.head ? PacketFlag::Compressed : 0U; - packet_flags |= flags.fragmented ? PacketFlag::Fragmented : 0U; - packet_flags |= flags.new_protocol ? PacketFlag::NewProtocol : 0U; +void VoiceClient::send_voice(const std::shared_ptr &source_client, uint16_t seq_no, uint8_t codec, const void *payload, size_t payload_length) { + /* TODO: Somehow set the head (compressed) flag for beginning voice packets? */ + auto packet = protocol::allocate_outgoing_packet(payload_length + 5); + packet->type_and_flags = protocol::PacketType::VOICE; - if(this->getType() == ClientType::CLIENT_TEASPEAK) { - this->connection->send_packet(PacketType::VOICE_WHISPER, packet_flags, teaspeak_packet.data_ptr(), teaspeak_packet.length()); + *((uint16_t*) packet->payload + 0) = htons(seq_no); + *((uint16_t*) packet->payload + 1) = htons(source_client->getClientId()); + packet->payload[4] = codec; + + if(payload) { + memcpy(packet->payload + 5, payload, payload_length); } else { - this->connection->send_packet(PacketType::VOICE_WHISPER, packet_flags, teamspeak_packet.data_ptr(), teamspeak_packet.length()); + assert(payload_length == 0); } + + this->getConnection()->send_packet(packet); +} + +void VoiceClient::send_voice_whisper(const std::shared_ptr &source_client, uint16_t seq_no, uint8_t codec, const void *payload, size_t payload_length) { + bool head{false}; + if(this->whisper_head_counter < 5) { + head = true; + this->whisper_head_counter++; + } + if(!payload) { + this->whisper_head_counter = 0; + } + + protocol::OutgoingServerPacket* packet; + size_t payload_offset{0}; + + if(head && this->getType() == ClientType::CLIENT_TEASPEAK) { + auto uniqueId = source_client->getUid(); + auto nickname = source_client->getDisplayName(); + + if(uniqueId.length() > kWhisperClientUniqueIdLength) { + logCritical(LOG_GENERAL, "Clients unique id is longer than the expected max length of {}. Unique length: {}", kWhisperClientUniqueIdLength, uniqueId.length()); + return; + } + + if(nickname.length() > kMaxWhisperClientNameLength) { + logCritical(LOG_GENERAL, "Clients name is longer than the expected max length of {}. Name length: {}", kMaxWhisperClientNameLength, nickname.length()); + return; + } + + packet = protocol::allocate_outgoing_packet(payload_length + 5 + kWhisperClientUniqueIdLength + 1 + nickname.length()); + packet->type_and_flags |= protocol::PacketFlag::Compressed; + + memset(packet->payload + payload_offset, 0, kWhisperClientUniqueIdLength); + memcpy(packet->payload + payload_offset, uniqueId.data(), uniqueId.length()); + payload_offset += kWhisperClientUniqueIdLength; + + packet->payload[payload_offset++] = nickname.length(); + memcpy(packet->payload + payload_offset, nickname.data(), nickname.length()); + payload_offset += nickname.length(); + } else { + packet = protocol::allocate_outgoing_packet(payload_length + 5); + } + packet->type_and_flags |= protocol::PacketType::VOICE_WHISPER; + + *((uint16_t*) &packet->payload[payload_offset]) = htons(seq_no); + payload_offset += 2; + + *((uint16_t*) &packet->payload[payload_offset]) = htons(source_client->getClientId()); + payload_offset += 2; + + packet->payload[payload_offset] = codec; + payload_offset += 1; + + if(payload) { + memcpy(packet->payload + payload_offset, payload, payload_length); + payload_offset += payload_length; + } else { + assert(payload_length == 0); + } + packet->payload_size = payload_offset; + + this->getConnection()->send_packet(packet); } float VoiceClient::current_ping_deviation() { @@ -279,8 +349,18 @@ float VoiceClient::current_packet_loss() const { void VoiceClient::processJoin() { SpeakingClient::processJoin(); if(this->rtc_client_id > 0) { - auto sender = this->server->rtc_server().create_audio_source_supplier_sender(this->rtc_client_id); - assert(sender.has_value()); - this->rtc_audio_supplier.reset(*sender); + { + /* Normal audio channel */ + auto sender = this->server->rtc_server().create_audio_source_supplier_sender(this->rtc_client_id, 1); + assert(sender.has_value()); + this->rtc_audio_supplier.reset(*sender); + } + + { + /* Audio whisper channel */ + auto sender = this->server->rtc_server().create_audio_source_supplier_sender(this->rtc_client_id, 2); + assert(sender.has_value()); + this->rtc_audio_whisper_supplier.reset(*sender); + } } } diff --git a/server/src/client/voice/VoiceClient.h b/server/src/client/voice/VoiceClient.h index 3cbd157..1a368d7 100644 --- a/server/src/client/voice/VoiceClient.h +++ b/server/src/client/voice/VoiceClient.h @@ -90,11 +90,8 @@ namespace ts { void handlePacketCommand(const pipes::buffer_view&); public: void send_voice_packet(const pipes::buffer_view &packet, const VoicePacketFlags &flags) override; - void send_voice_whisper_packet( - const pipes::buffer_view &/* teamspeak packet */, - const pipes::buffer_view &/* teaspeak packet */, - const VoicePacketFlags &flags - ) override; + void send_voice(const std::shared_ptr& /* source client */, uint16_t /* seq no */, uint8_t /* codec */, const void* /* payload */, size_t /* payload length */); + void send_voice_whisper(const std::shared_ptr& /* source client */, uint16_t /* seq no */, uint8_t /* codec */, const void* /* payload */, size_t /* payload length */); void processJoin() override; protected: @@ -105,7 +102,10 @@ namespace ts { bool final_disconnected = false; rtc::NativeAudioSourceSupplier rtc_audio_supplier{}; + rtc::NativeAudioSourceSupplier rtc_audio_whisper_supplier{}; + uint16_t stop_seq_counter{0}; + uint16_t whisper_head_counter{0}; //General TS3 manager commands command_result handleCommandClientInit(Command&) override; diff --git a/server/src/client/voice/VoiceClientConnection.h b/server/src/client/voice/VoiceClientConnection.h index 1fb6915..73255dd 100644 --- a/server/src/client/voice/VoiceClientConnection.h +++ b/server/src/client/voice/VoiceClientConnection.h @@ -1,26 +1,27 @@ #pragma once -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "VoiceClient.h" -#include "protocol/AcknowledgeManager.h" -#include -#include "./PacketStatistics.h" #include "./PacketDecoder.h" #include "./PacketEncoder.h" +#include "./PacketStatistics.h" #include "./ServerCommandExecutor.h" #include "CryptSetupHandler.h" #include "PingHandler.h" +#include "VoiceClient.h" +#include "protocol/AcknowledgeManager.h" +#include "src/client/shared/WhisperHandler.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include //#define LOG_ACK_SYSTEM #ifdef LOG_ACK_SYSTEM diff --git a/server/src/client/voice/VoiceClientConnectionPacketHandler.cpp b/server/src/client/voice/VoiceClientConnectionPacketHandler.cpp index 3d2f8ea..3fa926d 100644 --- a/server/src/client/voice/VoiceClientConnectionPacketHandler.cpp +++ b/server/src/client/voice/VoiceClientConnectionPacketHandler.cpp @@ -37,13 +37,31 @@ void VoiceClientConnection::handlePacketVoice(const protocol::ClientPacketParser sink.send_audio(vpacketId, false, vpacketId * 960, codec, std::string_view{payload.data_ptr() + 3, payload.length() - 3}); } + + client->resetIdleTime(); + client->updateSpeak(false, std::chrono::system_clock::now()); } void VoiceClientConnection::handlePacketVoiceWhisper(const ts::protocol::ClientPacketParser &packet) { auto client = this->getCurrentClient(); if(!client) return; - client->handlePacketVoiceWhisper(packet.payload(), (packet.flags() & PacketFlag::NewProtocol) > 0, (packet.flags() & PacketFlag::Compressed) > 0); + void* payload; + size_t payload_length; + + if(!client->whisper_handler().process_packet(packet, payload, payload_length)) { + /* packet invalid or session failed to initialize */ + return; + } + + auto voice_packet_id = ntohs(*packet.payload().data_ptr()); + auto voice_codec = packet.payload().data_ptr()[2]; + + auto& sink = client->rtc_audio_whisper_supplier; + sink.send_audio(voice_packet_id, false, voice_packet_id * 960, voice_codec, std::string_view{(const char*) payload, payload_length}); + + client->resetIdleTime(); + client->updateSpeak(false, std::chrono::system_clock::now()); } void VoiceClientConnection::handlePacketAck(const protocol::ClientPacketParser& packet) { diff --git a/server/src/client/web/WebClient.cpp b/server/src/client/web/WebClient.cpp index 7aef68c..b9ff979 100644 --- a/server/src/client/web/WebClient.cpp +++ b/server/src/client/web/WebClient.cpp @@ -265,10 +265,11 @@ command_result WebClient::handleCommand(Command &command) { return result; } } - if(command.command() == "setwhispertarget") { - return this->handleCommandSetWhisperTarget(command); - } else if(command.command() == "clearwhispertarget") { - return this->handleCommandClearWhisperTarget(command); + + if(command.command() == "whispersessioninitialize") { + return this->handleCommandWhisperSessionInitialize(command); + } else if(command.command() == "whispersessionreset") { + return this->handleCommandWhisperSessionReset(command); } return SpeakingClient::handleCommand(command); } @@ -528,11 +529,7 @@ void WebClient::send_voice_packet(const pipes::buffer_view &view, const Speaking /* Should never be called! */ } -void WebClient::send_voice_whisper_packet(const pipes::buffer_view &, const pipes::buffer_view &teaspeak_packet, const SpeakingClient::VoicePacketFlags &flags) { - /* Should never be called! */ -} - -command_result WebClient::handleCommandSetWhisperTarget(Command &command) { +command_result WebClient::handleCommandWhisperSessionInitialize(Command &command) { auto server = this->getServer(); if(!server) { return command_result{error::server_unbound}; @@ -620,7 +617,7 @@ command_result WebClient::handleCommandSetWhisperTarget(Command &command) { return command_result{error::ok}; } -command_result WebClient::handleCommandClearWhisperTarget(Command &command) { +command_result WebClient::handleCommandWhisperSessionReset(Command &command) { std::lock_guard whisper_buffer_lock{this->whisper.mutex}; this->whisper.is_set = false; return command_result{error::ok}; diff --git a/server/src/client/web/WebClient.h b/server/src/client/web/WebClient.h index 0e2a33a..b6f57d8 100644 --- a/server/src/client/web/WebClient.h +++ b/server/src/client/web/WebClient.h @@ -105,15 +105,14 @@ namespace ts::server { public: void send_voice_packet(const pipes::buffer_view &view, const VoicePacketFlags &flags) override; - void send_voice_whisper_packet(const pipes::buffer_view &/* teamspeak packet */, const pipes::buffer_view &/* teaspeak packet */, const VoicePacketFlags &flags) override; protected: command_result handleCommand(Command &command) override; command_result handleCommandClientInit(Command &command) override; - command_result handleCommandSetWhisperTarget(Command &command); - command_result handleCommandClearWhisperTarget(Command &command); + command_result handleCommandWhisperSessionInitialize(Command &command); + command_result handleCommandWhisperSessionReset(Command &command); }; } #endif \ No newline at end of file diff --git a/server/src/rtc/imports.h b/server/src/rtc/imports.h index 90b6332..13e0159 100644 --- a/server/src/rtc/imports.h +++ b/server/src/rtc/imports.h @@ -21,6 +21,8 @@ struct NativeCallbacks { void(*client_stream_stop)(const void* /* callback data */, uint32_t /* stream id */, const void* /* source callback data */); void(*client_audio_sender_data)(const void* /* callback data */, const void* /* source callback data */, uint8_t /* mode */, uint16_t /* seq. no. */, uint8_t /* codec */, const void* /* data */, uint32_t /* length */); + + void(*client_whisper_session_reset)(const void* /* callback data */); }; struct RtpClientConfigureOptions { @@ -39,22 +41,23 @@ extern const char* librtc_version(); extern void librtc_free_str(const char* /* ptr */); extern const char* librtc_init(const NativeCallbacks* /* */, size_t /* size of the callback struct */); +extern const char* librtc_rtc_configure(void* /* callback data */, const RtpClientConfigureOptions* /* config */, size_t /* config size */); extern void* librtc_create_server(); extern void librtc_destroy_server(void* /* server */); -extern uint32_t librtc_create_rtp_client(void* /* server */, void* /* callback data */, const char** /* error ptr */); -extern uint32_t librtc_create_native_client(void* /* server */, void* /* callback data */); +extern uint32_t librtc_create_client(void* /* server */, void* /* callback data */); extern void librtc_destroy_client(void* /* server */, uint32_t /* client id */); -extern const char* librtc_rtc_configure(void* /* callback data */, const RtpClientConfigureOptions* /* config */, size_t /* config size */); +extern const char* librtc_initialize_rtc_connection(void* /* server */, uint32_t /* client id */); +extern const char* librtc_initialize_native_connection(void* /* server */, uint32_t /* client id */); extern const char* librtc_reset_rtp_session(void* /* server */, uint32_t /* client id */); extern const char* librtc_apply_remote_description(void* /* server */, uint32_t /* client id */, uint32_t /* mode */, const char* /* description */); extern const char* librtc_generate_local_description(void* /* server */, uint32_t /* client id */, char** /* description */); extern const char* librtc_add_ice_candidate(void* /* server */, uint32_t /* client id */, uint32_t /* media line */, const char* /* candidate */); -extern void* librtc_create_audio_source_supplier(void* /* server */, uint32_t /* client id */); +extern void* librtc_create_audio_source_supplier(void* /* server */, uint32_t /* client id */, uint32_t /* stream id */); extern void librtc_audio_source_supply(void* /* sender */, uint16_t /* seq no */, bool /* marked */, @@ -69,6 +72,8 @@ extern uint32_t librtc_assign_channel(void* /* server */, uint32_t /* client id extern uint32_t librtc_client_broadcast(void* /* server */, uint32_t /* client id */, uint8_t /* broadcast type */, uint32_t /* stream id */); extern void librtc_destroy_channel(void* /* server */, uint32_t /* channel */); +extern const char* librtc_whisper_configure(void* /* server */, uint32_t /* client id */, uint32_t /* source stream id */, uint32_t* /* client ids */, uint32_t /* client id count */); + #ifdef __cplusplus }; #endif \ No newline at end of file diff --git a/server/src/rtc/lib.cpp b/server/src/rtc/lib.cpp index fb3a0a5..a5f7f83 100644 --- a/server/src/rtc/lib.cpp +++ b/server/src/rtc/lib.cpp @@ -187,31 +187,32 @@ void librtc_callback_client_audio_sender_data(const void* callback_data_ptr, con return; } - /* TODO: Somehow set the head (compressed) flag for beginning voice packets? */ - auto packet = protocol::allocate_outgoing_packet(length + 5); - packet->type_and_flags = protocol::PacketType::VOICE; - - *((uint16_t*) packet->payload + 0) = htons(seq_no); - *((uint16_t*) packet->payload + 1) = htons(source_data->client_id); - packet->payload[4] = codec; - if(data) { - memcpy(packet->payload + 5, data, length); - } else { - assert(length == 0); - } - - target_client->getConnection()->send_packet(packet); - } else { + target_client->send_voice(source_client, seq_no, codec, data, length); + } else if(mode == 1) { if(!target_client->shouldReceiveVoiceWhisper(source_client)) { return; } - /* FIXME: TODO! */ + target_client->send_voice_whisper(source_client, seq_no, codec, data, length); + } else { + /* we've received audio with an invalid mode.... */ } } +void librtc_client_whisper_session_reset(const void* callback_data_ptr) { + auto callback_data = (LibCallbackData*) callback_data_ptr; + + auto client = std::dynamic_pointer_cast(callback_data->weak_ref.lock()); + if(!client) { return; } + + client->whisper_handler().handle_session_reset(); + + ts::command_builder notify{"notifywhispersessionreset"}; + client->sendCommand(notify); +} + static NativeCallbacks native_callbacks{ - .version = 3, + .version = 4, .log = librtc_callback_log, .free_client_data = librtc_callback_free_client_data, @@ -224,7 +225,8 @@ static NativeCallbacks native_callbacks{ .client_stream_start = librtc_callback_client_audio_start, .client_stream_stop = librtc_callback_client_audio_stop, - .client_audio_sender_data = librtc_callback_client_audio_sender_data + .client_audio_sender_data = librtc_callback_client_audio_sender_data, + .client_whisper_session_reset = librtc_client_whisper_session_reset }; std::string_view rtc::version() { @@ -248,36 +250,31 @@ Server::~Server() { librtc_destroy_server(this->server_ptr); } -RTCClientId Server::create_rtp_client(const std::shared_ptr &client, std::string& error) { +RTCClientId Server::create_client(const std::shared_ptr &client) { auto data = new LibCallbackData{ .client_id = client->getClientId(), .weak_ref = client }; - const char* error_ptr{nullptr}; - auto client_id = librtc_create_rtp_client(this->server_ptr, data, &error_ptr); - if(client_id == 0) { - /* LibCallbackData will be automatically freed */ - - if(error_ptr) { - error.clear(); - error.append(error_ptr); - librtc_free_str(error_ptr); - } else { - error = "unknown error"; - } - - return 0; - } - return client_id; + return librtc_create_client(this->server_ptr, data); } -RTCClientId Server::create_native_client(const std::shared_ptr &client) { - auto data = new LibCallbackData{ - .client_id = client->getClientId(), - .weak_ref = client - }; - return librtc_create_native_client(this->server_ptr, data); +bool Server::initialize_rtc_connection(std::string &error, RTCClientId client_id) { + auto error_ptr = librtc_initialize_rtc_connection(this->server_ptr, client_id); + if(!error_ptr) { return true; } + + error = std::string{error_ptr}; + librtc_free_str(error_ptr); + return false; +} + +bool Server::initialize_native_connection(std::string &error, RTCClientId client_id) { + auto error_ptr = librtc_initialize_native_connection(this->server_ptr, client_id); + if(!error_ptr) { return true; } + + error = std::string{error_ptr}; + librtc_free_str(error_ptr); + return false; } void Server::destroy_client(RTCClientId client_id) { @@ -350,8 +347,8 @@ BroadcastStartResult Server::start_broadcast(uint32_t client_id, uint8_t btype, } } -std::optional Server::create_audio_source_supplier_sender(uint32_t client_id) { - auto result = librtc_create_audio_source_supplier(this->server_ptr, client_id); +std::optional Server::create_audio_source_supplier_sender(uint32_t client_id, uint32_t stream_id) { + auto result = librtc_create_audio_source_supplier(this->server_ptr, client_id, stream_id); if(!result) { return std::nullopt; } return std::make_optional(result); @@ -361,6 +358,15 @@ void Server::destroy_channel(uint32_t channel_id) { librtc_destroy_channel(this->server_ptr, channel_id); } +bool Server::configure_whisper_session(std::string &error, RTCClientId client_id, uint32_t source_stream_id, RTCClientId *client_ids, size_t client_id_count) { + auto error_ptr = librtc_whisper_configure(this->server_ptr, client_id, source_stream_id, client_ids, client_id_count); + if(!error_ptr) { return true; } + + error = std::string{error_ptr}; + librtc_free_str(error_ptr); + return false; +} + NativeAudioSourceSupplier::NativeAudioSourceSupplier(void *ptr) : sender_ptr{ptr} {} NativeAudioSourceSupplier::NativeAudioSourceSupplier(NativeAudioSourceSupplier &&other) noexcept : sender_ptr{other.sender_ptr} { other.sender_ptr = nullptr; diff --git a/server/src/rtc/lib.h b/server/src/rtc/lib.h index db0c5b4..f433963 100644 --- a/server/src/rtc/lib.h +++ b/server/src/rtc/lib.h @@ -38,8 +38,9 @@ namespace ts::rtc { Server(); ~Server(); - RTCClientId create_rtp_client(const std::shared_ptr& /* client */, std::string& /* error */); - RTCClientId create_native_client(const std::shared_ptr& /* client */); + RTCClientId create_client(const std::shared_ptr& /* client */); + bool initialize_rtc_connection(std::string& /* error */, RTCClientId /* client id */); + bool initialize_native_connection(std::string& /* error */, RTCClientId /* client id */); void destroy_client(RTCClientId /* client id */); /* RTC client actions */ @@ -50,13 +51,16 @@ namespace ts::rtc { void ice_candidates_finished(RTCClientId /* client id */); /* Native client actions */ - std::optional create_audio_source_supplier_sender(RTCClientId /* client id */); + std::optional create_audio_source_supplier_sender(RTCClientId /* client id */, uint32_t /* stream id */); /* channel actions */ uint32_t create_channel(); ChannelAssignResult assign_channel(RTCClientId /* client id */, RTCChannelId /* channel id */); BroadcastStartResult start_broadcast(RTCClientId /* client id */, uint8_t /* broadcast type */, RTCStreamId /* stream id */); void destroy_channel(RTCChannelId /* channel id */); + + /* whisper actions */ + bool configure_whisper_session(std::string& /* error */, RTCClientId /* client id */, uint32_t /* source stream id */, RTCClientId* /* session members */, size_t /* session member count */); private: void* server_ptr{nullptr}; }; diff --git a/shared b/shared index 23db0ed..2cc8a42 160000 --- a/shared +++ b/shared @@ -1 +1 @@ -Subproject commit 23db0edd229a4cc37390bfc5df870d7aaf191f03 +Subproject commit 2cc8a42ce7639efa2cdf6eb30a2a02f099ce1154