diff --git a/github b/github index 06391c6..2dd1f60 160000 --- a/github +++ b/github @@ -1 +1 @@ -Subproject commit 06391c6cdd772c2f83c1387960f7224f7cd9f514 +Subproject commit 2dd1f60e8d034a68bbca9afe294070c8a83caa9e diff --git a/modules/core/main_window.ts b/modules/core/main_window.ts index f3555c7..e08af47 100644 --- a/modules/core/main_window.ts +++ b/modules/core/main_window.ts @@ -61,11 +61,30 @@ function spawn_main_window(entry_point: string) { }); }); - main_window.webContents.on('new-window', (event, url, frameName, disposition, options, additionalFeatures) => { - console.log("Got new window " + frameName); - const url_preview = require("./url-preview"); - url_preview.open_preview(url); + main_window.webContents.on('new-window', (event, url_str, frameName, disposition, options, additionalFeatures) => { event.preventDefault(); + try { + let url: URL; + try { + url = new URL(url_str); + } catch(error) { + throw "failed to parse URL"; + } + + { + let protocol = url.protocol.endsWith(":") ? url.protocol.substring(0, url.protocol.length - 1) : url.protocol; + if(protocol !== "https" && protocol !== "http") { + throw "invalid protocol (" + protocol + "). HTTP(S) are only supported!"; + } + } + + console.log("Got new window " + frameName); + const url_preview = require("./url-preview"); + url_preview.open_preview(url_str); + } catch(error) { + console.error("Failed to open preview window for URL %s: %o", url_str, error); + dialog.showErrorBox("Failed to open preview", "Failed to open preview URL: " + url_str + "\nError: " + error); + } }); main_window.webContents.on('crashed', event => { diff --git a/modules/core/url-preview/index.ts b/modules/core/url-preview/index.ts index 79e9c95..408ab4b 100644 --- a/modules/core/url-preview/index.ts +++ b/modules/core/url-preview/index.ts @@ -77,6 +77,7 @@ export async function open_preview(url: string) { } } + console.log("Opening URL '%s' as preview.", url); global_window.webContents.send('preview', url); if(!global_window.isFocused()) global_window.focus(); diff --git a/native/serverconnection/src/EventLoop.cpp b/native/serverconnection/src/EventLoop.cpp index 1f51c6d..1f987dd 100644 --- a/native/serverconnection/src/EventLoop.cpp +++ b/native/serverconnection/src/EventLoop.cpp @@ -135,8 +135,18 @@ void EventExecutor::_executor(tc::event::EventExecutor *loop) { event_handler->_event_ptr = nullptr; lock.unlock(); - if(event_handler) - event_handler->event_execute(linked_entry->scheduled); + if(event_handler) { + if(event_handler->single_thread_executed()) { + auto execute_lock = event_handler->execute_lock(false); + if(!execute_lock) { + event_handler->event_execute_dropped(linked_entry->scheduled); + } else { + event_handler->event_execute(linked_entry->scheduled); + } + } else { + event_handler->event_execute(linked_entry->scheduled); + } + } delete linked_entry; } } \ No newline at end of file diff --git a/native/serverconnection/src/EventLoop.h b/native/serverconnection/src/EventLoop.h index 44596bb..ae1f98f 100644 --- a/native/serverconnection/src/EventLoop.h +++ b/native/serverconnection/src/EventLoop.h @@ -14,9 +14,21 @@ namespace tc { friend class EventExecutor; public: virtual void event_execute(const std::chrono::system_clock::time_point& /* scheduled timestamp */) = 0; + virtual void event_execute_dropped(const std::chrono::system_clock::time_point& /* scheduled timestamp */) {} + std::unique_lock execute_lock(bool force) { + if(force) + return std::unique_lock(this->_execute_mutex); + else + return std::unique_lock(this->_execute_mutex, std::try_to_lock); + } + + inline bool single_thread_executed() const { return this->_single_thread; } + inline void single_thread_executed(bool value) { this->_single_thread = value; } private: void* _event_ptr = nullptr; + bool _single_thread = true; /* if its set to true there might are some dropped executes! */ + std::mutex _execute_mutex; }; class EventExecutor { diff --git a/native/serverconnection/src/audio/AudioOutput.cpp b/native/serverconnection/src/audio/AudioOutput.cpp index 1c405c3..04e4561 100644 --- a/native/serverconnection/src/audio/AudioOutput.cpp +++ b/native/serverconnection/src/audio/AudioOutput.cpp @@ -28,7 +28,7 @@ ssize_t AudioOutputSource::pop_samples(void *buffer, size_t samples) { memcpy(buffer, (char *) buf->sample_data + this->channel_count * buf->sample_index * 4, sc * this->channel_count * 4); } else { #ifndef WIN32 - /* fot my debugger */ + /* for my debugger */ __asm__("nop"); #endif } diff --git a/native/serverconnection/src/bindings.cpp b/native/serverconnection/src/bindings.cpp index bf4167b..43ce921 100644 --- a/native/serverconnection/src/bindings.cpp +++ b/native/serverconnection/src/bindings.cpp @@ -23,6 +23,10 @@ #include "audio/js/AudioFilter.h" #include "connection/audio/AudioEventLoop.h" +#ifndef WIN32 + #include +#endif + extern "C" { #include #include @@ -69,7 +73,11 @@ tc::audio::AudioOutput* global_audio_output; NAN_MODULE_INIT(init) { logger::initialize_node(); - logger::info(category::general, "Hello World from C"); +#ifndef WIN32 + logger::info(category::general, tr("Hello World from C. PPID: {}, PID: {}"), getppid(), getpid()); +#else + logger::info(category::general, tr("Hello World from C."), getppid(), getpid()); +#endif /* { diff --git a/native/serverconnection/src/connection/ServerConnection.cpp b/native/serverconnection/src/connection/ServerConnection.cpp index 8c3d464..7acaa93 100644 --- a/native/serverconnection/src/connection/ServerConnection.cpp +++ b/native/serverconnection/src/connection/ServerConnection.cpp @@ -4,6 +4,7 @@ #include "audio/VoiceConnection.h" #include "audio/AudioSender.h" #include "../logger.h" +#include "../hwuid.h" #include #include @@ -11,8 +12,12 @@ #include #include #include +#include #include +//#define FUZZ_VOICE +//#define SHUFFLE_VOICE + using namespace std; using namespace std::chrono; using namespace tc::connection; @@ -195,6 +200,10 @@ void ServerConnection::schedule_resend(const std::chrono::system_clock::time_poi } } 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; @@ -231,8 +240,8 @@ NAN_METHOD(ServerConnection::connect) { return; } - unique_lock disconnect_lock(this->disconnect_lock, defer_lock); - if(!disconnect_lock.try_lock_for(chrono::milliseconds(500))) { + 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; } @@ -294,7 +303,6 @@ NAN_METHOD(ServerConnection::connect) { } 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(); @@ -333,7 +341,9 @@ NAN_METHOD(ServerConnection::disconnect) { return; } - this->protocol_handler->disconnect(*Nan::Utf8String(info[0])); + if(this->protocol_handler) { + this->protocol_handler->disconnect(*Nan::Utf8String(info[0])); + } } NAN_METHOD(ServerConnection::_error_message) { @@ -365,6 +375,11 @@ NAN_METHOD(ServerConnection::_send_command) { return ObjectWrap::Unwrap(info.Holder())->send_command(info); } 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; @@ -432,6 +447,7 @@ NAN_METHOD(ServerConnection::send_command) { cmd["client_version"] = "0.0.1 [Build: 1549713549]"; cmd["client_platform"] = "Linux"; cmd["client_version_sign"] = "7XvKmrk7uid2ixHFeERGqcC8vupeQqDypLtw2lY9slDNPojEv//F47UaDLG+TmVk4r6S0TseIKefzBpiRtLDAQ=="; + cmd[strobf("hwid").string()] = system_uuid(); /* we dont want anybody to patch this out */ } } this->protocol_handler->send_command(cmd); @@ -442,6 +458,11 @@ NAN_METHOD(ServerConnection::_send_voice_data) { } 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; @@ -480,11 +501,13 @@ NAN_METHOD(ServerConnection::send_voice_data_raw) { auto flag_head = info[2]->BooleanValue(info.GetIsolate()); auto voice_data = info[0].As()->Buffer(); - auto vs = this->voice_connection->voice_sender(); - - vs->send_data(voice_data->GetContents().Data(), voice_data->GetContents().ByteLength() / (4 * channels), sample_rate, channels); + 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 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 = make_shared(_buffer); @@ -501,13 +524,19 @@ void ServerConnection::send_voice_data(const void *buffer, size_t buffer_length, packet->enable_flag(ts::protocol::PacketFlag::Compressed); packet->enable_flag(ts::protocol::PacketFlag::Unencrypted); -//#define FUZZ_VOICE #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); #endif @@ -525,10 +554,10 @@ void ServerConnection::close_connection() { } this->event_loop_execute_connection_close = false; - if(this->socket) { + if(this->socket) this->socket->finalize(); - } - 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); } diff --git a/native/serverconnection/src/connection/audio/VoiceClient.cpp b/native/serverconnection/src/connection/audio/VoiceClient.cpp index 2a3ae73..7a47aec 100644 --- a/native/serverconnection/src/connection/audio/VoiceClient.cpp +++ b/native/serverconnection/src/connection/audio/VoiceClient.cpp @@ -14,6 +14,8 @@ using namespace tc::connection; extern tc::audio::AudioOutput* global_audio_output; +#define DEBUG_PREMATURE_PACKETS + #ifdef WIN32 #define _field_(name, value) value #else @@ -210,7 +212,7 @@ VoiceClient::VoiceClient(const std::shared_ptr&, uint16_t clien 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.025); + 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) @@ -221,14 +223,15 @@ VoiceClient::VoiceClient(const std::shared_ptr&, uint16_t clien 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."), this->_client_id); + 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(this->ref())); } } - 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(this->ref())); return false; }; this->output_source->on_overflow = [&](size_t count){ @@ -327,8 +330,10 @@ void VoiceClient::event_execute(const std::chrono::system_clock::time_point &sch play_count++; } +#ifdef DEBUG_PREMATURE_PACKETS if(play_count > 0) - log_debug(category::audio, tr("Replayed {} premature packets for client {}"), play_count, this->_client_id); + log_debug(category::audio, tr("Replayed (buffer underflow) {} premature packets for client {}"), play_count, this->_client_id); +#endif break; } } @@ -350,14 +355,19 @@ void VoiceClient::event_execute(const std::chrono::system_clock::time_point &sch 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(max_time).count()); audio::decode_event_loop->schedule(static_pointer_cast(this->ref())); } } -//FIXME premature packets dont work #define MAX_LOST_PACKETS (6) +//Note: This function must be executed single threaded void VoiceClient::process_encoded_buffer(const std::unique_ptr &buffer) { string error; @@ -425,9 +435,12 @@ void VoiceClient::process_encoded_buffer(const std::unique_ptr &b this->set_state(state::stopping); /* enqueue all premature packets (list should be already ordered!) */ - for(const auto& packet : codec_data->premature_packets) - this->output_source->enqueue_samples(packet.buffer); - codec_data->premature_packets.clear(); + { + 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; @@ -442,14 +455,15 @@ void VoiceClient::process_encoded_buffer(const std::unique_ptr &b if(diff <= MAX_LOST_PACKETS) { if(diff > 0) { - log_debug(category::voice_connection, - tr("Client {} dropped one or more audio packets. Old packet id: {}, New packet id: {}, Diff: {}"), - this->_client_id, old_packet_id, buffer->packet_id, diff); /* 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 = this->state() != state::stopped; + 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); @@ -459,31 +473,9 @@ void VoiceClient::process_encoded_buffer(const std::unique_ptr &b return; } } - - if(!premature) { + if(!premature) codec_data->last_packet_id = buffer->packet_id; - /* test if any premature got its original place */ - { - 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) - 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++; - } - - if(play_count > 0) - log_debug(category::audio, tr("Replayed {} premature packets for client {}"), play_count, this->_client_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())); @@ -523,23 +515,55 @@ void VoiceClient::process_encoded_buffer(const std::unique_ptr &b audio_buffer->sample_index = 0; memcpy(audio_buffer->sample_data, target_buffer, this->output_source->channel_count * resampled_samples * 4); - 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 */ + { + 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; + }); } - codec_data->premature_packets.insert(it, { - buffer->packet_id, - 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 + } } -} \ No newline at end of file +} + +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); +} diff --git a/native/serverconnection/src/connection/audio/VoiceClient.h b/native/serverconnection/src/connection/audio/VoiceClient.h index 9e3ce86..0400e1a 100644 --- a/native/serverconnection/src/connection/audio/VoiceClient.h +++ b/native/serverconnection/src/connection/audio/VoiceClient.h @@ -90,7 +90,7 @@ namespace tc { private: struct PrematureAudioPacket { uint16_t packet_id = 0; - std::shared_ptr buffer; + std::shared_ptr buffer{}; }; struct AudioCodec { @@ -138,9 +138,12 @@ namespace tc { 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> 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& /* buffer */); }; diff --git a/native/serverconnection/src/hwuid.cpp b/native/serverconnection/src/hwuid.cpp index 0ffe287..09cb289 100644 --- a/native/serverconnection/src/hwuid.cpp +++ b/native/serverconnection/src/hwuid.cpp @@ -121,9 +121,49 @@ std::string system_uuid() { return _cached_system_uuid; } +/* + +"Hello World": Avg raw: 18ms Avg obf: 20ms + +#include +using namespace std::chrono; int main() { - std::cout << "UUID: " << system_uuid() << "\n"; + #define TEST_MESSAGE ("Hello World I'm a bit longer that 50 character string! Hello World I'm a bit longer that 50 character string! Hello World I'm a bit longer that 50 character string! Hello World I'm a bit longer that 50 character string! Hello World I'm a bit longer that 50 character string! Hello World I'm a bit longer that 50 character string!") + + system_clock::time_point begin, end; + size_t sum_raw = 0, sum_obf = 0, runs = 50; + + std::cerr << "Testing raw\n"; + for(int i = 0; i < runs; i++) { + begin = system_clock::now(); + for(size_t index = 0; index < 1e6; index++) { + puts(TEST_MESSAGE); + } + end = system_clock::now(); + + auto ms = floor(end - begin).count(); + sum_raw += ms; + //std::cerr << "Raw test took " << ms << "ms\n"; + } + + std::cerr << "Testing obf\n"; + for(int i = 0; i < runs; i++) { + begin = system_clock::now(); + for(size_t index = 0; index < 1e6; index++) { + puts(strobf(TEST_MESSAGE).c_str()); + } + end = system_clock::now(); + + auto ms = floor(end - begin).count(); + sum_obf += ms; + //std::cerr << "Obf test took " << ms << "ms\n"; + } + std::cerr << "Avg raw: " << (sum_raw / runs) << "ms Avg obf: " << (sum_obf / runs) << "ms\n"; + + //std::cout << "UUID: " << system_uuid() << "\n"; return 1; } -//ADaX2mIRrC1uv83h \ No newline at end of file +//ADaX2mIRrC1uv83h + + */ \ No newline at end of file