300 lines
12 KiB
C++
300 lines
12 KiB
C++
//
|
|
// Created by WolverinDEV on 10/03/2020.
|
|
//
|
|
|
|
#include "PacketDecoder.h"
|
|
|
|
#include <protocol/buffers.h>
|
|
#include <protocol/AcknowledgeManager.h>
|
|
#include <protocol/CompressionHandler.h>
|
|
#include <protocol/CryptHandler.h>
|
|
|
|
#include "../../ConnectionStatistics.h"
|
|
|
|
using namespace ts;
|
|
using namespace ts::protocol;
|
|
using namespace ts::connection;
|
|
using namespace ts::server::server::udp;
|
|
|
|
PacketDecoder::PacketDecoder(ts::connection::CryptHandler *crypt_handler)
|
|
: crypt_handler_{crypt_handler} {
|
|
memtrack::allocated<PacketDecoder>(this);
|
|
}
|
|
|
|
PacketDecoder::~PacketDecoder() {
|
|
memtrack::freed<PacketDecoder>(this);
|
|
this->reset();
|
|
}
|
|
|
|
void PacketDecoder::reset() {
|
|
std::lock_guard buffer_lock(this->packet_buffer_lock);
|
|
for(auto& buffer : this->_command_fragment_buffers)
|
|
buffer.reset();
|
|
}
|
|
|
|
bool PacketDecoder::decode_incoming_data(const pipes::buffer_view &buffer) {
|
|
std::string error{};
|
|
bool needs_command_reassemble{false};
|
|
|
|
auto result = this->decode_incoming_data_(error, needs_command_reassemble, buffer);
|
|
if(result != PacketDecodeResult::SUCCESS)
|
|
if(auto callback{this->callback_decode_failed}; callback)
|
|
callback(this->callback_argument, result, error);
|
|
|
|
return needs_command_reassemble;
|
|
}
|
|
|
|
PacketDecodeResult PacketDecoder::decode_incoming_data_(std::string& error, bool& commands_pending, const pipes::buffer_view &buffer) {
|
|
#ifdef FUZZING_TESTING_INCOMMING
|
|
#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 incoming packet of length {}", CLIENT_STR_LOG_PREFIX_(this->client), buffer.length());
|
|
return;
|
|
}
|
|
#ifdef FIZZING_TESTING_DISABLE_HANDSHAKE
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
ClientPacketParser packet_parser{buffer};
|
|
if(!packet_parser.valid())
|
|
return PacketDecodeResult::INVALID_PACKET;
|
|
|
|
assert(packet_parser.type() >= 0 && packet_parser.type() < this->incoming_generation_estimators.size());
|
|
packet_parser.set_estimated_generation(this->incoming_generation_estimators[packet_parser.type()].visit_packet(packet_parser.packet_id()));
|
|
|
|
auto is_command = packet_parser.type() == protocol::COMMAND || packet_parser.type() == protocol::COMMAND_LOW;
|
|
/* pretest if the packet is worth the effort of decoding it */
|
|
if(is_command) {
|
|
/* handle the order stuff */
|
|
auto& fragment_buffer = this->_command_fragment_buffers[PacketDecoder::command_fragment_buffer_index(packet_parser.type())];
|
|
|
|
std::unique_lock queue_lock(fragment_buffer.buffer_lock);
|
|
auto result = fragment_buffer.accept_index(packet_parser.packet_id());
|
|
if(result != 0) { /* packet index is ahead buffer index */
|
|
error = "pid: " + std::to_string(packet_parser.packet_id()) + ",";
|
|
error += "bidx: " + std::to_string(fragment_buffer.current_index()) + ",";
|
|
error += "bcap: " + std::to_string(fragment_buffer.capacity());
|
|
|
|
if(result == -1) { /* underflow */
|
|
/* we've already got the packet, but the client dosn't know that so we've to send the acknowledge again */
|
|
this->callback_send_acknowledge(this->callback_argument, packet_parser.packet_id(), packet_parser.type() == protocol::COMMAND_LOW);
|
|
return PacketDecodeResult::DUPLICATED_PACKET;
|
|
}
|
|
|
|
return PacketDecodeResult::BUFFER_OVERFLOW;
|
|
}
|
|
}
|
|
|
|
//NOTICE I found out that the Compressed flag is set if the packet contains an audio header
|
|
/* decrypt the packet if needed */
|
|
if(packet_parser.is_encrypted()) {
|
|
CryptHandler::key_t crypt_key{};
|
|
CryptHandler::nonce_t crypt_nonce{};
|
|
|
|
auto data = (uint8_t*) packet_parser.mutable_data_ptr();
|
|
bool use_default_key{!this->protocol_encrypted}, decrypt_result;
|
|
|
|
decrypt_packet:
|
|
if(use_default_key) {
|
|
crypt_key = CryptHandler::default_key;
|
|
crypt_nonce = CryptHandler::default_nonce;
|
|
} else {
|
|
if(!this->crypt_handler_->generate_key_nonce(true, packet_parser.type(), packet_parser.packet_id(), packet_parser.estimated_generation(), crypt_key, crypt_nonce))
|
|
return PacketDecodeResult::DECRYPT_KEY_GEN_FAILED;
|
|
}
|
|
|
|
decrypt_result = this->crypt_handler_->decrypt(
|
|
data + ClientPacketParser::kHeaderOffset, ClientPacketParser::kHeaderLength,
|
|
data + ClientPacketParser::kPayloadOffset, packet_parser.payload_length(),
|
|
data,
|
|
crypt_key, crypt_nonce,
|
|
error
|
|
);
|
|
|
|
if(!decrypt_result) {
|
|
if(packet_parser.packet_id() < 10 && packet_parser.estimated_generation() == 0) {
|
|
if(use_default_key) {
|
|
return PacketDecodeResult::DECRYPT_FAILED;
|
|
} else {
|
|
use_default_key = true;
|
|
goto decrypt_packet;
|
|
}
|
|
} else {
|
|
return PacketDecodeResult::DECRYPT_FAILED;
|
|
}
|
|
}
|
|
packet_parser.set_decrypted();
|
|
}
|
|
|
|
if(auto statistics{this->statistics_}; statistics)
|
|
statistics->logIncomingPacket(stats::ConnectionStatistics::category::from_type(packet_parser.type()), buffer.length());
|
|
|
|
#ifdef LOG_INCOMPING_PACKET_FRAGMENTS
|
|
debugMessage(lstream << CLIENT_LOG_PREFIX << "Recived packet. PacketId: " << packet->packetId() << " PacketType: " << packet->type().name() << " Flags: " << packet->flags() << " - " << packet->data() << endl);
|
|
#endif
|
|
if(is_command) {
|
|
auto& fragment_buffer = this->_command_fragment_buffers[command_fragment_buffer_index(packet_parser.type())];
|
|
CommandFragment fragment_entry{
|
|
packet_parser.packet_id(),
|
|
packet_parser.estimated_generation(),
|
|
|
|
packet_parser.flags(),
|
|
(uint32_t) packet_parser.payload_length(),
|
|
packet_parser.payload().own_buffer()
|
|
};
|
|
|
|
{
|
|
std::unique_lock queue_lock(fragment_buffer.buffer_lock);
|
|
|
|
if(!fragment_buffer.insert_index(packet_parser.packet_id(), std::move(fragment_entry)))
|
|
return PacketDecodeResult::COMMAND_INSTERT_FAILED;
|
|
}
|
|
|
|
this->callback_send_acknowledge(this->callback_argument, packet_parser.packet_id(), packet_parser.type() == protocol::COMMAND_LOW);
|
|
commands_pending = true;
|
|
} else {
|
|
this->callback_decoded_packet(this->callback_argument, packet_parser);
|
|
}
|
|
|
|
return PacketDecodeResult::SUCCESS;
|
|
}
|
|
|
|
bool PacketDecoder::verify_encryption(const pipes::buffer_view &buffer) {
|
|
ClientPacketParser packet_parser{buffer};
|
|
if(!packet_parser.valid() || !packet_parser.is_encrypted()) return false;
|
|
|
|
assert(packet_parser.type() >= 0 && packet_parser.type() < this->incoming_generation_estimators.size());
|
|
return this->crypt_handler_->verify_encryption(buffer, packet_parser.packet_id(), this->incoming_generation_estimators[packet_parser.type()].generation());
|
|
}
|
|
|
|
CommandReassembleResult PacketDecoder::reassemble_command(pipes::buffer &result, bool &is_command_low) {
|
|
bool more_commands_pending{false};
|
|
command_fragment_buffer_t* buffer{nullptr};
|
|
std::unique_lock<std::recursive_timed_mutex> buffer_lock; /* general buffer lock */
|
|
|
|
{
|
|
//FIXME: Currently command low packets cant be handled if there is a command packet stuck in reassemble queue
|
|
|
|
/* handle commands before command low packets */
|
|
for(auto& buf : this->_command_fragment_buffers) {
|
|
std::unique_lock ring_lock(buf.buffer_lock, std::try_to_lock);
|
|
if(!ring_lock.owns_lock()) continue;
|
|
|
|
if(buf.front_set()) {
|
|
if(!buffer) { /* lets still test for reexecute */
|
|
buffer_lock = move(ring_lock);
|
|
buffer = &buf;
|
|
} else {
|
|
more_commands_pending = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!buffer)
|
|
return CommandReassembleResult::NO_COMMANDS_PENDING;
|
|
|
|
uint8_t packet_flags{0};
|
|
pipes::buffer payload{};
|
|
|
|
/* lets find out if we've to reassemble the packet */
|
|
auto& first_buffer = buffer->slot_value(0);
|
|
if(first_buffer.packet_flags & PacketFlag::Fragmented) {
|
|
uint16_t sequence_length{1};
|
|
size_t total_payload_length{first_buffer.payload_length};
|
|
do {
|
|
if(sequence_length >= buffer->capacity())
|
|
return CommandReassembleResult::SEQUENCE_LENGTH_TOO_LONG;
|
|
|
|
if(!buffer->slot_set(sequence_length))
|
|
return CommandReassembleResult::NO_COMMANDS_PENDING; /* we need more packets */
|
|
|
|
auto& packet = buffer->slot_value(sequence_length++);
|
|
total_payload_length += packet.payload_length;
|
|
if(packet.packet_flags & PacketFlag::Fragmented) {
|
|
/* yep we find the end */
|
|
break;
|
|
}
|
|
} while(true);
|
|
/* ok we have all fragments lets reassemble */
|
|
|
|
/*
|
|
* Packet sequence could never be so long. If it is so then the data_length() returned an invalid value.
|
|
* We're checking it here because we dont want to make a huge allocation
|
|
*/
|
|
assert(total_payload_length < 512 * 1024 * 1024);
|
|
|
|
pipes::buffer packet_buffer{total_payload_length};
|
|
char* packet_buffer_ptr = &packet_buffer[0];
|
|
size_t packet_count{0};
|
|
|
|
packet_flags = buffer->slot_value(0).packet_flags;
|
|
while(packet_count < sequence_length) {
|
|
auto fragment = buffer->pop_front();
|
|
memcpy(packet_buffer_ptr, fragment.payload.data_ptr(), fragment.payload_length);
|
|
|
|
packet_buffer_ptr += fragment.payload_length;
|
|
packet_count++;
|
|
}
|
|
|
|
#ifndef _NDEBUG
|
|
if((packet_buffer_ptr - 1) != &packet_buffer[packet_buffer.length() - 1]) {
|
|
logCritical(0,
|
|
"Buffer over/underflow: packet_buffer_ptr != &packet_buffer[packet_buffer.length() - 1]; packet_buffer_ptr := {}; packet_buffer.end() := {}",
|
|
(void*) packet_buffer_ptr,
|
|
(void*) &packet_buffer[packet_buffer.length() - 1]
|
|
);
|
|
}
|
|
#endif
|
|
payload = packet_buffer;
|
|
} else {
|
|
auto packet = buffer->pop_front();
|
|
packet_flags = packet.packet_flags;
|
|
payload = packet.payload;
|
|
}
|
|
|
|
more_commands_pending |= buffer->front_set(); /* set the more flag if we have more to process */
|
|
buffer_lock.unlock();
|
|
|
|
if(packet_flags & PacketFlag::Compressed) {
|
|
std::string error{};
|
|
|
|
auto decompressed_size = compression::qlz_decompressed_size(payload.data_ptr(), payload.length());
|
|
auto uncompressed_buffer = buffer::allocate_buffer(decompressed_size);
|
|
if(!compression::qlz_decompress_payload(payload.data_ptr(), uncompressed_buffer.data_ptr(), &decompressed_size))
|
|
return CommandReassembleResult::COMMAND_DECOMPRESS_FAILED;
|
|
|
|
payload = uncompressed_buffer.range(0, decompressed_size);
|
|
}
|
|
|
|
result = std::move(payload);
|
|
return more_commands_pending ? CommandReassembleResult::MORE_COMMANDS_PENDING : CommandReassembleResult::SUCCESS;
|
|
}
|
|
|
|
void PacketDecoder::force_insert_command(const pipes::buffer_view &buffer) {
|
|
CommandFragment fragment_entry{
|
|
0,
|
|
0,
|
|
|
|
PacketFlag::Unencrypted,
|
|
(uint32_t) buffer.length(),
|
|
buffer.own_buffer()
|
|
};
|
|
|
|
|
|
{
|
|
auto& fragment_buffer = this->_command_fragment_buffers[command_fragment_buffer_index(protocol::COMMAND)];
|
|
std::unique_lock queue_lock(fragment_buffer.buffer_lock);
|
|
fragment_buffer.push_front(std::move(fragment_entry));
|
|
}
|
|
}
|
|
|
|
void PacketDecoder::register_initiv_packet() {
|
|
auto& fragment_buffer = this->_command_fragment_buffers[command_fragment_buffer_index(protocol::COMMAND)];
|
|
std::unique_lock buffer_lock(fragment_buffer.buffer_lock);
|
|
fragment_buffer.set_full_index_to(1); /* the first packet (0) is already the clientinitiv packet */
|
|
} |