#include #include #include #include "../../server/VoiceServer.h" #include #include #include #include "VoiceClientConnection.h" #include "src/client/ConnectedClient.h" #include "VoiceClient.h" //#define LOG_AUTO_ACK_AUTORESPONSE //#define FUZZING_TESTING_INCOMMING //#define FUZZING_TESTING_OUTGOING //#define FIZZING_TESTING_DISABLE_HANDSHAKE #define FUZZING_TESTING_DROP 8 #define FUZZING_TESTING_DROP_MAX 10 //#define CONNECTION_NO_STATISTICS #define QLZ_COMPRESSION_LEVEL 1 #include "qlz/QuickLZ.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(VoiceClient* client) : client(client) { memtrack::allocated(this); this->acknowledge_handler.destroy_packet = [](void* packet) { reinterpret_cast(packet)->unref(); }; this->crypt_handler.reset(); debugMessage(client->getServer()->getServerId(), "Allocated new voice client connection at {}", (void*) this); } VoiceClientConnection::~VoiceClientConnection() { this->reset(); this->client = nullptr; memtrack::freed(this); } void VoiceClientConnection::triggerWrite() { if(this->client->voice_server) this->client->voice_server->triggerWrite(dynamic_pointer_cast(this->client->_this.lock())); } #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) { #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()) { logTrace(this->client->getServerId(), "{} Received invalid packet. Dropping.", CLIENT_STR_LOG_PREFIX_(this->client)); return; } 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())); #ifndef CONNECTION_NO_STATISTICS if(this->client) { auto stats = this->client->connectionStatistics; stats->logIncomingPacket(stats::ConnectionStatistics::category::from_type(packet_parser.type()), buffer.length() + 96); /* 96 for the UDP packet overhead */ } this->packet_statistics().received_packet((protocol::PacketType) packet_parser.type(), packet_parser.full_packet_id()); #endif auto is_command = packet_parser.type() == protocol::COMMAND || packet_parser.type() == protocol::COMMAND_LOW; /* in previous versions we checked if the arrived packet is "worth decoding". * But since in general a command buffer underflow is much more unlikely, especially because most packets are not even command packets, * it's better we just skip that step and decode it anyways */ #if 0 /* 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[command_fragment_buffer_index(packet_parser.type())]; 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 */ debugMessage(this->client->getServerId(), "{} Dropping command packet because command assembly buffer has an {} ({}|{}|{})", CLIENT_STR_LOG_PREFIX_(this->client), result == -1 ? "underflow" : "overflow", fragment_buffer.capacity(), fragment_buffer.current_index(), packet_parser.packet_id() ); 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 */ if(this->client->crypto.protocol_encrypted) this->client->sendAcknowledge(packet_parser.packet_id(), packet_parser.type() == protocol::COMMAND_LOW); } return; } } #endif //NOTICE I found out that the Compressed flag is set if the packet contains an audio header if(this->client->state == ConnectionState::INIT_LOW && packet_parser.type() != protocol::INIT1) return; /* decrypt the packet if needed */ if(packet_parser.is_encrypted()) { std::string error; CryptHandler::key_t crypt_key{}; CryptHandler::nonce_t crypt_nonce{}; auto data = (uint8_t*) packet_parser.mutable_data_ptr(); bool use_default_key{!this->client->crypto.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)) { logError(this->client->getServerId(), "{} Failed to generate crypt key/nonce. This should never happen! Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client)); return; } } 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(!this->client->crypto.client_init) { if(use_default_key) { logTrace(this->client->getServerId(), "{} Failed to decrypt packet with default key ({}). Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client), error); return; } else { logTrace(this->client->getServerId(), "{} Failed to decrypt packet ({}). Trying with default key.", CLIENT_STR_LOG_PREFIX_(this->client), error); use_default_key = true; goto decrypt_packet; } } else { logTrace(this->client->getServerId(), "{} Failed to decrypt packet ({}). Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client), error); return; } } packet_parser.set_decrypted(); } else if(is_command && this->client->state != ConnectionState::INIT_HIGH) { logTrace(this->client->getServerId(), "{} Voice client {}/{} tried to send a unencrypted command packet. Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client), client->getDisplayName(), this->client->getLoggingPeerIp()); return; } #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() }; { unique_lock queue_lock(fragment_buffer.buffer_lock); if(!fragment_buffer.insert_index(packet_parser.packet_id(), std::move(fragment_entry))) { auto ignore_type = fragment_buffer.accept_index(packet_parser.packet_id()); debugMessage(this->client->getServerId(), "{} Dropping command packet because command assembly buffer has an {} ({}|{}|{})", CLIENT_STR_LOG_PREFIX_(this->client), ignore_type == -1 ? "underflow" : "overflow", fragment_buffer.capacity(), fragment_buffer.current_index(), packet_parser.packet_id() ); if(ignore_type == -1) { /* underflow */ /* we've already got the packet, but the client dosn't know that so we've to send the acknowledge again */ this->client->sendAcknowledge(packet_parser.packet_id(), packet_parser.type() == protocol::COMMAND_LOW); } return; } } this->client->sendAcknowledge(packet_parser.packet_id(), packet_parser.type() == protocol::COMMAND_LOW); auto voice_server = this->client->voice_server; if(voice_server) voice_server->schedule_command_handling(this->client); } else { if(packet_parser.type() == protocol::VOICE) this->client->handlePacketVoice(packet_parser); else if(packet_parser.type() == protocol::VOICE_WHISPER) this->client->handlePacketVoiceWhisper(packet_parser); else if(packet_parser.type() == protocol::ACK || packet_parser.type() == protocol::ACK_LOW) this->client->handlePacketAck(packet_parser); else if(packet_parser.type() == protocol::PING || packet_parser.type() == protocol::PONG) this->client->handlePacketPing(packet_parser); else { logError(this->client->getServerId(), "{} Received hand decoded packet, but we've no method to handle it. Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client)); } } } bool VoiceClientConnection::verify_encryption(const pipes::buffer_view &buffer /* incl. mac etc */) { 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()); } void VoiceClientConnection::execute_handle_command_packets(const std::chrono::system_clock::time_point& /* scheduled */) { if(this->client->state >= ConnectionState::DISCONNECTING || !this->client->getServer()) return; //TODO: Remove the buffer_execute_lock and use the one within the this->client->handlePacketCommand method unique_lock buffer_execute_lock; pipes::buffer payload{}; uint16_t packet_id{}; auto reexecute_handle = this->next_reassembled_command(buffer_execute_lock, payload, packet_id); if(!payload.empty()){ auto startTime = system_clock::now(); try { this->client->handlePacketCommand(payload); } catch (std::exception& ex) { logCritical(this->client->getServerId(), "{} Exception reached root tree! {}", CLIENT_STR_LOG_PREFIX_(this->client), ex.what()); } auto end = system_clock::now(); if(end - startTime > milliseconds(10)) { logError(this->client->getServerId(), "{} Handling of command packet needs more than 10ms ({}ms)", CLIENT_STR_LOG_PREFIX_(this->client), duration_cast(end - startTime).count() ); } } if(buffer_execute_lock.owns_lock()) buffer_execute_lock.unlock(); auto voice_server = this->client->voice_server; if(voice_server && (reexecute_handle || this->should_reassembled_reschedule)) { should_reassembled_reschedule = false; this->client->voice_server->schedule_command_handling(this->client); } } /* buffer_execute_lock: lock for in order execution */ bool VoiceClientConnection::next_reassembled_command(unique_lock& buffer_execute_lock, pipes::buffer& result, uint16_t& packet_id) { command_fragment_buffer_t* buffer{nullptr}; unique_lock buffer_lock; /* general buffer lock */ bool have_more{false}; { //FIXME: Currently command low packets cant be handeled if there is a command packet stuck in reassamble /* handle commands before command low packets */ for(auto& buf : this->_command_fragment_buffers) { unique_lock ring_lock(buf.buffer_lock, try_to_lock); //Perm lock the buffer else, may command wount get handeled. Because we've more left, but say we waven't if(!ring_lock.owns_lock()) { this->should_reassembled_reschedule = true; continue; } if(buf.front_set()) { if(!buffer) { /* lets still test for reexecute */ buffer_execute_lock = unique_lock(buf.execute_lock, try_to_lock); if(!buffer_execute_lock.owns_lock()) { this->should_reassembled_reschedule = true; continue; } buffer_lock = move(ring_lock); buffer = &buf; } else { have_more = true; break; } } } } if(!buffer) return false; /* we've no packets */ 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); packet_id = first_buffer.packet_id; 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()) { logError(this->client->getServerId(), "{} Command fragment buffer is full, and there is not fragmented packet end. Dropping full buffer which will probably cause a connection loss.", CLIENT_STR_LOG_PREFIX_(this->client)); buffer->clear(); return false; /* we've nothing to handle */ } if(!buffer->slot_set(sequence_length)) return false; /* 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(this->client->getServer()->getServerId(), "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; } have_more |= 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()); if(decompressed_size == 0) { logTrace(this->client->getServerId(), "{} Failed to calculate decompressed size for received command. Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client)); return false; } else if(decompressed_size > 20 * 1024 * 1024) { /* max 20MB */ logTrace(this->client->getServerId(), "{} Command packet has a too large compressed size. Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client)); return false; } auto decompress_buffer = buffer::allocate_buffer(decompressed_size); if(!compression::qlz_decompress_payload(payload.data_ptr(), decompress_buffer.data_ptr(), &decompressed_size)) { logTrace(this->client->getServerId(), "{} Failed to decompress received command. Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client)); return false; } payload = decompress_buffer.range(0, decompressed_size); } result = std::move(payload); return have_more; } bool VoiceClientConnection::prepare_outgoing_packet(ts::protocol::OutgoingServerPacket *packet) { if(packet->type_and_flags & PacketFlag::Unencrypted) { this->crypt_handler.write_default_mac(packet->mac); } else { CryptHandler::key_t crypt_key{}; CryptHandler::nonce_t crypt_nonce{}; std::string error{}; if(!this->client->crypto.protocol_encrypted) { crypt_key = CryptHandler::default_key; crypt_nonce = CryptHandler::default_nonce; } else { if(!this->crypt_handler.generate_key_nonce(false, (uint8_t) packet->packet_type(), packet->packet_id(), packet->generation, crypt_key, crypt_nonce)) { logError(this->client->getServerId(), "{} Failed to generate crypt key/nonce for sending a packet. This should never happen! Dropping packet.", CLIENT_STR_LOG_PREFIX_(this->client)); return false; } } auto crypt_result = this->crypt_handler.encrypt((char*) packet->packet_data() + ServerPacketP::kHeaderOffset, ServerPacketP::kHeaderLength, packet->payload, packet->payload_size, packet->mac, crypt_key, crypt_nonce, error); if(!crypt_result){ logError(this->client->getServerId(), "{} Failed to encrypt packet. Error: {}", CLIENT_STR_LOG_PREFIX_(this->client), error); return false; } } return true; } VoiceClientConnection::WBufferPopResult VoiceClientConnection::pop_write_buffer(protocol::OutgoingServerPacket *&result) { if(this->client->state == ConnectionState::DISCONNECTED) return WBufferPopResult::DRAINED; bool need_prepare_packet{false}, more_packets{false}; { std::lock_guard wlock{this->write_queue_mutex}; if(this->resend_queue_head) { result = this->resend_queue_head; if(result->next) { assert(this->resend_queue_tail != &result->next); this->resend_queue_head = result->next; } else { assert(this->resend_queue_tail == &result->next); this->resend_queue_head = nullptr; this->resend_queue_tail = &this->resend_queue_head; } } else if(this->write_queue_head) { result = this->write_queue_head; if(result->next) { assert(this->write_queue_tail != &result->next); this->write_queue_head = result->next; } else { assert(this->write_queue_tail == &result->next); this->write_queue_head = nullptr; this->write_queue_tail = &this->write_queue_head; } need_prepare_packet = true; } else { return WBufferPopResult::DRAINED; } result->next = nullptr; more_packets = this->resend_queue_head != nullptr || this->write_queue_head != nullptr; } if(need_prepare_packet) this->prepare_outgoing_packet(result); return more_packets ? WBufferPopResult::MORE_AVAILABLE : WBufferPopResult::DRAINED; } void VoiceClientConnection::execute_resend(const std::chrono::system_clock::time_point &now, std::chrono::system_clock::time_point &next) { std::deque> buffers{}; std::string error{}; if (this->acknowledge_handler.execute_resend(now, next, buffers, error) < 0) { debugMessage(client->getServerId(), "{} Failed to execute packet resend: {}", CLIENT_STR_LOG_PREFIX_(this->client), error); if(this->client->state == ConnectionState::CONNECTED) { this->client->disconnect(ViewReasonId::VREASON_TIMEOUT, config::messages::timeout::packet_resend_failed, nullptr, true); } else { this->client->close_connection(system_clock::now() + seconds(1)); } } else if(!buffers.empty()) { size_t send_count{0}; { lock_guard wlock{this->write_queue_mutex}; for(auto& buffer : buffers) { auto packet = (protocol::OutgoingServerPacket*) buffer->packet_ptr; if(packet->next) continue; /* still in write queue (this shall not happen very often) */ if(&packet->next == this->write_queue_tail || &packet->next == this->resend_queue_tail) continue; packet->ref(); /* for the write queue again */ *this->resend_queue_tail = packet; this->resend_queue_tail = &packet->next; send_count++; buffer->resend_count++; this->packet_statistics().send_command((protocol::PacketType) buffer->packet_type, buffer->packet_full_id); } } logTrace(client->getServerId(), "{} Resending {} packets. Send actually {} packets.", CLIENT_STR_LOG_PREFIX_(client), buffers.size(), send_count); this->triggerWrite(); } } void VoiceClientConnection::encrypt_write_queue() { OutgoingServerPacket* packets_head, *packets_tail; { std::lock_guard wlock{this->write_queue_mutex}; packets_head = this->write_queue_head; this->write_queue_head = nullptr; this->write_queue_tail = &this->write_queue_head; } if(!packets_head) return; auto packet = packets_head; while(packet) { this->prepare_outgoing_packet(packet); packets_tail = packet; packet = packet->next; } { std::lock_guard wlock{this->write_queue_mutex}; *this->resend_queue_tail = packets_head; this->resend_queue_tail = &packets_tail->next; } } bool VoiceClientConnection::wait_empty_write_and_prepare_queue(chrono::time_point until) { while(true) { { std::lock_guard wlock{this->write_queue_mutex}; if(this->write_queue_head) goto _wait; if(this->resend_queue_head) goto _wait; } break; _wait: if(until.time_since_epoch().count() != 0 && system_clock::now() > until) return false; threads::self::sleep_for(milliseconds(5)); } return true; } void VoiceClientConnection::reset() { { std::lock_guard wlock{this->write_queue_mutex}; auto head = this->write_queue_head; while(head) { auto next = head->next; head->unref(); head = next; } this->write_queue_head = nullptr; this->write_queue_tail = &this->write_queue_head; head = this->resend_queue_head; while(head) { auto next = head->next; head->unref(); head = next; } this->resend_queue_head = nullptr; this->resend_queue_tail = &this->resend_queue_head; } this->acknowledge_handler.reset(); this->crypt_handler.reset(); this->packet_id_manager.reset(); { lock_guard buffer_lock(this->packet_buffer_lock); for(auto& buffer : this->_command_fragment_buffers) buffer.reset(); } } void VoiceClientConnection::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)]; unique_lock queue_lock(fragment_buffer.buffer_lock); fragment_buffer.push_front(std::move(fragment_entry)); } auto voice_server = this->client->voice_server; if(voice_server) voice_server->schedule_command_handling(this->client); } void VoiceClientConnection::register_initiv_packet() { auto& fragment_buffer = this->_command_fragment_buffers[command_fragment_buffer_index(protocol::COMMAND)]; unique_lock buffer_lock(fragment_buffer.buffer_lock); fragment_buffer.set_full_index_to(1); /* the first packet (0) is already the clientinitiv packet */ } void VoiceClientConnection::send_packet(protocol::OutgoingServerPacket *packet) { uint32_t full_id; { std::lock_guard id_lock{this->packet_id_mutex}; full_id = this->packet_id_manager.generate_full_id(packet->packet_type()); } packet->set_packet_id(full_id & 0xFFFFU); packet->generation = full_id >> 16U; { std::lock_guard qlock{this->write_queue_mutex}; *this->write_queue_tail = packet; this->write_queue_tail = &packet->next; } auto statistics = this->client ? this->client->connectionStatistics : nullptr; if(statistics) { auto category = stats::ConnectionStatistics::category::from_type(packet->packet_type()); statistics->logOutgoingPacket(category, packet->packet_length() + 96); /* 96 for the UDP packet overhead */ } this->triggerWrite(); } void VoiceClientConnection::send_packet(protocol::PacketType type, protocol::PacketFlag::PacketFlags flag, const void *payload, size_t payload_size) { auto packet = protocol::allocate_outgoing_packet(payload_size); packet->type_and_flags = (uint8_t) type | (uint8_t) flag; memcpy(packet->payload, payload, payload_size); this->send_packet(packet); } #define MAX_COMMAND_PACKET_PAYLOAD_LENGTH (487) void VoiceClientConnection::send_command(const std::string_view &command, bool low, std::unique_ptr> ack_listener) { bool own_data_buffer{false}; void* own_data_buffer_ptr; /* imutable! */ const char* data_buffer{command.data()}; size_t data_length{command.length()}; uint8_t head_pflags{0}; PacketType ptype{low ? PacketType::COMMAND_LOW : PacketType::COMMAND}; protocol::OutgoingServerPacket *packets_head{nullptr}; protocol::OutgoingServerPacket **packets_tail{&packets_head}; /* only compress "long" commands */ if(command.size() > 100) { size_t max_compressed_payload_size = compression::qlz_compressed_size(command.data(), command.length()); auto compressed_buffer = ::malloc(max_compressed_payload_size); size_t compressed_size{max_compressed_payload_size}; if(!compression::qlz_compress_payload(command.data(), command.length(), compressed_buffer, &compressed_size)) { logCritical(0, "Failed to compress command packet. Dropping packet"); ::free(compressed_buffer); return; } /* we don't need to make the command longer than it is */ if(compressed_size < command.length() || this->client->getType() == ClientType::CLIENT_TEAMSPEAK) { /* TS3 requires each splituped packet to be compressed (Update: Not 100% sure since there was another bug when discovering this but I've kept it since) */ own_data_buffer = true; data_buffer = (char*) compressed_buffer; own_data_buffer_ptr = compressed_buffer; data_length = compressed_size; head_pflags |= PacketFlag::Compressed; } else { ::free(compressed_buffer); } } uint8_t ptype_and_flags{(uint8_t) ((uint8_t) ptype | (uint8_t) PacketFlag::NewProtocol)}; if(data_length > MAX_COMMAND_PACKET_PAYLOAD_LENGTH) { auto chunk_count = (size_t) ceil((float) data_length / (float) MAX_COMMAND_PACKET_PAYLOAD_LENGTH); auto chunk_size = (size_t) ceil((float) data_length / (float) chunk_count); while(true) { auto bytes = min(chunk_size, data_length); auto packet = protocol::allocate_outgoing_packet(bytes); packet->type_and_flags = ptype_and_flags; memcpy(packet->payload, data_buffer, bytes); *packets_tail = packet; packets_tail = &packet->next; data_length -= bytes; if(data_length == 0) { packet->type_and_flags |= PacketFlag::Fragmented; break; } data_buffer += bytes; } packets_head->type_and_flags |= PacketFlag::Fragmented; } else { auto packet = protocol::allocate_outgoing_packet(data_length); packet->type_and_flags = ptype_and_flags; memcpy(packet->payload, data_buffer, data_length); packets_head = packet; packets_tail = &packet->next; } { std::lock_guard id_lock{this->packet_id_mutex}; uint32_t full_id; auto head = packets_head; while(head) { full_id = this->packet_id_manager.generate_full_id(ptype); head->set_packet_id(full_id & 0xFFFFU); head->generation = full_id >> 16U; head = head->next; } } packets_head->type_and_flags |= head_pflags; /* do this before the next ptr might get modified due to the write queue */ auto statistics = this->client ? this->client->connectionStatistics : nullptr; /* general stats */ if(statistics) { auto head = packets_head; while(head) { statistics->logOutgoingPacket(stats::ConnectionStatistics::category::COMMAND, head->packet_length() + 96); /* 96 for the UDP overhead */ head = head->next; } } /* loss stats */ { auto head = packets_head; while(head) { auto full_packet_id = (uint32_t) (head->generation << 16U) | head->packet_id(); this->packet_statistics_.send_command(head->packet_type(), full_packet_id); /* increase a reference for the ack handler */ head->ref(); /* Even thou the packet is yet unencrypted, it will be encrypted with the next write. The next write will be before the next resend because the next ptr must be null in order to resend a packet */ if(&head->next == packets_tail) this->acknowledge_handler.process_packet(ptype, full_packet_id, head, std::move(ack_listener)); else this->acknowledge_handler.process_packet(ptype, full_packet_id, head, nullptr); head = head->next; } } { std::lock_guard qlock{this->write_queue_mutex}; *this->write_queue_tail = packets_head; this->write_queue_tail = packets_tail; } this->triggerWrite(); if(own_data_buffer) ::free(own_data_buffer_ptr); }