A lot of updates

This commit is contained in:
WolverinDEV
2019-10-26 01:51:19 +02:00
parent 53d3814f92
commit b956bad3f7
1039 changed files with 0 additions and 262642 deletions
@@ -1,535 +0,0 @@
#ifdef WIN32
#include <WinSock2.h>
#endif
#include "ProtocolHandler.h"
#include "ServerConnection.h"
#include "Socket.h"
#include "../logger.h"
#include <misc/base64.h>
#include <misc/endianness.h>
#include <protocol/buffers.h>
#include <thread>
#include <iostream>
#include <protocol/Packet.h>
using namespace std;
using namespace std::chrono;
using namespace tc::connection;
using namespace ts::protocol;
using namespace ts;
ProtocolHandler::ProtocolHandler(ServerConnection* handle) : handle(handle) {
this->compression_handler.max_packet_size = 128 * 1024; /* max 128Kb */
}
ProtocolHandler::~ProtocolHandler() {
}
void ProtocolHandler::reset() {
this->server_type = server_type::UNKNOWN;
this->disconnect_id++; /* we've been resetted any pending disconnects are not from interest anymore */
this->client_id = 0;
this->acknowledge_handler.reset();
this->connection_state = connection_state::INITIALIZING;
{ /* initialize pow handler */
this->pow.state = pow_state::COOKIE_SET;
this->pow.last_buffer = pipes::buffer{};
this->pow.last_resend = system_clock::time_point{};
this->pow.last_response = system_clock::time_point{};
this->pow.client_control_data[0] = 0; /* clear set flag, so the client generates a new pack */
}
{
this->crypto.alpha[0] = 0;
this->crypto.initiv_command = "";
this->crypto.beta_length = 0;
if(this->crypto.identity.k)
ecc_free(&this->crypto.identity);
memset(&this->crypto.identity, 0, sizeof(this->crypto.identity));
}
for(auto& buffer : this->_packet_buffers) {
lock_guard lock(buffer.buffer_lock);
buffer.reset();
}
this->_packet_id_manager.reset();
this->crypt_handler.reset();
this->ping.ping_received_timestamp = system_clock::time_point{};
}
void ProtocolHandler::connect() {
this->connection_state = connection_state::INIT_LOW;
this->connect_timestamp = system_clock::now();
this->pow_send_cookie_get();
{
auto command = this->generate_client_initiv();
auto packet = make_shared<ClientPacket>(PacketTypeInfo::Command, pipes::buffer_view{command.data(), command.size()});
packet->enable_flag(PacketFlag::NewProtocol);
this->send_packet(packet);
}
}
void ProtocolHandler::execute_tick() {
auto now = system_clock::now();
if(this->connection_state < connection_state::DISCONNECTED) {
if(!this->pow.last_buffer.empty() && this->pow.last_resend < now - seconds(1)) {
this->pow.last_resend = now;
this->send_packet(make_shared<ClientPacket>(PacketTypeInfo::Init1, PacketFlag::Unencrypted, this->pow.last_buffer));
}
if(this->connection_state == connection_state::INIT_LOW || this->connection_state == connection_state::INIT_HIGH) {
if(this->connect_timestamp < now - seconds(15)) {
this->handle->call_connect_result.call(this->handle->errors.register_error("timeout (" + to_string(this->connection_state) + ")"), true);
this->handle->close_connection();
return;
}
}
if(this->connection_state == connection_state::DISCONNECTING) {
if(this->disconnect_timestamp < now - seconds(5)) { /* disconnect timeout */
this->handle->close_connection();
return;
}
}
this->execute_resend();
/* ping */
if(this->connection_state == connection_state::CONNECTED) {
if(this->ping.ping_send_timestamp + seconds(1) < now)
this->ping_send_request();
if(this->ping.ping_received_timestamp.time_since_epoch().count() > 0) {
if(now - this->ping.ping_received_timestamp > seconds(30)) {
this->handle->execute_callback_disconnect.call(tr("ping timeout"), true);
this->handle->close_connection();
return;
}
} else
this->ping.ping_received_timestamp = now;
}
}
}
void ProtocolHandler::execute_resend() {
if(this->connection_state >= connection_state::DISCONNECTED)
return;
deque<pipes::buffer> buffers;
auto now = system_clock::now();
system_clock::time_point next = now + seconds(5); /* in real we're doing it all 500ms */
string error;
auto resended = this->acknowledge_handler.execute_resend(now, next, buffers, error);
if(resended < 0) {
log_error(category::connection, tr("Failed to receive acknowledge: {}"), error);
this->handle->execute_callback_disconnect(tr("packet resend failed"));
this->handle->close_connection();
return;
}
auto socket = this->handle->get_socket();
if(socket) {
for(const auto& buffer : buffers)
socket->send_message(buffer);
}
this->handle->schedule_resend(next);
}
void ProtocolHandler::progress_packet(const pipes::buffer_view &buffer) {
if(this->connection_state >= connection_state::DISCONNECTED)
return;
if(buffer.length() < ServerPacket::META_SIZE) {
log_error(category::connection, tr("Received a packet which is too small. ({})"), buffer.length());
return;
}
auto packet = std::shared_ptr<ts::protocol::ServerPacket>(ts::protocol::ServerPacket::from_buffer(buffer).release());
auto packet_type = packet->type();
auto packet_id = packet->packetId();
auto ordered = packet_type.type() == protocol::COMMAND || packet_type.type() == protocol::COMMAND_LOW;
/* special handling */
if(packet_type.type() == protocol::INIT1) {
this->handlePacketInit(packet);
return;
}
if(packet_type.type() < 0 || packet_type.type() >= this->_packet_buffers.size()) {
log_error(category::connection, tr("Received packet with invalid type. ({})"), packet_type.type());
return;
}
auto& read_queue = this->_packet_buffers[packet_type.type()];
packet->generationId(read_queue.generation(packet_id));
if(ordered) {
unique_lock queue_lock(read_queue.buffer_lock);
auto result = read_queue.accept_index(packet_id);
if(result != 0) { /* packet index is ahead buffer index */
log_error(category::connection, tr("Failed to verify command packet: {} (Index: {} Current index: {})"), result, packet_id, read_queue.current_index());
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(packet->type() == PacketTypeInfo::Command || packet->type() == PacketTypeInfo::CommandLow)
this->send_acknowledge(packet->packetId(), packet->type() == PacketTypeInfo::CommandLow);
}
return;
}
}
packet->setEncrypted(!packet->has_flag(PacketFlag::Unencrypted));
if(packet->type() == PacketTypeInfo::Command || packet->type() == PacketTypeInfo::CommandLow){
packet->setCompressed(packet->has_flag(PacketFlag::Compressed));
}
//NOTICE I found out that the Compressed flag is set if the packet contains an audio header
string error = "success";
if(!this->crypt_handler.progressPacketIn(packet.get(), error, false)){
if(!this->crypt_handler.use_default()) {
if(!this->crypt_handler.progressPacketIn(packet.get(), error, true)){
log_error(category::connection, tr("Failed to decrypt packet ({}), even with default key: {}"), packet_type.name(), error);
return;
} else {
log_error(category::connection, tr("Successfully decrypt packet ({} | {}) with default key."), packet_type.name(), packet_id);
//FIXME Test if we're in init high
}
} else {
log_error(category::connection, tr("Failed to decrypt packet ({}) with default key: {}"), packet_type.name(), error);
return;
}
}
if(packet->type() == PacketTypeInfo::Command || packet->type() == PacketTypeInfo::CommandLow){
if(packet->has_flag(PacketFlag::Unencrypted))
return;
}
if(packet->type() == PacketTypeInfo::Command || packet->type() == PacketTypeInfo::CommandLow)
this->send_acknowledge(packet->packetId(), packet->type() == PacketTypeInfo::CommandLow);
{
unique_lock queue_lock(read_queue.buffer_lock);
if(ordered) { /* ordered */
if(!read_queue.insert_index(packet_id, std::forward<shared_ptr<ServerPacket>>(packet))) {
log_warn(category::connection, tr("Failed to insert ordered packet into queue. ({} | {} | {})"), packet_type.name(), read_queue.current_index(), packet_id);
}
} else {
if(!read_queue.push_back(std::forward<shared_ptr<ServerPacket>>(packet))) {
log_warn(category::connection, tr("Failed to insert unordered packet into queue. ({} | {} | {})"), packet_type.name(), read_queue.current_index(), packet_id);
/* return; dont stop here because we've to progress the packets */
} else {
read_queue.index_set(packet_id); /* may we've skipped one packet id */
}
}
}
while(this->handle_packets());
}
bool ProtocolHandler::handle_packets() {
if(this->connection_state >= connection_state::DISCONNECTED)
return false;
bool reexecute_handle = false;
shared_ptr<ServerPacket> current_packet = nullptr;
packet_buffer_t* buffer = nullptr;
unique_lock<std::recursive_timed_mutex> buffer_lock;
unique_lock<std::recursive_timed_mutex> buffer_execute_lock;
std::string error = "success";
{
auto base_index = this->_packet_buffers_index;
auto select_index = base_index;
auto max_index = this->_packet_buffers.size();
for(uint8_t index = 0; index < max_index; index++) {
if(!buffer) select_index++;
auto& buf = this->_packet_buffers[base_index++ % max_index];
unique_lock ring_lock(buf.buffer_lock, try_to_lock);
if(!ring_lock.owns_lock()) 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()) continue;
buffer_lock = move(ring_lock);
buffer = &buf;
} else {
reexecute_handle |= true;
break;
}
}
}
this->_packet_buffers_index = select_index % max_index; /* garante that we will not hangup with commands! */
}
if(buffer){
uint16_t sequence_length = 0;
current_packet = buffer->slot_value(sequence_length++);
if(current_packet) {
if((current_packet->type() == PacketTypeInfo::Command || current_packet->type() == PacketTypeInfo::CommandLow) && current_packet->has_flag(PacketFlag::Fragmented)) {
do {
if(sequence_length >= buffer->capacity()) {
log_warn(category::connection, tr("Received fragmented packets which have a too long order. Dropping queue, which will cause a client drop."));
buffer->clear();
return false;
}
current_packet = buffer->slot_value(sequence_length++);
} while(current_packet && !current_packet->has_flag(PacketFlag::Fragmented));
}
} else {
log_critical(category::connection, tr("buffer->slot_value(sequence_length++) returned nullptr!"));
//FIXME!
//logCritical(this->client->getServer()->getServerId(), "buffer->slot_value(sequence_length++) returned nullptr!")
};
if(current_packet) { //We could reconstruct a new packet!
if(sequence_length > 1) { //We have to merge
vector<pipes::buffer> append;
append.reserve(sequence_length - 1);
uint16_t packet_count = 0;
current_packet = buffer->pop_front();
packet_count++;
do {
auto packet = buffer->pop_front();
packet_count++;
if(!packet) {
log_critical(category::connection, tr("readQueue->peekNext(seqIndex++) => nullptr_t!"));
return false;
}
append.push_back(packet->data());
if(packet->has_flag(PacketFlag::Fragmented)) break;
} while(packet_count < sequence_length);
if(packet_count != sequence_length) {
log_critical(category::connection, tr("seqIndex != index failed! seqIndex: {} seqLength: {} This may cause a application crash!"), packet_count, sequence_length);
sequence_length = packet_count;
current_packet = nullptr;
} else {
current_packet->append_data(append);
}
} else {
if(buffer->pop_front() != current_packet) {
log_critical(category::connection, tr("buffer->pop_front() != current_packet failed."));
}
}
reexecute_handle |= buffer->front_set();
buffer_lock.unlock(); //We got our packet so release it
if(current_packet) {
if(!this->compression_handler.progressPacketIn(current_packet.get(), error)) {
log_error(category::connection, tr("Failed to decompress received packet. Error: {}"), error);
current_packet = nullptr;
}
}
}
}
if(current_packet){
auto startTime = chrono::system_clock::now();
try {
if(current_packet->type() == PacketTypeInfo::Command || current_packet->type() == PacketTypeInfo::CommandLow)
this->handlePacketCommand(current_packet);
else if(current_packet->type() == PacketTypeInfo::Ack || current_packet->type() == PacketTypeInfo::AckLow)
this->handlePacketAck(current_packet);
else if(current_packet->type() == PacketTypeInfo::Voice || current_packet->type() == PacketTypeInfo::VoiceWhisper)
this->handlePacketVoice(current_packet);
else if(current_packet->type() == PacketTypeInfo::Ping || current_packet->type() == PacketTypeInfo::Pong)
this->handlePacketPing(current_packet);
} catch (std::exception& ex) {
log_critical(category::connection, tr("Exception reached root tree! {}"), ex.what());
}
auto end = chrono::system_clock::now();
if(end - startTime > chrono::milliseconds(10)) {
if(current_packet->type() != PacketTypeInfo::Command && current_packet->type() != PacketTypeInfo::CommandLow) {
//FIXME!
/*
logError(this->client->getServerId(),
"{} Handling of packet {} needs more than 10ms ({}ms)",
CLIENT_STR_LOG_PREFIX_(this->client),
current_packet->type().name(),
duration_cast<milliseconds>(end - startTime).count()
);
*/
}
}
}
if(buffer_execute_lock.owns_lock())
buffer_execute_lock.unlock();
return reexecute_handle;
}
bool ProtocolHandler::create_datagram_packets(std::vector<pipes::buffer> &result, const std::shared_ptr<ts::protocol::ClientPacket> &packet) {
string error = "success";
if(packet->type().compressable() && !packet->memory_state.fragment_entry) {
packet->enable_flag(PacketFlag::Compressed);
if(!this->compression_handler.progressPacketOut(packet.get(), error)) {
log_error(category::connection, tr("Could not compress outgoing packet.\nThis could cause fatal failed for the client.\nError: {}"), error);
return false;
}
}
if(packet->data().length() > packet->type().max_length()){
if(!packet->type().fragmentable()) {
log_error(category::connection, tr("We've tried to send a too long, not fragmentable packet. Dropping packet of type {} with length {}"), packet->type().name(), packet->data().length());
return false;
}
std::vector<shared_ptr<ClientPacket>> siblings;
siblings.reserve(8);
{ //Split packets
auto buffer = packet->data();
const auto max_length = packet->type().max_length();
while(buffer.length() > max_length * 2) {
siblings.push_back(make_shared<ClientPacket>(packet->type(), buffer.view(0, max_length).dup(ts::buffer::allocate_buffer(max_length))));
buffer = buffer.range(max_length);
}
if(buffer.length() > max_length) { //Divide rest by 2
siblings.push_back(make_shared<ClientPacket>(packet->type(), buffer.view(0, buffer.length() / 2).dup(ts::buffer::allocate_buffer(buffer.length() / 2))));
buffer = buffer.range(buffer.length() / 2);
}
siblings.push_back(make_shared<ClientPacket>(packet->type(), buffer));
for(const auto& frag : siblings) {
frag->setFragmentedEntry(true);
frag->enable_flag(PacketFlag::NewProtocol);
}
}
assert(siblings.size() >= 2);
siblings.front()->enable_flag(PacketFlag::Fragmented);
if(packet->has_flag(PacketFlag::Compressed))
siblings.front()->enable_flag(PacketFlag::Compressed);
siblings.back()->enable_flag(PacketFlag::Fragmented);
if(packet->getListener())
siblings.back()->setListener(std::move(packet->getListener())); //Move the listener to the last :)
result.reserve(siblings.size());
for(const auto& frag : siblings)
create_datagram_packets(result, frag);
return true;
}
if(!packet->memory_state.id_branded) {
packet->clientId(this->client_id);
if(packet->type().type() == PacketType::INIT1) {
packet->applyPacketId(101, 0);
} else {
packet->applyPacketId(this->_packet_id_manager);
}
//log_trace(category::connection, tr("Packet {} got packet id {}"), packet->type().name(), packet->packetId());
}
if(!this->crypt_handler.progressPacketOut(packet.get(), error, false)) {
log_error(category::connection, tr("Failed to encrypt packet: {}"), error);
return false;
}
/*
#ifndef CONNECTION_NO_STATISTICS
if(this->client && this->client->getServer())
this->client->connectionStatistics->logOutgoingPacket(packet);
#endif
*/
result.push_back(packet->buffer());
this->acknowledge_handler.process_packet(*packet);
return true;
}
void ProtocolHandler::send_command(const ts::Command &cmd, const std::function<void(bool)> &ack_callback) {
auto data = cmd.build();
auto packet = make_shared<ClientPacket>(PacketTypeInfo::Command, pipes::buffer_view{data.data(), data.size()});
if(ack_callback) {
auto begin = chrono::system_clock::now();
packet->setListener(make_unique<threads::Future<bool>>());
packet->getListener()->waitAndGetLater([ack_callback, begin](bool f) {
auto end = chrono::system_clock::now();
if(ack_callback)
ack_callback(f);
log_trace(category::connection, tr("Time needed for command: {}"), chrono::duration_cast<chrono::milliseconds>(end - begin).count());
});
}
packet->enable_flag(PacketFlag::NewProtocol);
this->send_packet(packet);
}
void ProtocolHandler::send_packet(const std::shared_ptr<ts::protocol::ClientPacket> &packet) {
std::vector<pipes::buffer> result;
if(!this->create_datagram_packets(result, packet) || result.empty()) {
log_error(category::connection, tr("Failed to create datagram packets!"));
return;
}
auto socket = this->handle->get_socket();
if(!socket) {
log_error(category::connection, tr("Failed to get socket!"));
return;
}
for(const auto& buffer : result)
socket->send_message(buffer);
}
void ProtocolHandler::send_acknowledge(uint16_t packet_id, bool low) {
char buffer[2];
le2be16(packet_id, buffer);
auto packet = make_shared<protocol::ClientPacket>(low ? protocol::PacketTypeInfo::AckLow : protocol::PacketTypeInfo::Ack, 0, pipes::buffer_view{buffer, 2});
if(this->connection_state >= connection_state::CONNECTING) {
;//packet->toggle(protocol::PacketFlag::NewProtocol, !low);
//LivingBots DDOS protection dont want a new protocol here!
}
this->send_packet(packet);
}
void ProtocolHandler::do_close_connection() {
this->connection_state = connection_state::DISCONNECTED;
for(auto& buffer : this->_packet_buffers) {
lock_guard lock(buffer.buffer_lock);
buffer.clear();
}
}
void ProtocolHandler::disconnect(const std::string &reason) {
if(this->connection_state >= connection_state::DISCONNECTING)
return;
this->connection_state = connection_state::DISCONNECTING;
this->disconnect_timestamp = system_clock::now();
auto did = ++this->disconnect_id;
Command cmd("clientdisconnect");
cmd["reasonmsg"] = reason;
this->send_command(cmd, [&, did](bool success){
/* if !success then we'll have prop already triggered the timeout and this here is obsolete */
if(success && this->connection_state == connection_state::DISCONNECTING && this->disconnect_id == did)
this->handle->close_connection();
});
}
@@ -1,148 +0,0 @@
#pragma once
#include <chrono>
#include <cstdint>
#define NO_LOG
#ifdef WIN32
#include <WinSock2.h> //Needs to be included; No clue why
#endif
#include <protocol/ringbuffer.h>
#include <protocol/Packet.h>
#include <protocol/CryptionHandler.h>
#include <protocol/CompressionHandler.h>
#include <protocol/AcknowledgeManager.h>
#include "ServerConnection.h"
namespace ts {
namespace connection {
class CryptionHandler;
class CompressionHandler;
}
}
namespace tc {
namespace connection {
class ServerConnection;
namespace connection_state {
enum value {
INITIALIZING,
INIT_LOW,
INIT_HIGH,
CONNECTING,
CONNECTED,
DISCONNECTING,
DISCONNECTED
};
};
namespace pow_state {
enum value : uint8_t {
COOKIE_GET,
COOKIE_SET,
PUZZLE_GET,
PUZZLE_SET,
PUZZLE_SOLVE,
PUZZLE_RESET,
COMPLETED,
COMMAND_RESET = 127,
UNSET = 0xFB
};
};
class ProtocolHandler {
typedef ts::protocol::PacketRingBuffer<ts::protocol::ServerPacket, 86> packet_buffer_t;
typedef std::array<packet_buffer_t, 8> packet_buffers_t;
friend class ServerConnection;
public:
ProtocolHandler(ServerConnection*);
~ProtocolHandler();
void reset();
void connect();
void execute_tick();
void execute_resend();
void progress_packet(const pipes::buffer_view& /* buffer */);
bool handle_packets(); /* if true we have more left */
void send_packet(const std::shared_ptr<ts::protocol::ClientPacket>& /* packet */);
void send_command(const ts::Command& /* command */, const std::function<void(bool)> & /* acknowledge callback */ = NULL);
void disconnect(const std::string& /* message */);
void send_acknowledge(uint16_t /* packet id */, bool /* low */);
ecc_key& get_identity_key() { return this->crypto.identity; }
inline std::chrono::microseconds current_ping() { return this->ping.value; }
connection_state::value connection_state = connection_state::INITIALIZING;
server_type::value server_type = server_type::TEASPEAK;
private:
void do_close_connection(); /* only call from ServerConnection. Close all connections via ServerConnection! */
void handlePacketCommand(const std::shared_ptr<ts::protocol::ServerPacket>&);
void handlePacketAck(const std::shared_ptr<ts::protocol::ServerPacket>&);
void handlePacketVoice(const std::shared_ptr<ts::protocol::ServerPacket>&);
void handlePacketPing(const std::shared_ptr<ts::protocol::ServerPacket>&);
void handlePacketInit(const std::shared_ptr<ts::protocol::ServerPacket>&);
bool create_datagram_packets(std::vector<pipes::buffer> &result, const std::shared_ptr<ts::protocol::ClientPacket> &packet);
ServerConnection* handle;
std::chrono::system_clock::time_point connect_timestamp;
std::chrono::system_clock::time_point disconnect_timestamp;
uint8_t disconnect_id = 0;
struct {
pow_state::value state;
uint32_t client_ts3_build_timestamp = 173265950 /* TS3 */; /* needs to be lower than 173265950 for old stuff, else new protocol */
uint8_t client_control_data[4] = {0,0,0,0};
uint8_t server_control_data[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
uint8_t server_data[100];
std::chrono::system_clock::time_point last_response;
std::chrono::system_clock::time_point last_resend;
pipes::buffer last_buffer;
} pow;
void pow_send_cookie_get();
struct {
uint8_t alpha[10];
uint8_t beta[54];
uint8_t beta_length; /* 10 or 54 */
ecc_key identity{};
std::string initiv_command;
} crypto;
std::string generate_client_initiv();
uint16_t client_id = 0;
ts::protocol::PacketIdManager _packet_id_manager;
packet_buffers_t _packet_buffers;
uint8_t _packet_buffers_index = 0;
ts::connection::CryptionHandler crypt_handler;
ts::connection::CompressionHandler compression_handler;
ts::connection::AcknowledgeManager acknowledge_handler;
void handleCommandInitIVExpend(ts::Command&);
void handleCommandInitIVExpend2(ts::Command&);
void handleCommandInitServer(ts::Command&);
struct {
std::chrono::system_clock::time_point ping_send_timestamp;
std::chrono::system_clock::time_point ping_received_timestamp;
std::chrono::microseconds value;
uint16_t ping_id;
std::chrono::microseconds interval = std::chrono::microseconds(2500);
} ping;
void ping_send_request();
};
}
}
@@ -1,18 +0,0 @@
#include "ProtocolHandler.h"
#include "ServerConnection.h"
#include "../logger.h"
#include <protocol/buffers.h>
#include <thread>
using namespace std;
using namespace tc::connection;
using namespace ts::protocol;
using namespace ts;
void ProtocolHandler::handleCommandInitServer(ts::Command &cmd) {
this->client_id = cmd["aclid"];
this->connection_state = connection_state::CONNECTED;
log_info(category::connection, tr("Received own client id: {}"), this->client_id);
}
@@ -1,216 +0,0 @@
#include "ProtocolHandler.h"
#include "ServerConnection.h"
#include "Socket.h"
#include "../logger.h"
#include <protocol/buffers.h>
#include <thread>
#include <iostream>
#include <tomcrypt.h>
#include <tommath.h>
#include <misc/base64.h>
#include <misc/digest.h>
#include <License.h>
#include <ed25519/ed25519.h>
#include <ed25519/sha512.h>
using namespace std;
using namespace tc::connection;
using namespace ts::protocol;
using namespace ts;
inline void generate_random(uint8_t *destination, size_t length) {
while(length-- > 0)
*(destination++) = (uint8_t) rand();
}
std::string ProtocolHandler::generate_client_initiv() {
if(!this->crypto.initiv_command.empty())
return this->crypto.initiv_command;
/* setup basic parameters */
if((this->crypto.alpha[0] & 0x01) == 0) {
generate_random(this->crypto.alpha, 10);
this->crypto.alpha[0] |= 0x01;
}
Command command("clientinitiv");
command["alpha"] = base64::encode((char*) this->crypto.alpha, 10);
command["ot"] = 1;
command["ip"] = "unknown";
if(this->server_type != server_type::TEAMSPEAK) /* if TEAMSPEAK then TS3 has been enforced */
command.enableParm("teaspeak"); /* using "old" encryption system, we expect a teaspeak=1 within the response */
{
size_t buffer_length = 265;
char buffer[265];
auto result = ecc_export((unsigned char *) buffer, (unsigned long*) &buffer_length, PK_PUBLIC, &this->crypto.identity);
if(result == CRYPT_OK)
command["omega"] = base64::encode(buffer, buffer_length);
else
log_error(category::connection, tr("Failed to export identiry ({})"), result);
}
this->crypto.initiv_command = command.build(true);
return this->crypto.initiv_command;
}
void ProtocolHandler::handleCommandInitIVExpend(ts::Command &cmd) {
this->pow.last_buffer = pipes::buffer{};
auto alpha = base64::decode(cmd["alpha"]);
auto beta = base64::decode(cmd["beta"]);
auto omega = base64::decode(cmd["omega"]);
if(alpha.length() != 10 || memcmp(alpha.data(), this->crypto.alpha, 10) != 0) {
this->handle->call_connect_result.call(this->handle->errors.register_error(tr("alpha key missmatch")), true);
this->handle->close_connection();
log_error(category::connection, tr("InitIVExpend contains invalid alpha"));
return;
}
ecc_key server_key{};
if(ecc_import((u_char*) omega.data(), omega.length(), &server_key) != CRYPT_OK) {
this->handle->call_connect_result.call(this->handle->errors.register_error(tr("failed to import server key")), true);
this->handle->close_connection();
log_error(category::connection, tr("InitIVExpend contains invalid key"));
return;
}
string error;
if(!this->crypt_handler.setupSharedSecret(alpha, beta, &server_key, &this->crypto.identity, error)) {
this->handle->call_connect_result.call(this->handle->errors.register_error(tr("failed to setup encryption")), true);
this->handle->close_connection();
log_error(category::connection, tr("Failed to setup crypto ({})"), error);
return;
}
if(this->server_type == server_type::UNKNOWN) {
if(cmd[0].has("teaspeak") && cmd["teaspeak"].as<bool>()) {
this->server_type = server_type::TEASPEAK;
} else {
this->server_type = server_type::TEAMSPEAK;
}
}
this->handle->call_connect_result.call(0, true);
this->connection_state = connection_state::CONNECTING;
}
int __ed_sha512_init(sha512_context* ctx) {
ctx->context = new hash_state{};
return sha512_init((hash_state*) ctx->context) == CRYPT_OK;
}
int __ed_sha512_final(sha512_context* ctx, unsigned char *out) {
assert(ctx->context);
auto result = sha512_done((hash_state*) ctx->context, out) == CRYPT_OK;
delete (hash_state*) ctx->context;
return result;
}
int __ed_sha512_update(sha512_context* ctx, const unsigned char *msg, size_t len) {
assert(ctx->context);
return sha512_process((hash_state*) ctx->context, msg, len) == CRYPT_OK;
}
static sha512_functions __ed_sha512_functions {
__ed_sha512_init,
__ed_sha512_final,
__ed_sha512_update
};
void ProtocolHandler::handleCommandInitIVExpend2(ts::Command &cmd) {
this->pow.last_buffer = pipes::buffer{};
/* setup ed functions */
if(&__ed_sha512_functions != &_ed_sha512_functions)
_ed_sha512_functions = __ed_sha512_functions;
auto beta = base64::decode(cmd["beta"]);
auto omega = base64::decode(cmd["omega"]);
auto proof = base64::decode(cmd["proof"]);
auto crypto_chain_data = base64::decode(cmd["l"]);
auto crypto_root = cmd[0].has("root") ? base64::decode(cmd["root"]) : string((char*) license::teamspeak::public_root, 32);
auto crypto_hash = digest::sha256(crypto_chain_data);
/* suspecius, tries the server to hide himself? We dont know */
if(this->server_type == server_type::UNKNOWN) {
if(cmd[0].has("root"))
this->server_type = server_type::TEASPEAK;
else
this->server_type = server_type::TEAMSPEAK;
}
ecc_key server_key{};
if(ecc_import((u_char*) omega.data(), omega.length(), &server_key) != CRYPT_OK) {
this->handle->call_connect_result.call(this->handle->errors.register_error(tr("failed to import server key")), true);
this->handle->close_connection();
log_error(category::connection, tr("InitIVExpend contains invalid key"));
return;
}
int result, crypt_result;
if((crypt_result = ecc_verify_hash((u_char*) proof.data(), proof.length(), (u_char*) crypto_hash.data(), crypto_hash.length(), &result, &server_key)) != CRYPT_OK || result != 1) {
this->handle->call_connect_result.call(this->handle->errors.register_error(tr("failed to verify server integrity")), true);
this->handle->close_connection();
return;
}
string error;
auto crypto_chain = license::teamspeak::LicenseChain::parse(crypto_chain_data, error, false);
if(!crypto_chain) {
this->handle->call_connect_result.call(this->handle->errors.register_error(tr("failed to read crypto chain")), true);
this->handle->close_connection();
return;
}
auto server_public_key = crypto_chain->generatePublicKey(*(license::teamspeak::LicensePublicKey*) crypto_root.data());
crypto_chain->print();
u_char seed[32];
ed25519_create_seed(seed);
u_char public_key[32], private_key[64]; /* We need 64 bytes because we're doing some SHA512 actions */
ed25519_create_keypair(public_key, private_key, seed);
/* send clientek response */
{
size_t sign_buffer_length = 200;
char sign_buffer[200];
prng_state prng_state{};
memset(&prng_state, 0, sizeof(prng_state));
auto proof_data = digest::sha256(string((char*) public_key, 32) + beta);
if(ecc_sign_hash((uint8_t*) proof_data.data(), proof_data.length(), (uint8_t*) sign_buffer, (unsigned long*) &sign_buffer_length, &prng_state, find_prng("sprng"), &this->crypto.identity) != CRYPT_OK) {
this->handle->call_connect_result.call(this->handle->errors.register_error(tr("failed to generate proof of identity")), true);
this->handle->close_connection();
return;
}
Command response("clientek");
response["ek"] = base64::encode((char*) public_key, 32);
response["proof"] = base64::encode(sign_buffer, sign_buffer_length);
/* no need to send this because we're sending the clientinit as the begin packet along with the POW init */
//this->_packet_id_manager.nextPacketId(PacketTypeInfo::Command); /* skip the first because we've send our first command within the low level handshake packets */
this->send_command(response, [&](bool success){
if(success) {
/* trigger connected; because the connection has been established on protocol layer */
this->handle->call_connect_result.call(0, true);
this->connection_state = connection_state::CONNECTING;
}
}); /* needs to be encrypted at the time! */
}
if(!this->crypt_handler.setupSharedSecretNew(string((char*) this->crypto.alpha, 10), beta, (char*) private_key, server_public_key.data())) {
this->handle->call_connect_result.call(this->handle->errors.register_error(tr("failed to setup encryption")), true);
this->handle->close_connection();
return;
}
}
@@ -1,168 +0,0 @@
#include "ProtocolHandler.h"
#include "ServerConnection.h"
#include "Socket.h"
#include "../logger.h"
#include "Error.h"
#include <misc/endianness.h>
#include <protocol/buffers.h>
#include <thread>
#include <iostream>
#include <tomcrypt.h>
#include <tommath.h>
using namespace std;
using namespace std::chrono;
using namespace tc::connection;
using namespace ts::protocol;
using namespace ts;
inline void generate_random(uint8_t *destination, size_t length) {
while(length-- > 0)
*(destination++) = (uint8_t) rand();
}
inline void write_reversed(uint8_t* destination, uint8_t* source, size_t length) {
destination += length;
while(length-- > 0)
*(--destination) = *(source++);
}
inline bool solve_puzzle(mp_int& x, mp_int& n, mp_int& result, uint32_t level) {
mp_int exp{};
mp_init(&exp);
mp_2expt(&exp, level);
if (mp_exptmod(&x, &exp, &n, &result) != CRYPT_OK) { //Sometimes it fails (unknown why :D)
mp_clear(&exp);
return false;
}
mp_clear(&exp);
return true;
}
void ProtocolHandler::handlePacketInit(const std::shared_ptr<ts::protocol::ServerPacket> &packet) {
this->pow.last_response = system_clock::now();
auto data = packet->data();
auto packet_state = static_cast<pow_state::value>(data[0]);
if(packet_state == pow_state::COMMAND_RESET) {
log_trace(category::connection, tr("[POW] Received reset"));
this->pow.state = pow_state::COOKIE_SET; /* next expected packet state */
this->pow_send_cookie_get();
return;
}
log_trace(category::connection, tr("[POW] State {} | {}"), packet_state, data.length());
if(packet_state != this->pow.state)
return; //TODO handle error?
this->acknowledge_handler.reset(); /* we don't need an ack anymore for our init packet */
if(packet_state == pow_state::COOKIE_SET) {
if(data.length() != 21 && data.length() != 5) {
log_trace(category::connection, tr("[POW] Dropping cookie packet (got {} bytes expect 21 or 5 bytes)"), data.length());
return;
}
if(data.length() == 21) {
memcpy(&this->pow.server_control_data[0], &data[1], 16);
//TODO test client data reserved bytes
} else {
auto errc = ntohl(*(uint32_t*) &data[1]);
auto err = ts::findError(errc);
log_error(category::connection, tr("[POW] Received error code: {:x} ({})"), errc, err.message);
this->handle->call_connect_result.call(this->handle->errors.register_error(tr("received error: ") + to_string(errc) + " (" + err.message + ")"), true);
this->handle->close_connection();
return;
}
/* send puzzle get request */
{
this->pow.state = pow_state::PUZZLE_SET; /* next expected packet state */
uint8_t response_buffer[25];
le2be32(this->pow.client_ts3_build_timestamp, &response_buffer[0]);
response_buffer[4] = pow_state::PUZZLE_GET;
memcpy(&response_buffer[5], this->pow.server_control_data, 16);
memcpy(&response_buffer[21], &data[17], 4);
this->pow.last_buffer = pipes::buffer_view{response_buffer, 25}.own_buffer();
this->pow.last_resend = system_clock::now();
this->send_packet(make_shared<ClientPacket>(PacketTypeInfo::Init1, PacketFlag::Unencrypted, this->pow.last_buffer));
}
return;
} else if(packet_state == pow_state::PUZZLE_SET) {
constexpr auto expected_bytes = 1 + 64 * 2 + 4 + 100;
if(data.length() != 1 + 64 * 2 + 4 + 100) {
log_trace(category::connection, tr("[POW] Dropping puzzle packet (got {} bytes expect {} bytes)"), data.length(), expected_bytes);
return;
}
mp_int point_x{}, point_n{}, result{};
if(mp_read_unsigned_bin(&point_x, (u_char*) &data[1], 64) < 0)
return; //TODO handle error
if(mp_read_unsigned_bin(&point_n, (u_char*) &data[65], 64) < 0) {
mp_clear_multi(&point_x, nullptr);
return; //TODO handle error
}
log_trace(category::connection, tr("[POW] Received puzzle with level {}"), be2le32(&data[1 + 64 + 64]));
if(!solve_puzzle(point_x, point_n, result, be2le32(&data[1 + 64 + 64]))) {
mp_clear_multi(&point_x, &point_n, nullptr);
log_trace(connection, tr("[POW] Failed to solve puzzle!"));
return; //TODO handle error
}
{
auto command = this->generate_client_initiv();
size_t response_buffer_length = 301 + command.size();
auto response_buffer = buffer::allocate_buffer(response_buffer_length);
le2be32(this->pow.client_ts3_build_timestamp, &response_buffer[0]);
response_buffer[4] = pow_state::PUZZLE_SOLVE;
memcpy(&response_buffer[5], &data[1], 64 * 2 + 100 + 4);
auto offset = 4 + 1 + 2 * 64 + 04 + 100;
memset(&response_buffer[offset], 0, 64);
mp_to_unsigned_bin(&result, (u_char*) &response_buffer[offset]);
memcpy(&response_buffer[301], command.data(), command.size());
this->pow.last_buffer = response_buffer;
this->pow.last_resend = system_clock::now();
this->send_packet(make_shared<ClientPacket>(PacketTypeInfo::Init1, PacketFlag::Unencrypted, this->pow.last_buffer));
}
mp_clear_multi(&point_x, &point_n, &result, nullptr);
this->connection_state = connection_state::INIT_HIGH;
}
}
void ProtocolHandler::pow_send_cookie_get() {
this->pow.state = pow_state::COOKIE_SET; /* next expected packet state */
if((this->pow.client_control_data[0] & 0x01U) == 0) {
generate_random(this->pow.client_control_data, 4);
this->pow.client_control_data[0] |= 0x01U;
}
this->pow.client_ts3_build_timestamp = floor < seconds > ((system_clock::now() - hours{24}).time_since_epoch()).count();
uint8_t response_buffer[21];
le2be32(this->pow.client_ts3_build_timestamp, &response_buffer[0]);
response_buffer[4] = pow_state::COOKIE_GET;
memset(&response_buffer[5], 0, 4);
memcpy(&response_buffer[9], &this->pow.client_control_data, 4);
memset(&response_buffer[13], 0, 8);
this->pow.last_buffer = pipes::buffer_view{response_buffer, 21}.own_buffer();
this->pow.last_resend = system_clock::now();
this->send_packet(make_shared<ClientPacket>(PacketTypeInfo::Init1, PacketFlag::Unencrypted, this->pow.last_buffer));
}
@@ -1,97 +0,0 @@
#include "ProtocolHandler.h"
#include "ServerConnection.h"
#include "Socket.h"
#include <protocol/buffers.h>
#include <thread>
#include <iostream>
#include <tomcrypt.h>
#include <tommath.h>
#include <misc/base64.h>
#include <misc/endianness.h>
#include <query/command2.h>
#include <protocol/Packet.h>
#include "audio/VoiceConnection.h"
#include "../logger.h"
using namespace std;
using namespace tc::connection;
using namespace ts::protocol;
using namespace ts;
//#define LOG_PING
void ProtocolHandler::handlePacketAck(const std::shared_ptr<ts::protocol::ServerPacket> &ack) {
string error;
log_trace(category::connection, tr("Handle packet acknowledge for {}"), be2le16(&ack->data()[0]));
if(!this->acknowledge_handler.process_acknowledge(*ack, error)) { }
}
void ProtocolHandler::handlePacketCommand(const std::shared_ptr<ts::protocol::ServerPacket> &packet) {
//cout << "Received command: " << packet->data().string() << endl;
std::unique_ptr<Command> command;
try {
command = make_unique<Command>(packet->asCommand());
} catch(const std::invalid_argument& ex) {
log_error(category::connection, tr("Failed to parse command (invalid_argument): {}"), ex.what());
return;
} catch(const ts::command_malformed_exception& ex) {
log_error(category::connection, tr("Failed to parse command (command_malformed_exception): {}@{}"), ex.what(), ex.index());
return;
} catch(const std::exception& ex) {
log_error(category::connection, tr("Failed to parse command (exception): {}"), ex.what());
return;
}
if(command->command() == "initivexpand") {
this->handleCommandInitIVExpend(*command);
} else if(command->command() == "initivexpand2") {
this->handleCommandInitIVExpend2(*command);
} else if(command->command() == "initserver") {
this->handleCommandInitServer(*command);
}
{
lock_guard lock(this->handle->pending_commands_lock);
this->handle->pending_commands.push_back(move(command));
}
this->handle->execute_pending_commands();
}
void ProtocolHandler::handlePacketVoice(const std::shared_ptr<ts::protocol::ServerPacket> &packet) {
this->handle->voice_connection->process_packet(packet);
}
void ProtocolHandler::handlePacketPing(const std::shared_ptr<ts::protocol::ServerPacket> &packet) {
if(packet->type() == PacketTypeInfo::Pong) {
uint16_t id = be2le16((char*) packet->data().data_ptr());
#ifdef LOG_PING
cout << "Received pong (" << id << "|" << this->ping.ping_id << ")" << endl;
#endif
if(id == this->ping.ping_id) {
this->ping.ping_received_timestamp = chrono::system_clock::now();
this->ping.value = chrono::duration_cast<chrono::microseconds>(this->ping.ping_received_timestamp - this->ping.ping_send_timestamp);
#ifdef LOG_PING
cout << "Updating client ping: " << chrono::duration_cast<chrono::microseconds>(this->ping.value).count() << "us" << endl;
#endif
}
} else {
#ifdef LOG_PING
cout << "Received ping, sending pong" << endl;
#endif
char buffer[2];
le2be16(packet->packetId(), buffer);
this->send_packet(make_shared<ClientPacket>(PacketTypeInfo::Pong, PacketFlag::Unencrypted, pipes::buffer_view{buffer, 2}));
}
}
void ProtocolHandler::ping_send_request() {
auto packet = make_shared<ClientPacket>(PacketTypeInfo::Ping, pipes::buffer_view{});
packet->enable_flag(PacketFlag::Unencrypted);
this->send_packet(packet);
assert(packet->memory_state.id_branded);
this->ping.ping_send_timestamp = chrono::system_clock::now();
this->ping.ping_id = packet->packetId();
}
@@ -1,727 +0,0 @@
#include "ServerConnection.h"
#include "ProtocolHandler.h"
#include "Socket.h"
#include "audio/VoiceConnection.h"
#include "audio/AudioSender.h"
#include "../logger.h"
#include "../hwuid.h"
#include <sstream>
#include <thread>
#include <iostream>
#include <misc/net.h>
#include <misc/base64.h>
#include <misc/endianness.h>
#include <misc/strobf.h>
#include <iomanip>
//#define FUZZ_VOICE
//#define SHUFFLE_VOICE
using namespace std;
using namespace std::chrono;
using namespace tc::connection;
string ErrorHandler::get_message(ErrorHandler::error_id id) {
if(id == 0)
return "success";
auto index = (-id - 1) % this->error_varianz;
assert(index >= 0 && index < this->error_varianz);
return this->error_messages[index];
}
ErrorHandler::error_id ErrorHandler::register_error(const string &message) {
auto index = this->error_index++ % this->error_varianz;
this->error_messages[index] = message;
return -index - 1;
}
ServerConnection::ServerConnection() {
logger::debug(category::connection, tr("Allocated ServerConnection {}."), (void*) this);
}
ServerConnection::~ServerConnection() {
logger::debug(category::connection, tr("Begin deallocating ServerConnection {}."), (void*) this);
if(this->protocol_handler && this->protocol_handler->connection_state == connection_state::CONNECTED)
this->protocol_handler->disconnect("server connection has been destoryed");
this->close_connection();
this->finalize();
logger::debug(category::connection, tr("Finished deallocating ServerConnection {}."), (void*) this);
}
NAN_MODULE_INIT(ServerConnection::Init) {
auto klass = Nan::New<v8::FunctionTemplate>(ServerConnection::new_instance);
klass->SetClassName(Nan::New("NativeServerConnection").ToLocalChecked());
klass->InstanceTemplate()->SetInternalFieldCount(1);
Nan::SetPrototypeMethod(klass, "connect", ServerConnection::_connect);
Nan::SetPrototypeMethod(klass, "connected", ServerConnection::_connected);
Nan::SetPrototypeMethod(klass, "disconnect", ServerConnection::_disconnect);
Nan::SetPrototypeMethod(klass, "error_message", ServerConnection::_error_message);
Nan::SetPrototypeMethod(klass, "send_command", ServerConnection::_send_command);
Nan::SetPrototypeMethod(klass, "send_voice_data", ServerConnection::_send_voice_data);
Nan::SetPrototypeMethod(klass, "send_voice_data_raw", ServerConnection::_send_voice_data_raw);
Nan::SetPrototypeMethod(klass, "current_ping", ServerConnection::_current_ping);
constructor().Reset(Nan::GetFunction(klass).ToLocalChecked());
}
NAN_METHOD(ServerConnection::new_instance) {
//info.GetReturnValue().Set(Nan::New<v8::String>("Hello World").ToLocalChecked());
if (info.IsConstructCall()) {
auto instance = new ServerConnection();
instance->Wrap(info.This());
instance->initialize();
//Nan::Set(info.This(), New<String>("type").ToLocalChecked(), New<Number>(type));
info.GetReturnValue().Set(info.This());
} else {
v8::Local<v8::Function> cons = Nan::New(constructor());
Nan::TryCatch try_catch;
auto result = Nan::NewInstance(cons, 0, nullptr);
if(try_catch.HasCaught()) {
try_catch.ReThrow();
return;
}
info.GetReturnValue().Set(result.ToLocalChecked());
}
}
void ServerConnection::initialize() {
this->event_loop_exit = false;
this->event_thread = thread(&ServerConnection::event_loop, this);
this->protocol_handler = make_unique<ProtocolHandler>(this);
this->voice_connection = make_shared<VoiceConnection>(this);
this->voice_connection->_ref = this->voice_connection;
this->voice_connection->initialize_js_object();
this->execute_pending_commands = Nan::async_callback([&]{
Nan::HandleScope scope;
this->_execute_callback_commands();
});
this->execute_pending_voice = Nan::async_callback([&]{
Nan::HandleScope scope;
this->_execute_callback_voice();
});
this->execute_callback_disconnect = Nan::async_callback([&](std::string reason){
Nan::HandleScope scope;
this->_execute_callback_disconnect(reason);
});
this->call_connect_result = Nan::async_callback([&](ErrorHandler::error_id error_id) {
Nan::HandleScope scope;
/* lets update the server type */
{
auto js_this = this->handle();
Nan::Set(js_this, Nan::New<v8::String>("server_type").ToLocalChecked(), Nan::New<v8::Number>(this->protocol_handler->server_type));
}
/* lets call the connect callback */
{
v8::Local<v8::Value> argv[1];
argv[0] = Nan::New<v8::Number>(error_id);
if(this->callback_connect)
Nan::Call(*this->callback_connect, 1, argv);
this->callback_connect = nullptr;
}
});
this->call_disconnect_result = Nan::async_callback([&](ErrorHandler::error_id error_id) {
Nan::HandleScope scope;
v8::Local<v8::Value> argv[1];
argv[0] = Nan::New<v8::Number>(error_id);
if(this->callback_disconnect)
Nan::Call(*this->callback_disconnect, 1, argv);
this->callback_disconnect = nullptr;
});
auto js_this = this->handle();
Nan::Set(js_this, Nan::New<v8::String>("_voice_connection").ToLocalChecked(), this->voice_connection->js_handle());
}
void ServerConnection::finalize() {
this->event_loop_exit = true;
this->event_condition.notify_all();
this->event_thread.join();
}
void ServerConnection::event_loop() {
auto eval_timeout = [&]{
auto best = this->next_tick;
if(this->next_resend < best)
best = this->next_resend;
if(this->event_loop_execute_connection_close)
return system_clock::time_point{};
return best;
};
while(!this->event_loop_exit) {
auto timeout = eval_timeout();
{
unique_lock lock(this->event_lock);
this->event_condition.wait_until(lock, timeout, [&]{
if(eval_timeout() != timeout)
return true;
return this->event_loop_exit;
});
if(this->event_loop_exit)
break;
}
if(this->event_loop_execute_connection_close) {
this->close_connection();
this->event_loop_execute_connection_close = false;
}
auto date = chrono::system_clock::now();
if(this->next_tick <= date) {
this->next_tick = date + chrono::milliseconds(500);
this->execute_tick();
}
if(this->next_resend <= date) {
this->next_resend = date + chrono::seconds(5);
if(this->protocol_handler)
this->protocol_handler->execute_resend();
}
}
}
void ServerConnection::schedule_resend(const std::chrono::system_clock::time_point &timeout) {
if(this->next_resend > timeout) {
this->next_resend = timeout;
this->event_condition.notify_one();
}
}
NAN_METHOD(ServerConnection::connect) {
if(!this->protocol_handler) {
Nan::ThrowError("ServerConnection not initialized");
return;
}
if(info.Length() != 1) {
Nan::ThrowError(tr("Invalid argument count"));
return;
}
v8::Local arguments = info[0]->ToObject(Nan::GetCurrentContext()).ToLocalChecked();
if(!arguments->IsObject()) {
Nan::ThrowError(tr("Invalid argument"));
return;
}
auto remote_host = Nan::Get(arguments, Nan::New<v8::String>("remote_host").ToLocalChecked()).ToLocalChecked();
auto remote_port = Nan::Get(arguments, Nan::New<v8::String>("remote_port").ToLocalChecked()).ToLocalChecked();
auto timeout = Nan::Get(arguments, Nan::New<v8::String>("timeout").ToLocalChecked()).ToLocalChecked();
auto callback = Nan::Get(arguments, Nan::New<v8::String>("callback").ToLocalChecked()).ToLocalChecked();
auto identity_key = Nan::Get(arguments, Nan::New<v8::String>("identity_key").ToLocalChecked()).ToLocalChecked();
auto teamspeak = Nan::Get(arguments, Nan::New<v8::String>("teamspeak").ToLocalChecked()).ToLocalChecked();
if(!identity_key->IsString() && !identity_key->IsNullOrUndefined()) {
Nan::ThrowError(tr("Invalid identity"));
return;
}
if(!remote_host->IsString() || !remote_port->IsNumber()) {
Nan::ThrowError(tr("Invalid argument host/port"));
return;
}
if(!callback->IsFunction() ) {
Nan::ThrowError(tr("Invalid callback"));
return;
}
unique_lock _disconnect_lock(this->disconnect_lock, defer_lock);
if(!_disconnect_lock.try_lock_for(chrono::milliseconds(500))) {
Nan::ThrowError(tr("failed to acquire disconnect lock"));
return;
}
this->callback_connect = make_unique<Nan::Callback>(callback.As<v8::Function>());
this->voice_connection->reset();
this->protocol_handler->reset();
if(identity_key->IsString()) {
auto& identity = this->protocol_handler->get_identity_key();
auto key = base64::decode(*Nan::Utf8String(identity_key->ToString(Nan::GetCurrentContext()).ToLocalChecked()));
if(ecc_import((u_char*) key.data(), (unsigned long) key.length(), &identity) != CRYPT_OK) {
Nan::ThrowError(tr("failed to import identity"));
return;
}
} else {
auto& identity = this->protocol_handler->get_identity_key();
prng_state rndState{};
memset(&rndState, 0, sizeof(prng_state));
int err;
if((err = ecc_make_key_ex(&rndState, find_prng("sprng"), &identity, &ltc_ecc_sets[5])) != CRYPT_OK) {
Nan::ThrowError(tr("failed to generate ephemeral identity"));
return;
}
}
sockaddr_storage remote_address{};
/* resolve address */
{
addrinfo hints{}, *result;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
auto _remote_host = Nan::Utf8String(remote_host->ToString(Nan::GetCurrentContext()).ToLocalChecked());
if(getaddrinfo(*_remote_host, nullptr, &hints, &result) != 0 || !result) {
this->call_connect_result(this->errors.register_error(tr("failed to resolve hostname")));
return;
}
memcpy(&remote_address, result->ai_addr, result->ai_addrlen);
freeaddrinfo(result);
}
switch(remote_address.ss_family) {
case AF_INET:
((sockaddr_in*) &remote_address)->sin_port = htons(remote_port->Int32Value(Nan::GetCurrentContext()).FromMaybe(0));
case AF_INET6:
((sockaddr_in6*) &remote_address)->sin6_port = htons(remote_port->Int32Value(Nan::GetCurrentContext()).FromMaybe(0));
default:break;
}
logger::info(category::connection, tr("Connecting to {}."), net::to_string(remote_address));
this->socket = make_shared<UDPSocket>(remote_address);
if(!this->socket->initialize()) {
this->call_connect_result(this->errors.register_error("failed to initialize socket"));
this->socket = nullptr;
return;
}
this->socket->on_data = [&](const pipes::buffer_view& buffer) { this->protocol_handler->progress_packet(buffer); };
if(teamspeak->IsBoolean() && teamspeak->BooleanValue(info.GetIsolate()))
this->protocol_handler->server_type = server_type::TEAMSPEAK;
this->protocol_handler->connect();
}
NAN_METHOD(ServerConnection::_connected) {
return ObjectWrap::Unwrap<ServerConnection>(info.Holder())->connected(info);
}
NAN_METHOD(ServerConnection::_connect) {
return ObjectWrap::Unwrap<ServerConnection>(info.Holder())->connect(info);
}
NAN_METHOD(ServerConnection::connected) {
bool result = this->protocol_handler && this->protocol_handler->connection_state >= connection_state::CONNECTING && this->protocol_handler->connection_state < connection_state::DISCONNECTING;
info.GetReturnValue().Set(result);
}
NAN_METHOD(ServerConnection::_disconnect) {
return ObjectWrap::Unwrap<ServerConnection>(info.Holder())->disconnect(info);
}
NAN_METHOD(ServerConnection::disconnect) {
if(info.Length() != 2) {
Nan::ThrowError("Invalid argument count");
return;
}
if(!info[1]->IsFunction() || !info[0]->IsString()) {
Nan::ThrowError("Invalid argument");
return;
}
this->callback_disconnect = make_unique<Nan::Callback>(info[1].As<v8::Function>());
if(!this->socket) {
this->call_disconnect_result(0); /* this->errors.register_error("not connected") */
return;
}
if(this->protocol_handler) {
this->protocol_handler->disconnect(*Nan::Utf8String(info[0]));
}
}
NAN_METHOD(ServerConnection::_error_message) {
return ObjectWrap::Unwrap<ServerConnection>(info.Holder())->error_message(info);
}
NAN_METHOD(ServerConnection::error_message) {
if(info.Length() != 1 || !info[0]->IsNumber()) {
Nan::ThrowError("Invalid argument");
return;
}
auto error = this->errors.get_message((ErrorHandler::error_id) info[0]->IntegerValue(Nan::GetCurrentContext()).FromMaybe(0));
info.GetReturnValue().Set(Nan::New<v8::String>(error).ToLocalChecked());
}
//send_command(command: string, arguments: any[], switches: string[]);
template <typename T>
std::string _to_string(const T value) {
std::string result(15, '\0');
auto written = std::snprintf(&result[0], result.size(), "%.6f", value);
if(written < 0) {
log_warn(general, "Failed to format float value: {}; {}", value, written);
return "0";
}
result.resize(written);
return result;
}
NAN_METHOD(ServerConnection::_send_command) {
return ObjectWrap::Unwrap<ServerConnection>(info.Holder())->send_command(info);
}
struct TS3VersionSettings {
std::string build;
std::string platform;
std::string sign;
};
NAN_METHOD(ServerConnection::send_command) {
if(!this->protocol_handler) {
Nan::ThrowError("ServerConnection not initialized");
return;
}
if(info.Length() != 3) {
Nan::ThrowError("invalid argument count");
return;
}
if(!info[0]->IsString() || !info[1]->IsArray() || !info[2]->IsArray()) {
Nan::ThrowError("invalid argument type");
return;
}
auto begin = chrono::system_clock::now();
auto command = info[0]->ToString(Nan::GetCurrentContext()).ToLocalChecked();
auto arguments = info[1].As<v8::Array>();
auto switches = info[2].As<v8::Array>();
ts::Command cmd(*Nan::Utf8String(command));
for(size_t index = 0; index < arguments->Length(); index++) {
auto object = arguments->Get((uint32_t) index);
if(!object->IsObject()) {
Nan::ThrowError(Nan::New<v8::String>("invalid parameter (" + to_string(index) + ")").ToLocalChecked());
return;
}
v8::Local<v8::Array> properties = object->ToObject(Nan::GetCurrentContext()).ToLocalChecked()->GetOwnPropertyNames(Nan::GetCurrentContext()).ToLocalChecked();
for(uint32_t i = 0; i < properties->Length(); i++) {
auto key = properties->Get(i)->ToString(Nan::GetCurrentContext()).ToLocalChecked();
auto value = object->ToObject(Nan::GetCurrentContext()).ToLocalChecked()->Get(Nan::GetCurrentContext(), key).ToLocalChecked();
string key_string = *Nan::Utf8String(key);
if(value->IsInt32())
cmd[index][key_string] = value->Int32Value(Nan::GetCurrentContext()).FromMaybe(0);
else if(value->IsNumber() || value->IsNumberObject())
cmd[index][key_string] = _to_string<double>(value->NumberValue(Nan::GetCurrentContext()).FromMaybe(0)); /* requires our own conversation because node overrides stuff to 0,0000*/
else if(value->IsString())
cmd[index][key_string] = *Nan::Utf8String(value->ToString(Nan::GetCurrentContext()).ToLocalChecked());
else if(value->IsBoolean() || value->IsBooleanObject())
cmd[index][key_string] = value->BooleanValue(info.GetIsolate());
else if(value->IsNullOrUndefined())
cmd[index][key_string] = "";
else {
Nan::ThrowError(Nan::New<v8::String>("invalid parameter (" + to_string(index) + ":" + key_string + ")").ToLocalChecked());
return;
}
}
}
for(size_t index = 0; index < switches->Length(); index++) {
auto object = switches->Get((uint32_t) index);
if(!object->IsString()) {
Nan::ThrowError(Nan::New<v8::String>("invalid switch (" + to_string(index) + ")").ToLocalChecked());
return;
}
cmd.enableParm(*Nan::Utf8String(object));
}
if(this->protocol_handler->server_type == server_type::TEAMSPEAK) {
if(cmd.command() == "clientinit") {
/* If we have a return code here some strange stuff happens (Ghost client) */
if(cmd[0].has("return_code"))
cmd["return_code"] = nullptr;
TS3VersionSettings ts_version{};
#ifdef WIN32
/*
ts_version = {
"0.0.1 [Build: 1549713549]",
"Linux",
"7XvKmrk7uid2ixHFeERGqcC8vupeQqDypLtw2lY9slDNPojEv//F47UaDLG+TmVk4r6S0TseIKefzBpiRtLDAQ=="
};
*/
ts_version = {
"3.?.? [Build: 5680278000]",
"Windows",
"DX5NIYLvfJEUjuIbCidnoeozxIDRRkpq3I9vVMBmE9L2qnekOoBzSenkzsg2lC9CMv8K5hkEzhr2TYUYSwUXCg=="
};
#else
/*
ts_version = {
"0.0.1 [Build: 1549713549]",
"Linux",
"7XvKmrk7uid2ixHFeERGqcC8vupeQqDypLtw2lY9slDNPojEv//F47UaDLG+TmVk4r6S0TseIKefzBpiRtLDAQ=="
};
*/
ts_version = {
"3.?.? [Build: 5680278000]",
"Linux",
"Hjd+N58Gv3ENhoKmGYy2bNRBsNNgm5kpiaQWxOj5HN2DXttG6REjymSwJtpJ8muC2gSwRuZi0R+8Laan5ts5CQ=="
};
#endif
if(std::getenv("teaclient_ts3_build") && std::getenv("teaclient_ts3_platform") && std::getenv("teaclient_ts3_sign")) {
ts_version = {
std::getenv("teaclient_ts3_build"),
std::getenv("teaclient_ts3_platform"),
std::getenv("teaclient_ts3_sign")
};
}
cmd["client_version"] = ts_version.build;
cmd["client_platform"] = ts_version.platform;
cmd["client_version_sign"] = ts_version.sign;
cmd[strobf("hwid").string()] = system_uuid(); /* we dont want anybody to patch this out */
}
}
this->protocol_handler->send_command(cmd);
auto end = chrono::system_clock::now();
}
NAN_METHOD(ServerConnection::_send_voice_data) {
return ObjectWrap::Unwrap<ServerConnection>(info.Holder())->send_voice_data(info);
}
NAN_METHOD(ServerConnection::send_voice_data) {
if(!this->protocol_handler) {
Nan::ThrowError("ServerConnection not initialized");
return;
}
if(info.Length() != 3) {
Nan::ThrowError("invalid argument count");
return;
}
if(!info[0]->IsUint8Array() || !info[1]->IsInt32() || !info[2]->IsBoolean()) {
Nan::ThrowError("invalid argument type");
return;
}
auto voice_data = info[0].As<v8::Uint8Array>()->Buffer();
this->send_voice_data(voice_data->GetContents().Data(), voice_data->GetContents().ByteLength(), (uint8_t) info[1]->Int32Value(Nan::GetCurrentContext()).FromMaybe(0), info[2]->BooleanValue(info.GetIsolate()));
}
NAN_METHOD(ServerConnection::_send_voice_data_raw) {
return ObjectWrap::Unwrap<ServerConnection>(info.Holder())->send_voice_data_raw(info);
}
NAN_METHOD(ServerConnection::send_voice_data_raw) {
//send_voice_data_raw(buffer: Float32Array, channels: number, sample_rate: number, header: boolean);
if(info.Length() != 4) {
Nan::ThrowError("invalid argument count");
return;
}
if(!info[0]->IsFloat32Array() || !info[1]->IsInt32() || !info[2]->IsInt32()) {
Nan::ThrowError("invalid argument type");
return;
}
auto channels = info[1]->Int32Value(Nan::GetCurrentContext()).FromMaybe(0);
auto sample_rate = info[2]->Int32Value(Nan::GetCurrentContext()).FromMaybe(0);
auto flag_head = info[2]->BooleanValue(info.GetIsolate());
auto voice_data = info[0].As<v8::Float32Array>()->Buffer();
auto vs = this->voice_connection ? this->voice_connection->voice_sender() : nullptr;
if(vs) vs->send_data(voice_data->GetContents().Data(), voice_data->GetContents().ByteLength() / (4 * channels), sample_rate, channels);
}
#ifdef SHUFFLE_VOICE
static shared_ptr<ts::protocol::ClientPacket> shuffle_cached_packet;
#endif
void ServerConnection::send_voice_data(const void *buffer, size_t buffer_length, uint8_t codec, bool head) {
auto _buffer = pipes::buffer{ts::protocol::ClientPacket::META_SIZE + buffer_length + 3};
auto packet = ts::protocol::ClientPacket::from_buffer(_buffer);
memset(&_buffer[ts::protocol::ClientPacket::META_MAC_SIZE], 0, ts::protocol::ClientPacket::META_HEADER_SIZE); /* reset all header data */
packet->type(ts::protocol::PacketTypeInfo::Voice);
auto data_buffer = packet->data();
le2be16(this->voice_packet_id++, &data_buffer[0]); /* set voice packet id */
data_buffer[2] = (uint8_t) codec; /* set voice codec */
if(buffer_length > 0 && buffer)
memcpy(&data_buffer[3], buffer, buffer_length);
if(head) /* head packet */
packet->enable_flag(ts::protocol::PacketFlag::Compressed);
packet->enable_flag(ts::protocol::PacketFlag::Unencrypted);
#ifdef FUZZ_VOICE
if((rand() % 10) < 2) {
log_info(category::connection, tr("Dropping voice packet"));
} else {
this->protocol_handler->send_packet(packet);
}
#elif defined(SHUFFLE_VOICE)
if(shuffle_cached_packet) {
this->protocol_handler->send_packet(packet);
this->protocol_handler->send_packet(std::exchange(shuffle_cached_packet, nullptr));
} else {
shuffle_cached_packet = packet;
}
#else
this->protocol_handler->send_packet(std::shared_ptr<ts::protocol::ClientPacket>(packet.release()));
#endif
}
void ServerConnection::close_connection() {
lock_guard lock(this->disconnect_lock);
if(this->socket && this_thread::get_id() == this->socket->io_thread().get_id()) {
logger::debug(category::connection, tr("close_connection() called in IO thread. Closing connection within event loop!"));
if(!this->event_loop_execute_connection_close) {
this->event_loop_execute_connection_close = true;
this->event_condition.notify_one();
}
return;
}
this->event_loop_execute_connection_close = false;
if(this->socket)
this->socket->finalize();
if(this->protocol_handler)
this->protocol_handler->do_close_connection();
this->socket = nullptr;
this->call_disconnect_result.call(0, true);
}
void ServerConnection::execute_tick() {
if(this->protocol_handler)
this->protocol_handler->execute_tick();
}
void ServerConnection::_execute_callback_commands() {
unique_ptr<ts::Command> next_command;
v8::Local<v8::Function> callback;
while(true) {
{
lock_guard lock(this->pending_commands_lock);
if(this->pending_commands.empty())
return;
next_command = move(this->pending_commands.front());
this->pending_commands.pop_front();
}
if(!next_command)
continue;
if(callback.IsEmpty()) {
callback = Nan::Get(this->handle(), Nan::New<v8::String>("callback_command").ToLocalChecked()).ToLocalChecked().As<v8::Function>();
if(callback.IsEmpty()) {
logger::warn(category::connection, tr("Missing command callback! Dropping commands."));
lock_guard lock(this->pending_commands_lock);
this->pending_commands.clear();
return;
}
}
v8::Local<v8::Value> arguments[3];
arguments[0] = Nan::New<v8::String>(next_command->command()).ToLocalChecked();
auto parameters = Nan::New<v8::Array>((int) next_command->bulkCount());
for(size_t index = 0; index < next_command->bulkCount(); index++) {
auto object = Nan::New<v8::Object>();
auto& bulk = next_command->operator[](index);
for(const auto& key : bulk.keys())
Nan::Set(object, Nan::New<v8::String>(key).ToLocalChecked(), Nan::New<v8::String>(bulk[key].string()).ToLocalChecked());
parameters->Set((uint32_t) index, object);
}
arguments[1] = parameters;
auto switched = Nan::New<v8::Array>((int) next_command->parms().size());
for(size_t index = 0; index < next_command->parms().size(); index++) {
auto& key = next_command->parms()[index];
parameters->Set((uint32_t) index, Nan::New<v8::String>(key).ToLocalChecked());
}
arguments[2] = switched;
callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 3, arguments);
}
}
void ServerConnection::_execute_callback_voice() {
unique_ptr<VoicePacket> next_packet;
v8::Local<v8::Function> callback;
while(true) {
{
lock_guard lock(this->pending_voice_lock);
if(this->pending_voice.empty())
return;
next_packet = move(this->pending_voice.front());
this->pending_voice.pop_front();
}
if(!next_packet)
continue;
if(callback.IsEmpty()) {
auto _callback = Nan::Get(this->handle(), Nan::New<v8::String>("callback_voice_data").ToLocalChecked()).ToLocalChecked();
if(_callback.IsEmpty() || _callback->IsUndefined()) {
logger::warn(category::audio, tr("Missing voice callback! Dropping packets!"));
lock_guard lock(this->pending_voice_lock);
this->pending_voice.clear();
return;
}
callback = _callback.As<v8::Function>();
}
v8::Local<v8::Value> arguments[5];
v8::Local<v8::ArrayBuffer> buffer = v8::ArrayBuffer::New(
Nan::GetCurrentContext()->GetIsolate(),
next_packet->voice_data.length()
);
memcpy(buffer->GetContents().Data(), next_packet->voice_data.data_ptr(), next_packet->voice_data.length());
arguments[0] = v8::Uint8Array::New(buffer, 0, buffer->ByteLength());
arguments[1] = Nan::New<v8::Integer>(next_packet->client_id);
arguments[2] = Nan::New<v8::Integer>(next_packet->codec_id);
arguments[3] = Nan::New<v8::Boolean>(next_packet->flag_head);
arguments[4] = Nan::New<v8::Integer>(next_packet->packet_id);
callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 5, arguments);
}
}
void ServerConnection::_execute_callback_disconnect(const std::string &reason) {
auto callback = Nan::Get(this->handle(), Nan::New<v8::String>("callback_disconnect").ToLocalChecked()).ToLocalChecked().As<v8::Function>();
if(callback.IsEmpty()) {
cout << "Missing disconnect callback!" << endl;
return;
}
v8::Local<v8::Value> arguments[1];
arguments[0] = Nan::New<v8::String>(reason).ToLocalChecked();
callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, arguments);
}
NAN_METHOD(ServerConnection::_current_ping) {
auto connection = ObjectWrap::Unwrap<ServerConnection>(info.Holder());
auto& phandler = connection->protocol_handler;
if(phandler)
info.GetReturnValue().Set((uint32_t) chrono::floor<microseconds>(phandler->current_ping()).count());
else
info.GetReturnValue().Set(-1);
}
@@ -1,131 +0,0 @@
#pragma once
#include <array>
#include <string>
#include <thread>
#include <nan.h>
#include <NanEventCallback.h>
#include <condition_variable>
#include <pipes/buffer.h>
namespace ts {
class Command;
}
namespace tc {
namespace connection {
namespace server_type {
enum value : uint8_t {
UNKNOWN,
TEASPEAK,
TEAMSPEAK
};
}
class UDPSocket;
class ProtocolHandler;
class VoiceConnection;
class ErrorHandler {
public:
typedef int16_t error_id;
static constexpr error_id error_success = 0;
static constexpr error_id error_varianz = 5;
std::array<std::string, error_varianz> error_messages;
error_id error_index = 0;
std::string get_message(error_id);
error_id register_error(const std::string& /* message */);
};
class ServerConnection : public Nan::ObjectWrap {
friend class ProtocolHandler;
public:
static NAN_MODULE_INIT(Init);
static NAN_METHOD(new_instance);
static inline Nan::Persistent<v8::Function> & constructor() {
static Nan::Persistent<v8::Function> my_constructor;
return my_constructor;
}
ServerConnection();
~ServerConnection() override;
NAN_METHOD(connect);
NAN_METHOD(connected);
NAN_METHOD(disconnect);
NAN_METHOD(error_message);
NAN_METHOD(send_command);
NAN_METHOD(send_voice_data);
void send_voice_data(const void* /* buffer */, size_t /* buffer length */, uint8_t /* codec */, bool /* head */);
NAN_METHOD(send_voice_data_raw);
void initialize();
void finalize();
void close_connection(); /* directly closes connection without notify etc */
std::shared_ptr<UDPSocket> get_socket() { return this->socket; }
private:
struct VoicePacket {
pipes::buffer voice_data;
uint16_t client_id;
uint16_t packet_id;
uint8_t codec_id;
bool flag_head;
};
static NAN_METHOD(_connect);
static NAN_METHOD(_connected);
static NAN_METHOD(_disconnect);
static NAN_METHOD(_send_command);
static NAN_METHOD(_send_voice_data);
static NAN_METHOD(_send_voice_data_raw);
static NAN_METHOD(_error_message);
static NAN_METHOD(_current_ping);
std::unique_ptr<Nan::Callback> callback_connect;
std::unique_ptr<Nan::Callback> callback_disconnect;
Nan::callback_t<ErrorHandler::error_id> call_connect_result;
Nan::callback_t<ErrorHandler::error_id> call_disconnect_result;
Nan::callback_t<> execute_pending_commands;
Nan::callback_t<> execute_pending_voice;
Nan::callback_t<std::string> execute_callback_disconnect;
ErrorHandler errors;
std::shared_ptr<UDPSocket> socket;
std::unique_ptr<ProtocolHandler> protocol_handler;
std::recursive_timed_mutex disconnect_lock;
std::thread event_thread;
std::mutex event_lock;
std::condition_variable event_condition;
bool event_loop_exit = false; /* set to true if we want to exit */
void event_loop();
bool event_loop_execute_connection_close = false;
std::chrono::system_clock::time_point next_tick;
std::chrono::system_clock::time_point next_resend;
void execute_tick();
void schedule_resend(const std::chrono::system_clock::time_point& /* timestamp */);
std::mutex pending_commands_lock;
std::deque<std::unique_ptr<ts::Command>> pending_commands;
void _execute_callback_commands();
std::mutex pending_voice_lock;
std::deque<std::unique_ptr<VoicePacket>> pending_voice;
void _execute_callback_voice();
uint16_t voice_packet_id = 0;
void _execute_callback_disconnect(const std::string&);
std::shared_ptr<VoiceConnection> voice_connection;
};
}
}
@@ -1,180 +0,0 @@
#include "Socket.h"
#include "../logger.h"
#include <thread>
#include <cstring>
#include <string>
#include <iostream>
#ifdef WIN32
#include <WinSock2.h>
typedef int socklen_t;
#else
#include <unistd.h>
#include <netinet/ip.h>
#endif
using namespace std;
using namespace tc::connection;
UDPSocket::UDPSocket(const sockaddr_storage &address) {
memcpy(&this->_remote_address, &address, sizeof(sockaddr_storage));
}
UDPSocket::~UDPSocket() {
this->finalize();
}
bool UDPSocket::initialize() {
if(this->file_descriptor > 0)
return false;
this->file_descriptor = socket(this->_remote_address.ss_family, SOCK_DGRAM, 0);
if(this->file_descriptor < 2) {
this->file_descriptor = 0;
return false;
}
/*
* TODO: Make configurable
*/
//uint8_t value = IPTOS_DSCP_EF;
//if(setsockopt(this->file_descriptor, IPPROTO_IP, IP_TOS, &value, sizeof(value)) < 0)
// log_warn(category::connection, "Failed to set TOS high priority on socket");
this->io_base = event_base_new();
if(!this->io_base) { /* may too many file descriptors already open */
this->finalize();
return false;
}
this->event_read = event_new(this->io_base, this->file_descriptor, EV_READ | EV_PERSIST, &UDPSocket::_callback_read, this);
this->event_write = event_new(this->io_base, this->file_descriptor, EV_WRITE, &UDPSocket::_callback_write, this);
event_add(this->event_read, nullptr);
this->_io_thread = thread(&UDPSocket::_io_execute, this);
#ifdef WIN32
//TODO set thread name
#else
auto handle = this->_io_thread.native_handle();
pthread_setname_np(handle, "UDPSocket loop");
#endif
return true;
}
void UDPSocket::finalize() {
if(this->file_descriptor == 0)
return;
unique_lock lock(this->io_lock);
auto event_read = this->event_read, event_write = this->event_write;
auto io_base = this->io_base;
this->io_base = nullptr;
this->event_read = nullptr;
this->event_write = nullptr;
lock.unlock();
assert(this_thread::get_id() != this->_io_thread.get_id());
if(event_read)
event_del_block(event_read);
if(event_write)
event_del_block(event_write);
if(event_write)
event_del(event_write);
if(event_read)
event_del(event_read);
if(io_base) {
timeval seconds{1, 0};
event_base_loopexit(io_base, &seconds);
event_base_loopexit(io_base, nullptr);
}
if(this->_io_thread.joinable())
this->_io_thread.join();
if(io_base)
event_base_free(io_base);
#ifdef WIN32
if(::closesocket(this->file_descriptor) != 0) {
#else
if(::close(this->file_descriptor) != 0) {
#endif
if(errno != EBADF)
logger::warn(category::socket, tr("Failed to close file descriptor ({}/{})"), to_string(errno), strerror(errno));
}
this->file_descriptor = 0;
}
void UDPSocket::_callback_write(evutil_socket_t fd, short, void *_ptr_socket) {
((UDPSocket*) _ptr_socket)->callback_write(fd);
}
void UDPSocket::_callback_read(evutil_socket_t fd, short, void *_ptr_socket) {
((UDPSocket*) _ptr_socket)->callback_read(fd);
}
void UDPSocket::_io_execute(void *_ptr_socket) {
((UDPSocket*) _ptr_socket)->io_execute();
}
void UDPSocket::io_execute() {
while(this->io_base) {
event_base_loop(this->io_base, 0);
}
}
void UDPSocket::callback_read(evutil_socket_t fd) {
sockaddr source_address{};
socklen_t source_address_length = sizeof(sockaddr);
ssize_t buffer_length = 1600; /* IPv6 MTU is ~1.5k */
char buffer[1600];
buffer_length = recvfrom(fd, (char*) buffer, buffer_length, 0, &source_address, &source_address_length);
if(buffer_length <= 0) {
if(errno == EAGAIN)
return;
logger::warn(category::socket, tr("Failed to receive data: {}/{}"), errno, strerror(errno));
return; /* this should never happen! */
}
if(this->on_data)
this->on_data(pipes::buffer_view{buffer, (size_t) buffer_length});
}
void UDPSocket::callback_write(evutil_socket_t fd) {
unique_lock lock(this->io_lock);
if(this->write_queue.empty())
return;
auto buffer = this->write_queue.front();
this->write_queue.pop_front();
lock.unlock();
auto written = sendto(fd, buffer.data_ptr<char>(), buffer.length(), 0, (sockaddr*) &this->_remote_address, sizeof(this->_remote_address));
if(written != buffer.length()) {
if(errno == EAGAIN) {
lock.lock();
this->write_queue.push_front(buffer);
if(this->event_write)
event_add(this->event_write, nullptr);
return;
}
return; /* this should never happen! */
}
lock.lock();
if(!this->write_queue.empty() && this->event_write)
event_add(this->event_write, nullptr);
}
void UDPSocket::send_message(const pipes::buffer_view &buffer) {
auto buf = buffer.own_buffer();
unique_lock lock(this->io_lock);
this->write_queue.push_back(buf);
if(this->event_write)
event_add(this->event_write, nullptr);
}
@@ -1,58 +0,0 @@
#pragma once
#include <thread>
#include <event.h>
#include <memory>
#include <deque>
#include <mutex>
#include <pipes/buffer.h>
#include <functional>
#ifndef WIN32
#include <netinet/in.h>
#else
#include <WinSock2.h>
#endif
namespace tc {
namespace connection {
class UDPSocket {
public:
UDPSocket(const sockaddr_storage& /* target */);
~UDPSocket();
const sockaddr_storage& remote_address() { return this->_remote_address; }
bool initialize();
void finalize();
void send_message(const pipes::buffer_view& /* message */);
std::function<void(const pipes::buffer_view& /* message */)> on_data;
const std::thread& io_thread() { return this->_io_thread; }
private:
static void _io_execute(void *_ptr_socket);
static void _callback_read(evutil_socket_t, short, void*);
static void _callback_write(evutil_socket_t, short, void*);
void io_execute();
void callback_read(evutil_socket_t);
void callback_write(evutil_socket_t);
sockaddr_storage _remote_address;
int file_descriptor = 0;
std::recursive_mutex io_lock;
std::thread _io_thread;
event_base* io_base = nullptr;
event* event_read = nullptr;
event* event_write = nullptr;
std::deque<pipes::buffer> write_queue;
};
}
}
@@ -1,27 +0,0 @@
//
// Created by wolverindev on 19.06.19.
//
#include "AudioEventLoop.h"
using namespace tc;
event::EventExecutor* audio::decode_event_loop = nullptr;
event::EventExecutor* audio::encode_event_loop = nullptr;
void audio::init_event_loops() {
audio::shutdown_event_loops(); /* just to ensure */
audio::decode_event_loop = new event::EventExecutor("a en/decode ");
audio::encode_event_loop = audio::decode_event_loop;
audio::decode_event_loop->initialize(2);
}
void audio::shutdown_event_loops() {
if(audio::decode_event_loop) {
delete audio::decode_event_loop;
audio::decode_event_loop = nullptr;
}
audio::encode_event_loop = nullptr;
}
@@ -1,13 +0,0 @@
#pragma once
#include "../../EventLoop.h"
namespace tc {
namespace audio {
extern event::EventExecutor* encode_event_loop;
extern event::EventExecutor* decode_event_loop;
extern void init_event_loops();
extern void shutdown_event_loops();
}
}
@@ -1,224 +0,0 @@
#include "AudioSender.h"
#include "VoiceConnection.h"
#include "../ServerConnection.h"
#include "../../logger.h"
#include "AudioEventLoop.h"
#include "../../audio/AudioMerger.h"
using namespace std;
using namespace tc;
using namespace tc::audio;
using namespace tc::audio::codec;
using namespace tc::connection;
VoiceSender::VoiceSender(tc::connection::VoiceConnection *handle) : handle(handle) {}
VoiceSender::~VoiceSender() {
audio::encode_event_loop->cancel(static_pointer_cast<event::EventEntry>(this->_ref.lock()));
this->clear_buffer(); /* buffer might be accessed within encode_raw_frame, but this could not be trigered while this will be deallocated! */
}
bool VoiceSender::initialize_codec(std::string& error, connection::codec::value codec, size_t channels, size_t rate, bool reset_encoder) {
auto& data = this->codec[codec];
bool new_allocated = !data;
if(new_allocated) {
data = make_unique<AudioCodec>();
data->packet_counter = 0;
data->last_packet = chrono::system_clock::now();
}
auto info = codec::get_info(codec);
if(!info || !info->supported) {
if(new_allocated)
log_error(category::voice_connection, tr("Tried to send voice packet but we dont support the current codec ({})"), codec);
return false;
}
if(!data->converter) {
data->converter = info->new_converter(error);
if(!data->converter)
return false;
} else if(reset_encoder) {
data->converter->reset_encoder();
}
if(!data->resampler || data->resampler->input_rate() != rate)
data->resampler = make_shared<AudioResampler>(rate, data->converter->sample_rate(), data->converter->channels());
if(!data->resampler->valid()) {
error = "resampler is invalid";
return false;
}
return true;
}
void VoiceSender::set_voice_send_enabled(bool flag) {
this->voice_send_enabled = flag;
}
void VoiceSender::send_data(const void *data, size_t samples, size_t rate, size_t channels) {
unique_lock lock(this->_execute_lock);
if(!this->handle) {
log_warn(category::voice_connection, tr("Dropping raw audio frame because of an invalid handle."));
return;
}
lock.unlock();
if(!this->voice_send_enabled) {
log_warn(category::voice_connection, tr("Dropping raw audio frame because voice sending has been disabled!"));
return;
}
auto frame = make_unique<AudioFrame>();
frame->sample_rate = rate;
frame->channels = channels;
frame->buffer = pipes::buffer{(void*) data, samples * channels * 4};
frame->timestamp = chrono::system_clock::now();
{
lock_guard buffer_lock(this->raw_audio_buffer_lock);
this->raw_audio_buffers.push_back(move(frame));
}
audio::encode_event_loop->schedule(static_pointer_cast<event::EventEntry>(this->_ref.lock()));
}
void VoiceSender::send_stop() {
unique_lock lock(this->_execute_lock);
if(!this->handle) {
log_warn(category::voice_connection, tr("Dropping audio end frame because of an invalid handle."));
return;
}
lock.unlock();
auto frame = make_unique<AudioFrame>();
frame->sample_rate = 0;
frame->channels = 0;
frame->buffer = pipes::buffer{nullptr, 0};
frame->timestamp = chrono::system_clock::now();
{
lock_guard buffer_lock(this->raw_audio_buffer_lock);
this->raw_audio_buffers.push_back(move(frame));
}
audio::encode_event_loop->schedule(static_pointer_cast<event::EventEntry>(this->_ref.lock()));
}
void VoiceSender::finalize() {
lock_guard lock(this->_execute_lock);
this->handle = nullptr;
}
void VoiceSender::event_execute(const std::chrono::system_clock::time_point &point) {
static auto max_time = chrono::milliseconds(10);
bool reschedule = false;
auto now = chrono::system_clock::now();
while(true) {
unique_lock buffer_lock(this->raw_audio_buffer_lock);
if(this->raw_audio_buffers.empty())
break;
if(chrono::system_clock::now() - now > max_time) {
reschedule = true;
break;
}
auto entry = move(this->raw_audio_buffers.front());
this->raw_audio_buffers.pop_front();
buffer_lock.unlock();
//TODO: Drop too old buffers!
this->encode_raw_frame(entry);
}
if(reschedule) {
log_warn(category::voice_connection, tr("Audio data decode will take longer than {} us. Enqueueing for later"), chrono::duration_cast<chrono::microseconds>(max_time).count());
audio::decode_event_loop->schedule(static_pointer_cast<event::EventEntry>(this->_ref.lock()));
}
}
void VoiceSender::encode_raw_frame(const std::unique_ptr<AudioFrame> &frame) {
auto codec = this->_current_codec;
auto& codec_data = this->codec[codec];
bool flag_head = true, flag_reset = true;
if(codec_data) {
if(codec_data->last_packet + chrono::seconds(1) < frame->timestamp)
codec_data->packet_counter = 0;
flag_head = codec_data->packet_counter < 5;
flag_reset = codec_data->packet_counter == 0;
codec_data->packet_counter++;
codec_data->last_packet = frame->timestamp;
}
if(frame->channels == 0 || frame->sample_rate == 0 || frame->buffer.empty()) {
lock_guard lock(this->_execute_lock);
if(!this->handle) {
log_warn(category::voice_connection, tr("Dropping audio end because of an invalid handle."));
return;
}
if(codec_data)
codec_data->packet_counter = 0;
auto server = this->handle->handle();
server->send_voice_data(this->_buffer, 0, codec, flag_head);
return;
}
string error;
if(flag_reset) {
log_trace(category::voice_connection, tr("Resetting encoder for voice sender"));
}
if(!this->initialize_codec(error, codec, frame->channels, frame->sample_rate, flag_reset)) {
log_error(category::voice_connection, tr("Failed to initialize codec: {}"), error);
return;
}
auto merged_channel_byte_size = codec_data->converter->channels() * (frame->buffer.length() / frame->channels);
auto estimated_resampled_byte_size = codec_data->resampler->estimated_output_size(merged_channel_byte_size);
this->ensure_buffer(max(estimated_resampled_byte_size, merged_channel_byte_size));
auto codec_channels = codec_data->converter->channels();
if(!audio::merge::merge_channels_interleaved(this->_buffer, codec_channels, frame->buffer.data_ptr(), frame->channels, frame->buffer.length() / frame->channels / 4)) {
log_warn(category::voice_connection, tr("Failed to merge channels to output stream channel count! Dropping local voice packet"));
return;
}
auto resampled_samples = codec_data->resampler->process(this->_buffer, this->_buffer, merged_channel_byte_size / codec_channels / 4);
if(resampled_samples <= 0) {
log_error(category::voice_connection, tr("Resampler returned {}"), resampled_samples);
return;
}
if(resampled_samples * codec_channels * 4 != codec_data->converter->bytes_per_frame()) {
log_error(category::voice_connection,
tr("Could not encode audio frame. Frame length is not equal to code frame length! Codec: {}, Packet: {}"),
codec_data->converter->bytes_per_frame(), resampled_samples * codec_channels * 4
);
return;
}
char _packet_buffer[512];
auto encoded_bytes = codec_data->converter->encode(error, this->_buffer, _packet_buffer, 512);
if(encoded_bytes <= 0) {
log_error(category::voice_connection, tr("Failed to encode voice: {}"), error);
return;
}
{
lock_guard lock(this->_execute_lock);
if(!this->handle) {
log_warn(category::voice_connection, tr("Dropping audio frame because of an invalid handle."));
return;
}
auto server = this->handle->handle();
server->send_voice_data(_packet_buffer, encoded_bytes, codec, flag_head);
}
}
@@ -1,90 +0,0 @@
#pragma once
#include <mutex>
#include <memory>
#include "VoiceClient.h"
namespace tc {
namespace audio {
namespace codec {
class Converter;
}
class AudioResampler;
class AudioOutputSource;
}
namespace connection {
class VoiceConnection;
class VoiceSender : private event::EventEntry {
template<typename _Tp, typename _Up>
friend inline std::shared_ptr<_Tp> std::static_pointer_cast(const std::shared_ptr<_Up>& __r) noexcept;
friend class VoiceConnection;
public:
VoiceSender(VoiceConnection*);
virtual ~VoiceSender();
codec::value get_codec() { return this->_current_codec; }
void set_codec(codec::value value) { this->_current_codec = value; }
void finalize();
void send_data(const void* /* buffer */, size_t /* samples */, size_t /* sample rate */, size_t /* channels */);
void send_stop();
void set_voice_send_enabled(bool /* flag */);
private:
std::weak_ptr<VoiceSender> _ref;
VoiceConnection* handle;
struct AudioCodec {
size_t packet_counter = 0;
std::chrono::system_clock::time_point last_packet;
std::shared_ptr<audio::codec::Converter> converter;
std::shared_ptr<audio::AudioResampler> resampler;
};
std::array<std::unique_ptr<AudioCodec>, codec::MAX + 1> codec{nullptr};
bool initialize_codec(std::string&, codec::value /* codec */, size_t /* channels */, size_t /* source sample rate */, bool /* reset decoder */);
codec::value _current_codec = codec::OPUS_VOICE;
std::mutex _execute_lock;
void* _buffer = nullptr;
size_t _buffer_size = 0;
void clear_buffer() {
if(this->_buffer)
::free(this->_buffer);
this->_buffer = nullptr;
this->_buffer_size = 0;
}
void ensure_buffer(size_t length) {
if(!this->_buffer || this->_buffer_size < length) {
if(this->_buffer)
::free(this->_buffer);
this->_buffer = malloc(length);
this->_buffer_size = length;
}
}
struct AudioFrame {
pipes::buffer buffer;
size_t sample_rate;
size_t channels;
std::chrono::system_clock::time_point timestamp;
};
std::mutex raw_audio_buffer_lock;
std::deque<std::unique_ptr<AudioFrame>> raw_audio_buffers;
bool voice_send_enabled = false;
void encode_raw_frame(const std::unique_ptr<AudioFrame>&);
void event_execute(const std::chrono::system_clock::time_point &point) override;
};
}
}
@@ -1,569 +0,0 @@
#include "VoiceClient.h"
#include "../../audio/AudioOutput.h"
#include "../../audio/codec/Converter.h"
#include "../../audio/codec/OpusConverter.h"
#include "../../audio/AudioMerger.h"
#include "../../audio/js/AudioOutputStream.h"
#include "AudioEventLoop.h"
#include "../../logger.h"
using namespace std;
using namespace tc;
using namespace tc::audio::codec;
using namespace tc::connection;
extern tc::audio::AudioOutput* global_audio_output;
#define DEBUG_PREMATURE_PACKETS
#ifdef WIN32
#define _field_(name, value) value
#else
#define _field_(name, value) .name = value
#endif
const codec::condec_info codec::info[6] = {
{
_field_(supported, false),
_field_(name, "speex_narrowband"),
_field_(new_converter, nullptr)
},
{
_field_(supported, false),
_field_(name, "speex_wideband"),
_field_(new_converter, nullptr)
},
{
_field_(supported, false),
_field_(name, "speex_ultra_wideband"),
_field_(new_converter, nullptr)
},
{
_field_(supported, false),
_field_(name, "celt_mono"),
_field_(new_converter, nullptr)
},
{
_field_(supported, true),
_field_(name, "opus_voice"),
_field_(new_converter, [](string& error) -> shared_ptr<Converter> {
auto result = make_shared<OpusConverter>(1, 48000, 960);
if(!result->initialize(error, OPUS_APPLICATION_VOIP))
return nullptr;
return dynamic_pointer_cast<Converter>(result);
})
},
{
_field_(supported, true),
_field_(name, "opus_music"),
_field_(new_converter, [](string& error) -> shared_ptr<Converter> {
auto result = make_shared<OpusConverter>(2, 48000, 960);
if(!result->initialize(error, OPUS_APPLICATION_AUDIO))
return nullptr;
return dynamic_pointer_cast<Converter>(result);
})
}
};
void VoiceClientWrap::do_wrap(const v8::Local<v8::Object> &object) {
this->Wrap(object);
auto handle = this->_handle.lock();
if(!handle) {
Nan::ThrowError("weak handle");
return;
}
Nan::Set(object, Nan::New<v8::String>("client_id").ToLocalChecked(), Nan::New<v8::Number>(handle->client_id()));
handle->on_state_changed = [&]{ this->call_state_changed(); };
this->call_state_changed = Nan::async_callback([&]{
Nan::HandleScope scope;
this->_call_state_changed();
});
}
void VoiceClientWrap::_call_state_changed() {
auto handle = this->_handle.lock();
if(!handle) {
log_warn(category::voice_connection, tr("State changed on invalid handle!"));
return;
}
auto state = handle->state();
auto call_playback_callback = state == VoiceClient::state::playing && !this->_currently_playing;
auto call_stopped_callback = state == VoiceClient::state::stopped && this->_currently_playing;
if(state == VoiceClient::state::stopped)
this->_currently_playing = false;
if(state == VoiceClient::state::playing)
this->_currently_playing = true;
if(call_playback_callback) {
auto callback = Nan::Get(this->handle(), Nan::New<v8::String>("callback_playback").ToLocalChecked()).ToLocalChecked();
if(callback->IsFunction())
callback.As<v8::Function>()->Call(Nan::GetCurrentContext(), Nan::Undefined(), 0, nullptr);
}
if(call_stopped_callback) {
auto callback = Nan::Get(this->handle(), Nan::New<v8::String>("callback_stopped").ToLocalChecked()).ToLocalChecked();
if(callback->IsFunction())
callback.As<v8::Function>()->Call(Nan::GetCurrentContext(), Nan::Undefined(), 0, nullptr);
}
auto callback = Nan::Get(this->handle(), Nan::New<v8::String>("callback_state_changed").ToLocalChecked()).ToLocalChecked();
if(callback->IsFunction()) {
v8::Local<v8::Value> argv[1] = {
Nan::New<v8::Number>(state)
};
callback.As<v8::Function>()->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, argv);
}
}
NAN_MODULE_INIT(VoiceClientWrap::Init) {
auto klass = Nan::New<v8::FunctionTemplate>(VoiceClientWrap::NewInstance);
klass->SetClassName(Nan::New("VoiceConnection").ToLocalChecked());
klass->InstanceTemplate()->SetInternalFieldCount(1);
Nan::SetPrototypeMethod(klass, "get_state", VoiceClientWrap::_get_state);
Nan::SetPrototypeMethod(klass, "get_volume", VoiceClientWrap::_get_volume);
Nan::SetPrototypeMethod(klass, "set_volume", VoiceClientWrap::_set_volume);
Nan::SetPrototypeMethod(klass, "abort_replay", VoiceClientWrap::_abort_replay);
Nan::SetPrototypeMethod(klass, "get_stream", VoiceClientWrap::_get_stream);
constructor().Reset(Nan::GetFunction(klass).ToLocalChecked());
}
NAN_METHOD(VoiceClientWrap::NewInstance) {
if(!info.IsConstructCall())
Nan::ThrowError("invalid invoke!");
}
NAN_METHOD(VoiceClientWrap::_get_volume) {
auto client = ObjectWrap::Unwrap<VoiceClientWrap>(info.Holder());
auto handle = client->_handle.lock();
if(!handle) {
Nan::ThrowError("weak handle");
return;
}
info.GetReturnValue().Set(handle->get_volume());
}
NAN_METHOD(VoiceClientWrap::_set_volume) {
auto client = ObjectWrap::Unwrap<VoiceClientWrap>(info.Holder());
auto handle = client->_handle.lock();
if(!handle) {
Nan::ThrowError("weak handle");
return;
}
if(info.Length() != 1 || !info[0]->IsNumber()) {
Nan::ThrowError("Invalid arguments");
return;
}
handle->set_volume(info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0));
}
NAN_METHOD(VoiceClientWrap::_abort_replay) {
auto client = ObjectWrap::Unwrap<VoiceClientWrap>(info.Holder());
auto handle = client->_handle.lock();
if(!handle) {
Nan::ThrowError("weak handle");
return;
}
handle->cancel_replay();
}
NAN_METHOD(VoiceClientWrap::_get_state) {
auto client = ObjectWrap::Unwrap<VoiceClientWrap>(info.Holder());
auto handle = client->_handle.lock();
if(!handle) {
Nan::ThrowError("weak handle");
return;
}
info.GetReturnValue().Set(handle->state());
}
NAN_METHOD(VoiceClientWrap::_get_stream) {
auto client = ObjectWrap::Unwrap<VoiceClientWrap>(info.Holder());
auto handle = client->_handle.lock();
if(!handle) {
Nan::ThrowError("weak handle");
return;
}
auto wrapper = new audio::AudioOutputStreamWrapper(handle->output_stream(), false);
auto object = Nan::NewInstance(Nan::New(audio::AudioOutputStreamWrapper::constructor()), 0, nullptr).ToLocalChecked();
wrapper->do_wrap(object);
info.GetReturnValue().Set(object);
}
VoiceClientWrap::VoiceClientWrap(const std::shared_ptr<VoiceClient>& client) : _handle(client) { }
VoiceClientWrap::~VoiceClientWrap() {}
VoiceClient::VoiceClient(const std::shared_ptr<VoiceConnection>&, uint16_t client_id) : _client_id(client_id) {
this->output_source = global_audio_output->create_source();
this->output_source->overflow_strategy = audio::overflow_strategy::ignore;
this->output_source->max_latency = (size_t) ceil(this->output_source->sample_rate * 1);
this->output_source->min_buffer = (size_t) ceil(this->output_source->sample_rate * 0.04);
this->output_source->on_underflow = [&]{
if(this->_state == state::stopping)
this->set_state(state::stopped);
else if(this->_state != state::stopped) {
if(this->_last_received_packet + chrono::seconds(1) < chrono::system_clock::now()) {
this->set_state(state::stopped);
log_warn(category::audio, tr("Client {} has a audio buffer underflow and not received any data for one second. Stopping replay."), this->_client_id);
} else {
if(this->_state != state::buffering) {
log_warn(category::audio, tr("Client {} has a audio buffer underflow. Buffer again and try to replay prematured packets."), this->_client_id);
this->set_state(state::buffering);
}
play_premature_packets = true; /* try to replay any premature packets because we assume that the other packets got lost */
audio::decode_event_loop->schedule(static_pointer_cast<event::EventEntry>(this->ref()));
}
}
return false;
};
this->output_source->on_overflow = [&](size_t count){
log_warn(category::audio, tr("Client {} has a audio buffer overflow of {}."), this->_client_id, count);
};
}
VoiceClient::~VoiceClient() {
if(v8::Isolate::GetCurrent())
this->finalize_js_object();
else {
assert(this->_js_handle.IsEmpty());
}
this->output_source->on_underflow = nullptr; /* to ensure */
global_audio_output->delete_source(this->output_source);
}
void VoiceClient::initialize_js_object() {
if(!this->_js_handle.IsEmpty())
return;
auto object_wrap = new VoiceClientWrap(this->ref());
auto object = Nan::NewInstance(Nan::New(VoiceClientWrap::constructor()), 0, nullptr).ToLocalChecked();
Nan::TryCatch tc;
object_wrap->do_wrap(object);
if(tc.HasCaught()) {
tc.ReThrow();
return;
}
this->_js_handle.Reset(Nan::GetCurrentContext()->GetIsolate(), object);
}
void VoiceClient::finalize_js_object() {
this->_js_handle.Reset();
}
#define target_buffer_length 16384
void VoiceClient::process_packet(uint16_t packet_id, const pipes::buffer_view& buffer, codec::value codec, bool head) {
if(this->_volume == 0)
return;
if(codec < 0 || codec > this->codec.size()) {
log_warn(category::voice_connection, tr("Received voice packet from client {} with unknown codec ({})"), this->_client_id, codec);
return;
}
auto encoded_buffer = make_unique<EncodedBuffer>();
encoded_buffer->packet_id = packet_id;
encoded_buffer->codec = codec;
encoded_buffer->receive_timestamp = chrono::system_clock::now();
encoded_buffer->buffer = buffer.own_buffer();
encoded_buffer->head = head;
this->_last_received_packet = encoded_buffer->receive_timestamp;
{
lock_guard lock(this->audio_decode_queue_lock);
this->audio_decode_queue.push_back(move(encoded_buffer));
}
audio::decode_event_loop->schedule(static_pointer_cast<event::EventEntry>(this->ref()));
}
void VoiceClient::cancel_replay() {
log_trace(category::voice_connection, tr("Cancel replay for client {}"), this->_client_id);
this->output_source->clear();
this->set_state(state::stopped);
}
void VoiceClient::event_execute(const std::chrono::system_clock::time_point &scheduled) {
static auto max_time = chrono::milliseconds(10);
bool reschedule = false;
auto now = chrono::system_clock::now();
while(true) {
unique_lock buffer_lock(this->audio_decode_queue_lock);
if(this->play_premature_packets) {
this->play_premature_packets = false;
for(auto& codec_data : this->codec) {
if(!codec_data) continue;
if(!codec_data->premature_packets.empty()) {
size_t play_count = 0;
while(!codec_data->premature_packets.empty()) {
auto& packet = codec_data->premature_packets.front();
//Test if we're able to replay stuff again
if((uint16_t) (codec_data->last_packet_id + 1) < packet.packet_id && play_count > 0) //Only check for the order if we replayed one already
break; //Nothing new
this->output_source->enqueue_samples(packet.buffer);
codec_data->last_packet_id = packet.packet_id;
codec_data->premature_packets.pop_front();
play_count++;
}
#ifdef DEBUG_PREMATURE_PACKETS
if(play_count > 0)
log_debug(category::audio, tr("Replayed (buffer underflow) {} premature packets for client {}"), play_count, this->_client_id);
#endif
break;
}
}
}
if(this->audio_decode_queue.empty())
break;
if(chrono::system_clock::now() - now > max_time) {
reschedule = true;
break;
}
auto entry = move(this->audio_decode_queue.front());
this->audio_decode_queue.pop_front();
buffer_lock.unlock();
//TODO: Drop too old buffers!
this->process_encoded_buffer(entry);
}
if(audio_decode_event_dropped.exchange(false) && !reschedule) {
//Is not really a warning, it happens all the time and isn't really an issue
//log_warn(category::voice_connection, tr("Dropped auto enqueue event execution for client {}. No reschedulling planned, hopefully we processed all buffers."), this->_client_id);
}
if(reschedule) {
log_warn(category::voice_connection, tr("Audio data decode will take longer than {} us. Enqueueing for later"), chrono::duration_cast<chrono::microseconds>(max_time).count());
audio::decode_event_loop->schedule(static_pointer_cast<event::EventEntry>(this->ref()));
}
}
#define MAX_LOST_PACKETS (6)
//Note: This function must be executed single threaded
void VoiceClient::process_encoded_buffer(const std::unique_ptr<EncodedBuffer> &buffer) {
string error;
auto& codec_data = this->codec[buffer->codec];
if(!codec_data) {
auto info = codec::get_info(buffer->codec);
if(!info || !info->supported) {
log_warn(category::voice_connection, tr("Received voice packet from client {}, but we dont support it ({})"), this->_client_id, buffer->codec);
return;
}
auto instance = make_unique<AudioCodec>();
instance->successfully_initialized = false;
instance->last_packet_id = (uint16_t) (buffer->packet_id - 1); /* could be 0xFFFF */
instance->converter = info->new_converter(error);
if(!instance->converter) {
codec_data = move(instance);
log_warn(category::voice_connection, tr("Failed to initialize new codec {} for client {}: {}"), buffer->codec, this->_client_id, error);
return;
}
instance->resampler = make_shared<audio::AudioResampler>(instance->converter->sample_rate(), this->output_source->sample_rate, instance->converter->channels());
if(!instance->resampler->valid()) {
codec_data = move(instance);
log_warn(category::voice_connection, tr("Failed to initialize new codec resampler {} for client {}"), buffer->codec, this->_client_id);
return;
}
instance->successfully_initialized = true;
codec_data = move(instance);
} else if(!codec_data->successfully_initialized) {
return; /* already failed ignore that stuff */
}
uint16_t diff;
bool premature = false;
if(codec_data->last_packet_timestamp + chrono::seconds(1) < buffer->receive_timestamp || this->_state >= state::stopping) {
diff = 0xFFFF;
} else {
if(codec_data->last_packet_id > buffer->packet_id) {
auto local_index = (uint16_t) (codec_data->last_packet_id + MAX_LOST_PACKETS);
if(local_index < buffer->packet_id)
diff = 0xFF; /* we got in a new generation */
else {
log_warn(category::audio,
tr("Received voice packet for client {} with is older than the last we received (Current index: {}, Packet index: {}). Dropping packet."),
this->_client_id, buffer->packet_id, codec_data->last_packet_id
);
return;
}
} else {
diff = buffer->packet_id - codec_data->last_packet_id;
}
}
const auto old_packet_id = codec_data->last_packet_id;
codec_data->last_packet_timestamp = buffer->receive_timestamp;
if(buffer->buffer.empty()) {
/* lets playback the last samples and we're done */
this->set_state(state::stopping);
/* enqueue all premature packets (list should be already ordered!) */
{
unique_lock buffer_lock(this->audio_decode_queue_lock);
for(const auto& packet : codec_data->premature_packets)
this->output_source->enqueue_samples(packet.buffer);
codec_data->premature_packets.clear();
}
log_trace(category::voice_connection, tr("Stopping replay for client {}. Empty buffer!"), this->_client_id);
return;
}
if(diff == 0) {
//Duplicated packets
log_warn(category::audio, tr("Received voice packet with the same ID then the last one. Dropping packet."));
return;
} else
diff--; /* because the diff is normally 1 (ofc) */
if(diff <= MAX_LOST_PACKETS) {
if(diff > 0) {
/* lets first handle packet as "lost", even thou we're enqueueing it as premature */
//auto status = codec_data->converter->decode_lost(error, diff);
//if(status < 0)
// log_warn(category::voice_connection, tr("Failed to decode (skip) dropped packets. Return code {} => {}"), status, error);
premature = !buffer->head && this->state() != state::stopped;
log_debug(category::voice_connection,
tr("Client {} dropped one or more audio packets. Old packet id: {}, New packet id: {}, Diff: {}. Head: {}. Flagging chunk as premature: {}"),
this->_client_id, old_packet_id, buffer->packet_id, diff, buffer->head, premature);
}
} else {
log_debug(category::voice_connection, tr("Client {} resetted decoder. Old packet id: {}, New packet id: {}, diff: {}"), this->_client_id, old_packet_id, buffer->packet_id, diff);
codec_data->converter->reset_decoder();
if(!codec_data->converter) {
log_warn(category::voice_connection, tr("Failed to reset codec decoder {} for client {}: {}"), buffer->codec, this->_client_id, error);
return;
}
}
if(!premature)
codec_data->last_packet_id = buffer->packet_id;
char target_buffer[target_buffer_length];
if(target_buffer_length < codec_data->converter->expected_decoded_length(buffer->buffer.data_ptr(), buffer->buffer.length())) {
log_warn(category::voice_connection, tr("Failed to decode audio data. Target buffer is smaller then expected bytes ({} < {})"), target_buffer_length, codec_data->converter->expected_decoded_length(buffer->buffer.data_ptr(), buffer->buffer.length()));
return;
}
auto samples = codec_data->converter->decode(error, buffer->buffer.data_ptr(), buffer->buffer.length(), target_buffer);
if(samples < 0) {
log_warn(category::voice_connection, tr("Failed to decode audio data: {}"), error);
return;
}
if(target_buffer_length < codec_data->resampler->estimated_output_size(samples) * codec_data->resampler->channels() * 4) {
log_warn(category::voice_connection, tr("Failed to resample audio data. Target buffer is smaller then expected bytes ({} < {})"), target_buffer_length, (codec_data->resampler->estimated_output_size(samples) * codec_data->resampler->channels() * 4));
return;
}
auto resampled_samples = codec_data->resampler->process(target_buffer, target_buffer, samples);
if(resampled_samples <= 0) {
log_warn(category::voice_connection, tr("Failed to resample audio data. Resampler resulted in {}"), resampled_samples);
return;
}
if(!audio::merge::merge_channels_interleaved(target_buffer, this->output_source->channel_count, target_buffer, codec_data->resampler->channels(), resampled_samples)) {
log_warn(category::voice_connection, tr("Failed to merge channels to output stream channel count!"));
return;
}
if(this->_volume != 1) {
auto buf = (float*) target_buffer;
auto count = this->output_source->channel_count * resampled_samples;
while(count-- > 0)
*(buf++) *= this->_volume;
}
if(premature) {
auto audio_buffer = audio::SampleBuffer::allocate((uint8_t) this->output_source->channel_count, (uint16_t) resampled_samples);
audio_buffer->sample_index = 0;
memcpy(audio_buffer->sample_data, target_buffer, this->output_source->channel_count * resampled_samples * 4);
{
unique_lock buffer_lock(this->audio_decode_queue_lock);
auto it = codec_data->premature_packets.begin();
for(; it != codec_data->premature_packets.end(); it++) {
if(it->packet_id > buffer->packet_id) {
break; /* it is set to the right position */
}
}
codec_data->premature_packets.insert(it, {
buffer->packet_id,
move(audio_buffer)
});
std::stable_sort(codec_data->premature_packets.begin(), codec_data->premature_packets.end(), [](const PrematureAudioPacket& a, const PrematureAudioPacket& b) {
return a.packet_id < b.packet_id;
});
}
} else {
auto enqueued = this->output_source->enqueue_samples(target_buffer, resampled_samples);
if(enqueued != resampled_samples)
log_warn(category::voice_connection, tr("Failed to enqueue all samples for client {}. Enqueued {} of {}"), this->_client_id, enqueued, resampled_samples);
this->set_state(state::playing);
/* test if any premature got its original place */
{
unique_lock buffer_lock(this->audio_decode_queue_lock);
size_t play_count = 0;
while(!codec_data->premature_packets.empty()) {
auto& packet = codec_data->premature_packets[0];
//Test if we're able to replay stuff again
if((uint16_t) (codec_data->last_packet_id + 1) < packet.packet_id)
break; //Nothing new
this->output_source->enqueue_samples(packet.buffer);
codec_data->last_packet_id = packet.packet_id;
codec_data->premature_packets.pop_front();
play_count++;
}
#ifdef DEBUG_PREMATURE_PACKETS
if(play_count > 0)
log_debug(category::audio, tr("Replayed (id match) {} premature packets for client {}"), play_count, this->_client_id);
#endif
}
}
}
void VoiceClient::event_execute_dropped(const std::chrono::system_clock::time_point &point) {
if(audio_decode_event_dropped.exchange(true))
//Is not really a warning, it happens all the time and isn't really an issue
;//log_warn(category::voice_connection, tr("Dropped auto enqueue event execution two or more times in a row for client {}"), this->_client_id);
}
@@ -1,178 +0,0 @@
#pragma once
#include <array>
#include <nan.h>
#include <NanEventCallback.h>
#include <functional>
#include <pipes/buffer.h>
#include "../../audio/AudioResampler.h"
#include "../../audio/codec/Converter.h"
#include "../../audio/AudioOutput.h"
#include "../../EventLoop.h"
namespace tc {
namespace connection {
class ServerConnection;
class VoiceConnection;
class VoiceClient;
namespace codec {
enum value {
MIN = 0,
SPEEX_NARROWBAND = 0,
SPEEX_WIDEBAND = 1,
SPEEX_ULTRA_WIDEBAND = 2,
CELT_MONO = 3,
OPUS_VOICE = 4,
OPUS_MUSIC = 5,
MAX = 5,
};
struct condec_info {
bool supported;
std::string name;
std::function<std::shared_ptr<audio::codec::Converter>(std::string&)> new_converter;
};
extern const condec_info info[6];
inline const condec_info* get_info(value codec) {
if(codec > value::MAX || codec < value::MIN)
return nullptr;
return &info[codec];
}
}
class VoiceClient : private event::EventEntry {
friend class VoiceConnection;
template<typename _Tp, typename _Up>
friend inline std::shared_ptr<_Tp> std::static_pointer_cast(const std::shared_ptr<_Up>& __r) noexcept;
public:
struct state {
enum value {
buffering, /* this state is never active */
playing,
stopping,
stopped
};
};
VoiceClient(const std::shared_ptr<VoiceConnection>& /* connection */, uint16_t /* client id */);
virtual ~VoiceClient();
inline uint16_t client_id() { return this->_client_id; }
void initialize_js_object();
void finalize_js_object();
v8::Local<v8::Object> js_handle() {
assert(v8::Isolate::GetCurrent());
return this->_js_handle.Get(Nan::GetCurrentContext()->GetIsolate());
}
inline std::shared_ptr<VoiceClient> ref() { return this->_ref.lock(); }
void process_packet(uint16_t packet_id, const pipes::buffer_view& /* buffer */, codec::value /* codec */, bool /* head */);
inline float get_volume() { return this->_volume; }
inline void set_volume(float value) { this->_volume = value; }
inline state::value state() { return this->_state; }
void cancel_replay();
std::function<void()> on_state_changed;
inline std::shared_ptr<audio::AudioOutputSource> output_stream() { return this->output_source; }
private:
struct PrematureAudioPacket {
uint16_t packet_id = 0;
std::shared_ptr<tc::audio::SampleBuffer> buffer{};
};
struct AudioCodec {
uint16_t last_packet_id = 0;
std::chrono::system_clock::time_point last_packet_timestamp;
bool successfully_initialized;
std::shared_ptr<audio::codec::Converter> converter;
std::shared_ptr<audio::AudioResampler> resampler;
std::deque<PrematureAudioPacket> premature_packets;
};
std::array<std::unique_ptr<AudioCodec>, codec::MAX + 1> codec{
nullptr,
nullptr,
nullptr,
nullptr,
nullptr
};
std::shared_ptr<audio::AudioOutputSource> output_source;
std::weak_ptr<VoiceClient> _ref;
v8::Persistent<v8::Object> _js_handle;
uint16_t _client_id;
float _volume = 1.f;
bool play_premature_packets = false;
std::chrono::system_clock::time_point _last_received_packet;
state::value _state = state::stopped;
inline void set_state(state::value value) {
if(value == this->_state)
return;
this->_state = value;
if(this->on_state_changed)
this->on_state_changed();
}
struct EncodedBuffer {
bool head;
uint16_t packet_id;
pipes::buffer buffer;
codec::value codec;
std::chrono::system_clock::time_point receive_timestamp;
};
std::atomic_bool audio_decode_event_dropped{false};
std::mutex audio_decode_queue_lock;
std::deque<std::unique_ptr<EncodedBuffer>> audio_decode_queue;
void event_execute(const std::chrono::system_clock::time_point &point) override;
void event_execute_dropped(const std::chrono::system_clock::time_point &point) override;
void process_encoded_buffer(const std::unique_ptr<EncodedBuffer>& /* buffer */);
};
class VoiceClientWrap : public Nan::ObjectWrap {
public:
static NAN_MODULE_INIT(Init);
static NAN_METHOD(NewInstance);
static inline Nan::Persistent<v8::Function> & constructor() {
static Nan::Persistent<v8::Function> my_constructor;
return my_constructor;
}
VoiceClientWrap(const std::shared_ptr<VoiceClient>&);
virtual ~VoiceClientWrap();
void do_wrap(const v8::Local<v8::Object>&);
private:
static NAN_METHOD(_get_state);
static NAN_METHOD(_get_volume);
static NAN_METHOD(_set_volume);
static NAN_METHOD(_abort_replay);
static NAN_METHOD(_get_stream);
std::weak_ptr<VoiceClient> _handle;
bool _currently_playing = false;
Nan::callback_t<> call_state_changed;
void _call_state_changed();
};
}
}
@@ -1,386 +0,0 @@
#include "VoiceConnection.h"
#include "VoiceClient.h"
#include "../ServerConnection.h"
#include "AudioSender.h"
#include "../../audio/js/AudioConsumer.h"
#include "../../audio/AudioInput.h"
#include "../../logger.h"
#include <misc/endianness.h> /* MUST be included as last file */
using namespace std;
using namespace tc;
using namespace tc::connection;
using namespace ts;
using namespace ts::protocol;
using namespace audio::recorder;
VoiceConnectionWrap::VoiceConnectionWrap(const std::shared_ptr<VoiceConnection>& handle) : handle(handle) {}
VoiceConnectionWrap::~VoiceConnectionWrap() {
if(!this->_voice_recoder_handle.IsEmpty()) {
auto old_consumer = this->_voice_recoder_ptr;
assert(old_consumer);
lock_guard read_lock(old_consumer->native_consumer()->on_read_lock);
old_consumer->native_consumer()->on_read = nullptr;
}
}
void VoiceConnectionWrap::do_wrap(const v8::Local<v8::Object> &object) {
this->Wrap(object);
}
NAN_MODULE_INIT(VoiceConnectionWrap::Init) {
auto klass = Nan::New<v8::FunctionTemplate>(VoiceConnectionWrap::NewInstance);
klass->SetClassName(Nan::New("VoiceConnection").ToLocalChecked());
klass->InstanceTemplate()->SetInternalFieldCount(1);
Nan::SetPrototypeMethod(klass, "decoding_supported", VoiceConnectionWrap::_decoding_supported);
Nan::SetPrototypeMethod(klass, "encoding_supported", VoiceConnectionWrap::_encoding_supported);
Nan::SetPrototypeMethod(klass, "register_client", VoiceConnectionWrap::_register_client);
Nan::SetPrototypeMethod(klass, "available_clients", VoiceConnectionWrap::_available_clients);
Nan::SetPrototypeMethod(klass, "unregister_client", VoiceConnectionWrap::_unregister_client);
Nan::SetPrototypeMethod(klass, "audio_source", VoiceConnectionWrap::_audio_source);
Nan::SetPrototypeMethod(klass, "set_audio_source", VoiceConnectionWrap::_set_audio_source);
Nan::SetPrototypeMethod(klass, "get_encoder_codec", VoiceConnectionWrap::_get_encoder_codec);
Nan::SetPrototypeMethod(klass, "set_encoder_codec", VoiceConnectionWrap::_set_encoder_codec);
Nan::SetPrototypeMethod(klass, "enable_voice_send", VoiceConnectionWrap::_enable_voice_send);
constructor().Reset(Nan::GetFunction(klass).ToLocalChecked());
}
NAN_METHOD(VoiceConnectionWrap::NewInstance) {
if(!info.IsConstructCall())
Nan::ThrowError("invalid invoke!");
}
NAN_METHOD(VoiceConnectionWrap::_connected) {
info.GetReturnValue().Set(true);
}
NAN_METHOD(VoiceConnectionWrap::_encoding_supported) {
if(info.Length() != 1) {
Nan::ThrowError("invalid argument count");
return;
}
auto codec = info[0]->Uint32Value(Nan::GetCurrentContext()).FromMaybe(0);
info.GetReturnValue().Set(codec >= 4 && codec <= 5); /* ignore SPEX currently :/ */
}
NAN_METHOD(VoiceConnectionWrap::_decoding_supported) {
if(info.Length() != 1) {
Nan::ThrowError("invalid argument count");
return;
}
auto codec = info[0]->Uint32Value(Nan::GetCurrentContext()).FromMaybe(0);
info.GetReturnValue().Set(codec >= 4 && codec <= 5); /* ignore SPEX currently :/ */
}
NAN_METHOD(VoiceConnectionWrap::_register_client) {
return ObjectWrap::Unwrap<VoiceConnectionWrap>(info.Holder())->register_client(info);
}
NAN_METHOD(VoiceConnectionWrap::register_client) {
if(info.Length() != 1) {
Nan::ThrowError("invalid argument count");
return;
}
auto id = info[0]->Uint32Value(Nan::GetCurrentContext()).FromMaybe(0);
auto handle = this->handle.lock();
if(!handle) {
Nan::ThrowError("handle has been deallocated");
return;
}
auto client = handle->register_client(id);
if(!client) {
Nan::ThrowError("failed to register client");
return;
}
client->initialize_js_object();
info.GetReturnValue().Set(client->js_handle());
}
NAN_METHOD(VoiceConnectionWrap::_available_clients) {
return ObjectWrap::Unwrap<VoiceConnectionWrap>(info.Holder())->available_clients(info);
}
NAN_METHOD(VoiceConnectionWrap::available_clients) {
auto handle = this->handle.lock();
if(!handle) {
Nan::ThrowError("handle has been deallocated");
return;
}
auto client = handle->clients();
v8::Local<v8::Array> result = Nan::New<v8::Array>(client.size());
for(size_t index = 0; index < client.size(); index++)
Nan::Set(result, index, client[index]->js_handle());
info.GetReturnValue().Set(result);
}
NAN_METHOD(VoiceConnectionWrap::_unregister_client) {
return ObjectWrap::Unwrap<VoiceConnectionWrap>(info.Holder())->unregister_client(info);
}
NAN_METHOD(VoiceConnectionWrap::unregister_client) {
if(info.Length() != 1) {
Nan::ThrowError("invalid argument count");
return;
}
auto id = info[0]->Uint32Value(Nan::GetCurrentContext()).FromMaybe(0);
auto handle = this->handle.lock();
if(!handle) {
Nan::ThrowError("handle has been deallocated");
return;
}
auto client = handle->find_client(id);
if(!client) {
Nan::ThrowError("missing client");
return;
}
client->finalize_js_object();
handle->delete_client(client);
}
NAN_METHOD(VoiceConnectionWrap::_audio_source) {
auto client = ObjectWrap::Unwrap<VoiceConnectionWrap>(info.Holder());
info.GetReturnValue().Set(client->_voice_recoder_handle.Get(info.GetIsolate()));
}
NAN_METHOD(VoiceConnectionWrap::_set_audio_source) {
ObjectWrap::Unwrap<VoiceConnectionWrap>(info.Holder())->set_audio_source(info);
}
NAN_METHOD(VoiceConnectionWrap::set_audio_source) {
if(info.Length() != 1) {
Nan::ThrowError("invalid argument count");
return;
}
if(!Nan::New(AudioConsumerWrapper::constructor_template())->HasInstance(info[0]) && !info[0]->IsNullOrUndefined()) {
Nan::ThrowError("invalid consumer (Consumer must be native!)");
return;
}
if(!this->handle.lock()) {
Nan::ThrowError("handle has been deallocated");
return;
}
this->release_recorder();
if(!info[0]->IsNullOrUndefined()) {
this->_voice_recoder_ptr = ObjectWrap::Unwrap<audio::recorder::AudioConsumerWrapper>(info[0]->ToObject(Nan::GetCurrentContext()).ToLocalChecked());
this->_voice_recoder_handle.Reset(info[0]->ToObject(Nan::GetCurrentContext()).ToLocalChecked());
auto native_consumer = this->_voice_recoder_ptr->native_consumer();
weak_ptr weak_handle = this->handle;
auto sample_rate = native_consumer->sample_rate;
auto channels = native_consumer->channel_count;
lock_guard read_lock(this->_voice_recoder_ptr->native_read_callback_lock);
this->_voice_recoder_ptr->native_read_callback = [weak_handle, sample_rate, channels](const void* buffer, size_t length) {
auto handle = weak_handle.lock();
if(!handle) {
log_warn(category::audio, tr("Missing voice connection handle. Dropping input!"));
return;
}
shared_ptr<VoiceSender> sender = handle->voice_sender();
if(sender) {
if(length > 0 && buffer)
sender->send_data(buffer, length, sample_rate, channels);
else
sender->send_stop();
} else {
log_warn(category::audio, tr("Missing voice connection audio sender. Dropping input!"));
return;
}
};
}
}
NAN_METHOD(VoiceConnectionWrap::_get_encoder_codec) {
auto _this = ObjectWrap::Unwrap<VoiceConnectionWrap>(info.Holder());
auto handle = _this->handle.lock();
if(!handle) {
Nan::ThrowError("handle has been deallocated");
return;
}
info.GetReturnValue().Set(handle->get_encoder_codec());
}
NAN_METHOD(VoiceConnectionWrap::_set_encoder_codec) {
auto _this = ObjectWrap::Unwrap<VoiceConnectionWrap>(info.Holder());
auto handle = _this->handle.lock();
if(!handle) {
Nan::ThrowError("handle has been deallocated");
return;
}
if(info.Length() != 1 || !info[0]->IsNumber()) {
Nan::ThrowError("Invalid arguments");
return;
}
handle->set_encoder_codec((uint8_t) info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0));
}
NAN_METHOD(VoiceConnectionWrap::_enable_voice_send) {
auto _this = ObjectWrap::Unwrap<VoiceConnectionWrap>(info.Holder());
auto handle = _this->handle.lock();
if(!handle) {
Nan::ThrowError("handle has been deallocated");
return;
}
if(info.Length() != 1 || !info[0]->IsBoolean()) {
Nan::ThrowError("Invalid arguments");
return;
}
auto sender = handle->voice_sender();
if(!sender) {
Nan::ThrowError("Voice sender has been deallocated");
return;
}
sender->set_voice_send_enabled(info[0]->BooleanValue(info.GetIsolate()));
}
void VoiceConnectionWrap::release_recorder() {
if(!this->_voice_recoder_handle.IsEmpty()) {
assert(v8::Isolate::GetCurrent());
auto old_consumer = this->_voice_recoder_ptr;
assert(old_consumer);
lock_guard read_lock(this->_voice_recoder_ptr->native_read_callback_lock);
this->_voice_recoder_ptr->native_read_callback = nullptr;
} else {
assert(!this->_voice_recoder_ptr);
}
this->_voice_recoder_ptr = nullptr;
this->_voice_recoder_handle.Reset();
}
VoiceConnection::VoiceConnection(ServerConnection *handle) : _handle(handle) {
this->_voice_sender = make_shared<VoiceSender>(this);
this->_voice_sender->_ref = this->_voice_sender;
this->_voice_sender->set_codec(codec::OPUS_MUSIC);
}
VoiceConnection::~VoiceConnection() {
if(v8::Isolate::GetCurrent())
this->finalize_js_object();
else {
assert(this->_js_handle.IsEmpty());
}
this->_voice_sender->finalize();
}
void VoiceConnection::reset() {
lock_guard lock(this->_clients_lock);
this->_clients.clear();
}
void VoiceConnection::initialize_js_object() {
auto object_wrap = new VoiceConnectionWrap(this->ref());
auto object = Nan::NewInstance(Nan::New(VoiceConnectionWrap::constructor()), 0, nullptr).ToLocalChecked();
object_wrap->do_wrap(object);
this->_js_handle.Reset(Nan::GetCurrentContext()->GetIsolate(), object);
}
void VoiceConnection::finalize_js_object() {
this->_js_handle.Reset();
}
std::shared_ptr<VoiceClient> VoiceConnection::find_client(uint16_t client_id) {
lock_guard lock(this->_clients_lock);
for(const auto& client : this->_clients)
if(client->client_id() == client_id)
return client;
return nullptr;
}
std::shared_ptr<VoiceClient> VoiceConnection::register_client(uint16_t client_id) {
lock_guard lock(this->_clients_lock);
auto client = this->find_client(client_id);
if(client) return client;
client = make_shared<VoiceClient>(this->ref(), client_id);
client->_ref = client;
this->_clients.push_back(client);
return client;
}
void VoiceConnection::delete_client(const std::shared_ptr<tc::connection::VoiceClient> &client) {
{
lock_guard lock(this->_clients_lock);
auto it = find(this->_clients.begin(), this->_clients.end(), client);
if(it != this->_clients.end()) {
this->_clients.erase(it);
}
}
//TODO deinitialize client
}
void VoiceConnection::process_packet(const std::shared_ptr<ts::protocol::ServerPacket> &packet) {
if(packet->type() == PacketTypeInfo::Voice) {
if(packet->data().length() < 5) {
//TODO log invalid voice packet
return;
}
auto packet_id = be2le16(&packet->data()[0]);
auto client_id = be2le16(&packet->data()[2]);
auto codec_id = (uint8_t) packet->data()[4];
auto flag_head = packet->has_flag(PacketFlag::Compressed);
//container->voice_data = packet->data().length() > 5 ? packet->data().range(5) : pipes::buffer{};
//log_info(category::voice_connection, tr("Received voice packet from {}. Packet ID: {}"), client_id, packet_id);
auto client = this->find_client(client_id);
if(!client) {
log_warn(category::voice_connection, tr("Received voice packet from unknown client {}. Dropping packet!"), client_id);
return;
}
if(packet->data().length() > 5)
client->process_packet(packet_id, packet->data().range(5), (codec::value) codec_id, flag_head);
else
client->process_packet(packet_id, pipes::buffer_view{nullptr, 0}, (codec::value) codec_id, flag_head);
} else {
//TODO implement whisper
}
}
void VoiceConnection::set_encoder_codec(const uint8_t &codec) {
if(codec > codec::MAX) return;
auto vs = this->_voice_sender;
if(vs)
vs->set_codec((codec::value) codec);
}
uint8_t VoiceConnection::get_encoder_codec() {
auto vs = this->_voice_sender;
return vs ? vs->get_codec() : 0;
}
@@ -1,109 +0,0 @@
#pragma once
#include <v8.h>
#include <nan.h>
#include <memory>
#include <mutex>
#include <protocol/Packet.h>
namespace tc {
namespace audio {
namespace recorder {
class AudioConsumerWrapper;
}
}
namespace connection {
class ServerConnection;
class VoiceConnection;
class VoiceClient;
class VoiceSender;
class VoiceConnectionWrap : public Nan::ObjectWrap {
public:
static NAN_MODULE_INIT(Init);
static NAN_METHOD(NewInstance);
static inline Nan::Persistent<v8::Function> & constructor() {
static Nan::Persistent<v8::Function> my_constructor;
return my_constructor;
}
explicit VoiceConnectionWrap(const std::shared_ptr<VoiceConnection>&);
virtual ~VoiceConnectionWrap();
void do_wrap(const v8::Local<v8::Object>&);
private:
static NAN_METHOD(_connected);
static NAN_METHOD(_encoding_supported);
static NAN_METHOD(_decoding_supported);
static NAN_METHOD(_register_client);
NAN_METHOD(register_client);
static NAN_METHOD(_available_clients);
NAN_METHOD(available_clients);
static NAN_METHOD(_unregister_client);
NAN_METHOD(unregister_client);
static NAN_METHOD(_audio_source);
static NAN_METHOD(_set_audio_source);
NAN_METHOD(set_audio_source);
static NAN_METHOD(_get_encoder_codec);
static NAN_METHOD(_set_encoder_codec);
static NAN_METHOD(_enable_voice_send);
void release_recorder();
std::function<void(const void* /* buffer */, size_t /* samples */)> _read_callback;
audio::recorder::AudioConsumerWrapper* _voice_recoder_ptr = nullptr;
Nan::Persistent<v8::Object> _voice_recoder_handle;
std::weak_ptr<VoiceConnection> handle;
};
class VoiceConnection {
friend class ServerConnection;
friend class VoiceConnectionWrap;
public:
explicit VoiceConnection(ServerConnection*);
virtual ~VoiceConnection();
void reset();
void initialize_js_object();
void finalize_js_object();
ServerConnection* handle() { return this->_handle; }
v8::Local<v8::Object> js_handle() {
assert(v8::Isolate::GetCurrent());
return this->_js_handle.Get(Nan::GetCurrentContext()->GetIsolate());
}
inline std::shared_ptr<VoiceConnection> ref() { return this->_ref.lock(); }
inline std::deque<std::shared_ptr<VoiceClient>> clients() {
std::lock_guard lock(this->_clients_lock);
return this->_clients;
}
inline std::shared_ptr<VoiceSender> voice_sender() { return this->_voice_sender; }
std::shared_ptr<VoiceClient> find_client(uint16_t /* client id */);
std::shared_ptr<VoiceClient> register_client(uint16_t /* client id */);
void delete_client(const std::shared_ptr<VoiceClient>&);
void process_packet(const std::shared_ptr<ts::protocol::ServerPacket>&);
void set_encoder_codec(const uint8_t& /* codec */);
uint8_t get_encoder_codec();
private:
ServerConnection* _handle;
std::weak_ptr<VoiceConnection> _ref;
v8::Persistent<v8::Object> _js_handle;
std::recursive_mutex _clients_lock;
std::deque<std::shared_ptr<VoiceClient>> _clients;
std::shared_ptr<VoiceSender> _voice_sender;
};
}
}
@@ -1,773 +0,0 @@
#include "FileTransferManager.h"
#include "FileTransferObject.h"
#include <algorithm>
#include <fcntl.h>
#include <iostream>
#ifndef WIN32
#include <unistd.h>
#include <misc/net.h>
#else
#include <ws2tcpip.h>
#define SOCK_NONBLOCK (0)
#define MSG_DONTWAIT (0)
#endif
using namespace tc;
using namespace tc::ft;
using namespace std;
using namespace std::chrono;
tc::ft::FileTransferManager* transfer_manager = nullptr;
Transfer::~Transfer() {
log_free("Transfer", this);
}
bool Transfer::initialize(std::string &error) {
if(this->_state != state::UNINITIALIZED) {
error = tr("invalid state");
return false;
}
if(!this->_transfer_object->initialize(error)) {
error = tr("failed to initialize transfer object: ") + error;
return false;
}
this->_state = state::CONNECTING;
/* resolve address */
{
addrinfo hints{}, *result;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
if(getaddrinfo(this->_options->remote_address.data(), nullptr, &hints, &result) != 0 || !result) {
error = tr("failed to resolve hostname");
this->_state = state::UNINITIALIZED;
return false;
}
memcpy(&this->remote_address, result->ai_addr, result->ai_addrlen);
freeaddrinfo(result);
}
switch(this->remote_address.ss_family) {
case AF_INET:
((sockaddr_in*) &this->remote_address)->sin_port = htons(this->_options->remote_port);
case AF_INET6:
((sockaddr_in6*) &this->remote_address)->sin6_port = htons(this->_options->remote_port);
default:
break;
}
log_info(category::file_transfer, tr("Setting remote port to {}"), this->_options->remote_port);
this->_socket = (int) ::socket(this->remote_address.ss_family, SOCK_STREAM | SOCK_NONBLOCK, 0);
if(this->_socket < 0) {
this->finalize();
error = tr("failed to spawn socket");
return false;
}
#ifdef WIN32
u_long enabled = 0;
auto non_block_rs = ioctlsocket(this->_socket, FIONBIO, &enabled);
if (non_block_rs != NO_ERROR) {
this->finalize();
error = "failed to enable non blocking more";
return false;
}
#endif
{
lock_guard lock(this->event_lock);
this->event_read = event_new(this->event_io, this->_socket, EV_READ | EV_PERSIST, &Transfer::_callback_read, this);
this->event_write = event_new(this->event_io, this->_socket, EV_WRITE, &Transfer::_callback_write, this);
}
return true;
}
bool Transfer::connect() {
int result = ::connect(this->_socket, reinterpret_cast<sockaddr *> (&this->remote_address), sizeof(this->remote_address));
if (result < 0) {
#ifdef WIN32
auto error = WSAGetLastError();
if(error != WSAEWOULDBLOCK) {
wchar_t *s = nullptr;
FormatMessageW(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr,
error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPWSTR)&s,
0,
nullptr
);
fprintf(stdout, "Connect failed with code %d. Error: %ld/%S\n", result, error, s);
LocalFree(s);
return false;
}
#else
if(errno != EINPROGRESS) {
log_error(category::file_transfer, tr("Failed to connect with code: {} => {}/{}"), result, errno, strerror(errno));
this->finalize();
return false;
}
#endif
} else {
this->_state = state::CONNECTED; /* we're connected */
}
log_debug(category::file_transfer, tr("Connect result: {} - {}"), result, errno);
timeval connect_timeout{5, 0};
event_add(this->event_write, &connect_timeout); /* enabled if socket is connected */
////event_add(this->event_read, &connect_timeout); /* enabled if socket is connected */
this->handle()->execute_event_loop();
if(this->_state == state::CONNECTED)
this->handle_connected();
return true;
}
void Transfer::finalize(bool blocking) {
if(this->_state == state::UNINITIALIZED)
return;
this->_state = state::UNINITIALIZED;
{
unique_lock lock(this->event_lock);
auto event_read = this->event_read, event_write = this->event_write;
this->event_read = nullptr;
this->event_write = nullptr;
lock.unlock();
if(event_read) {
if(blocking)
event_del_block(event_read);
else
event_del_noblock(event_read);
event_free(event_read);
}
if(event_write) {
if(blocking)
event_del_block(event_write);
else
event_del_noblock(event_write);
event_free(event_write);
}
}
if(this->_socket > 0) {
#ifdef WIN32
closesocket(this->_socket);
#else
shutdown(this->_socket, SHUT_RDWR);
close(this->_socket);
#endif
this->_socket = 0;
}
this->_transfer_object->finalize();
this->_handle->remove_transfer(this);
}
void Transfer::_callback_write(evutil_socket_t, short flags, void *_ptr_transfer) {
reinterpret_cast<Transfer*>(_ptr_transfer)->callback_write(flags);
}
void Transfer::_callback_read(evutil_socket_t, short flags, void *_ptr_transfer) {
reinterpret_cast<Transfer*>(_ptr_transfer)->callback_read(flags);
}
void Transfer::callback_read(short flags) {
if(this->_state < state::CONNECTING && this->_state > state::DISCONNECTING)
return;
if(flags & EV_TIMEOUT) {
auto target = dynamic_pointer_cast<TransferTarget>(this->_transfer_object);
if(target) {
if(this->last_target_write.time_since_epoch().count() == 0)
this->last_target_write = system_clock::now();
else if(system_clock::now() - this->last_target_write > seconds(5)) {
this->call_callback_failed("timeout (write)");
this->finalize(false);
return;
}
} else {
if(this->last_source_read.time_since_epoch().count() == 0)
this->last_source_read = system_clock::now();
else if(system_clock::now() - this->last_source_read > seconds(5)) {
this->call_callback_failed("timeout (read)");
this->finalize(false);
return;
}
}
{
lock_guard lock(this->event_lock);
if(this->event_read) {
event_add(this->event_read, &this->alive_check_timeout);
this->handle()->execute_event_loop();
}
}
}
if(flags & EV_READ) {
if(this->_state == state::CONNECTING) {
log_debug(category::file_transfer, tr("Connected (read event)"));
this->handle_connected();
}
int64_t buffer_length = 1024;
char buffer[1024];
buffer_length = recv(this->_socket, buffer, (int) buffer_length, MSG_DONTWAIT);
if(buffer_length < 0) {
#ifdef WIN32
auto error = WSAGetLastError();
if(error != WSAEWOULDBLOCK)
return;
#else
if(errno == EAGAIN)
return;
#endif
log_error(category::file_transfer, tr("Received an error while receivig data: {}/{}"), errno, strerror(errno));
//TODO may handle this error message?
this->handle_disconnect();
return;
} else if(buffer_length == 0) {
log_info(category::file_transfer, tr("Received an disconnect"));
this->handle_disconnect();
return;
}
auto target = dynamic_pointer_cast<TransferTarget>(this->_transfer_object);
if(target) {
log_trace(category::file_transfer, tr("Read {} bytes"), buffer_length);
string error;
auto state = target->write_bytes(error, (uint8_t*) buffer, buffer_length);
this->last_target_write = system_clock::now();
if(state == error::out_of_space) {
log_error(category::file_transfer, tr("Failed to write read data (out of space)"));
this->call_callback_failed(tr("out of local space"));
this->finalize(true);
return;
} else if(state == error::custom) {
log_error(category::file_transfer, tr("Failed to write read data ({})"), error);
this->call_callback_failed(error);
this->finalize(true);
return;
} else if(state == error::custom_recoverable) {
log_error(category::file_transfer, tr("Failed to write read data ({})"), error);
} else if(state != error::success) {
log_error(category::file_transfer, tr("invalid local write return code! ({})"), state);
}
auto stream_index = target->stream_index();
auto expected_bytes = target->expected_length();
if(stream_index >= expected_bytes) {
this->call_callback_finished(false);
this->finalize(false);
}
this->call_callback_process(stream_index, expected_bytes);
} else {
log_warn(category::file_transfer, tr("Read {} bytes, but we're not in download mode"), buffer_length);
}
}
}
void Transfer::callback_write(short flags) {
if(this->_state < state::CONNECTING && this->_state > state::DISCONNECTING)
return;
if(flags & EV_TIMEOUT) {
//we received a timeout! (May just for creating buffers)
if(this->_state == state::CONNECTING) {
this->call_callback_failed(tr("failed to connect"));
this->finalize(false);
return;
}
}
bool readd_write = false, readd_write_for_read = false;
if(flags & EV_WRITE) {
if(this->_state == state::CONNECTING)
this->handle_connected();
pipes::buffer buffer;
while(true) {
{
lock_guard lock(this->queue_lock);
auto size = this->write_queue.size();
if(size == 0)
break;
buffer = this->write_queue.front();
this->write_queue.pop_front();
readd_write = size > 1;
}
auto written = send(this->_socket, buffer.data_ptr<char>(), buffer.length(), MSG_DONTWAIT);
if(written <= 0) {
{
lock_guard lock(this->queue_lock);
this->write_queue.push_front(buffer);
readd_write = true;
}
#ifdef WIN32
auto _error = WSAGetLastError();
#else
auto _error = errno;
#define WSAEWOULDBLOCK (0)
#define WSAECONNREFUSED (0)
#define WSAECONNRESET (0)
#define WSAENOTCONN (0)
#endif
if(_error == EAGAIN || _error == WSAEWOULDBLOCK) {
break; /* event will be added with e.g. a timeout */
} else if(_error == ECONNREFUSED || _error == WSAECONNREFUSED) {
this->call_callback_failed("connection refused");
this->finalize(false);
} else if(_error == ECONNRESET || _error == WSAECONNRESET) {
this->call_callback_failed("connection reset");
this->finalize(false);
} else if(_error == ENOTCONN || _error == WSAENOTCONN) {
this->call_callback_failed("not connected");
this->finalize(false);
} else if(written == 0) {
this->handle_disconnect();
} else {
log_error(category::file_transfer, "Encountered write error: {}/{}", _error, strerror(_error));
this->handle_disconnect();
}
return;
}
if(written < buffer.length()) {
lock_guard lock(this->queue_lock);
this->write_queue.push_front(buffer.range(written));
readd_write = true;
}
}
}
if(this->_state == state::CONNECTED) {
auto source = dynamic_pointer_cast<TransferSource>(this->_transfer_object);
if(source) {
size_t queue_length = 0;
{
lock_guard lock(this->queue_lock);
queue_length = this->write_queue.size();
}
string error;
auto total_bytes = source->byte_length();
auto bytes_to_write = total_bytes - source->stream_index();
while(queue_length < 8 && bytes_to_write > 0) {
uint64_t buffer_size = 1400; /* best TCP packet size (equal to the MTU) */
pipes::buffer buffer{buffer_size};
auto read_status = source->read_bytes(error, buffer.data_ptr<uint8_t>(), buffer_size);
this->last_source_read = system_clock::now();
if(read_status != error::success) {
if(read_status == error::would_block) {
readd_write_for_read = true;
break;
} else if(read_status == error::custom) {
this->call_callback_failed(tr("failed to read from source: ") + error);
this->finalize(false);
return;
} else if(read_status == error::custom_recoverable) {
log_warn(category::file_transfer, tr("Failed to read from source (but its recoverable): {}"), error);
break;
} else {
log_error(category::file_transfer, tr("invalid source read status ({})"), read_status);
this->finalize(false);
return;
}
} else if(buffer_size == 0) {
log_warn(category::file_transfer, tr("Invalid source read size! ({})"), buffer_size);
break;
}
{
lock_guard lock(this->queue_lock);
this->write_queue.push_back(buffer.range(0, buffer_size));
queue_length = this->write_queue.size();
}
bytes_to_write -= buffer_size;
}
this->call_callback_process(total_bytes - bytes_to_write, total_bytes);
if(queue_length == 0) {
if(source->stream_index() == source->byte_length()) {
this->call_callback_finished(false);
this->finalize(false);
return;
}
}
readd_write = queue_length > 0;
}
}
/* we only need write for connect */
if(readd_write || readd_write_for_read) {
lock_guard lock(this->event_lock);
if(this->event_write) {
timeval timeout{};
if(readd_write) {
/* we should be writeable within the next second or we do a keep alive circle */
timeout.tv_sec = 1;
timeout.tv_usec = 0;
} else if(readd_write_for_read) {
/* Schedule a next read attempt of our source */
timeout.tv_sec = 0;
timeout.tv_usec = 50000;
}
event_add(this->event_write, &timeout);
this->handle()->execute_event_loop();
}
} else {
log_debug(category::general, tr("No readd"));
}
}
void Transfer::_write_message(const pipes::buffer_view &buffer) {
{
lock_guard lock(this->queue_lock);
this->write_queue.push_back(buffer.own_buffer());
}
if(this->_state >= state::CONNECTED) {
lock_guard lock(this->event_lock);
if(this->event_write) {
event_add(this->event_write, nullptr);
this->handle()->execute_event_loop();
}
}
}
void Transfer::handle_disconnect() {
if(this->_state != state::DISCONNECTING) {
auto source = dynamic_pointer_cast<TransferSource>(this->_transfer_object);
auto target = dynamic_pointer_cast<TransferTarget>(this->_transfer_object);
if(source && source->stream_index() != source->byte_length()) {
this->call_callback_failed("received disconnect while transmitting (" + to_string(target->stream_index()) + "/" + to_string(target->expected_length()) + ")");
} else if(target && target->stream_index() != target->expected_length()) {
this->call_callback_failed("received disconnect while receiving (" + to_string(target->stream_index()) + "/" + to_string(target->expected_length()) + ")");
} else
this->call_callback_finished(false);
}
this->finalize(false);
}
void Transfer::handle_connected() {
log_info(category::file_transfer, tr("Transfer connected. Sending key {}"), this->_options->transfer_key);
this->_state = state::CONNECTED;
event_add(this->event_read, &this->alive_check_timeout);
this->handle()->execute_event_loop();
this->_write_message(pipes::buffer_view{this->_options->transfer_key.data(), this->_options->transfer_key.length()});
this->call_callback_connected();
//We dont have to add a timeout to write for prebuffering because its already done by writing this message
}
void Transfer::call_callback_connected() {
if(this->callback_start)
this->callback_start();
}
void Transfer::call_callback_failed(const std::string &error) {
if(this->callback_failed)
this->callback_failed(error);
}
void Transfer::call_callback_finished(bool aborted) {
if(this->callback_finished)
this->callback_finished(aborted);
}
void Transfer::call_callback_process(size_t current, size_t max) {
auto now = system_clock::now();
if(now - milliseconds{500} > this->last_process_call)
this->last_process_call = now;
else
return;
if(this->callback_process)
this->callback_process(current, max);
}
FileTransferManager::FileTransferManager() {}
FileTransferManager::~FileTransferManager() {}
void FileTransferManager::initialize() {
this->event_io_canceled = false;
this->event_io = event_base_new();
this->event_io_thread = std::thread(&FileTransferManager::_execute_event_loop, this);
}
void FileTransferManager::finalize() {
this->event_io_canceled = true;
this->event_io_condition.notify_all();
event_base_loopexit(this->event_io, nullptr);
this->event_io_thread.join();
//TODO drop all file transfers!
event_base_free(this->event_io);
this->event_io = nullptr;
}
void FileTransferManager::_execute_event_loop() {
while(!this->event_io_canceled) {
this->event_execute = false;
event_base_loop(this->event_io, 0);
if(this->running_transfers().size() > 0) {
this_thread::sleep_for(milliseconds(50));
} else {
unique_lock lock(this->event_io_lock);
this->event_io_condition.wait_for(lock, minutes(1), [&]{
return this->event_io_canceled || this->event_execute;
});
}
}
}
std::shared_ptr<Transfer> FileTransferManager::register_transfer(std::string& error, const std::shared_ptr<tc::ft::TransferObject> &object, std::unique_ptr<tc::ft::TransferOptions> options) {
auto transfer = make_shared<Transfer>(this, object, move(options));
transfer->event_io = this->event_io;
if(!transfer->initialize(error)) {
//error = "failed to initialize transfer: " + error;
return nullptr;
}
{
lock_guard lock(this->_transfer_lock);
this->_running_transfers.push_back(transfer);
}
return transfer;
}
void FileTransferManager::drop_transfer(const std::shared_ptr<Transfer> &transfer) {
transfer->finalize(true);
{
lock_guard lock(this->_transfer_lock);
auto it = find(this->_running_transfers.begin(), this->_running_transfers.end(), transfer);
if(it != this->_running_transfers.end())
this->_running_transfers.erase(it);
}
}
void FileTransferManager::remove_transfer(tc::ft::Transfer *transfer) {
lock_guard lock(this->_transfer_lock);
this->_running_transfers.erase(remove_if(this->_running_transfers.begin(), this->_running_transfers.end(), [&](const shared_ptr<Transfer>& _t) {
return &*_t == transfer;
}), this->_running_transfers.end());
}
#ifdef NODEJS_API
NAN_MODULE_INIT(JSTransfer::Init) {
auto klass = Nan::New<v8::FunctionTemplate>(JSTransfer::NewInstance);
klass->SetClassName(Nan::New("JSTransfer").ToLocalChecked());
klass->InstanceTemplate()->SetInternalFieldCount(1);
Nan::SetPrototypeMethod(klass, "start", JSTransfer::_start);
constructor().Reset(Nan::GetFunction(klass).ToLocalChecked());
}
NAN_METHOD(JSTransfer::NewInstance) {
if (info.IsConstructCall()) {
if(info.Length() != 1 || !info[0]->IsObject()) {
Nan::ThrowError("invalid arguments");
return;
}
/*
* transfer_key: string;
* client_transfer_id: number;
* server_transfer_id: number;
* object: HandledTransferObject;
*/
auto options = info[0]->ToObject(Nan::GetCurrentContext()).ToLocalChecked();
v8::Local<v8::String> key = options->Get(Nan::New<v8::String>("transfer_key").ToLocalChecked()).As<v8::String>();
v8::Local<v8::Number> client_transfer_id = options->Get(Nan::New<v8::String>("client_transfer_id").ToLocalChecked()).As<v8::Number>();
v8::Local<v8::Number> server_transfer_id = options->Get(Nan::New<v8::String>("server_transfer_id").ToLocalChecked()).As<v8::Number>();
v8::Local<v8::String> remote_address = options->Get(Nan::New<v8::String>("remote_address").ToLocalChecked()).As<v8::String>();
v8::Local<v8::Number> remote_port = options->Get(Nan::New<v8::String>("remote_port").ToLocalChecked()).As<v8::Number>();
if(
key.IsEmpty() || !key->IsString() ||
remote_address.IsEmpty() || !remote_address->IsString() ||
remote_port.IsEmpty() || !remote_port->IsInt32() ||
client_transfer_id.IsEmpty() || !client_transfer_id->IsInt32() ||
server_transfer_id.IsEmpty() || !server_transfer_id->IsInt32()
) {
Nan::ThrowError("invalid argument types");
return;
}
auto wrapped_options = options->Get(Nan::New<v8::String>("object").ToLocalChecked()).As<v8::Object>();
if(!TransferObjectWrap::is_wrap(wrapped_options)) {
Nan::ThrowError("invalid handle");
return;
}
auto transfer_object = ObjectWrap::Unwrap<TransferObjectWrap>(wrapped_options)->target();
assert(transfer_object);
auto t_options = make_unique<TransferOptions>();
t_options->transfer_key = *Nan::Utf8String(key);
t_options->client_transfer_id = client_transfer_id->Int32Value(Nan::GetCurrentContext()).FromMaybe(0);
t_options->server_transfer_id = server_transfer_id->Int32Value(Nan::GetCurrentContext()).FromMaybe(0);
t_options->remote_address = *Nan::Utf8String(remote_address);
t_options->remote_port = remote_port->Int32Value(Nan::GetCurrentContext()).FromMaybe(0);
string error;
auto transfer = transfer_manager->register_transfer(error, transfer_object, move(t_options));
if(!transfer) {
Nan::ThrowError(Nan::New<v8::String>("failed to create transfer: " + error).ToLocalChecked());
return;
}
auto js_instance = new JSTransfer(transfer);
js_instance->Wrap(info.This());
js_instance->_self_ref = true;
js_instance->Ref(); /* increase ref counter because file transfer is running */
info.GetReturnValue().Set(info.This());
} else {
if(info.Length() != 1) {
Nan::ThrowError("invalid argument count");
return;
}
v8::Local<v8::Function> cons = Nan::New(constructor());
v8::Local<v8::Value> argv[1] = {info[0]};
Nan::TryCatch try_catch;
auto result = Nan::NewInstance(cons, info.Length(), argv);
if(try_catch.HasCaught()) {
try_catch.ReThrow();
return;
}
info.GetReturnValue().Set(result.ToLocalChecked());
}
}
JSTransfer::JSTransfer(std::shared_ptr<tc::ft::Transfer> transfer) : _transfer(move(transfer)) {
this->call_failed = Nan::async_callback([&](std::string error) {
Nan::HandleScope scope;
this->callback_failed(error);
});
this->call_finished = Nan::async_callback([&](bool f) {
Nan::HandleScope scope;
this->callback_finished(f);
});
this->call_start = Nan::async_callback([&] {
Nan::HandleScope scope;
this->callback_start();
});
this->call_progress = Nan::async_callback([&](uint64_t a, uint64_t b) {
Nan::HandleScope scope;
this->callback_progress(a, b);
});
this->_transfer->callback_failed = [&](std::string error) { this->call_failed(std::forward<string>(error)); };
this->_transfer->callback_finished = [&](bool f) { this->call_finished(std::forward<bool>(f)); };
this->_transfer->callback_start = [&] { this->call_start(); };
this->_transfer->callback_process = [&](uint64_t a, uint64_t b) { this->call_progress.call_cpy(a, b); };
}
JSTransfer::~JSTransfer() {
cout << "JS dealloc" << endl;
this->_transfer->callback_failed = NULL;
this->_transfer->callback_finished = NULL;
this->_transfer->callback_start = NULL;
this->_transfer->callback_process = NULL;
}
NAN_METHOD(JSTransfer::destory_transfer) {
//TODO!
Nan::ThrowError("Not implemented!");
}
NAN_METHOD(JSTransfer::_start) {
return ObjectWrap::Unwrap<JSTransfer>(info.Holder())->start(info);
}
NAN_METHOD(JSTransfer::start) {
if(!this->_transfer->connect()) {
Nan::ThrowError("failed to connect");
return;
}
log_info(category::file_transfer, tr("Connecting to {}:{}"), this->_transfer->options().remote_address, this->_transfer->options().remote_port);
}
NAN_METHOD(JSTransfer::_abort) {
return ObjectWrap::Unwrap<JSTransfer>(info.Holder())->abort(info);
}
NAN_METHOD(JSTransfer::abort) {
//TODO!
}
void JSTransfer::callback_finished(bool flag) {
if(this->_self_ref) {
this->_self_ref = false;
this->Unref();
}
auto callback = Nan::Get(this->handle(), Nan::New<v8::String>("callback_finished").ToLocalChecked()).ToLocalChecked().As<v8::Function>();
if(callback.IsEmpty() || !callback->IsFunction())
return;
v8::Local<v8::Value> arguments[1];
arguments[0] = Nan::New<v8::Boolean>(flag);
callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, arguments);
}
void JSTransfer::callback_start() {
auto callback = Nan::Get(this->handle(), Nan::New<v8::String>("callback_start").ToLocalChecked()).ToLocalChecked().As<v8::Function>();
if(callback.IsEmpty() || !callback->IsFunction())
return;
callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 0, nullptr);
}
void JSTransfer::callback_progress(uint64_t a, uint64_t b) {
auto callback = Nan::Get(this->handle(), Nan::New<v8::String>("callback_progress").ToLocalChecked()).ToLocalChecked().As<v8::Function>();
if(callback.IsEmpty() || !callback->IsFunction())
return;
v8::Local<v8::Value> arguments[2];
arguments[0] = Nan::New<v8::Number>(a);
arguments[1] = Nan::New<v8::Number>(b);
callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 2, arguments);
}
void JSTransfer::callback_failed(std::string error) {
if(this->_self_ref) {
this->_self_ref = false;
this->Unref();
}
auto callback = Nan::Get(this->handle(), Nan::New<v8::String>("callback_failed").ToLocalChecked()).ToLocalChecked().As<v8::Function>();
if(callback.IsEmpty() || !callback->IsFunction())
return;
v8::Local<v8::Value> arguments[1];
arguments[0] = Nan::New<v8::String>(error).ToLocalChecked();
callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, arguments);
}
#endif
@@ -1,228 +0,0 @@
#pragma once
#include <deque>
#include <mutex>
#include <memory>
#include <utility>
#include <cstdio>
#include <string>
#include <thread>
#include <functional>
#include <condition_variable>
#include <event.h>
#include <pipes/buffer.h>
#include <cstring>
#if NODEJS_API
#include <nan.h>
#include <NanEventCallback.h>
#include "../../logger.h"
#endif
namespace tc {
namespace ft {
namespace error {
enum value : int8_t {
success = 0,
custom = 1,
custom_recoverable = 2,
would_block = 3,
out_of_space = 4
};
}
class TransferObject {
public:
explicit TransferObject() {}
virtual std::string name() const = 0;
virtual bool initialize(std::string& /* error */) = 0;
virtual void finalize() = 0;
};
class TransferSource : public TransferObject {
public:
virtual uint64_t byte_length() const = 0;
virtual uint64_t stream_index() const = 0;
virtual error::value read_bytes(std::string& /* error */, uint8_t* /* buffer */, uint64_t& /* max length/result length */) = 0;
private:
};
class TransferTarget : public TransferObject {
public:
TransferTarget() {}
virtual uint64_t expected_length() const = 0;
virtual uint64_t stream_index() const = 0;
virtual error::value write_bytes(std::string& /* error */, uint8_t* /* buffer */, uint64_t /* max length */) = 0;
};
struct TransferOptions {
std::string remote_address;
uint16_t remote_port = 0;
std::string transfer_key{};
uint32_t client_transfer_id = 0;
uint32_t server_transfer_id = 0;
};
class FileTransferManager;
class Transfer {
friend class FileTransferManager;
public:
struct state {
enum value {
UNINITIALIZED,
CONNECTING,
CONNECTED,
DISCONNECTING
};
};
typedef std::function<void()> callback_start_t;
typedef std::function<void(bool /* aborted */)> callback_finished_t;
typedef std::function<void(const std::string& /* error */)> callback_failed_t;
typedef std::function<void(uint64_t /* current index */, uint64_t /* max index */)> callback_process_t;
explicit Transfer(FileTransferManager* handle, std::shared_ptr<TransferObject> transfer_object, std::unique_ptr<TransferOptions> options) :
_transfer_object(std::move(transfer_object)),
_handle(handle),
_options(std::move(options)) {
log_allocate("Transfer", this);
}
~Transfer();
bool initialize(std::string& /* error */);
void finalize(bool /* blocking */ = true);
bool connect();
bool connected() { return this->_state > state::UNINITIALIZED; }
FileTransferManager* handle() { return this->_handle; }
std::shared_ptr<TransferObject> transfer_object() { return this->_transfer_object; }
const TransferOptions& options() { return *this->_options; }
callback_start_t callback_start{nullptr};
callback_finished_t callback_finished{nullptr};
callback_failed_t callback_failed{nullptr};
callback_process_t callback_process{nullptr};
private:
static void _callback_read(evutil_socket_t, short, void*);
static void _callback_write(evutil_socket_t, short, void*);
sockaddr_storage remote_address{};
FileTransferManager* _handle;
std::unique_ptr<TransferOptions> _options;
state::value _state = state::UNINITIALIZED;
std::shared_ptr<TransferObject> _transfer_object;
std::mutex event_lock;
event_base* event_io = nullptr; /* gets assigned by the manager */
::event* event_read = nullptr;
::event* event_write = nullptr;
std::chrono::system_clock::time_point last_source_read;
std::chrono::system_clock::time_point last_target_write;
std::mutex queue_lock;
std::deque<pipes::buffer> write_queue;
void _write_message(const pipes::buffer_view& /* buffer */);
int _socket = 0;
timeval alive_check_timeout{1, 0};
timeval write_timeout{1, 0};
/*
* Upload mode:
* Write the buffers left in write_queue, and if the queue length is less then 12 create new buffers.
* This event will as well be triggered every second as timeout, to create new buffers if needed
*/
void callback_write(short /* flags */);
void callback_read(short /* flags */);
/* called within the write/read callback */
void handle_disconnect();
void handle_connected();
void call_callback_connected();
void call_callback_failed(const std::string& /* reason */);
void call_callback_finished(bool /* aborted */);
void call_callback_process(size_t /* current */, size_t /* max */);
std::chrono::system_clock::time_point last_process_call;
};
class FileTransferManager {
public:
FileTransferManager();
~FileTransferManager();
void initialize();
void finalize();
std::shared_ptr<Transfer> register_transfer(std::string& error, const std::shared_ptr<TransferObject>& /* object */, std::unique_ptr<TransferOptions> /* options */);
std::deque<std::shared_ptr<Transfer>> running_transfers() {
std::lock_guard lock(this->_transfer_lock);
return this->_running_transfers;
}
void drop_transfer(const std::shared_ptr<Transfer>& /* transfer */);
void remove_transfer(Transfer*); /* internal use */
inline void execute_event_loop() {
this->event_execute = true;
this->event_io_condition.notify_all();
}
private:
bool event_execute = false;
bool event_io_canceled = false;
std::mutex event_io_lock;
std::condition_variable event_io_condition;
std::thread event_io_thread;
event_base* event_io = nullptr;
::event* event_cleanup = nullptr;
std::mutex _transfer_lock;
std::deque<std::shared_ptr<Transfer>> _running_transfers;
void _execute_event_loop();
};
#ifdef NODEJS_API
class JSTransfer : public Nan::ObjectWrap {
public:
static NAN_MODULE_INIT(Init);
static NAN_METHOD(NewInstance);
static inline Nan::Persistent<v8::Function> & constructor() {
static Nan::Persistent<v8::Function> my_constructor;
return my_constructor;
}
explicit JSTransfer(std::shared_ptr<Transfer> transfer);
~JSTransfer();
NAN_METHOD(start);
NAN_METHOD(abort);
static NAN_METHOD(destory_transfer);
private:
static NAN_METHOD(_start);
static NAN_METHOD(_abort);
std::shared_ptr<Transfer> _transfer;
Nan::callback_t<bool> call_finished;
Nan::callback_t<> call_start;
Nan::callback_t<uint64_t, uint64_t> call_progress;
Nan::callback_t<std::string> call_failed;
void callback_finished(bool);
void callback_start();
void callback_progress(uint64_t, uint64_t);
void callback_failed(std::string);
bool _self_ref = false;
};
#endif
}
}
extern tc::ft::FileTransferManager* transfer_manager;
@@ -1,290 +0,0 @@
#include "FileTransferObject.h"
#include "../../logger.h"
#include <iostream>
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
using namespace tc;
using namespace tc::ft;
using namespace std;
#ifdef NODEJS_API
TransferJSBufferTarget::TransferJSBufferTarget() {
log_allocate("TransferJSBufferTarget", this);
if(!this->_js_buffer.IsEmpty()) {
assert(v8::Isolate::GetCurrent());
this->_js_buffer.Reset();
}
}
TransferJSBufferTarget::~TransferJSBufferTarget() {
log_free("TransferJSBufferTarget", this);
}
bool TransferJSBufferTarget::initialize(std::string &error) {
return true; /* we've already have data */
}
void TransferJSBufferTarget::finalize() { }
uint64_t TransferJSBufferTarget::stream_index() const {
return this->_js_buffer_index;
}
error::value TransferJSBufferTarget::write_bytes(std::string &error, uint8_t *source, uint64_t length) {
uint64_t write_length = length;
if(length > this->_js_buffer_length - this->_js_buffer_index)
write_length = this->_js_buffer_length - this->_js_buffer_index;
if(write_length > 0) {
memcpy((char*) this->_js_buffer_source + this->_js_buffer_index, source, write_length);
this->_js_buffer_index += write_length;
}
if(write_length == 0)
return error::out_of_space;
return error::success;
}
NAN_METHOD(TransferJSBufferTarget::create_from_buffer) {
if(info.Length() != 1 || !info[0]->IsArrayBuffer()) {
Nan::ThrowError("invalid argument");
return;
}
auto buffer = info[0].As<v8::ArrayBuffer>();
auto instance = make_shared<TransferJSBufferTarget>();
instance->_js_buffer = v8::Global<v8::ArrayBuffer>(info.GetIsolate(), info[0].As<v8::ArrayBuffer>());
instance->_js_buffer_source = buffer->GetContents().Data();
instance->_js_buffer_length = buffer->GetContents().ByteLength();
instance->_js_buffer_index = 0;
auto object_wrap = new TransferObjectWrap(instance);
auto object = Nan::NewInstance(Nan::New(TransferObjectWrap::constructor()), 0, nullptr).ToLocalChecked();
object_wrap->do_wrap(object);
info.GetReturnValue().Set(object);
}
TransferJSBufferSource::~TransferJSBufferSource() {
log_free("TransferJSBufferSource", this);
if(!this->_js_buffer.IsEmpty()) {
assert(v8::Isolate::GetCurrent());
this->_js_buffer.Reset();
}
}
TransferJSBufferSource::TransferJSBufferSource() {
log_allocate("TransferJSBufferSource", this);
}
bool TransferJSBufferSource::initialize(std::string &string) { return true; }
void TransferJSBufferSource::finalize() { }
uint64_t TransferJSBufferSource::stream_index() const {
return this->_js_buffer_index;
}
uint64_t TransferJSBufferSource::byte_length() const {
return this->_js_buffer_length;
}
error::value TransferJSBufferSource::read_bytes(std::string &error, uint8_t *target, uint64_t &length) {
auto org_length = length;
if(this->_js_buffer_index + length > this->_js_buffer_length)
length = this->_js_buffer_length - this->_js_buffer_index;
memcpy(target, (char*) this->_js_buffer_source + this->_js_buffer_index, length);
this->_js_buffer_index += length;
if(org_length != 0 && length == 0)
return error::out_of_space;
return error::success;
}
NAN_METHOD(TransferJSBufferSource::create_from_buffer) {
if(info.Length() != 1 || !info[0]->IsArrayBuffer()) {
Nan::ThrowError("invalid argument");
return;
}
auto buffer = info[0].As<v8::ArrayBuffer>();
auto instance = make_shared<TransferJSBufferSource>();
instance->_js_buffer = v8::Global<v8::ArrayBuffer>(info.GetIsolate(), info[0].As<v8::ArrayBuffer>());
instance->_js_buffer_source = buffer->GetContents().Data();
instance->_js_buffer_length = buffer->GetContents().ByteLength();
instance->_js_buffer_index = 0;
auto object_wrap = new TransferObjectWrap(instance);
auto object = Nan::NewInstance(Nan::New(TransferObjectWrap::constructor()), 0, nullptr).ToLocalChecked();
object_wrap->do_wrap(object);
info.GetReturnValue().Set(object);
}
NAN_MODULE_INIT(TransferObjectWrap::Init) {
auto klass = Nan::New<v8::FunctionTemplate>(TransferObjectWrap::NewInstance);
klass->SetClassName(Nan::New("TransferObjectWrap").ToLocalChecked());
klass->InstanceTemplate()->SetInternalFieldCount(1);
constructor().Reset(Nan::GetFunction(klass).ToLocalChecked());
}
NAN_METHOD(TransferObjectWrap::NewInstance) {
if(!info.IsConstructCall())
Nan::ThrowError("invalid invoke!");
}
void TransferObjectWrap::do_wrap(v8::Local<v8::Object> object) {
this->Wrap(object);
auto source = dynamic_pointer_cast<TransferSource>(this->target());
auto target = dynamic_pointer_cast<TransferTarget>(this->target());
auto direction = source ? "upload" : "download";
Nan::Set(object,
Nan::New<v8::String>("direction").ToLocalChecked(),
v8::String::NewFromUtf8(Nan::GetCurrentContext()->GetIsolate(), direction).ToLocalChecked()
);
Nan::Set(object,
Nan::New<v8::String>("name").ToLocalChecked(),
v8::String::NewFromUtf8(Nan::GetCurrentContext()->GetIsolate(), this->target()->name().c_str()).ToLocalChecked()
);
if(source) {
Nan::Set(object,
Nan::New<v8::String>("total_size").ToLocalChecked(),
Nan::New<v8::Number>(source->byte_length())
);
}
if(target) {
Nan::Set(object,
Nan::New<v8::String>("expected_size").ToLocalChecked(),
Nan::New<v8::Number>(target->expected_length())
);
}
}
#endif
TransferFileSource::TransferFileSource(std::string path, std::string name) : _path{std::move(path)}, _name{std::move(name)} {
if(!this->_path.empty()) {
if(this->_path.back() == '/')
this->_path.pop_back();
#ifdef WIN32
if(this->_path.back() == '\\')
this->_path.pop_back();
#endif
}
}
#ifdef WIN32
#define u8path path
#endif
uint64_t TransferFileSource::byte_length() const {
if(file_size.has_value())
return file_size.value();
auto file = fs::u8path(this->_path) / fs::u8path(this->_name);
error_code error;
auto size = fs::file_size(file,error);
if(error)
size = 0;
return (this->file_size = std::make_optional<size_t>(size)).value();
}
bool TransferFileSource::initialize(std::string &error) {
auto file = fs::u8path(this->_path) / fs::u8path(this->_name);
error_code errc;
if(!fs::exists(file)) {
error = "file not found";
return false;
}
if(errc) {
error = "failed to test for file existence: " + to_string(errc.value()) + "/" + errc.message();
return false;
}
if(!fs::is_regular_file(file, errc)) {
error = "target file isn't a regular file";
return false;
}
if(errc) {
error = "failed to test for file regularity: " + to_string(errc.value()) + "/" + errc.message();
return false;
}
this->file_stream = std::ifstream{file, std::ifstream::in | std::ifstream::binary};
if(!this->file_stream) {
error = "failed to open file";
return false;
}
this->file_stream.seekg(0, std::ifstream::end);
auto length = this->file_stream.tellg();
if(length != this->byte_length()) {
error = "file length missmatch";
return false;
}
this->file_stream.seekg(0, std::ifstream::beg);
this->position = 0;
return true;
}
void TransferFileSource::finalize() {
if(this->file_stream)
this->file_stream.close();
this->position = 0;
}
error::value TransferFileSource::read_bytes(std::string &error, uint8_t *buffer, uint64_t &length) {
auto result = this->file_stream.readsome((char*) buffer, length);
if(result > 0) {
length = result;
this->position += result;
return error::success;
}
if(!this->file_stream) {
if(this->file_stream.eof())
error = "eof reached";
else
error = "io error. failed to read";
} else {
error = "read returned " + to_string(result) + "/" + to_string(length);
}
return error::custom;
}
uint64_t TransferFileSource::stream_index() const {
return this->position;
}
#ifdef NODEJS_API
NAN_METHOD(TransferFileSource::create) {
if(info.Length() != 2 || !info[0]->IsString() || !info[1]->IsString()) {
Nan::ThrowError("invalid argument");
return;
}
auto instance = make_shared<TransferFileSource>(*Nan::Utf8String{info[0]}, *Nan::Utf8String{info[1]});
auto object_wrap = new TransferObjectWrap(instance);
auto object = Nan::NewInstance(Nan::New(TransferObjectWrap::constructor()), 0, nullptr).ToLocalChecked();
object_wrap->do_wrap(object);
info.GetReturnValue().Set(object);
}
#endif
@@ -1,115 +0,0 @@
#pragma once
#include <fstream>
#include "FileTransferManager.h"
namespace tc {
namespace ft {
class TransferFileSource : public TransferSource {
public:
TransferFileSource(std::string /* path */, std::string /* name */);
[[nodiscard]] inline std::string file_path() const { return this->_path; }
[[nodiscard]] inline std::string file_name() const { return this->_name; }
std::string name() const override { return "TransferFileSource"; }
bool initialize(std::string &string) override;
void finalize() override;
uint64_t byte_length() const override;
uint64_t stream_index() const override;
error::value read_bytes(std::string &string, uint8_t *uint8, uint64_t &uint64) override;
#ifdef NODEJS_API
static NAN_METHOD(create);
#endif
private:
std::string _path;
std::string _name;
uint64_t position{0};
std::ifstream file_stream{};
mutable std::optional<size_t> file_size;
};
#ifdef NODEJS_API
class TransferObjectWrap : public Nan::ObjectWrap {
public:
static NAN_MODULE_INIT(Init);
static NAN_METHOD(NewInstance);
static inline bool is_wrap(const v8::Local<v8::Value>& value) {
if(value.As<v8::Object>().IsEmpty())
return false;
return value->InstanceOf(Nan::GetCurrentContext(), Nan::New<v8::Function>(constructor())).FromMaybe(false);
}
static inline Nan::Persistent<v8::Function> & constructor() {
static Nan::Persistent<v8::Function> my_constructor;
return my_constructor;
}
explicit TransferObjectWrap(std::shared_ptr<TransferObject> object) : _transfer(std::move(object)) {
}
~TransferObjectWrap() = default;
void do_wrap(v8::Local<v8::Object> object);
std::shared_ptr<TransferObject> target() { return this->_transfer; }
private:
std::shared_ptr<TransferObject> _transfer;
};
class TransferJSBufferSource : public TransferSource {
public:
TransferJSBufferSource();
virtual ~TransferJSBufferSource();
std::string name() const override { return "TransferJSBufferSource"; }
bool initialize(std::string &string) override;
void finalize() override;
uint64_t stream_index() const override;
uint64_t byte_length() const override;
error::value read_bytes(std::string &string, uint8_t *uint8, uint64_t &uint64) override;
static NAN_METHOD(create_from_buffer);
private:
v8::Global<v8::ArrayBuffer> _js_buffer;
void* _js_buffer_source;
uint64_t _js_buffer_length;
uint64_t _js_buffer_index;
};
class TransferJSBufferTarget : public TransferTarget {
public:
TransferJSBufferTarget();
virtual ~TransferJSBufferTarget();
std::string name() const override { return "TransferJSBufferTarget"; }
bool initialize(std::string &string) override;
void finalize() override;
uint64_t stream_index() const override;
uint64_t expected_length() const override { return this->_js_buffer_length; }
error::value write_bytes(std::string &string, uint8_t *uint8, uint64_t uint64) override;
static NAN_METHOD(create_from_buffer);
private:
v8::Global<v8::ArrayBuffer> _js_buffer;
void* _js_buffer_source;
uint64_t _js_buffer_length;
uint64_t _js_buffer_index;
};
#endif
}
}