#include "ServerConnection.h" #include "ProtocolHandler.h" #include "Socket.h" #include "audio/VoiceConnection.h" #include "audio/AudioSender.h" #include "../logger.h" #include "../hwuid.h" #include #include #include #include #include #include #include #include #include //#define FUZZ_VOICE //#define SHUFFLE_VOICE using namespace std; using namespace std::chrono; using namespace tc::connection; string ErrorHandler::get_error_message(ErrorHandler::ErrorId id) const { if(id == 0) { return "success"; } auto index = (-id - 1) % this->kErrorBacklog; assert(index >= 0 && index < this->kErrorBacklog); return this->error_messages[index]; } ErrorHandler::ErrorId ErrorHandler::register_error(const string &message) { auto index = this->error_index++ % this->kErrorBacklog; 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(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); Nan::SetPrototypeMethod(klass, "statistics", ServerConnection::statistics); constructor().Reset(Nan::GetFunction(klass).ToLocalChecked()); } NAN_METHOD(ServerConnection::new_instance) { //info.GetReturnValue().Set(Nan::New("Hello World").ToLocalChecked()); if (info.IsConstructCall()) { auto instance = new ServerConnection(); instance->Wrap(info.This()); instance->initialize(); //Nan::Set(info.This(), New("type").ToLocalChecked(), New(type)); info.GetReturnValue().Set(info.This()); } else { v8::Local 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(this); this->voice_connection = make_shared(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::ErrorId error_id) { Nan::HandleScope scope; /* lets update the server type */ { auto js_this = this->handle(); Nan::Set(js_this, Nan::New("server_type").ToLocalChecked(), Nan::New(this->protocol_handler->server_type)); } /* lets call the connect callback */ { v8::Local argv[1]; argv[0] = Nan::New(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::ErrorId error_id) { Nan::HandleScope scope; v8::Local argv[1]; argv[0] = Nan::New(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("_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(); } } #ifdef WIN32 inline std::string wsa_error_str(int code) { int err; char msgbuf[256]; // for a message up to 255 bytes. msgbuf[0] = '\0'; // Microsoft doesn't guarantee this on man page. err = WSAGetLastError(); FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, // flags NULL, // lpsource err, // message id MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT), // languageid msgbuf, // output buffer sizeof(msgbuf), // size of msgbuf, bytes NULL); // va_list of arguments if (!*msgbuf) sprintf(msgbuf, "%d", err); // provide error # if no string available return std::string{msgbuf}; } #endif 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("remote_host").ToLocalChecked()).ToLocalChecked(); auto remote_port = Nan::Get(arguments, Nan::New("remote_port").ToLocalChecked()).ToLocalChecked(); auto timeout = Nan::Get(arguments, Nan::New("timeout").ToLocalChecked()).ToLocalChecked(); auto callback = Nan::Get(arguments, Nan::New("callback").ToLocalChecked()).ToLocalChecked(); auto identity_key = Nan::Get(arguments, Nan::New("identity_key").ToLocalChecked()).ToLocalChecked(); auto teamspeak = Nan::Get(arguments, Nan::New("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(callback.As()); this->voice_connection->reset(); this->protocol_handler->reset(); std::optional identity{std::nullopt}; if(identity_key->IsString()) { auto identity_encoded = Nan::Utf8String{identity_key->ToString(Nan::GetCurrentContext()).ToLocalChecked()}; identity = std::make_optional( base64::decode(std::string_view{*identity_encoded, (size_t) identity_encoded.length()}) ); } if(!this->protocol_handler->initialize_identity(identity)) { Nan::ThrowError(tr("failed to initialize crypto 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)); break; case AF_INET6: ((sockaddr_in6*) &remote_address)->sin6_port = htons(remote_port->Int32Value(Nan::GetCurrentContext()).FromMaybe(0)); break; default: break; } log_info(category::connection, tr("Connecting to {}."), net::to_string(remote_address)); this->socket = make_shared(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); }; this->socket->on_fatal_error = [&](int code, int detail) { #if WIN32 auto message = wsa_error_str(detail); #else auto message = std::string{strerror(detail)}; #endif this->execute_callback_disconnect.call((code == 1 ? tr("Failed to received data: ") : tr("Failed to send data: ")) + message, false); this->close_connection(); }; 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(info.Holder())->connected(info); } NAN_METHOD(ServerConnection::_connect) { return ObjectWrap::Unwrap(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(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(info[1].As()); 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(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_error_message( (ErrorHandler::ErrorId) info[0]->IntegerValue(Nan::GetCurrentContext()).FromMaybe(0) ); info.GetReturnValue().Set(Nan::New(error).ToLocalChecked()); } //send_command(command: string, arguments: any[], switches: string[]); template 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(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(); auto switches = info[2].As(); ts::Command cmd(*Nan::Utf8String(command)); for(size_t index = 0; index < arguments->Length(); index++) { auto object = Nan::GetLocal(arguments, (uint32_t) index); if(object.IsEmpty() || !object->IsObject()) { Nan::ThrowError(Nan::New("invalid parameter (" + to_string(index) + ")").ToLocalChecked()); return; } v8::Local properties = object->ToObject(Nan::GetCurrentContext()).ToLocalChecked()->GetOwnPropertyNames(Nan::GetCurrentContext()).ToLocalChecked(); for(uint32_t i = 0; i < properties->Length(); i++) { auto key = Nan::GetStringLocal(properties, i); 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(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("invalid parameter (" + to_string(index) + ":" + key_string + ")").ToLocalChecked()); return; } } } for(size_t index = 0; index < switches->Length(); index++) { auto object = Nan::GetStringLocal(switches, (uint32_t) index); if(object.IsEmpty()) { Nan::ThrowError(Nan::New("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, false); auto end = chrono::system_clock::now(); } NAN_METHOD(ServerConnection::_send_voice_data) { return ObjectWrap::Unwrap(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()->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(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()->Buffer(); auto vs = this->voice_connection ? this->voice_connection->voice_sender() : nullptr; if(vs) { vs->send_data((float*) voice_data->GetContents().Data(), voice_data->GetContents().ByteLength() / (4 * channels), sample_rate, channels); } } #ifdef SHUFFLE_VOICE static shared_ptr shuffle_cached_packet; #endif void ServerConnection::send_voice_data(const void *buffer, size_t buffer_length, uint8_t codec, bool head) { auto packet = ts::protocol::allocate_outgoing_client_packet(buffer_length + 3); packet->type_and_flags_ = ts::protocol::PacketType::VOICE; auto packet_payload = (uint8_t*) packet->payload; *(uint16_t*) packet_payload = htons(this->voice_packet_id++); packet_payload[2] = (uint8_t) codec; if(buffer_length > 0 && buffer) { memcpy(&packet_payload[3], buffer, buffer_length); } if(head) { packet->type_and_flags_ |= ts::protocol::PacketFlag::Compressed; } packet->type_and_flags_ |= 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(packet, false); #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->protocol_handler->do_close_connection(); } 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(); } if(auto vc{this->voice_connection}; vc) { vc->execute_tick(); } } void ServerConnection::_execute_callback_commands() { unique_ptr next_command; v8::Local 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("callback_command").ToLocalChecked()).ToLocalChecked().As(); 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 arguments[3]; arguments[0] = Nan::New(next_command->command()).ToLocalChecked(); auto parameters = Nan::New((int) next_command->bulkCount()); for(size_t index = 0; index < next_command->bulkCount(); index++) { auto object = Nan::New(); auto& bulk = next_command->operator[](index); for(const auto& key : bulk.keys()) Nan::Set(object, Nan::New(key).ToLocalChecked(), Nan::New(bulk[key].string()).ToLocalChecked()); Nan::Set(parameters, (uint32_t) index, object); } arguments[1] = parameters; auto switched = Nan::New((int) next_command->parms().size()); for(size_t index = 0; index < next_command->parms().size(); index++) { auto& key = next_command->parms()[index]; Nan::Set(parameters, (uint32_t) index, Nan::New(key).ToLocalChecked()); } arguments[2] = switched; callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 3, arguments); } } void ServerConnection::_execute_callback_voice() { unique_ptr next_packet; v8::Local 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("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::Local arguments[5]; v8::Local 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(next_packet->client_id); arguments[2] = Nan::New(next_packet->codec_id); arguments[3] = Nan::New(next_packet->flag_head); arguments[4] = Nan::New(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("callback_disconnect").ToLocalChecked()).ToLocalChecked().As(); if(callback.IsEmpty()) { cout << "Missing disconnect callback!" << endl; return; } v8::Local arguments[1]; arguments[0] = Nan::New(reason).ToLocalChecked(); callback->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, arguments); } NAN_METHOD(ServerConnection::_current_ping) { auto connection = ObjectWrap::Unwrap(info.Holder()); lock_guard lock{connection->disconnect_lock}; auto& phandler = connection->protocol_handler; if(phandler) info.GetReturnValue().Set((uint32_t) chrono::floor(phandler->current_ping()).count()); else info.GetReturnValue().Set(-1); } NAN_METHOD(ServerConnection::statistics) { auto connection = ObjectWrap::Unwrap(info.Holder()); lock_guard lock{connection->disconnect_lock}; auto& phandler = connection->protocol_handler; if(phandler) { auto statistics = phandler->statistics(); auto result = Nan::New(); Nan::Set(result, Nan::LocalString("voice_bytes_received"), Nan::New(statistics.voice_bytes_received)); Nan::Set(result, Nan::LocalString("voice_bytes_send"), Nan::New(statistics.voice_bytes_send)); Nan::Set(result, Nan::LocalString("control_bytes_received"), Nan::New(statistics.control_bytes_received)); Nan::Set(result, Nan::LocalString("control_bytes_send"), Nan::New(statistics.control_bytes_send)); info.GetReturnValue().Set(result); } else { info.GetReturnValue().Set(Nan::Undefined()); } }