Supporting voice whisper again
This commit is contained in:
parent
ccc3bad705
commit
f0b094d7e4
@ -1 +1 @@
|
||||
Subproject commit be995e99c45ff7e5ba4adb36529a53558e06fa61
|
||||
Subproject commit c08922955d079e74910e7a755d757b50fda3065f
|
@ -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
|
||||
|
||||
|
@ -267,6 +267,11 @@ namespace ts {
|
||||
inline std::shared_ptr<ConnectedClient> 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<GroupId>& current_server_groups() { return this->cached_server_groups; }
|
||||
[[nodiscard]] inline GroupId& current_channel_group() { return this->cached_channel_group; }
|
||||
|
||||
/*
|
||||
* permission stuff
|
||||
*/
|
||||
|
@ -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<VirtualServer> &b) : ConnectedClient(a, b) {
|
||||
SpeakingClient::SpeakingClient(sql::SqlManager *a, const std::shared_ptr<VirtualServer> &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 := <server group id>
|
||||
//Channel group => type := CHANNEL_GROUP and target_id := <channel group 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<std::shared_ptr<SpeakingClient>> 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<SpeakingClient>(this->ref()));
|
||||
} else {
|
||||
for(const auto& client : this->server->getClients()) {
|
||||
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(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<bool>())
|
||||
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<SpeakingClient>& 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<SpeakingClient>& target) {
|
||||
return target->currentChannel != current_parent;
|
||||
}), target_clients.end());
|
||||
} else if(target == WhisperTarget::CHANNEL_ALL_PARENT) {
|
||||
shared_ptr<BasicChannel> 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<SpeakingClient>& 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<BasicChannel> current = this->currentChannel;
|
||||
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr<SpeakingClient>& 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<BasicChannel> 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<SpeakingClient>& 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<BasicChannel> current = this->currentChannel;
|
||||
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr<SpeakingClient>& 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<ConnectedClient>& cl) {
|
||||
auto speakingClient = dynamic_pointer_cast<SpeakingClient>(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<size_t>()) {
|
||||
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<SpeakingClient>(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<size_t>()) {
|
||||
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<type>())\
|
||||
@ -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<SpeakingClient>(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<SpeakingClient>(this->ref()));
|
||||
} else if(this->getType() == ClientType::CLIENT_WEB) {
|
||||
std::string error;
|
||||
auto result = this->server->rtc_server().create_rtp_client(dynamic_pointer_cast<SpeakingClient>(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<SpeakingClient>(this->ref()));
|
||||
|
||||
if(auto voice_client{dynamic_cast<VoiceClient*>(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());
|
||||
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "ConnectedClient.h"
|
||||
#include "./shared/WhisperHandler.h"
|
||||
#include <json/json.h>
|
||||
#include <src/rtc/lib.h>
|
||||
|
||||
@ -35,19 +36,20 @@ namespace ts::server {
|
||||
SpeakingClient(sql::SqlManager* a, const std::shared_ptr<VirtualServer>& 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<ConnectedClient> &sender);
|
||||
bool should_handle_voice_packet(size_t /* size */);
|
||||
|
||||
//Whisper
|
||||
virtual bool shouldReceiveVoice(const std::shared_ptr<ConnectedClient> &sender);
|
||||
bool shouldReceiveVoiceWhisper(const std::shared_ptr<ConnectedClient> &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<Json::Value> identityData;
|
||||
} handshake;
|
||||
|
||||
whisper::WhisperHandler whisper_handler_;
|
||||
|
||||
bool rtc_session_pending_describe{false};
|
||||
rtc::RTCClientId rtc_client_id{0};
|
||||
};
|
||||
|
@ -383,9 +383,11 @@ void MusicClient::broadcast_music_stop() {
|
||||
|
||||
|
||||
SpeakingClient::VoicePacketFlags flags{};
|
||||
for(const auto& cl : this->server->getClientsByChannel<SpeakingClient>(this->currentChannel))
|
||||
if(cl->shouldReceiveVoice(_this.lock()))
|
||||
for(const auto& cl : this->server->getClientsByChannel<SpeakingClient>(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<ts::music::PlayableSong>& song) {
|
||||
|
364
server/src/client/shared/WhisperHandler.cpp
Normal file
364
server/src/client/shared/WhisperHandler.cpp
Normal file
@ -0,0 +1,364 @@
|
||||
//
|
||||
// Created by WolverinDEV on 26/11/2020.
|
||||
//
|
||||
|
||||
#include "WhisperHandler.h"
|
||||
#include "src/client/voice/VoiceClientConnection.h"
|
||||
#include <misc/endianness.h>
|
||||
|
||||
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<uint8_t>() + 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<uint8_t>();
|
||||
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<size_t>([]{ 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<std::shared_ptr<SpeakingClient>> 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<SpeakingClient>(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<std::shared_ptr<SpeakingClient>> target_clients{};
|
||||
if(type == WhisperType::ECHO) {
|
||||
target_clients.push_back(dynamic_pointer_cast<SpeakingClient>(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<SpeakingClient>(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<bool>([] { 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<SpeakingClient> &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<SpeakingClient> &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<SpeakingClient> &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<SpeakingClient> &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<BasicChannel> 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<SpeakingClient> &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<BasicChannel> current = this->handle->getChannel();
|
||||
target_clients.erase(std::remove_if(target_clients.begin(), target_clients.end(), [&](const shared_ptr<SpeakingClient> &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<std::shared_ptr<SpeakingClient>>& 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};
|
||||
}
|
76
server/src/client/shared/WhisperHandler.h
Normal file
76
server/src/client/shared/WhisperHandler.h
Normal file
@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <Error.h>
|
||||
#include <protocol/Packet.h>
|
||||
#include <string_view>
|
||||
|
||||
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<std::shared_ptr<SpeakingClient>>& /* clients */);
|
||||
|
||||
[[nodiscard]] size_t max_whisper_targets();
|
||||
};
|
||||
}
|
@ -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<VoiceServer>& server, const sockaddr_storage* address) : SpeakingClient(server->server->sql, server->server), voice_server(server) {
|
||||
assert(address);
|
||||
memtrack::allocated<VoiceClient>(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<void>(), 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<SpeakingClient> &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<void>(), 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<void>(), teamspeak_packet.length());
|
||||
assert(payload_length == 0);
|
||||
}
|
||||
|
||||
this->getConnection()->send_packet(packet);
|
||||
}
|
||||
|
||||
void VoiceClient::send_voice_whisper(const std::shared_ptr<SpeakingClient> &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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<SpeakingClient>& /* source client */, uint16_t /* seq no */, uint8_t /* codec */, const void* /* payload */, size_t /* payload length */);
|
||||
void send_voice_whisper(const std::shared_ptr<SpeakingClient>& /* 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;
|
||||
|
@ -1,26 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <protocol/ringbuffer.h>
|
||||
#include <protocol/CompressionHandler.h>
|
||||
#include <protocol/CryptHandler.h>
|
||||
#include <ThreadPool/Thread.h>
|
||||
#include <ThreadPool/Mutex.h>
|
||||
#include <protocol/buffers.h>
|
||||
#include <chrono>
|
||||
#include <deque>
|
||||
#include <event.h>
|
||||
#include <condition_variable>
|
||||
#include <utility>
|
||||
#include <pipes/buffer.h>
|
||||
#include "VoiceClient.h"
|
||||
#include "protocol/AcknowledgeManager.h"
|
||||
#include <protocol/generation.h>
|
||||
#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 <ThreadPool/Mutex.h>
|
||||
#include <ThreadPool/Thread.h>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <deque>
|
||||
#include <event.h>
|
||||
#include <pipes/buffer.h>
|
||||
#include <protocol/CompressionHandler.h>
|
||||
#include <protocol/CryptHandler.h>
|
||||
#include <protocol/buffers.h>
|
||||
#include <protocol/generation.h>
|
||||
#include <protocol/ringbuffer.h>
|
||||
#include <utility>
|
||||
|
||||
//#define LOG_ACK_SYSTEM
|
||||
#ifdef LOG_ACK_SYSTEM
|
||||
|
@ -37,13 +37,31 @@ void VoiceClientConnection::handlePacketVoice(const protocol::ClientPacketParser
|
||||
|
||||
sink.send_audio(vpacketId, false, vpacketId * 960, codec, std::string_view{payload.data_ptr<char>() + 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<uint16_t>());
|
||||
auto voice_codec = packet.payload().data_ptr<uint8_t>()[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) {
|
||||
|
@ -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};
|
||||
|
@ -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
|
@ -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
|
@ -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<SpeakingClient>(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<server::SpeakingClient> &client, std::string& error) {
|
||||
RTCClientId Server::create_client(const std::shared_ptr<server::SpeakingClient> &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<server::SpeakingClient> &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<NativeAudioSourceSupplier> Server::create_audio_source_supplier_sender(uint32_t client_id) {
|
||||
auto result = librtc_create_audio_source_supplier(this->server_ptr, client_id);
|
||||
std::optional<NativeAudioSourceSupplier> 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<NativeAudioSourceSupplier>(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;
|
||||
|
@ -38,8 +38,9 @@ namespace ts::rtc {
|
||||
Server();
|
||||
~Server();
|
||||
|
||||
RTCClientId create_rtp_client(const std::shared_ptr<server::SpeakingClient>& /* client */, std::string& /* error */);
|
||||
RTCClientId create_native_client(const std::shared_ptr<server::SpeakingClient>& /* client */);
|
||||
RTCClientId create_client(const std::shared_ptr<server::SpeakingClient>& /* 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<NativeAudioSourceSupplier> create_audio_source_supplier_sender(RTCClientId /* client id */);
|
||||
std::optional<NativeAudioSourceSupplier> 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};
|
||||
};
|
||||
|
2
shared
2
shared
@ -1 +1 @@
|
||||
Subproject commit 23db0edd229a4cc37390bfc5df870d7aaf191f03
|
||||
Subproject commit 2cc8a42ce7639efa2cdf6eb30a2a02f099ce1154
|
Loading…
Reference in New Issue
Block a user