387 lines
16 KiB
C++
387 lines
16 KiB
C++
#include <misc/endianness.h>
|
|
#include <algorithm>
|
|
#include <log/LogUtils.h>
|
|
#include <misc/memtracker.h>
|
|
#include <protocol/Packet.h>
|
|
#include <ThreadPool/Timer.h>
|
|
#include "./VoiceClientConnection.h"
|
|
#include "./VoiceClient.h"
|
|
#include "src/server/udp-server/UDPServer.h"
|
|
#include "src/InstanceHandler.h"
|
|
|
|
using namespace std;
|
|
using namespace std::chrono;
|
|
using namespace ts;
|
|
using namespace ts::connection;
|
|
using namespace ts::protocol;
|
|
using namespace ts::server;
|
|
|
|
VoiceClientConnection::VoiceClientConnection(server::server::udp::Server* server, const std::shared_ptr<server::VoiceClient>& client, int socket) :
|
|
socket{socket},
|
|
udp_server{server},
|
|
packet_encoder_{&this->crypt_handler, &this->compress_handler, &this->acknowledge_handler},
|
|
packet_decoder_{&this->crypt_handler} {
|
|
memtrack::allocated<VoiceClientConnection>(this);
|
|
|
|
this->packet_encoder_.callback_argument = this;
|
|
this->packet_encoder_.callback_encoded_buffers = [](auto _this, const auto& a1) {
|
|
reinterpret_cast<VoiceClientConnection*>(_this)->handle_encoded_buffers(a1);
|
|
};
|
|
this->packet_encoder_.callback_encode_failed = [](auto _this, const auto& a1, auto a2, const auto& a3) {
|
|
reinterpret_cast<VoiceClientConnection*>(_this)->handle_encode_error(a1, a2, a3);
|
|
};
|
|
|
|
this->packet_decoder_.callback_argument = this;
|
|
this->packet_decoder_.callback_decoded_packet = [](auto _this, const auto& a1) {
|
|
reinterpret_cast<VoiceClientConnection*>(_this)->handle_decoded_packet(a1);
|
|
};
|
|
this->packet_decoder_.callback_decode_failed = [](auto _this, auto a1, const auto& a2) {
|
|
reinterpret_cast<VoiceClientConnection*>(_this)->handle_decode_error(a1, a2);
|
|
};
|
|
this->packet_decoder_.callback_send_acknowledge = [](auto _this, auto a1, auto a2) {
|
|
reinterpret_cast<VoiceClientConnection*>(_this)->send_packet_acknowledge(a1, a2);
|
|
};
|
|
|
|
this->ping_handler_.callback_argument = this;
|
|
this->ping_handler_.callback_send_ping = [](auto _this, auto& a1) {
|
|
reinterpret_cast<VoiceClientConnection*>(_this)->send_packet_ping(a1);
|
|
};
|
|
this->ping_handler_.callback_send_recovery_command = [](auto _this) {
|
|
reinterpret_cast<VoiceClientConnection*>(_this)->send_packet_ping_recovery();
|
|
};
|
|
this->ping_handler_.callback_time_outed = [](auto _this) {
|
|
reinterpret_cast<VoiceClientConnection*>(_this)->handle_ping_timeout();
|
|
};
|
|
|
|
this->server_id = client->getServerId();
|
|
this->client_handle_ = client;
|
|
|
|
this->crypt_handler.reset();
|
|
debugMessage(this->server_id, "Allocated new voice client connection at {}", (void*) this);
|
|
}
|
|
|
|
void VoiceClientConnection::initialize(const std::shared_ptr<VoiceClientConnection> &self) {
|
|
assert(&*self == this);
|
|
this->weak_this = self;
|
|
this->event_handle_packet = make_shared<event::ProxiedEventEntry<VoiceClientConnection>>(self, &VoiceClientConnection::execute_handle_command_packets);
|
|
}
|
|
|
|
VoiceClientConnection::~VoiceClientConnection() {
|
|
debugMessage(this->server_id, "Deleted voice client connection at {}", (void*) this);
|
|
|
|
/* locking here should be useless, but just to ensure! */
|
|
{
|
|
lock_guard wqlock(this->write_queue_lock);
|
|
this->write_queue.clear();
|
|
}
|
|
|
|
this->client_handle_ = nullptr;
|
|
memtrack::freed<VoiceClientConnection>(this);
|
|
}
|
|
|
|
void VoiceClientConnection::register_for_write() {
|
|
auto self = this->weak_this.lock();
|
|
assert(self);
|
|
this->udp_server->schedule_client_write(self);
|
|
}
|
|
|
|
void VoiceClientConnection::register_for_command_handling() {
|
|
auto vmanager = serverInstance->getVoiceServerManager();
|
|
if(!vmanager)
|
|
return;
|
|
auto evloop = vmanager->get_executor_loop();
|
|
if(!evloop)
|
|
return;
|
|
|
|
evloop->schedule(this->event_handle_packet);
|
|
}
|
|
|
|
#ifdef CLIENT_LOG_PREFIX
|
|
#undef CLIENT_LOG_PREFIX
|
|
#endif
|
|
#define CLIENT_LOG_PREFIX "[" << this->client->getPeerIp() << ":" << this->client->getPeerPort() << " | " << this->client->getDisplayName() << "]"
|
|
|
|
//Message handle methods
|
|
|
|
void VoiceClientConnection::handle_incoming_datagram(const pipes::buffer_view& buffer) {
|
|
auto command_pending = this->packet_decoder_.decode_incoming_data(buffer);
|
|
if(command_pending) this->register_for_command_handling();
|
|
}
|
|
|
|
bool VoiceClientConnection::verify_encryption(const pipes::buffer_view &buffer) {
|
|
return this->packet_decoder_.verify_encryption(buffer);
|
|
}
|
|
|
|
void VoiceClientConnection::handle_decoded_packet(const ts::protocol::ClientPacketParser &packet) {
|
|
auto packet_type = packet.type();
|
|
if(packet_type == PacketType::VOICE) {
|
|
auto client = this->client_handle_;
|
|
if(!client) [[unlikely]] {
|
|
logWarning(this->server_id, "Received voice data for client, but we've no client associated with the connection.");
|
|
return;
|
|
}
|
|
|
|
client->handlePacketVoice(packet.payload(), (packet.flags() & PacketFlag::Compressed) > 0, (packet.flags() & PacketFlag::Fragmented) > 0);
|
|
} else if(packet_type == PacketType::VOICE_WHISPER) {
|
|
auto client = this->client_handle_;
|
|
if(!client) [[unlikely]] {
|
|
logWarning(this->server_id, "Received voice whisper data for client, but we've no client associated with the connection.");
|
|
return;
|
|
}
|
|
|
|
client->handlePacketVoice(packet.payload(), (packet.flags() & PacketFlag::Compressed) > 0, (packet.flags() & PacketFlag::Fragmented) > 0);
|
|
} else if(packet_type == PacketType::ACK || packet_type == PacketType::ACK_LOW) {
|
|
string error{};
|
|
if(!this->acknowledge_handler.process_acknowledge(packet.type(), packet.payload(), error))
|
|
debugMessage(this->server_id, "{} Failed to handle acknowledge: {}", this->client_log_prefix(), error);
|
|
this->ping_handler_.received_command_acknowledged();
|
|
} else if(packet_type == PacketType::PING) {
|
|
/* just send a pong response */
|
|
char buffer[2];
|
|
le2be16(packet.packet_id(), buffer);
|
|
auto pkt = make_shared<ServerPacket>(PacketTypeInfo::Pong, pipes::buffer_view{buffer, 2});
|
|
pkt->enable_flag(PacketFlag::Unencrypted);
|
|
this->send_packet(pkt);
|
|
} else if(packet_type == PacketType::PONG) {
|
|
if(packet.payload_length() < 2) return;
|
|
|
|
this->ping_handler_.received_pong(be2le16((char*) packet.payload().data_ptr()));
|
|
} else if(packet_type == PacketType::COMMAND || packet_type == PacketType::COMMAND_LOW) {
|
|
logCritical(this->server_id, "{} Received command packet within handle_decoded_packet callback.", this->client_log_prefix());
|
|
} else if(packet_type == PacketType::INIT1) {
|
|
logCritical(this->server_id, "{} Received init packet within handle_decoded_packet callback.", this->client_log_prefix());
|
|
}
|
|
}
|
|
|
|
void VoiceClientConnection::handle_decode_error(ts::server::server::udp::PacketDecodeResult error, const std::string &message) {
|
|
using PacketDecodeResult = ts::server::server::udp::PacketDecodeResult;
|
|
switch (error) {
|
|
case PacketDecodeResult::DECRYPT_FAILED:
|
|
logWarning(this->server_id, "{} Dropping incoming packet. Failed to decrypt packet ({}).", this->client_log_prefix(), message);
|
|
break;
|
|
|
|
case PacketDecodeResult::DECRYPT_KEY_GEN_FAILED:
|
|
logWarning(this->server_id, "{} Dropping incoming packet. Failed to generate crypto key for packet.", this->client_log_prefix());
|
|
break;
|
|
|
|
case PacketDecodeResult::BUFFER_OVERFLOW:
|
|
logWarning(this->server_id, "{} Dropping incoming packet because queue has a buffer overflow ({}).", this->client_log_prefix(), message);
|
|
break;
|
|
|
|
case PacketDecodeResult::COMMAND_INSTERT_FAILED:
|
|
logWarning(this->server_id, "{} Dropping incoming packet because we failed to register the command packet.", this->client_log_prefix());
|
|
break;
|
|
|
|
#if 0
|
|
case PacketDecodeResult::DUPLICATED_PACKET:
|
|
logWarning(this->server_id, "{} Dropping incoming packet because it has already be processed.", this->client_log_prefix());
|
|
break;
|
|
|
|
case PacketDecodeResult::INVALID_PACKET:
|
|
logWarning(this->server_id, "{} Dropping incoming packet because its invalid.", this->client_log_prefix());
|
|
break;
|
|
#else
|
|
case PacketDecodeResult::INVALID_PACKET:
|
|
case PacketDecodeResult::DUPLICATED_PACKET:
|
|
#endif
|
|
case PacketDecodeResult::SUCCESS:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void VoiceClientConnection::send_packet_acknowledge(uint16_t packet_id, bool command_low) {
|
|
char buffer[2];
|
|
le2be16(packet_id, buffer);
|
|
|
|
auto packet = make_shared<protocol::ServerPacket>(command_low ? protocol::PacketTypeInfo::AckLow : protocol::PacketTypeInfo::Ack, pipes::buffer_view{buffer, 2});
|
|
packet->enable_flag(PacketFlag::Unencrypted);
|
|
if(!command_low) packet->enable_flag(protocol::PacketFlag::NewProtocol);
|
|
this->send_packet(packet);
|
|
#ifdef PKT_LOG_ACK
|
|
logTrace(this->getServerId(), "{}[Acknowledge][Server -> Client] Sending acknowledge for {}", CLIENT_STR_LOG_PREFIX, packetId);
|
|
#endif
|
|
}
|
|
|
|
void VoiceClientConnection::send_packet_ping(uint16_t& ping_id) {
|
|
auto packet = make_shared<ServerPacket>(PacketTypeInfo::Ping, pipes::buffer_view{});
|
|
packet->enable_flag(PacketFlag::Unencrypted);
|
|
this->send_packet(packet, false, true); /* prepare directly so the packet gets a packet id */
|
|
|
|
ping_id = packet->packetId();
|
|
}
|
|
|
|
void VoiceClientConnection::send_packet_ping_recovery() {
|
|
const char* command = "notifyserverpingrecovery";
|
|
auto packet = make_shared<protocol::ServerPacket>(protocol::PacketTypeInfo::Command,
|
|
pipes::buffer_view{(void*) command, strlen(command)}
|
|
);
|
|
this->send_packet(packet);
|
|
}
|
|
|
|
void VoiceClientConnection::execute_handle_command_packets(const std::chrono::system_clock::time_point& /* scheduled */) {
|
|
if((int) this->connection_state_ >= (int) ClientConnectionState::DISCONNECTING)
|
|
return;
|
|
|
|
auto client = this->client_handle_;
|
|
if(!client) [[unlikely]]
|
|
return;
|
|
std::lock_guard clock{client->command_lock};
|
|
|
|
using CommandReassembleResult = ts::server::server::udp::CommandReassembleResult;
|
|
|
|
pipes::buffer payload{};
|
|
bool command_low{false};
|
|
auto command_status = this->packet_decoder_.reassemble_command(payload, command_low);
|
|
switch (command_status) {
|
|
case CommandReassembleResult::SUCCESS:
|
|
case CommandReassembleResult::MORE_COMMANDS_PENDING:
|
|
break;
|
|
|
|
case CommandReassembleResult::NO_COMMANDS_PENDING:
|
|
return;
|
|
|
|
case CommandReassembleResult::COMMAND_DECOMPRESS_FAILED:
|
|
case CommandReassembleResult::COMMAND_TOO_LARGE:
|
|
case CommandReassembleResult::SEQUENCE_LENGTH_TOO_LONG:
|
|
//TODO: Shutdown connection?
|
|
logError(this->server_id, "{} Failed to reassemble next command ({}). This will cause the connection to fail.", this->client_log_prefix(), (int) command_status);
|
|
return;
|
|
}
|
|
|
|
auto startTime = system_clock::now();
|
|
try {
|
|
client->handlePacketCommand(payload);
|
|
} catch (std::exception& ex) {
|
|
logCritical(this->server_id, "{} An exception has been thrown within command handling, which reached to root handler. This should not happen! (Message: {})", this->client_log_prefix(), ex.what());
|
|
}
|
|
|
|
auto end = system_clock::now();
|
|
if(end - startTime > milliseconds(10)) {
|
|
auto index = payload.find(" ");
|
|
std::string command{};
|
|
if(index == std::string::npos)
|
|
command = payload.string();
|
|
else
|
|
command = payload.view(0, index).string();
|
|
logWarning(this->server_id, "{} Command handling of command \"{}\" required more than 10ms ({}ms)",this->client_log_prefix(),
|
|
command,
|
|
std::chrono::duration_cast<std::chrono::milliseconds>(end - startTime).count()
|
|
);
|
|
}
|
|
|
|
if(command_status == CommandReassembleResult::MORE_COMMANDS_PENDING)
|
|
this->register_for_command_handling();
|
|
}
|
|
|
|
|
|
void VoiceClientConnection::send_packet(const shared_ptr<protocol::ServerPacket>& original_packet, bool copy, bool prepare_directly) {
|
|
if(this->connection_state_ == ClientConnectionState::DISCONNECTED)
|
|
return;
|
|
|
|
using EncodeFlags = server::server::udp::PacketEncoder::EncodeFlags;
|
|
int flags{EncodeFlags::none};
|
|
if(!copy)
|
|
flags |= (unsigned) EncodeFlags::no_copy;
|
|
if(prepare_directly)
|
|
flags |= (unsigned) EncodeFlags::sync;
|
|
|
|
if(this->packet_encoder_.encode_packet(original_packet, (EncodeFlags) flags))
|
|
this->register_for_write();
|
|
}
|
|
|
|
void VoiceClientConnection::handle_encode_error(const shared_ptr<protocol::ServerPacket> &packet,
|
|
ts::server::server::udp::PacketEncodeResult result, const std::string &message) {
|
|
using PacketEncodeResult = ts::server::server::udp::PacketEncodeResult;
|
|
switch (result) {
|
|
case PacketEncodeResult::PACKET_TOO_LARGE:
|
|
logWarning(this->server_id, "{} Dropping packet of type {}. Packet is too large ({}bytes).", this->client_log_prefix(), packet->type().name(), packet->length());
|
|
break;
|
|
|
|
case PacketEncodeResult::COMPRESS_FAILED:
|
|
logWarning(this->server_id, "{} Dropping packet of type {}. Failed to compress packet ({}).", this->client_log_prefix(), packet->type().name(), message);
|
|
break;
|
|
|
|
case PacketEncodeResult::ENCRYPT_KEY_GEN_FAILED:
|
|
logWarning(this->server_id, "{} Dropping packet of type {}. Failed to generate crypto key for packet.", this->client_log_prefix(), packet->type().name());
|
|
break;
|
|
|
|
case PacketEncodeResult::ENCRYPT_FAILED:
|
|
logWarning(this->server_id, "{} Dropping packet of type {}. Failed to encrypt packet ({}).", this->client_log_prefix(), packet->type().name(), message);
|
|
break;
|
|
|
|
case PacketEncodeResult::SUCCESS:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void VoiceClientConnection::handle_encoded_buffers(const std::vector<pipes::buffer> &buffers) {
|
|
{
|
|
std::lock_guard lock{this->write_queue_lock};
|
|
this->write_queue.insert(this->write_queue.begin(), buffers.begin(), buffers.end());
|
|
}
|
|
this->register_for_write();
|
|
}
|
|
|
|
bool VoiceClientConnection::encode_packets() {
|
|
return this->packet_encoder_.do_encode();
|
|
}
|
|
|
|
WriteBufferStatus VoiceClientConnection::pop_write_buffer(pipes::buffer& target) {
|
|
lock_guard wqlock(this->write_queue_lock);
|
|
size_t size = this->write_queue.size();
|
|
if(size == 0)
|
|
return WriteBufferStatus::EMPTY;
|
|
|
|
target = std::move(this->write_queue.front());
|
|
this->write_queue.pop_front();
|
|
|
|
#ifdef FUZZING_TESTING_OUTGOING
|
|
#ifdef FIZZING_TESTING_DISABLE_HANDSHAKE
|
|
if (this->client->state == ConnectionState::CONNECTED) {
|
|
#endif
|
|
if ((rand() % FUZZING_TESTING_DROP_MAX) < FUZZING_TESTING_DROP) {
|
|
debugMessage(this->client->getServerId(), "{}[FUZZING] Dropping outgoing packet", CLIENT_STR_LOG_PREFIX_(this->client));
|
|
return 0;
|
|
}
|
|
#ifdef FIZZING_TESTING_DISABLE_HANDSHAKE
|
|
}
|
|
#endif
|
|
#endif
|
|
return size > 1 ? WriteBufferStatus::BUFFERS_LEFT : WriteBufferStatus::EMPTY;
|
|
}
|
|
|
|
void VoiceClientConnection::reset() {
|
|
this->packet_encoder_.reset();
|
|
this->packet_decoder_.reset();
|
|
|
|
this->acknowledge_handler.reset();
|
|
this->crypt_handler.reset();
|
|
}
|
|
|
|
void VoiceClientConnection::force_insert_command(const pipes::buffer_view &buffer) {
|
|
this->packet_decoder_.force_insert_command(buffer);
|
|
this->register_for_command_handling();
|
|
}
|
|
|
|
void VoiceClientConnection::close_connection(const std::chrono::system_clock::time_point &timeout) {
|
|
//TODO!
|
|
if(timeout.time_since_epoch().count() > 0) {
|
|
|
|
} else {
|
|
this->connection_state_ = ClientConnectionState::DISCONNECTED;
|
|
|
|
/* Unregister connection from server */
|
|
}
|
|
}
|
|
|
|
void VoiceClientConnection::tick() {
|
|
auto now = std::chrono::system_clock::now();
|
|
this->ping_handler_.tick(now);
|
|
|
|
if(this->connection_state_ == ClientConnectionState::DISCONNECTING) {
|
|
//TODO!
|
|
if(now > this->disconnect_timeout_) {
|
|
//TODO!
|
|
}
|
|
}
|
|
} |