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
-152
View File
@@ -1,152 +0,0 @@
#include <thread>
#include <vector>
#include <condition_variable>
#include <cassert>
#include "EventLoop.h"
using namespace std;
using namespace tc::event;
EventExecutor::EventExecutor(const std::string& thread_prefix) : thread_prefix(thread_prefix) {}
EventExecutor::~EventExecutor() {
unique_lock lock(this->lock);
this->_shutdown(lock);
this->_reset_events(lock);
}
bool EventExecutor::initialize(int threads) {
unique_lock lock(this->lock);
this->_shutdown(lock);
if(!lock.owns_lock())
lock.lock();
this->should_shutdown = false;
while(threads-- > 0) {
this->threads.emplace_back(&EventExecutor::_executor, this);
#ifndef WIN32
{
auto handle = this->threads.back().native_handle();
auto name = this->thread_prefix + to_string(this->threads.size());
pthread_setname_np(handle, name.c_str());
}
#endif
}
return true;
}
void EventExecutor::shutdown() {
unique_lock lock(this->lock);
this->_shutdown(lock);
}
bool EventExecutor::schedule(const std::shared_ptr<tc::event::EventEntry> &entry) {
unique_lock lock(this->lock);
if(!entry || entry->_event_ptr)
return true; /* already scheduled */
auto linked_entry = new LinkedEntry{};
linked_entry->entry = entry;
linked_entry->next = nullptr;
linked_entry->previous = this->tail;
linked_entry->scheduled = std::chrono::system_clock::now();
if(this->tail) {
this->tail->next = linked_entry;
this->tail = linked_entry;
} else {
this->head = linked_entry;
this->tail = linked_entry;
}
this->condition.notify_one();
entry->_event_ptr = linked_entry;
return true;
}
bool EventExecutor::cancel(const std::shared_ptr<tc::event::EventEntry> &entry) {
unique_lock lock(this->lock);
if(!entry || !entry->_event_ptr)
return false;
auto linked_entry = (LinkedEntry*) entry->_event_ptr;
this->head = linked_entry->next;
if(this->head) {
assert(linked_entry == this->head->previous);
this->head->previous = nullptr;
} else {
assert(linked_entry == this->tail);
this->tail = nullptr;
}
delete linked_entry;
entry->_event_ptr = nullptr;
return true;
}
void EventExecutor::_shutdown(std::unique_lock<std::mutex> &lock) {
if(!lock.owns_lock())
lock.lock();
this->should_shutdown = true;
this->condition.notify_all();
lock.unlock();
for(auto& thread : this->threads)
thread.join(); /* TODO: Timeout? */
lock.lock();
this->should_shutdown = false;
}
void EventExecutor::_reset_events(std::unique_lock<std::mutex> &lock) {
if(!lock.owns_lock())
lock.lock();
auto entry = this->head;
while(entry) {
auto next = entry->next;
delete entry;
entry = next;
}
this->head = nullptr;
this->tail = nullptr;
}
void EventExecutor::_executor(tc::event::EventExecutor *loop) {
while(true) {
unique_lock lock(loop->lock);
loop->condition.wait(lock, [&] { return loop->should_shutdown || loop->head != nullptr; });
if(loop->should_shutdown)
break;
if(!loop->head)
continue;
auto linked_entry = loop->head;
loop->head = linked_entry->next;
if(loop->head) {
assert(linked_entry == loop->head->previous);
loop->head->previous = nullptr;
} else {
assert(linked_entry == loop->tail);
loop->tail = nullptr;
}
auto event_handler = linked_entry->entry.lock();
assert(event_handler->_event_ptr == linked_entry);
event_handler->_event_ptr = nullptr;
lock.unlock();
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;
}
}
-67
View File
@@ -1,67 +0,0 @@
#pragma once
#include <mutex>
#include <memory>
#include <vector>
#include <string>
#include <thread>
#include <condition_variable>
namespace tc {
namespace event {
class EventExecutor;
class EventEntry {
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<std::mutex> execute_lock(bool force) {
if(force)
return std::unique_lock<std::mutex>(this->_execute_mutex);
else
return std::unique_lock<std::mutex>(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 {
public:
explicit EventExecutor(const std::string& /* thread prefix */);
virtual ~EventExecutor();
bool initialize(int /* num threads */);
bool schedule(const std::shared_ptr<EventEntry>& /* entry */);
bool cancel(const std::shared_ptr<EventEntry>& /* entry */); /* Note: Will not cancel already running executes */
void shutdown();
private:
struct LinkedEntry {
LinkedEntry* previous;
LinkedEntry* next;
std::chrono::system_clock::time_point scheduled;
std::weak_ptr<EventEntry> entry;
};
static void _executor(EventExecutor*);
void _shutdown(std::unique_lock<std::mutex>&);
void _reset_events(std::unique_lock<std::mutex>&);
bool should_shutdown = true;
std::vector<std::thread> threads;
std::mutex lock;
std::condition_variable condition;
LinkedEntry* head = nullptr;
LinkedEntry* tail = nullptr;
std::string thread_prefix;
};
}
}
@@ -1,102 +0,0 @@
#include "AudioDevice.h"
#include "../logger.h"
using namespace std;
using namespace tc;
using namespace tc::audio;
extern bool devices_cached(); /* if the result is false then the call to devices() may take a while */
extern std::deque<std::shared_ptr<AudioDevice>> devices();
bool _devices_cached = false;
mutex _audio_devices_lock;
deque<shared_ptr<AudioDevice>> _audio_devices{};
bool audio::devices_cached() {
return _devices_cached;
}
void audio::clear_device_cache() {
lock_guard lock(_audio_devices_lock);
_audio_devices.clear();
_devices_cached = false;
}
deque<shared_ptr<AudioDevice>> audio::devices() {
lock_guard lock(_audio_devices_lock);
if(_devices_cached)
return _audio_devices;
/* query devices */
auto device_count = Pa_GetDeviceCount();
if(device_count < 0) {
log_error(category::audio, tr("Pa_GetDeviceCount() returned {}"), device_count);
return {};
}
auto default_input_device = Pa_GetDefaultInputDevice();
auto default_output_device = Pa_GetDefaultOutputDevice();
for(PaDeviceIndex device_index = 0; device_index < device_count; device_index++) {
auto device_info = Pa_GetDeviceInfo(device_index);
if(!device_info) {
log_warn(category::audio, tr("Pa_GetDeviceInfo(...) failed for device {}"), device_index);
continue;
}
auto device_host_info = Pa_GetHostApiInfo(device_info->hostApi);
if(!device_host_info) {
log_warn(category::audio, tr("Pa_GetHostApiInfo(...) failed for device {} with host api {}"), device_index, device_info->hostApi);
continue;
}
auto info = make_shared<AudioDevice>();
info->device_id = device_index;
info->name = device_info->name;
info->max_inputs = device_info->maxInputChannels;
info->input_supported = device_info->maxInputChannels > 0;
info->max_outputs = device_info->maxOutputChannels;
info->output_supported = device_info->maxOutputChannels > 0;
info->is_default_input = device_index == default_input_device;
info->is_default_output = device_index == default_output_device;
info->driver = device_host_info->name;
info->is_default_driver_input = device_index == device_host_info->defaultInputDevice;
info->is_default_driver_output = device_index == device_host_info->defaultOutputDevice;
PaStreamParameters test_parameters{};
test_parameters.device = device_index;
test_parameters.sampleFormat = paFloat32;
test_parameters.suggestedLatency = 0; /* ignored by Pa_IsFormatSupported() */
test_parameters.hostApiSpecificStreamInfo = nullptr;
/*
if(info->input_supported) {
test_parameters.channelCount = device_info->maxInputChannels;
for(size_t index = 0; standard_sample_rates[index] > 0; index++) {
auto rate = standard_sample_rates[index];
auto err = Pa_IsFormatSupported(&test_parameters, nullptr, rate);
if(err == paFormatIsSupported)
info->supported_input_rates.push_back(rate);
}
}
if(info->output_supported) {
test_parameters.channelCount = device_info->maxOutputChannels;
for(size_t index = 0; standard_sample_rates[index] > 0; index++) {
auto rate = standard_sample_rates[index];
auto err = Pa_IsFormatSupported(nullptr, &test_parameters, rate);
if(err == paFormatIsSupported)
info->supported_output_rates.push_back(rate);
}
}
*/
_audio_devices.push_back(info);
}
_devices_cached = true;
return _audio_devices;
}
@@ -1,44 +0,0 @@
#pragma once
#include <mutex>
#include <deque>
#include <memory>
#include <iostream>
#include <functional>
#include <portaudio.h>
namespace tc {
namespace audio {
static constexpr double standard_sample_rates[] = {
8000.0, 9600.0, 11025.0, 12000.0, 16000.0, 22050.0, 24000.0, 32000.0,
44100.0, 48000.0, 88200.0, 96000.0, 192000.0, -1 /* negative terminated list */
};
struct AudioDevice {
PaDeviceIndex device_id;
bool is_default_output;
bool is_default_input;
bool is_default_driver_output;
bool is_default_driver_input;
bool input_supported;
bool output_supported;
std::string name;
std::string driver;
/*
std::vector<double> supported_input_rates;
std::vector<double> supported_output_rates;
*/
int max_inputs;
int max_outputs;
};
extern void clear_device_cache();
extern bool devices_cached(); /* if the result is false then the call to devices() may take a while */
extern std::deque<std::shared_ptr<AudioDevice>> devices();
}
}
@@ -1,165 +0,0 @@
#include <cstring>
#include <string>
#include "AudioInput.h"
#include "AudioReframer.h"
#include "../logger.h"
using namespace std;
using namespace tc;
using namespace tc::audio;
AudioConsumer::AudioConsumer(tc::audio::AudioInput *handle, size_t channel_count, size_t sample_rate, size_t frame_size) :
handle(handle),
channel_count(channel_count),
sample_rate(sample_rate) ,
frame_size(frame_size) {
if(this->frame_size > 0) {
this->reframer = make_unique<Reframer>(channel_count, frame_size);
this->reframer->on_frame = [&](const void* buffer) { this->handle_framed_data(buffer, this->frame_size); };
}
}
void AudioConsumer::handle_framed_data(const void *buffer, size_t samples) {
unique_lock read_callback_lock(this->on_read_lock);
auto function = this->on_read; /* copy */
read_callback_lock.unlock();
if(!function)
return;
function(buffer, samples);
}
void AudioConsumer::process_data(const void *buffer, size_t samples) {
if(this->reframer)
this->reframer->process(buffer, samples);
else
this->handle_framed_data(buffer, samples);
}
AudioInput::AudioInput(size_t channels, size_t rate) : _channel_count(channels), _sample_rate(rate) {}
AudioInput::~AudioInput() {
this->close_device();
lock_guard lock(this->consumers_lock);
for(const auto& consumer : this->_consumers)
consumer->handle = nullptr;
}
bool AudioInput::open_device(std::string& error, PaDeviceIndex index) {
lock_guard lock(this->input_stream_lock);
if(index == this->_current_device_index)
return true;
this->close_device();
this->_current_device_index = index;
if(index == paNoDevice)
return true;
this->_current_device = Pa_GetDeviceInfo(index);
if(!this->_current_device) {
this->_current_device_index = paNoDevice;
error = "failed to get device info";
return false;
}
PaStreamParameters parameters{};
memset(&parameters, 0, sizeof(parameters));
parameters.channelCount = (int) this->_channel_count;
parameters.device = this->_current_device_index;
parameters.sampleFormat = paFloat32;
parameters.suggestedLatency = this->_current_device->defaultLowOutputLatency;
auto err = Pa_OpenStream(&this->input_stream, &parameters, nullptr, (double) this->_sample_rate, paFramesPerBufferUnspecified, paClipOff, &AudioInput::_audio_callback, this);
if(err != paNoError) {
error = "Pa_OpenStream returned " + to_string(err);
return false;
}
return true;
}
bool AudioInput::record() {
lock_guard lock(this->input_stream_lock);
if(!this->input_stream)
return false;
if(Pa_IsStreamActive(this->input_stream))
return true;
auto err = Pa_StartStream(this->input_stream);
if(err != paNoError && err != paStreamIsNotStopped) {
log_error(category::audio, tr("Pa_StartStream returned {}"), err);
return false;
}
return true;
}
bool AudioInput::recording() {
lock_guard lock(this->input_stream_lock);
return this->input_stream && Pa_IsStreamActive(this->input_stream);
}
void AudioInput::stop() {
lock_guard lock(this->input_stream_lock);
if(this->input_stream) {
Pa_AbortStream(this->input_stream);
}
}
void AudioInput::close_device() {
lock_guard lock(this->input_stream_lock);
if(this->input_stream) {
/* TODO: Test for errors */
Pa_AbortStream(this->input_stream);
Pa_CloseStream(this->input_stream);
this->input_stream = nullptr;
}
}
std::shared_ptr<AudioConsumer> AudioInput::create_consumer(size_t frame_length) {
auto result = shared_ptr<AudioConsumer>(new AudioConsumer(this, this->_channel_count, this->_sample_rate, frame_length));
{
lock_guard lock(this->consumers_lock);
this->_consumers.push_back(result);
}
return result;
}
void AudioInput::delete_consumer(const std::shared_ptr<AudioConsumer> &source) {
{
lock_guard lock(this->consumers_lock);
auto it = find(this->_consumers.begin(), this->_consumers.end(), source);
if(it != this->_consumers.end())
this->_consumers.erase(it);
}
source->handle = nullptr;
}
int AudioInput::_audio_callback(const void *a, void *b, unsigned long c, const PaStreamCallbackTimeInfo* d, PaStreamCallbackFlags e, void *_ptr_audio_output) {
return reinterpret_cast<AudioInput*>(_ptr_audio_output)->audio_callback(a, b, c, d, e);
}
int AudioInput::audio_callback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags) {
if (!input) /* hmmm.. suspicious */
return 0;
if(this->_volume != 1) {
auto ptr = (float*) input;
auto left = frameCount * this->_channel_count;
while(left-- > 0)
*(ptr++) *= this->_volume;
}
auto begin = chrono::system_clock::now();
for(const auto& consumer : this->consumers()) {
consumer->process_data(input, frameCount);
}
auto end = chrono::system_clock::now();
auto ms = chrono::duration_cast<chrono::milliseconds>(end - begin).count();
if(ms > 5) {
log_warn(category::audio, tr("Processing of audio input needed {}ms. This could be an issue!"), chrono::duration_cast<chrono::milliseconds>(end - begin).count());
}
return 0;
}
@@ -1,84 +0,0 @@
#pragma once
#include <mutex>
#include <deque>
#include <memory>
#include <iostream>
#include <functional>
#include <portaudio.h>
#include <misc/spin_lock.h>
#include "AudioSamples.h"
namespace tc {
namespace audio {
class AudioInput;
class Reframer;
class AudioConsumer {
friend class AudioInput;
public:
AudioInput* handle;
size_t const channel_count = 0;
size_t const sample_rate = 0;
size_t const frame_size = 0;
spin_lock on_read_lock; /* locked to access the function */
std::function<void(const void* /* buffer */, size_t /* samples */)> on_read;
private:
AudioConsumer(AudioInput* handle, size_t channel_count, size_t sample_rate, size_t frame_size);
std::unique_ptr<Reframer> reframer;
std::mutex buffer_lock;
size_t buffered_samples = 0;
std::deque<std::shared_ptr<SampleBuffer>> sample_buffers;
void process_data(const void* /* buffer */, size_t /* samples */);
void handle_framed_data(const void* /* buffer */, size_t /* samples */);
};
class AudioInput {
public:
AudioInput(size_t /* channels */, size_t /* rate */);
virtual ~AudioInput();
bool open_device(std::string& /* error */, PaDeviceIndex);
bool record();
bool recording();
void stop();
void close_device();
PaDeviceIndex current_device() { return this->_current_device_index; }
std::deque<std::shared_ptr<AudioConsumer>> consumers() {
std::lock_guard lock(this->consumers_lock);
return this->_consumers;
}
std::shared_ptr<AudioConsumer> create_consumer(size_t /* frame size */);
void delete_consumer(const std::shared_ptr<AudioConsumer>& /* source */);
inline size_t channel_count() { return this->_channel_count; }
inline size_t sample_rate() { return this->_sample_rate; }
inline float volume() { return this->_volume; }
inline void set_volume(float value) { this->_volume = value; }
private:
static int _audio_callback(const void *, void *, unsigned long, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void*);
int audio_callback(const void *, void *, unsigned long, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags);
size_t const _channel_count;
size_t const _sample_rate;
std::mutex consumers_lock;
std::deque<std::shared_ptr<AudioConsumer>> _consumers;
std::recursive_mutex input_stream_lock;
const PaDeviceInfo* _current_device = nullptr;
PaDeviceIndex _current_device_index = paNoDevice;
PaStream* input_stream = nullptr;
float _volume = 1.f;
};
}
}
@@ -1,111 +0,0 @@
#include <cassert>
#include "AudioMerger.h"
#include "../logger.h"
#include <iostream>
using namespace std;
using namespace tc::audio;
/* technique based on http://www.vttoth.com/CMS/index.php/technical-notes/68 */
inline float merge_ab(float a, float b) {
/*
* Form: A,B := [0;n]
* Z = 2(A + B) - (2/n) * A * B - n
*
* For a range from 0 to 2: Z = 2(A + B) - AB - 2
*/
a += 1;
b += 1;
return (2 * (a + b) - a * b - 2) - 1;
}
bool merge::merge_sources(void *_dest, void *_src_a, void *_src_b, size_t channels, size_t samples) {
auto dest = (float*) _dest;
auto src_a = (float*) _src_a;
auto src_b = (float*) _src_b;
for(size_t index = 0; index < channels * samples; index++)
dest[index] = merge_ab(src_a[index], src_b[index]);
return true;
}
bool merge::merge_n_sources(void *dest, void **srcs, size_t src_length, size_t channels, size_t samples) {
assert(src_length > 0);
/* find the first non empty source */
while(!srcs[0]) {
srcs++;
src_length--;
if(src_length == 0)
return false;
}
memcpy(dest, srcs[0], channels * samples * 4);
srcs++;
src_length--;
while(src_length > 0) {
/* only invoke is srcs is not null! */
if(srcs[0] && !merge::merge_sources(dest, srcs[0], dest, channels, samples))
return false;
srcs++;
src_length--;
}
return true;
}
#define stack_buffer_length (1024 * 8)
bool merge::merge_channels_interleaved(void *target, size_t target_channels, const void *src, size_t src_channels, size_t samples) {
assert(src_channels == 1 || src_channels == 2);
assert(target_channels == 1 || target_channels == 2);
if(src_channels == target_channels) {
if(src == target)
return true;
memcpy(target, src, samples * src_channels * 4);
return true;
}
if(src_channels == 1 && target_channels == 2) {
auto source_array = (float*) src;
auto target_array = (float*) target;
if(source_array == target_array) {
if(stack_buffer_length < samples * src_channels) {
log_error(category::audio, tr("channel merge failed due to large inputs"));
return false;
}
float stack_buffer[stack_buffer_length];
memcpy(stack_buffer, src, src_channels * samples * 4);
source_array = stack_buffer;
while(samples-- > 0) {
*(target_array++) = *source_array;
*(target_array++) = *source_array;
source_array++;
}
} else {
while(samples-- > 0) {
*(target_array++) = *source_array;
*(target_array++) = *source_array;
source_array++;
}
}
} else if(src_channels == 2 && target_channels == 1) {
auto source_array = (float*) src;
auto target_array = (float*) target;
while(samples-- > 0) {
*(target_array++) = merge_ab(*(source_array), *(source_array + 1));
source_array += 2;
}
} else
return false;
return true;
}
@@ -1,18 +0,0 @@
#pragma once
#include <cstring>
namespace tc {
namespace audio {
namespace merge {
/* the result buffer could be equal to one of the source buffers to prevent unnecessary allocations
* Note: The sample order is irrelevant
*/
extern bool merge_sources(void* /* result */, void* /* source a */, void* /* source b */, size_t /* channels */, size_t /* samples */);
extern bool merge_n_sources(void* /* result */, void** /* sources */, size_t /* size_t sources count */, size_t /* channels */, size_t /* samples */);
extern bool merge_channels_interleaved(void* /* result */, size_t /* result channels */, const void* /* source */, size_t /* source channels */, size_t /* samples */);
}
}
}
@@ -1,293 +0,0 @@
#include "AudioOutput.h"
#include "AudioMerger.h"
#include "../logger.h"
#include <cstring>
#include <algorithm>
#include <string>
using namespace std;
using namespace tc;
using namespace tc::audio;
void AudioOutputSource::clear() {
lock_guard lock(this->buffer_lock);
this->sample_buffers.clear();
this->buffered_samples = 0;
}
ssize_t AudioOutputSource::pop_samples(void *buffer, size_t samples) {
auto sample_count = samples;
_retest:
{
lock_guard lock(this->buffer_lock);
while(sample_count > 0 && !this->sample_buffers.empty()) {
auto buf = this->sample_buffers[0];
auto sc = min((size_t) (buf->sample_size - buf->sample_index), (size_t) sample_count);
if(sc > 0 && buffer) { /* just to ensure */
memcpy(buffer, (char *) buf->sample_data + this->channel_count * buf->sample_index * 4, sc * this->channel_count * 4);
} else {
#ifndef WIN32
/* for my debugger */
__asm__("nop");
#endif
}
sample_count -= sc;
buf->sample_index += (uint16_t) sc;
if(buf->sample_index == buf->sample_size)
this->sample_buffers.pop_front();
if(buffer)
buffer = (char*) buffer + sc * this->channel_count * 4;
}
this->buffered_samples -= samples - sample_count;
}
if(sample_count > 0) {
if(this->on_underflow) {
if(this->on_underflow()) {
goto _retest;
}
}
this->buffering = true;
}
if(this->on_read)
this->on_read();
return samples - sample_count; /* return the written samples */
}
ssize_t AudioOutputSource::enqueue_samples(const void *buffer, size_t samples) {
auto buf = SampleBuffer::allocate((uint8_t) this->channel_count, (uint16_t) samples);
if(!buf)
return -1;
buf->sample_index = 0;
memcpy(buf->sample_data, buffer, this->channel_count * samples * 4);
return this->enqueue_samples(buf);
}
ssize_t AudioOutputSource::enqueue_samples(const std::shared_ptr<tc::audio::SampleBuffer> &buf) {
if(!buf) return 0;
{
unique_lock lock(this->buffer_lock);
if(this->max_latency > 0 && this->buffered_samples + buf->sample_size > this->max_latency) {
/* overflow! */
auto overflow_length = this->buffered_samples + buf->sample_size - this->max_latency;
if(this->on_overflow) {
lock.unlock();
this->on_overflow(overflow_length);
lock.lock();
}
switch (this->overflow_strategy) {
case overflow_strategy::discard_input:
return -2;
case overflow_strategy::discard_buffer_all:
this->sample_buffers.clear();
break;
case overflow_strategy::discard_buffer_half:
this->sample_buffers.erase(this->sample_buffers.begin(), this->sample_buffers.begin() + (int) ceil(this->sample_buffers.size() / 2));
break;
case overflow_strategy::ignore:
break;
}
}
this->sample_buffers.push_back(buf);
this->buffered_samples += buf->sample_size;
}
return buf->sample_size;
}
AudioOutput::AudioOutput(size_t channels, size_t rate) : _channel_count(channels), _sample_rate(rate) {}
AudioOutput::~AudioOutput() {
this->close_device();
this->cleanup_buffers();
}
std::shared_ptr<AudioOutputSource> AudioOutput::create_source() {
auto result = shared_ptr<AudioOutputSource>(new AudioOutputSource(this, this->_channel_count, this->_sample_rate));
{
lock_guard lock(this->sources_lock);
this->_sources.push_back(result);
}
return result;
}
void AudioOutput::delete_source(const std::shared_ptr<tc::audio::AudioOutputSource> &source) {
{
lock_guard lock(this->sources_lock);
auto it = find(this->_sources.begin(), this->_sources.end(), source);
if(it != this->_sources.end())
this->_sources.erase(it);
}
source->handle = nullptr;
}
void AudioOutput::cleanup_buffers() {
lock_guard buffer_lock(this->buffer_lock);
if(this->source_buffer)
free(this->source_buffer);
if(this->source_merge_buffer)
free(this->source_merge_buffer);
this->source_merge_buffer = nullptr;
this->source_buffer = nullptr;
this->source_merge_buffer_length = 0;
this->source_buffer_length = 0;
}
int AudioOutput::_audio_callback(const void *a, void *b, unsigned long c, const PaStreamCallbackTimeInfo* d, PaStreamCallbackFlags e, void *_ptr_audio_output) {
return reinterpret_cast<AudioOutput*>(_ptr_audio_output)->audio_callback(a, b, c, d, e);
}
int AudioOutput::audio_callback(const void *input, void *output, unsigned long frameCount, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags) {
if(!output) /* hmmm.. suspicious */
return 0;
lock_guard buffer_lock(this->buffer_lock);
size_t buffer_length = frameCount * 4 * this->_channel_count;
size_t sources = 0;
size_t actual_sources = 0;
auto volume = this->_volume;
{
lock_guard lock(this->sources_lock);
sources = this->_sources.size();
actual_sources = sources;
if(sources > 0) {
if(volume > 0) { /* allocate the required space */
auto source_buffer_length = buffer_length * sources;
auto source_merge_buffer_length = sizeof(void*) * sources;
if(this->source_buffer_length < source_buffer_length || !this->source_buffer) {
if(this->source_buffer)
free(this->source_buffer);
this->source_buffer = malloc(source_buffer_length);
this->source_buffer_length = source_buffer_length;
}
if(this->source_merge_buffer_length < source_merge_buffer_length || !this->source_merge_buffer) {
if(this->source_merge_buffer)
free(this->source_merge_buffer);
this->source_merge_buffer = (void**) malloc(source_merge_buffer_length);
this->source_merge_buffer_length = source_merge_buffer_length;
}
}
for(size_t index = 0; index < sources; index++) {
auto& source = this->_sources[index];
{
lock_guard lock(source->buffer_lock);
if(source->buffering) {
if(source->buffered_samples > source->min_buffer) {
source->buffering = false;
} else {
this->source_merge_buffer[index] = nullptr;
actual_sources--;
continue;
}
}
}
if(volume > 0) {
this->source_merge_buffer[index] = (char*) this->source_buffer + (buffer_length * index);
auto written_frames = this->_sources[index]->pop_samples(this->source_merge_buffer[index], frameCount);
if(written_frames != frameCount) {
if(written_frames == 0) {
this->source_merge_buffer[index] = nullptr;
actual_sources--;
} else {
/* fill up the rest with silence (0) */
auto written = written_frames * this->_channel_count * 4;
memset((char*) this->source_merge_buffer[index] + written, 0, (frameCount - written_frames) * this->_channel_count * 4);
}
}
} else {
this->_sources[index]->pop_samples(nullptr, frameCount);
}
}
}
}
if(actual_sources > 0 && volume > 0) {
if(!merge::merge_n_sources(output, this->source_merge_buffer, sources, this->_channel_count, frameCount)) {
log_warn(category::audio, tr("failed to merge buffers!"));
}
auto float_length = this->_channel_count * frameCount;
auto data = (float*) output;
while(float_length-- > 0)
*data++ *= volume;
} else {
memset(output, 0, this->_channel_count * 4 * frameCount);
}
return 0;
}
bool AudioOutput::open_device(std::string& error, PaDeviceIndex index) {
lock_guard lock(this->output_stream_lock);
if(index == this->_current_device_index)
return true;
this->close_device();
this->_current_device_index = index;
this->_current_device = Pa_GetDeviceInfo(index);
if(!this->_current_device) {
this->_current_device_index = paNoDevice;
error = "failed to get device info";
return false;
}
PaStreamParameters output_parameters{};
memset(&output_parameters, 0, sizeof(output_parameters));
output_parameters.channelCount = (int) this->_channel_count;
output_parameters.device = this->_current_device_index;
output_parameters.sampleFormat = paFloat32;
output_parameters.suggestedLatency = this->_current_device->defaultLowOutputLatency;
auto err = Pa_OpenStream(&output_stream, nullptr, &output_parameters, (double) this->_sample_rate, paFramesPerBufferUnspecified, paClipOff, &AudioOutput::_audio_callback, this);
if(err != paNoError) {
error = "Pa_OpenStream returned " + to_string(err);
return false;
}
return true;
}
bool AudioOutput::playback() {
lock_guard lock(this->output_stream_lock);
if(!this->output_stream)
return false;
auto err = Pa_StartStream(this->output_stream);
if(err != paNoError && err != paStreamIsNotStopped) {
log_error(category::audio, tr("Pa_StartStream returned {}"), err);
return false;
}
return true;
}
void AudioOutput::close_device() {
lock_guard lock(this->output_stream_lock);
if(this->output_stream) {
/* TODO: Test for errors */
Pa_StopStream(this->output_stream);
Pa_CloseStream(this->output_stream);
this->output_stream = nullptr;
}
}
@@ -1,110 +0,0 @@
#pragma once
#include <mutex>
#include <deque>
#include <memory>
#include <iostream>
#include <functional>
#include <portaudio.h>
#include "AudioSamples.h"
#ifdef WIN32
#define ssize_t int64_t
#endif
namespace tc {
namespace audio {
class AudioOutput;
namespace overflow_strategy {
enum value {
ignore,
discard_buffer_all,
discard_buffer_half,
discard_input
};
}
class AudioOutputSource {
friend class AudioOutput;
public:
AudioOutput* handle;
size_t const channel_count = 0;
size_t const sample_rate = 0;
bool buffering = true;
size_t min_buffer = 0;
/* For stream set it to the max latency in samples you want.
* Zero means unlimited
*/
size_t max_latency = 0;
overflow_strategy::value overflow_strategy = overflow_strategy::discard_buffer_half;
/* if it returns true then the it means that the buffer has been refilled, we have to test again */
std::function<bool()> on_underflow;
std::function<void(size_t /* sample count */)> on_overflow;
std::function<void()> on_read; /* will be invoked after sample read, e.g. for buffer fullup */
void clear();
ssize_t pop_samples(void* /* output buffer */, size_t /* sample count */);
ssize_t enqueue_samples(const void * /* input buffer */, size_t /* sample count */);
ssize_t enqueue_samples(const std::shared_ptr<SampleBuffer>& /* buffer */);
private:
AudioOutputSource(AudioOutput* handle, size_t channel_count, size_t sample_rate) : handle(handle), channel_count(channel_count), sample_rate(sample_rate) {}
std::mutex buffer_lock;
size_t buffered_samples = 0;
std::deque<std::shared_ptr<SampleBuffer>> sample_buffers;
};
class AudioOutput {
public:
AudioOutput(size_t /* channels */, size_t /* rate */);
virtual ~AudioOutput();
bool open_device(std::string& /* error */, PaDeviceIndex);
bool playback();
void close_device();
PaDeviceIndex current_device() { return this->_current_device_index; }
std::deque<std::shared_ptr<AudioOutputSource>> sources() {
std::lock_guard lock(this->sources_lock);
return this->_sources;
}
std::shared_ptr<AudioOutputSource> create_source();
void delete_source(const std::shared_ptr<AudioOutputSource>& /* source */);
inline size_t channel_count() { return this->_channel_count; }
inline size_t sample_rate() { return this->_sample_rate; }
inline float volume() { return this->_volume; }
inline void set_volume(float value) { this->_volume = value; }
private:
static int _audio_callback(const void *, void *, unsigned long, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void*);
int audio_callback(const void *, void *, unsigned long, const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags);
size_t const _channel_count;
size_t const _sample_rate;
std::mutex sources_lock;
std::deque<std::shared_ptr<AudioOutputSource>> _sources;
std::recursive_mutex output_stream_lock;
const PaDeviceInfo* _current_device = nullptr;
PaDeviceIndex _current_device_index = paNoDevice;
PaStream* output_stream = nullptr;
std::mutex buffer_lock; /* not required, but why not. Usually only used within audio_callback! */
void* source_buffer = nullptr;
void** source_merge_buffer = nullptr;
size_t source_buffer_length = 0;
size_t source_merge_buffer_length = 0;
void cleanup_buffers();
float _volume = 1.f;
};
}
}
@@ -1,49 +0,0 @@
#include <cassert>
#include <cstring>
#include "AudioReframer.h"
using namespace tc::audio;
Reframer::Reframer(size_t channels, size_t frame_size) : _frame_size(frame_size), _channels(channels) {
this->buffer = nullptr;
this->_buffer_index = 0;
}
Reframer::~Reframer() {
if(this->buffer)
free(this->buffer);
}
void Reframer::process(const void *source, size_t samples) {
if(!this->buffer)
this->buffer = (float*) malloc(this->_channels * this->_frame_size * sizeof(float));
assert(this->on_frame);
if(this->_buffer_index > 0) {
if(this->_buffer_index + samples > this->_frame_size) {
auto required = this->_frame_size - this->_buffer_index;
auto length = required * this->_channels * 4;
memcpy((char*) this->buffer + this->_buffer_index * 4 * this->_channels, source, length);
samples -= required;
source = (char*) source + length;
this->on_frame(this->buffer);
} else {
memcpy((char*) this->buffer + this->_buffer_index * 4 * this->_channels, source, samples * this->_channels * 4);
this->_buffer_index += samples;
return;
}
}
auto _on_frame = this->on_frame;
while(samples > this->_frame_size) {
if(_on_frame)
_on_frame(source);
samples -= this->_frame_size;
source = (char*) source + this->_frame_size * this->_channels * 4;
}
if(samples > 0)
memcpy((char*) this->buffer, source, samples * this->_channels * 4);
this->_buffer_index = samples;
}
@@ -1,27 +0,0 @@
#pragma once
#include <functional>
#include <cstdio>
namespace tc {
namespace audio {
class Reframer {
public:
Reframer(size_t channels, size_t frame_size);
virtual ~Reframer();
void process(const void* /* source */, size_t /* samples */);
inline size_t channels() { return this->_channels; }
inline size_t frame_size() { return this->_frame_size; }
std::function<void(const void* /* buffer */)> on_frame;
private:
void* buffer;
size_t _buffer_index;
size_t _channels;
size_t _frame_size;
};
}
}
@@ -1,43 +0,0 @@
#include <iostream>
#include <cstring>
#include "AudioResampler.h"
#include "../logger.h"
using namespace std;
using namespace tc::audio;
AudioResampler::AudioResampler(size_t irate, size_t orate, size_t channels) : _input_rate(irate), _output_rate(orate), _channels(channels) {
if(this->input_rate() != this->output_rate()) {
soxr_error_t error;
this->soxr_handle = soxr_create((double) this->_input_rate, (double) this->_output_rate, (unsigned) this->_channels, &error, nullptr, nullptr, nullptr);
if(!this->soxr_handle) {
log_error(category::audio, tr("Failed to create soxr resampler: {}. Input: {}; Output: {}; Channels: {}"), error, this->_input_rate, this->_output_rate, this->_channels);
}
}
}
AudioResampler::~AudioResampler() {
if(this->soxr_handle)
soxr_delete(this->soxr_handle);
}
ssize_t AudioResampler::process(void *output, const void *input, size_t input_length) {
if(this->io_ratio() == 1) {
if(input != output)
memcpy(output, input, input_length * this->_channels * 4);
return input_length;
}
if(!this->soxr_handle)
return -2;
size_t output_length = 0;
auto error = soxr_process(this->soxr_handle, input, input_length, nullptr, output, this->estimated_output_size(input_length), &output_length);
if(error) {
log_error(category::audio, tr("Failed to process resample: {}"), error);
return -1;
}
return output_length;
}
@@ -1,40 +0,0 @@
#pragma once
#include <memory>
#include <cmath>
#include <soxr.h>
#include <deque>
#include <mutex>
#if defined(WIN32) && !defined(ssize_t)
#define ssize_t int64_t
#endif
namespace tc {
namespace audio {
class AudioResampler {
public:
AudioResampler(size_t /* input rate */, size_t /* output rate */, size_t /* channels */);
virtual ~AudioResampler();
inline size_t channels() { return this->_channels; }
inline size_t input_rate() { return this->_input_rate; }
inline size_t output_rate() { return this->_output_rate; }
inline long double io_ratio() { return (long double) this->_output_rate / (long double) this->_input_rate; }
inline size_t estimated_output_size(size_t input_length) {
return (size_t) lroundl(this->io_ratio() * input_length) + 1;
}
inline bool valid() { return this->io_ratio() == 1 || this->soxr_handle != nullptr; }
ssize_t process(void* /* output */, const void* /* input */, size_t /* input length */);
private:
size_t const _channels = 0;
size_t const _input_rate = 0;
size_t const _output_rate = 0;
soxr_t soxr_handle = nullptr;
};
}
}
@@ -1,15 +0,0 @@
#include "AudioSamples.h"
using namespace std;
using namespace tc;
using namespace tc::audio;
std::shared_ptr<SampleBuffer> SampleBuffer::allocate(uint8_t channels, uint16_t samples) {
auto _buffer = (SampleBuffer*) malloc(SampleBuffer::HEAD_LENGTH + channels * samples * 4);
if(!_buffer)
return nullptr;
_buffer->sample_size = samples;
_buffer->sample_index = 0;
return shared_ptr<SampleBuffer>(_buffer, ::free);
}
@@ -1,40 +0,0 @@
#pragma once
#include <cstdint>
#include <memory>
namespace tc {
namespace audio {
#ifdef WIN32
#pragma pack(push,1)
#define __attribute__packed_1
#else
#define __attribute__packed_1 __attribute__((packed, aligned(1)))
#endif
/* Every sample is a float (4byte) */
struct __attribute__packed_1 SampleBuffer {
static constexpr size_t HEAD_LENGTH = 4;
uint16_t sample_size;
uint16_t sample_index;
char sample_data[
#ifndef WIN32
0
#else
1 /* windows does not allow zero sized arrays */
#endif
];
static std::shared_ptr<SampleBuffer> allocate(uint8_t /* channels */, uint16_t /* samples */);
};
#ifndef WIN32
static_assert(sizeof(SampleBuffer) == 4, "Invalid SampleBuffer packaging!");
#else
#pragma pack(pop)
static_assert(sizeof(SampleBuffer) == 5, "Invalid SampleBuffer packaging!");
#endif
}
}
@@ -1,34 +0,0 @@
//
// Created by wolverindev on 28.04.19.
//
#include "Converter.h"
using namespace tc::audio::codec;
Converter::Converter(size_t c, size_t s, size_t f) : _channels(c), _sample_rate(s), _frame_size(f) {}
Converter::~Converter() {}
bool type::supported(value type) {
#ifdef HAVE_CODEC_OPUS
if(type == type::opus)
return true;
#endif
#ifdef HAVE_CODEC_SPEEX
if(type == type::speex)
return true;
#endif
#ifdef HAVE_CODEC_FLAC
if(type == type::flac)
return true;
#endif
#ifdef HAVE_CODEC_CELT
if(type == type::celt)
return true;
#endif
return false;
}
@@ -1,68 +0,0 @@
#pragma once
#include <string>
#if !defined(ssize_t) && defined(WIN32)
#define ssize_t int64_t
#endif
namespace tc {
namespace audio {
namespace codec {
namespace type {
enum value {
undefined,
/* supported */
opus,
speex,
/* unsupported */
flac,
celt
};
extern bool supported(value);
}
class Converter {
public:
Converter(size_t /* channels */, size_t /* sample rate */, size_t /* frame size */);
virtual ~Converter();
/* initialize parameters depend on the codec */
virtual bool valid() = 0;
virtual void finalize() = 0;
virtual void reset_encoder() = 0;
virtual void reset_decoder() = 0;
/**
* @return number of bytes written on success
*/
virtual ssize_t encode(std::string& /* error */, const void* /* source */, void* /* destination */, size_t /* destination byte length */) = 0;
/**
* @return number of samples on success
*/
virtual ssize_t decode(std::string& /* error */, const void* /* source */, size_t /* source byte length */, void* /* destination */) = 0;
virtual ssize_t decode_lost(std::string& /* error */, size_t /* packets */) = 0;
virtual size_t expected_encoded_length(size_t /* sample count */) = 0;
virtual size_t expected_decoded_length(const void* /* source */, size_t /* source byte length */) {
return this->bytes_per_frame();
}
inline size_t channels() { return this->_channels; }
inline size_t sample_rate() { return this->_sample_rate; }
inline size_t frame_size() { return this->_frame_size; }
inline size_t bytes_per_frame() { return this->_channels * this->_frame_size * 4; }
protected:
size_t _frame_size;
size_t _channels;
size_t _sample_rate;
};
}
}
}
@@ -1,143 +0,0 @@
#include "OpusConverter.h"
#include "../../logger.h"
using namespace std;
using namespace tc::audio::codec;
OpusConverter::OpusConverter(size_t c, size_t s, size_t f) : Converter(c, s, f) { }
OpusConverter::~OpusConverter() {}
bool OpusConverter::valid() {
return this->encoder && this->decoder;
}
bool OpusConverter::initialize(std::string &error, int application_type) {
lock_guard lock(this->coder_lock);
this->_application_type = application_type;
if(!this->_initialize_encoder(error))
return false;
if(!this->_initialize_decoder(error)) {
this->reset_encoder();
return false;
}
return true;
}
void OpusConverter::reset_encoder() {
lock_guard lock(this->coder_lock);
auto result = opus_encoder_ctl(this->encoder, OPUS_RESET_STATE);
if(result != OPUS_OK)
log_warn(category::audio, tr("Failed to reset opus encoder. Opus result: {}"), result);
}
void OpusConverter::reset_decoder() {
lock_guard lock(this->coder_lock);
auto result = opus_decoder_ctl(this->decoder, OPUS_RESET_STATE);
if(result != OPUS_OK)
log_warn(category::audio, tr("Failed to reset opus decoder. Opus result: {}"), result);
}
void OpusConverter::finalize() {
lock_guard lock(this->coder_lock);
if(this->encoder) opus_encoder_destroy(this->encoder);
this->encoder = nullptr;
if(this->decoder) opus_decoder_destroy(this->decoder);
this->decoder = nullptr;
}
ssize_t OpusConverter::encode(std::string &error, const void *source, void *target, size_t target_length) {
lock_guard lock(this->coder_lock);
auto result = opus_encode_float(this->encoder, (float*) source, (int) this->_frame_size, (uint8_t*) target, (opus_int32) target_length);
if(result < OPUS_OK) {
error = to_string(result) + "|" + opus_strerror(result);
return -1;
}
return result;
}
ssize_t OpusConverter::decode(std::string &error, const void *source, size_t source_length, void *target) {
lock_guard lock(this->coder_lock);
auto result = opus_decode_float(this->decoder, (uint8_t*) source, (opus_int32) source_length, (float*) target, (int) this->_frame_size, 0);
if(result < OPUS_OK) {
error = to_string(result) + "|" + opus_strerror(result);
return -1;
}
return result;
}
ssize_t OpusConverter::decode_lost(std::string &error, size_t packets) {
lock_guard lock(this->coder_lock);
auto buffer = (float*) malloc(this->_frame_size * this->_channels * sizeof(float));
while (packets-- > 0) {
auto result = opus_decode_float(this->decoder, nullptr, 0, buffer, (int) this->_frame_size, false);
if(result < OPUS_OK)
log_warn(category::audio, tr("Opus decode lost resulted in error: {}"), result);
}
free(buffer);
return 0;
}
size_t OpusConverter::expected_encoded_length(size_t sample_count) {
//TODO calculate stuff
return 512;
}
bool OpusConverter::_initialize_decoder(std::string &error) {
if(!this->_finalize_decoder(error))
return false;
int error_id = 0;
this->decoder = opus_decoder_create((opus_int32) this->_sample_rate, (int) this->_channels, &error_id);
if(!this->encoder || error_id) {
error = "failed to create decoder (" + to_string(error_id) + ")";
return false;
}
return true;
}
bool OpusConverter::_initialize_encoder(std::string &error) {
if(!this->_finalize_encoder(error))
return false;
int error_id = 0;
this->encoder = opus_encoder_create((opus_int32) this->_sample_rate, (int) this->_channels, this->_application_type, &error_id);
if(!this->encoder || error_id) {
error = "failed to create encoder (" + to_string(error_id) + ")";
return false;
}
error_id = opus_encoder_ctl(encoder, OPUS_SET_BITRATE(64000));
if(error_id) {
error = "failed to set bitrate (" + to_string(error_id) + ")";
return false;
}
return true;
}
bool OpusConverter::_finalize_decoder(std::string &) {
if(this->decoder) {
opus_decoder_destroy(this->decoder);
this->decoder = nullptr;
}
return true;
}
bool OpusConverter::_finalize_encoder(std::string &) {
if(this->encoder) {
opus_encoder_destroy(this->encoder);
this->encoder = nullptr;
}
return true;
}
@@ -1,43 +0,0 @@
#pragma once
#include "Converter.h"
#include <opus/opus.h>
#include <mutex>
namespace tc {
namespace audio {
namespace codec {
class OpusConverter : public Converter {
public:
OpusConverter(size_t /* channels */, size_t /* sample rate */, size_t /* frame size */);
virtual ~OpusConverter();
bool valid() override;
bool initialize(std::string& /* error */, int /* application type */);
void finalize() override;
void reset_encoder() override;
void reset_decoder() override;
ssize_t encode(std::string & /* error */, const void * /* source */, void * /* target */, size_t /* target size */) override;
ssize_t decode(std::string & /* error */, const void * /* source */, size_t /* source size */, void *pVoid1) override;
ssize_t decode_lost(std::string &string, size_t /* packets */) override;
size_t expected_encoded_length(size_t size) override;
private:
std::mutex coder_lock;
OpusDecoder* decoder = nullptr;
OpusEncoder* encoder = nullptr;
int _application_type = 0;
bool _finalize_encoder(std::string& /* error */);
bool _finalize_decoder(std::string& /* error */);
bool _initialize_encoder(std::string& /* error */);
bool _initialize_decoder(std::string& /* error */);
};
}
}
}
@@ -1,27 +0,0 @@
#pragma once
#include <cstdint>
#include <cstddef>
namespace tc {
namespace audio {
namespace filter {
class Filter {
public:
Filter(size_t channel_count, size_t sample_rate, size_t frame_size) :
_frame_size(frame_size), _sample_rate(sample_rate), _channels(channel_count) {}
virtual bool process(const void* /* buffer */) = 0;
inline size_t sample_rate() { return this->_sample_rate; }
inline size_t channels() { return this->_channels; }
inline size_t frame_size() { return this->_frame_size; }
protected:
size_t _frame_size;
size_t _sample_rate;
size_t _channels;
};
}
}
}
@@ -1,17 +0,0 @@
#include "FilterState.h"
#include "Filter.h"
using namespace std;
using namespace tc::audio;
using namespace tc::audio::filter;
StateFilter::StateFilter(size_t a, size_t b, size_t c) : Filter(a, b, c) {}
StateFilter::~StateFilter() {}
bool StateFilter::initialize(std::string &) {
return true;
}
bool StateFilter::process(const void *_buffer) {
return !this->_consume;
}
@@ -1,25 +0,0 @@
#pragma once
#include "Filter.h"
#include <mutex>
#include <fvad.h>
namespace tc {
namespace audio {
namespace filter {
class StateFilter : public Filter {
public:
StateFilter(size_t /* channel count */, size_t /* sample rate */, size_t /* frame size */);
virtual ~StateFilter();
bool initialize(std::string& /* error */);
bool process(const void* /* buffer */) override;
inline bool consumes_input() { return this->_consume; }
inline void set_consume_input(bool state) { this->_consume = state; }
private:
bool _consume = false;
};
}
}
}
@@ -1,98 +0,0 @@
#include <iostream>
#include <algorithm>
#include <cmath>
#include "FilterThreshold.h"
#include "../../logger.h"
using namespace std;
using namespace tc::audio;
using namespace tc::audio::filter;
ThresholdFilter::ThresholdFilter(size_t a, size_t b, size_t c) : Filter(a, b, c) {}
ThresholdFilter::~ThresholdFilter() {}
bool ThresholdFilter::initialize(std::string &, float val, size_t margin) {
this->_threshold = val;
this->_margin_frames = margin;
return true;
}
/* technique based on http://www.vttoth.com/CMS/index.php/technical-notes/68 */
inline float merge_ab(float a, float b, float n) {
/*
* Form: A,B := [0;n]
* IF A <= n/2 AND B <= N/2
* Z = (2 * A * B) / n
* ELSE
* Z = 2(A + B) - (2/n) * A * B - n
*
* For a range from 0 to 2: Z = 2(A + B) - AB - 2
*/
auto half_n = n / 2;
auto inv_half_n = 1 / half_n;
if(a < half_n && b < half_n)
return inv_half_n * a * b;
return 2 * (a + b) - inv_half_n * a * b - n;
}
bool ThresholdFilter::process(const void *_buffer) {
float value = -1;
auto analyze_callback = this->on_analyze;
for(size_t channel = 0; channel < this->_channels; channel++) {
auto percentage = this->analyze(_buffer, channel);
if(channel == 0)
value = (float) percentage;
else
value = merge_ab(value, (float) percentage, 100);
}
auto last_level = this->_current_level;
float smooth;
if(this->_margin_processed_frames == 0) /* we're in release */
smooth = this->_release_smooth;
else
smooth = this->_attack_smooth;
this->_current_level = last_level * smooth + value * (1 - smooth);
//log_trace(category::audio, "Vad level: before: {}, edit: {}, now: {}, smooth: {}", last_level, value, this->_current_level, smooth);
if(analyze_callback)
analyze_callback(this->_current_level);
if(this->_current_level >= this->_threshold) {
this->_margin_processed_frames = 0;
return true;
}
return this->_margin_processed_frames++ < this->_margin_frames;
}
long double ThresholdFilter::analyze(const void *_buffer, size_t channel) {
/* equation taken from the web client */
auto buffer = (float*) _buffer;
buffer += channel;
long double value = 0;
auto sample = this->_frame_size;
while(sample-- > 0) {
const auto num = floorl(*buffer * 127) / 127.f; /* to be like the web client */
value += num * num;
buffer += this->_channels;
}
long double rms = sqrtl(value / (long double) this->_frame_size);
auto db = (long double) 20 * (log(rms) / log(10));
db = max((long double) -192, min(db, (long double) 0));
float percentage = (float) (100 + (db * 1.92f));
return max(0.f, min(percentage, 100.f));
}
@@ -1,46 +0,0 @@
#pragma once
#include "Filter.h"
#include <mutex>
#include <fvad.h>
#include <functional>
namespace tc {
namespace audio {
namespace filter {
class ThresholdFilter : public Filter {
public:
ThresholdFilter(size_t /* channel count */, size_t /* sample rate */, size_t /* frame size */);
virtual ~ThresholdFilter();
bool initialize(std::string& /* error */, float /* threshold */, size_t /* margin frames */);
bool process(const void* /* buffer */) override;
long double analyze(const void* /* buffer */, size_t /* channel */);
inline float threshold() { return this->_threshold; }
inline void set_threshold(float value) { this->_threshold = value; }
inline size_t margin_frames() { return this->_margin_frames; }
inline void set_margin_frames(size_t value) { this->_margin_frames = value; }
inline void attack_smooth(float value) { this->_attack_smooth = value; }
inline float attack_smooth() { return this->_attack_smooth; }
inline void release_smooth(float value) { this->_release_smooth = value; }
inline float release_smooth() { return this->_release_smooth; }
std::function<void(float)> on_analyze;
private:
float _attack_smooth = 0;
float _release_smooth = 0;
float _current_level = 0;
float _threshold;
size_t _margin_frames = 0;
size_t _margin_processed_frames = 0;
};
}
}
}
@@ -1,108 +0,0 @@
#include <iostream>
#include "FilterVad.h"
#include "../AudioMerger.h"
#include "../../logger.h"
using namespace std;
using namespace tc::audio;
using namespace tc::audio::filter;
VadFilter::VadFilter(size_t channels, size_t rate, size_t frames) : Filter(channels, rate, frames) { }
VadFilter::~VadFilter() {
this->cleanup_buffer();
if(this->_vad_handle) {
fvad_free(this->_vad_handle);
this->_vad_handle = nullptr;
}
}
void VadFilter::cleanup_buffer() {
lock_guard lock(this->_buffer_lock);
if(this->_buffer)
free(this->_buffer);
this->_buffer = nullptr;
this->_buffer_size = 0;
}
bool VadFilter::initialize(std::string &error, size_t mode, size_t margin) {
this->_vad_handle = fvad_new();
if(!this->_vad_handle) {
error = "failed to allocate handle";
return false;
}
if(fvad_set_sample_rate(this->_vad_handle, (int) this->_sample_rate) != 0) {
error = "invalid sample rate. Sample rate must be one of [8000, 16000, 32000 and 48000]";
return false;
}
if(fvad_set_mode(this->_vad_handle, (int) mode) != 0) {
error = "failed to set mode";
return false;
}
this->_mode = mode;
this->_margin_frames = margin;
if(this->_channels > 1) {
this->ensure_buffer(this->_frame_size * this->_channels * 4); /* buffer to merge the channels into one channel */
} else {
this->ensure_buffer(this->_frame_size * 2);
}
return true;
}
bool VadFilter::process(const void *buffer) {
if(!this->_vad_handle) {
log_warn(category::audio, tr("Vad filter hasn't been initialized!"));
return false;
}
lock_guard lock(this->_buffer_lock);
if(this->_channels > 1) {
if(!merge::merge_channels_interleaved(this->_buffer, 1, buffer, this->_channels, this->_frame_size)) {
log_warn(category::audio, tr("Failed to merge channels"));
return false;
}
buffer = this->_buffer;
}
/* convert float32 samples to signed int16 */
{
auto target = (int16_t*) this->_buffer;
auto source = (float*) buffer;
auto sample = this->_frame_size;
float tmp;
while(sample-- > 0) {
tmp = *source++;
tmp *= 32768;
if(tmp > 32767)
tmp = 32767;
if(tmp < -32768)
tmp = -32768;
*target++ = (int16_t) tmp;
}
}
auto result = fvad_process(this->_vad_handle, (int16_t*) this->_buffer, this->_frame_size);
if(result == -1) {
log_warn(category::audio, tr("Invalid frame length"));
return false;
}
auto flag_vad = result == 1;
if(!flag_vad) {
this->_margin_processed_frames++;
return this->_margin_processed_frames <= this->_margin_frames;
} else {
this->_margin_processed_frames = 0;
}
return flag_vad;
}
@@ -1,46 +0,0 @@
#pragma once
#include "Filter.h"
#include <mutex>
#include <fvad.h>
namespace tc {
namespace audio {
namespace filter {
class VadFilter : public Filter {
public:
VadFilter(size_t /* channel count */, size_t /* sample rate */, size_t /* frame size */);
virtual ~VadFilter();
bool initialize(std::string& /* error */, size_t /* mode */, size_t /* margin frames */);
bool process(const void* /* buffer */) override;
inline size_t margin_frames() { return this->_margin_frames; }
inline void set_margin_frames(size_t value) { this->_margin_frames = value; }
inline size_t mode() { return this->_mode; }
private:
Fvad* _vad_handle = nullptr;
size_t _mode = 0;
size_t _margin_frames = 0;
size_t _margin_processed_frames = 0;
std::mutex _buffer_lock;
void* _buffer = nullptr;
size_t _buffer_size = 0;
void cleanup_buffer();
inline void ensure_buffer(size_t length) {
if(this->_buffer_size < length) {
if(this->_buffer)
free(this->_buffer);
this->_buffer_size = length;
this->_buffer = malloc(this->_buffer_size);
}
}
};
}
}
}
@@ -1,322 +0,0 @@
#include "AudioConsumer.h"
#include "AudioRecorder.h"
#include "AudioFilter.h"
#include "../AudioInput.h"
#include "../filter/Filter.h"
#include "../filter/FilterVad.h"
#include "../filter/FilterThreshold.h"
#include "../filter/FilterState.h"
#include "../../logger.h"
using namespace std;
using namespace tc::audio;
using namespace tc::audio::recorder;
NAN_MODULE_INIT(AudioConsumerWrapper::Init) {
auto klass = Nan::New<v8::FunctionTemplate>(AudioConsumerWrapper::NewInstance);
klass->SetClassName(Nan::New("AudioConsumer").ToLocalChecked());
klass->InstanceTemplate()->SetInternalFieldCount(1);
Nan::SetPrototypeMethod(klass, "get_filters", AudioConsumerWrapper::_get_filters);
Nan::SetPrototypeMethod(klass, "unregister_filter", AudioConsumerWrapper::_unregister_filter);
Nan::SetPrototypeMethod(klass, "create_filter_vad", AudioConsumerWrapper::_create_filter_vad);
Nan::SetPrototypeMethod(klass, "create_filter_threshold", AudioConsumerWrapper::_create_filter_threshold);
Nan::SetPrototypeMethod(klass, "create_filter_state", AudioConsumerWrapper::_create_filter_state);
constructor_template().Reset(klass);
constructor().Reset(Nan::GetFunction(klass).ToLocalChecked());
}
NAN_METHOD(AudioConsumerWrapper::NewInstance) {
if(!info.IsConstructCall())
Nan::ThrowError("invalid invoke!");
}
AudioConsumerWrapper::AudioConsumerWrapper(AudioRecorderWrapper* h, const std::shared_ptr<tc::audio::AudioConsumer> &handle) : _handle(handle), _recorder(h) {
log_allocate("AudioConsumerWrapper", this);
{
lock_guard read_lock(handle->on_read_lock);
handle->on_read = [&](const void* buffer, size_t length){ this->process_data(buffer, length); };
}
#ifdef DO_DEADLOCK_REF
this->_recorder->js_ref(); /* FML Mem leak! (In general the consumer live is related to the recorder handle, but for nodejs testing we want to keep this reference ) */
#endif
}
AudioConsumerWrapper::~AudioConsumerWrapper() {
log_free("AudioConsumerWrapper", this);
lock_guard lock(this->execute_lock);
this->unbind();
if(this->_handle->handle) {
this->_handle->handle->delete_consumer(this->_handle);
this->_handle = nullptr;
}
#ifdef DO_DEADLOCK_REF
if(this->_recorder)
this->_recorder->js_unref();
#endif
}
void AudioConsumerWrapper::do_wrap(const v8::Local<v8::Object> &obj) {
this->Wrap(obj);
this->_call_data = Nan::async_callback([&] {
Nan::HandleScope scope;
auto handle = this->handle();
v8::Local<v8::Value> callback_function = handle->Get(Nan::New<v8::String>("callback_data").ToLocalChecked());
if(callback_function.IsEmpty() || callback_function->IsNullOrUndefined() || !callback_function->IsFunction()) {
lock_guard lock(this->_data_lock);
this->_data_entries.clear();
}
std::unique_ptr<DataEntry> buffer;
while(true) {
{
lock_guard lock(this->_data_lock);
if(this->_data_entries.empty())
break;
buffer = move(this->_data_entries.front());
this->_data_entries.pop_front();
}
const auto byte_length = buffer->sample_count * this->_handle->channel_count * 4;
auto js_buffer = v8::ArrayBuffer::New(Nan::GetCurrentContext()->GetIsolate(), byte_length);
auto js_fbuffer = v8::Float32Array::New(js_buffer, 0, byte_length / 4);
memcpy(js_buffer->GetContents().Data(), buffer->buffer, byte_length);
v8::Local<v8::Value> argv[1];
argv[0] = js_fbuffer;
callback_function.As<v8::Function>()->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, argv);
}
});
this->_call_ended = Nan::async_callback([&]{
Nan::HandleScope scope;
auto handle = this->handle();
v8::Local<v8::Value> callback_function = handle->Get(Nan::New<v8::String>("callback_ended").ToLocalChecked());
if(callback_function.IsEmpty() || callback_function->IsNullOrUndefined() || !callback_function->IsFunction())
return;
callback_function.As<v8::Function>()->Call(Nan::GetCurrentContext(), Nan::Undefined(), 0, nullptr);
});
this->_call_started = Nan::async_callback([&]{
Nan::HandleScope scope;
auto handle = this->handle();
v8::Local<v8::Value> callback_function = handle->Get(Nan::New<v8::String>("callback_started").ToLocalChecked());
if(callback_function.IsEmpty() || callback_function->IsNullOrUndefined() || !callback_function->IsFunction())
return;
callback_function.As<v8::Function>()->Call(Nan::GetCurrentContext(), Nan::Undefined(), 0, nullptr);
});
Nan::Set(this->handle(), Nan::New<v8::String>("frame_size").ToLocalChecked(), Nan::New<v8::Number>(this->_handle->frame_size));
Nan::Set(this->handle(), Nan::New<v8::String>("sample_rate").ToLocalChecked(), Nan::New<v8::Number>(this->_handle->sample_rate));
Nan::Set(this->handle(), Nan::New<v8::String>("channels").ToLocalChecked(), Nan::New<v8::Number>(this->_handle->channel_count));
}
void AudioConsumerWrapper::unbind() {
if(this->_handle) {
lock_guard lock(this->_handle->on_read_lock);
this->_handle->on_read = nullptr;
}
}
void AudioConsumerWrapper::process_data(const void *buffer, size_t samples) {
lock_guard lock(this->execute_lock);
auto filters = this->filters();
for(const auto& filter : filters) {
auto _filter = filter->filter();
if(!_filter) continue;
if(_filter->frame_size() != samples) {
cerr << "Tried to use a filter, but frame size does not match!" << endl;
continue;
}
if(!_filter->process(buffer)) {
if(!this->last_consumed) {
this->last_consumed = true;
this->_call_ended();
unique_lock native_read_lock(this->native_read_callback_lock);
if(this->native_read_callback) {
auto callback = this->native_read_callback; /* copy */
native_read_lock.unlock();
callback(nullptr, 0); /* notify end */
}
}
return;
}
}
if(this->last_consumed)
this->_call_started();
this->last_consumed = false;
{
unique_lock native_read_lock(this->native_read_callback_lock);
if(this->native_read_callback) {
auto callback = this->native_read_callback; /* copy */
native_read_lock.unlock();
callback(buffer, samples);
return;
}
}
auto byte_length = samples * this->_handle->channel_count * 4;
auto buf = make_unique<DataEntry>();
buf->buffer = malloc(byte_length);
memcpy(buf->buffer, buffer, byte_length);
buf->sample_count = samples;
{
lock_guard lock(this->_data_lock);
this->_data_entries.push_back(move(buf));
}
this->_call_data();
}
std::shared_ptr<AudioFilterWrapper> AudioConsumerWrapper::create_filter(const std::string& name, const std::shared_ptr<filter::Filter> &impl) {
auto result = shared_ptr<AudioFilterWrapper>(new AudioFilterWrapper(name, impl), [](AudioFilterWrapper* ptr) {
assert(v8::Isolate::GetCurrent());
ptr->Unref();
});
/* wrap into object */
{
auto js_object = Nan::NewInstance(Nan::New(AudioFilterWrapper::constructor()), 0, nullptr).ToLocalChecked();
result->do_wrap(js_object);
result->Ref();
}
{
lock_guard lock(this->_filters_lock);
this->_filters.push_back(result);
}
return result;
}
void AudioConsumerWrapper::delete_filter(const AudioFilterWrapper* filter) {
shared_ptr<AudioFilterWrapper> handle; /* need to keep the handle 'till everything has been finished */
{
lock_guard lock(this->_filters_lock);
for(auto& c : this->_filters) {
if(&*c == filter) {
handle = c;
break;
}
}
if(!handle)
return;
{
auto it = find(this->_filters.begin(), this->_filters.end(), handle);
if(it != this->_filters.end())
this->_filters.erase(it);
}
}
{
lock_guard lock(this->execute_lock); /* ensure that the filter isn't used right now */
handle->_filter = nullptr;
}
}
NAN_METHOD(AudioConsumerWrapper::_get_filters) {
auto handle = ObjectWrap::Unwrap<AudioConsumerWrapper>(info.Holder());
auto filters = handle->filters();
auto result = Nan::New<v8::Array>(filters.size());
for(size_t index = 0; index < filters.size(); index++)
result->Set(index, filters[index]->handle());
info.GetReturnValue().Set(result);
}
NAN_METHOD(AudioConsumerWrapper::_unregister_filter) {
auto handle = ObjectWrap::Unwrap<AudioConsumerWrapper>(info.Holder());
if(info.Length() != 1 || !info[0]->IsObject()) {
Nan::ThrowError("invalid argument");
return;
}
if(!Nan::New(AudioFilterWrapper::constructor_template())->HasInstance(info[0])) {
Nan::ThrowError("invalid consumer");
return;
}
auto consumer = ObjectWrap::Unwrap<AudioFilterWrapper>(info[0]->ToObject(Nan::GetCurrentContext()).ToLocalChecked());
handle->delete_filter(consumer);
}
NAN_METHOD(AudioConsumerWrapper::_create_filter_vad) {
auto handle = ObjectWrap::Unwrap<AudioConsumerWrapper>(info.Holder());
auto consumer = handle->_handle;
assert(consumer); /* should never be null! */
if(info.Length() != 1 || !info[0]->IsNumber()) {
Nan::ThrowError("invalid argument");
return;
}
string error;
auto filter = make_shared<filter::VadFilter>(consumer->channel_count,consumer->sample_rate,consumer->frame_size);
if(!filter->initialize(error, info[0]->Int32Value(Nan::GetCurrentContext()).FromMaybe(0), 2)) {
Nan::ThrowError(Nan::New<v8::String>("failed to initialize filter (" + error + ")").ToLocalChecked());
return;
}
auto object = handle->create_filter("vad", filter);
info.GetReturnValue().Set(object->handle());
}
NAN_METHOD(AudioConsumerWrapper::_create_filter_threshold) {
auto handle = ObjectWrap::Unwrap<AudioConsumerWrapper>(info.Holder());
auto consumer = handle->_handle;
assert(consumer); /* should never be null! */
if(info.Length() != 1 || !info[0]->IsNumber()) {
Nan::ThrowError("invalid argument");
return;
}
string error;
auto filter = make_shared<filter::ThresholdFilter>(consumer->channel_count,consumer->sample_rate,consumer->frame_size);
if(!filter->initialize(error, info[0]->Int32Value(Nan::GetCurrentContext()).FromMaybe(0), 2)) {
Nan::ThrowError(Nan::New<v8::String>("failed to initialize filter (" + error + ")").ToLocalChecked());
return;
}
auto object = handle->create_filter("threshold", filter);
info.GetReturnValue().Set(object->handle());
}
NAN_METHOD(AudioConsumerWrapper::_create_filter_state) {
auto handle = ObjectWrap::Unwrap<AudioConsumerWrapper>(info.Holder());
auto consumer = handle->_handle;
assert(consumer); /* should never be null! */
string error;
auto filter = make_shared<filter::StateFilter>(consumer->channel_count,consumer->sample_rate,consumer->frame_size);
if(!filter->initialize(error)) {
Nan::ThrowError(Nan::New<v8::String>("failed to initialize filter (" + error + ")").ToLocalChecked());
return;
}
auto object = handle->create_filter("state", filter);
info.GetReturnValue().Set(object->handle());
}
@@ -1,105 +0,0 @@
#pragma once
#include <nan.h>
#include <mutex>
#include <deque>
#include <NanEventCallback.h>
namespace tc {
namespace audio {
class AudioInput;
class AudioConsumer;
namespace filter {
class Filter;
}
namespace recorder {
class AudioFilterWrapper;
class AudioRecorderWrapper;
/*
get_filters() : ConsumeFilter[];
register_filter(filter: ConsumeFilter);
unregister_filter(filter: ConsumeFilter);
create_filter_vad() : VADConsumeFilter;
create_filter_threshold() : ThresholdConsumeFilter;
*/
class AudioConsumerWrapper : public Nan::ObjectWrap {
friend class AudioRecorderWrapper;
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;
}
static inline Nan::Persistent<v8::FunctionTemplate> & constructor_template() {
static Nan::Persistent<v8::FunctionTemplate> my_constructor_template;
return my_constructor_template;
}
AudioConsumerWrapper(AudioRecorderWrapper*, const std::shared_ptr<AudioConsumer>& /* handle */);
~AudioConsumerWrapper() override;
static NAN_METHOD(_get_filters);
static NAN_METHOD(_unregister_filter);
static NAN_METHOD(_create_filter_vad);
static NAN_METHOD(_create_filter_threshold);
static NAN_METHOD(_create_filter_state);
std::shared_ptr<AudioFilterWrapper> create_filter(const std::string& /* name */, const std::shared_ptr<filter::Filter>& /* filter impl */);
void delete_filter(const AudioFilterWrapper*);
inline std::deque<std::shared_ptr<AudioFilterWrapper>> filters() {
std::lock_guard lock(this->_filters_lock);
return this->_filters;
}
inline std::shared_ptr<AudioConsumer> native_consumer() { return this->_handle; }
std::mutex native_read_callback_lock;
std::function<void(const void * /* buffer */, size_t /* samples */)> native_read_callback;
private:
AudioRecorderWrapper* _recorder;
std::mutex execute_lock;
std::shared_ptr<AudioConsumer> _handle;
std::mutex _filters_lock;
std::deque<std::shared_ptr<AudioFilterWrapper>> _filters;
bool last_consumed = false;
void do_wrap(const v8::Local<v8::Object>& /* object */);
void unbind(); /* called with execute_lock locked */
void process_data(const void* /* buffer */, size_t /* samples */);
struct DataEntry {
void* buffer = nullptr;
size_t sample_count = 0;
~DataEntry() {
if(buffer)
free(buffer);
}
};
std::mutex _data_lock;
std::deque<std::unique_ptr<DataEntry>> _data_entries;
Nan::callback_t<> _call_data;
Nan::callback_t<> _call_ended;
Nan::callback_t<> _call_started;
/*
callback_data: (buffer: Float32Array) => any;
callback_ended: () => any;
*/
};
}
}
}
@@ -1,321 +0,0 @@
#include "AudioFilter.h"
#include "../filter/FilterVad.h"
#include "../filter/FilterThreshold.h"
#include "../filter/FilterState.h"
#include "../../logger.h"
using namespace std;
using namespace tc::audio;
using namespace tc::audio::recorder;
NAN_MODULE_INIT(AudioFilterWrapper::Init) {
auto klass = Nan::New<v8::FunctionTemplate>(AudioFilterWrapper::NewInstance);
klass->SetClassName(Nan::New("AudioFilter").ToLocalChecked());
klass->InstanceTemplate()->SetInternalFieldCount(1);
Nan::SetPrototypeMethod(klass, "get_name", AudioFilterWrapper::_get_name);
Nan::SetPrototypeMethod(klass, "get_margin_frames", AudioFilterWrapper::_get_margin_frames);
Nan::SetPrototypeMethod(klass, "set_margin_frames", AudioFilterWrapper::_set_margin_frames);
Nan::SetPrototypeMethod(klass, "get_level", AudioFilterWrapper::_get_level);
Nan::SetPrototypeMethod(klass, "get_threshold", AudioFilterWrapper::_get_threshold);
Nan::SetPrototypeMethod(klass, "set_threshold", AudioFilterWrapper::_set_threshold);
Nan::SetPrototypeMethod(klass, "get_attack_smooth", AudioFilterWrapper::_get_attack_smooth);
Nan::SetPrototypeMethod(klass, "set_attack_smooth", AudioFilterWrapper::_set_attack_smooth);
Nan::SetPrototypeMethod(klass, "get_release_smooth", AudioFilterWrapper::_get_release_smooth);
Nan::SetPrototypeMethod(klass, "set_release_smooth", AudioFilterWrapper::_set_release_smooth);
Nan::SetPrototypeMethod(klass, "set_analyze_filter", AudioFilterWrapper::_set_analyze_filter);
Nan::SetPrototypeMethod(klass, "is_consuming", AudioFilterWrapper::_is_consuming);
Nan::SetPrototypeMethod(klass, "set_consuming", AudioFilterWrapper::_set_consuming);
constructor_template().Reset(klass);
constructor().Reset(Nan::GetFunction(klass).ToLocalChecked());
}
NAN_METHOD(AudioFilterWrapper::NewInstance) {
if(!info.IsConstructCall())
Nan::ThrowError("invalid invoke!");
}
AudioFilterWrapper::AudioFilterWrapper(const std::string& name, const std::shared_ptr<tc::audio::filter::Filter> &filter) : _filter(filter), _name(name) {
auto threshold_filter = dynamic_pointer_cast<filter::ThresholdFilter>(this->_filter);
if(threshold_filter) {
this->_call_analyzed = Nan::async_callback([&](float value) {
Nan::HandleScope scope;
if(!this->_callback_analyzed.IsEmpty()) {
auto cb = Nan::New<v8::Function>(this->_callback_analyzed);
v8::Local<v8::Value> argv[1];
argv[0] = Nan::New<v8::Number>(value);
cb->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, argv);
}
});
}
log_allocate("AudioFilterWrapper", this);
}
AudioFilterWrapper::~AudioFilterWrapper() {
log_free("AudioFilterWrapper", this);
auto threshold_filter = dynamic_pointer_cast<filter::ThresholdFilter>(this->_filter);
if(threshold_filter)
threshold_filter->on_analyze = nullptr;
this->_callback_analyzed.Reset();
}
void AudioFilterWrapper::do_wrap(const v8::Local<v8::Object> &obj) {
this->Wrap(obj);
}
NAN_METHOD(AudioFilterWrapper::_get_name) {
auto handle = ObjectWrap::Unwrap<AudioFilterWrapper>(info.Holder());
if(!handle->_filter) {
Nan::ThrowError("invalid handle");
return;
}
info.GetReturnValue().Set(Nan::New<v8::String>(handle->_name).ToLocalChecked());
}
NAN_METHOD(AudioFilterWrapper::_get_level) {
auto handle = ObjectWrap::Unwrap<AudioFilterWrapper>(info.Holder());
if(!handle->_filter) {
Nan::ThrowError("invalid handle");
return;
}
auto filter = dynamic_pointer_cast<filter::VadFilter>(handle->_filter);
if(!filter) {
Nan::ThrowError("filter does not support this method");
return;
}
info.GetReturnValue().Set((int) filter->mode());
}
NAN_METHOD(AudioFilterWrapper::_get_margin_frames) {
auto handle = ObjectWrap::Unwrap<AudioFilterWrapper>(info.Holder());
if(!handle->_filter) {
Nan::ThrowError("invalid handle");
return;
}
auto vad_filter = dynamic_pointer_cast<filter::VadFilter>(handle->_filter);
auto threshold_filter = dynamic_pointer_cast<filter::ThresholdFilter>(handle->_filter);
if(vad_filter) {
info.GetReturnValue().Set((int) vad_filter->margin_frames());
} else if(threshold_filter) {
info.GetReturnValue().Set((int) threshold_filter->margin_frames());
} else {
Nan::ThrowError("invalid handle");
return;
}
}
NAN_METHOD(AudioFilterWrapper::_set_margin_frames) {
auto handle = ObjectWrap::Unwrap<AudioFilterWrapper>(info.Holder());
if(!handle->_filter) {
Nan::ThrowError("invalid handle");
return;
}
if(info.Length() != 1 || !info[0]->IsNumber()) {
Nan::ThrowError("invalid argument");
return;
}
auto vad_filter = dynamic_pointer_cast<filter::VadFilter>(handle->_filter);
auto threshold_filter = dynamic_pointer_cast<filter::ThresholdFilter>(handle->_filter);
if(vad_filter) {
vad_filter->set_margin_frames(info[0]->Int32Value(Nan::GetCurrentContext()).FromMaybe(0));
} else if(threshold_filter) {
threshold_filter->set_margin_frames(info[0]->Int32Value(Nan::GetCurrentContext()).FromMaybe(0));
} else {
Nan::ThrowError("invalid handle");
return;
}
}
NAN_METHOD(AudioFilterWrapper::_get_threshold) {
auto handle = ObjectWrap::Unwrap<AudioFilterWrapper>(info.Holder());
if(!handle->_filter) {
Nan::ThrowError("invalid handle");
return;
}
auto filter = dynamic_pointer_cast<filter::ThresholdFilter>(handle->_filter);
if(!filter) {
Nan::ThrowError("filter does not support this method");
return;
}
info.GetReturnValue().Set((int) filter->threshold());
}
NAN_METHOD(AudioFilterWrapper::_set_threshold) {
auto handle = ObjectWrap::Unwrap<AudioFilterWrapper>(info.Holder());
if(!handle->_filter) {
Nan::ThrowError("invalid handle");
return;
}
if(info.Length() != 1 || !info[0]->IsNumber()) {
Nan::ThrowError("invalid argument");
return;
}
auto filter = dynamic_pointer_cast<filter::ThresholdFilter>(handle->_filter);
if(!filter) {
Nan::ThrowError("filter does not support this method");
return;
}
filter->set_threshold(info[0]->Int32Value(Nan::GetCurrentContext()).FromMaybe(0));
}
NAN_METHOD(AudioFilterWrapper::_get_attack_smooth) {
auto handle = ObjectWrap::Unwrap<AudioFilterWrapper>(info.Holder());
if(!handle->_filter) {
Nan::ThrowError("invalid handle");
return;
}
auto filter = dynamic_pointer_cast<filter::ThresholdFilter>(handle->_filter);
if(!filter) {
Nan::ThrowError("filter does not support this method");
return;
}
info.GetReturnValue().Set((int) filter->attack_smooth());
}
NAN_METHOD(AudioFilterWrapper::_set_attack_smooth) {
auto handle = ObjectWrap::Unwrap<AudioFilterWrapper>(info.Holder());
if(!handle->_filter) {
Nan::ThrowError("invalid handle");
return;
}
if(info.Length() != 1 || !info[0]->IsNumber()) {
Nan::ThrowError("invalid argument");
return;
}
auto filter = dynamic_pointer_cast<filter::ThresholdFilter>(handle->_filter);
if(!filter) {
Nan::ThrowError("filter does not support this method");
return;
}
filter->attack_smooth(info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0));
}
NAN_METHOD(AudioFilterWrapper::_get_release_smooth) {
auto handle = ObjectWrap::Unwrap<AudioFilterWrapper>(info.Holder());
if(!handle->_filter) {
Nan::ThrowError("invalid handle");
return;
}
auto filter = dynamic_pointer_cast<filter::ThresholdFilter>(handle->_filter);
if(!filter) {
Nan::ThrowError("filter does not support this method");
return;
}
info.GetReturnValue().Set((int) filter->release_smooth());
}
NAN_METHOD(AudioFilterWrapper::_set_release_smooth) {
auto handle = ObjectWrap::Unwrap<AudioFilterWrapper>(info.Holder());
if(!handle->_filter) {
Nan::ThrowError("invalid handle");
return;
}
if(info.Length() != 1 || !info[0]->IsNumber()) {
Nan::ThrowError("invalid argument");
return;
}
auto filter = dynamic_pointer_cast<filter::ThresholdFilter>(handle->_filter);
if(!filter) {
Nan::ThrowError("filter does not support this method");
return;
}
filter->release_smooth(info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0));
}
NAN_METHOD(AudioFilterWrapper::_set_analyze_filter) {
auto handle = ObjectWrap::Unwrap<AudioFilterWrapper>(info.Holder());
if(!handle->_filter) {
Nan::ThrowError("invalid handle");
return;
}
if(info.Length() != 1 || !(info[0]->IsFunction() || info[0]->IsNullOrUndefined())) {
Nan::ThrowError("invalid argument");
return;
}
auto filter = dynamic_pointer_cast<filter::ThresholdFilter>(handle->_filter);
if(!filter) {
Nan::ThrowError("filter does not support this method");
return;
}
if(info[0]->IsNullOrUndefined()) {
handle->_callback_analyzed.Reset();
filter->on_analyze = nullptr;
} else {
handle->_callback_analyzed.Reset(info[0].As<v8::Function>());
filter->on_analyze = [handle](float value){
handle->_call_analyzed.call(std::forward<float>(value), true);
};
}
}
NAN_METHOD(AudioFilterWrapper::_is_consuming) {
auto handle = ObjectWrap::Unwrap<AudioFilterWrapper>(info.Holder());
if(!handle->_filter) {
Nan::ThrowError("invalid handle");
return;
}
auto filter = dynamic_pointer_cast<filter::StateFilter>(handle->_filter);
if(!filter) {
Nan::ThrowError("filter does not support this method");
return;
}
info.GetReturnValue().Set(filter->consumes_input());
}
NAN_METHOD(AudioFilterWrapper::_set_consuming) {
auto handle = ObjectWrap::Unwrap<AudioFilterWrapper>(info.Holder());
if(!handle->_filter) {
Nan::ThrowError("invalid handle");
return;
}
if(info.Length() != 1 || !info[0]->IsBoolean()) {
Nan::ThrowError("invalid argument");
return;
}
auto filter = dynamic_pointer_cast<filter::StateFilter>(handle->_filter);
if(!filter) {
Nan::ThrowError("filter does not support this method");
return;
}
filter->set_consume_input(info[0]->BooleanValue(info.GetIsolate()));
}
@@ -1,69 +0,0 @@
#pragma once
#include <nan.h>
#include <NanEventCallback.h>
namespace tc {
namespace audio {
namespace filter {
class Filter;
}
namespace recorder {
class AudioConsumerWrapper;
class AudioFilterWrapper : public Nan::ObjectWrap {
friend class AudioConsumerWrapper;
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;
}
static inline Nan::Persistent<v8::FunctionTemplate> & constructor_template() {
static Nan::Persistent<v8::FunctionTemplate> my_constructor_template;
return my_constructor_template;
}
AudioFilterWrapper(const std::string& name, const std::shared_ptr<filter::Filter>& /* handle */);
virtual ~AudioFilterWrapper();
static NAN_METHOD(_get_name);
/* VAD and Threshold */
static NAN_METHOD(_get_margin_frames);
static NAN_METHOD(_set_margin_frames);
/* VAD relevant */
static NAN_METHOD(_get_level);
/* threshold filter relevant */
static NAN_METHOD(_get_threshold);
static NAN_METHOD(_set_threshold);
static NAN_METHOD(_get_attack_smooth);
static NAN_METHOD(_set_attack_smooth);
static NAN_METHOD(_get_release_smooth);
static NAN_METHOD(_set_release_smooth);
static NAN_METHOD(_set_analyze_filter);
/* consume filter */
static NAN_METHOD(_is_consuming);
static NAN_METHOD(_set_consuming);
inline std::shared_ptr<filter::Filter> filter() { return this->_filter; }
private:
std::shared_ptr<filter::Filter> _filter;
std::string _name;
void do_wrap(const v8::Local<v8::Object>& /* object */);
Nan::callback_t<float> _call_analyzed;
Nan::Persistent<v8::Function> _callback_analyzed;
};
}
}
}
@@ -1,285 +0,0 @@
#include <cmath>
#include "AudioOutputStream.h"
#include "../AudioOutput.h"
#include "../AudioResampler.h"
using namespace std;
using namespace tc;
using namespace tc::audio;
NAN_MODULE_INIT(AudioOutputStreamWrapper::Init) {
auto klass = Nan::New<v8::FunctionTemplate>(AudioOutputStreamWrapper::NewInstance);
klass->SetClassName(Nan::New("AudioOutputStream").ToLocalChecked());
klass->InstanceTemplate()->SetInternalFieldCount(1);
Nan::SetPrototypeMethod(klass, "write_data", AudioOutputStreamWrapper::_write_data);
Nan::SetPrototypeMethod(klass, "get_buffer_latency", AudioOutputStreamWrapper::_get_buffer_latency);
Nan::SetPrototypeMethod(klass, "set_buffer_latency", AudioOutputStreamWrapper::_set_buffer_latency);
Nan::SetPrototypeMethod(klass, "get_buffer_max_latency", AudioOutputStreamWrapper::_get_buffer_max_latency);
Nan::SetPrototypeMethod(klass, "set_buffer_max_latency", AudioOutputStreamWrapper::_set_buffer_max_latency);
Nan::SetPrototypeMethod(klass, "write_data", AudioOutputStreamWrapper::_write_data);
Nan::SetPrototypeMethod(klass, "write_data_rated", AudioOutputStreamWrapper::_write_data_rated);
Nan::SetPrototypeMethod(klass, "deleted", AudioOutputStreamWrapper::_deleted);
Nan::SetPrototypeMethod(klass, "delete", AudioOutputStreamWrapper::_delete);
Nan::SetPrototypeMethod(klass, "clear", AudioOutputStreamWrapper::_clear);
constructor().Reset(Nan::GetFunction(klass).ToLocalChecked());
}
NAN_METHOD(AudioOutputStreamWrapper::NewInstance) {
if(!info.IsConstructCall())
Nan::ThrowError("invalid invoke!");
}
AudioOutputStreamWrapper::AudioOutputStreamWrapper(const std::shared_ptr<tc::audio::AudioOutputSource> &stream, bool owns) {
this->_handle = stream;
if(owns) {
this->_own_handle = stream;
}
}
AudioOutputStreamWrapper::~AudioOutputStreamWrapper() {
this->drop_stream();
}
void AudioOutputStreamWrapper::drop_stream() {
if(this->_own_handle) {
auto handle = this->_own_handle->handle;
if(handle) {
handle->delete_source(this->_own_handle);
}
this->_own_handle->on_underflow = nullptr;
this->_own_handle->on_overflow = nullptr;
}
this->_handle.reset();
this->_own_handle = nullptr;
}
void AudioOutputStreamWrapper::do_wrap(const v8::Local<v8::Object> &obj) {
this->Wrap(obj);
auto handle = this->_handle.lock();
if(!handle) {
Nan::ThrowError("weak handle");
return;
}
Nan::ForceSet(this->handle(), Nan::New<v8::String>("sample_rate").ToLocalChecked(), Nan::New<v8::Number>(handle->sample_rate), v8::ReadOnly);
Nan::ForceSet(this->handle(), Nan::New<v8::String>("channels").ToLocalChecked(), Nan::New<v8::Number>(handle->channel_count), v8::ReadOnly);
if(this->_own_handle) {
this->call_underflow = Nan::async_callback([&]{
Nan::HandleScope scope;
auto handle = this->handle();
auto callback = Nan::Get(handle, Nan::New<v8::String>("callback_underflow").ToLocalChecked()).ToLocalChecked();
if(callback->IsFunction())
callback.As<v8::Function>()->Call(Nan::GetCurrentContext(), Nan::Undefined(), 0, nullptr);
});
this->call_overflow = Nan::async_callback([&]{
Nan::HandleScope scope;
auto handle = this->handle();
auto callback = Nan::Get(handle, Nan::New<v8::String>("callback_overflow").ToLocalChecked()).ToLocalChecked();
if(callback->IsFunction())
callback.As<v8::Function>()->Call(Nan::GetCurrentContext(), Nan::Undefined(), 0, nullptr);
});
this->_own_handle->on_overflow = [&](size_t){ this->call_overflow(); };
this->_own_handle->on_underflow = [&]{ this->call_underflow(); return false; };
}
}
NAN_METHOD(AudioOutputStreamWrapper::_clear) {
auto client = ObjectWrap::Unwrap<AudioOutputStreamWrapper>(info.Holder());
auto handle = client->_own_handle;
if(!handle) {
Nan::ThrowError("invalid handle");
return;
}
handle->clear();
}
NAN_METHOD(AudioOutputStreamWrapper::_deleted) {
auto client = ObjectWrap::Unwrap<AudioOutputStreamWrapper>(info.Holder());
info.GetReturnValue().Set(!client->_own_handle || !client->_own_handle->handle);
}
NAN_METHOD(AudioOutputStreamWrapper::_delete) {
auto client = ObjectWrap::Unwrap<AudioOutputStreamWrapper>(info.Holder());
client->drop_stream();
}
ssize_t AudioOutputStreamWrapper::write_data(const std::shared_ptr<AudioOutputSource>& handle, void *source, size_t samples, bool interleaved) {
ssize_t result = 0;
if(interleaved) {
result = handle->enqueue_samples(source, samples);
} else {
auto buffer = SampleBuffer::allocate(handle->channel_count, samples);
auto src_buffer = (float*) source;
auto target_buffer = (float*) buffer->sample_data;
while (samples-- > 0) {
*target_buffer = *src_buffer;
*(target_buffer + 1) = *(src_buffer + buffer->sample_size);
target_buffer += 2;
src_buffer++;
}
result = handle->enqueue_samples(buffer);
}
return result;
}
NAN_METHOD(AudioOutputStreamWrapper::_write_data) {
auto client = ObjectWrap::Unwrap<AudioOutputStreamWrapper>(info.Holder());
auto handle = client->_own_handle;
if(!handle) {
Nan::ThrowError("invalid handle");
return;
}
if(info.Length() != 2 || !info[0]->IsArrayBuffer() || !info[1]->IsBoolean()) {
Nan::ThrowError("Invalid arguments");
return;
}
auto interleaved = info[1]->BooleanValue(info.GetIsolate());
auto js_buffer = info[0].As<v8::ArrayBuffer>()->GetContents();
if(js_buffer.ByteLength() % (handle->channel_count * 4) != 0) {
Nan::ThrowError("input buffer invalid size");
return;
}
auto samples = js_buffer.ByteLength() / handle->channel_count / 4;
info.GetReturnValue().Set((int32_t) write_data(handle, js_buffer.Data(), samples, interleaved));
}
NAN_METHOD(AudioOutputStreamWrapper::_write_data_rated) {
auto client = ObjectWrap::Unwrap<AudioOutputStreamWrapper>(info.Holder());
auto handle = client->_own_handle;
if(!handle) {
Nan::ThrowError("invalid handle");
return;
}
if(info.Length() != 3 || !info[0]->IsArrayBuffer() || !info[1]->IsBoolean() || !info[2]->IsNumber()) {
Nan::ThrowError("Invalid arguments");
return;
}
auto sample_rate = info[2]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0);
auto interleaved = info[1]->BooleanValue(info.GetIsolate());
auto js_buffer = info[0].As<v8::ArrayBuffer>()->GetContents();
auto samples = js_buffer.ByteLength() / handle->channel_count / 4;
if(sample_rate == handle->sample_rate) {
info.GetReturnValue().Set((int32_t) write_data(handle, js_buffer.Data(), samples, interleaved));
} else {
if(!client->_resampler || client->_resampler->input_rate() != sample_rate)
client->_resampler = make_unique<AudioResampler>(sample_rate, handle->sample_rate, handle->channel_count);
if(!client->_resampler || !client->_resampler->valid()) {
Nan::ThrowError("Resampling failed (invalid resampler)");
return;
}
ssize_t target_samples = client->_resampler->estimated_output_size(samples);
auto buffer = SampleBuffer::allocate(handle->channel_count, max((size_t) samples, (size_t) target_samples));
auto source_buffer = js_buffer.Data();
if(!interleaved) {
auto src_buffer = (float*) js_buffer.Data();
auto target_buffer = (float*) buffer->sample_data;
auto samples_count = samples;
while (samples_count-- > 0) {
*target_buffer = *src_buffer;
*(target_buffer + 1) = *(src_buffer + samples);
target_buffer += 2;
src_buffer++;
}
source_buffer = buffer->sample_data;
}
target_samples = client->_resampler->process(buffer->sample_data, source_buffer, samples);
if(target_samples < 0) {
Nan::ThrowError("Resampling failed");
return;
}
buffer->sample_index = 0;
buffer->sample_size = target_samples;
info.GetReturnValue().Set((int32_t) handle->enqueue_samples(buffer));
}
}
NAN_METHOD(AudioOutputStreamWrapper::_get_buffer_latency) {
auto client = ObjectWrap::Unwrap<AudioOutputStreamWrapper>(info.Holder());
auto handle = client->_handle.lock();
if(!handle) {
Nan::ThrowError("weak handle");
return;
}
info.GetReturnValue().Set((float) handle->min_buffer / (float) handle->sample_rate);
}
NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_latency) {
auto client = ObjectWrap::Unwrap<AudioOutputStreamWrapper>(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->min_buffer = (size_t) ceil(handle->sample_rate * info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0));
}
NAN_METHOD(AudioOutputStreamWrapper::_get_buffer_max_latency) {
auto client = ObjectWrap::Unwrap<AudioOutputStreamWrapper>(info.Holder());
auto handle = client->_handle.lock();
if(!handle) {
Nan::ThrowError("weak handle");
return;
}
info.GetReturnValue().Set((float) handle->max_latency / (float) handle->sample_rate);
}
NAN_METHOD(AudioOutputStreamWrapper::_set_buffer_max_latency) {
auto client = ObjectWrap::Unwrap<AudioOutputStreamWrapper>(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->max_latency = (size_t) ceil(handle->sample_rate * info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0));
}
@@ -1,50 +0,0 @@
#pragma once
#include <nan.h>
#include <NanEventCallback.h>
namespace tc {
namespace audio {
class AudioResampler;
class AudioOutputSource;
class AudioOutputStreamWrapper : 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;
}
AudioOutputStreamWrapper(const std::shared_ptr<AudioOutputSource>& /* stream */, bool /* own */);
virtual ~AudioOutputStreamWrapper();
void do_wrap(const v8::Local<v8::Object>&);
void drop_stream();
private:
static ssize_t write_data(const std::shared_ptr<AudioOutputSource>&, void* source, size_t samples, bool interleaved);
/* general methods */
static NAN_METHOD(_get_buffer_latency);
static NAN_METHOD(_set_buffer_latency);
static NAN_METHOD(_get_buffer_max_latency);
static NAN_METHOD(_set_buffer_max_latency);
/* methods for owned streams only */
static NAN_METHOD(_write_data);
static NAN_METHOD(_write_data_rated);
static NAN_METHOD(_clear);
static NAN_METHOD(_deleted);
static NAN_METHOD(_delete);
std::unique_ptr<AudioResampler> _resampler;
std::shared_ptr<AudioOutputSource> _own_handle;
std::weak_ptr<AudioOutputSource> _handle;
Nan::callback_t<> call_underflow;
Nan::callback_t<> call_overflow;
};
}
}
@@ -1,113 +0,0 @@
#include <misc/base64.h>
#include <misc/digest.h>
#include "AudioPlayer.h"
#include "../AudioOutput.h"
#include "../AudioDevice.h"
#include "AudioOutputStream.h"
using namespace tc;
using namespace tc::audio;
extern tc::audio::AudioOutput* global_audio_output;
NAN_MODULE_INIT(player::init_js) {
Nan::Set(target, Nan::New<v8::String>("current_device").ToLocalChecked(), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(player::current_playback_device)).ToLocalChecked());
Nan::Set(target, Nan::New<v8::String>("set_device").ToLocalChecked(), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(player::set_playback_device)).ToLocalChecked());
Nan::Set(target, Nan::New<v8::String>("create_stream").ToLocalChecked(), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(player::create_stream)).ToLocalChecked());
Nan::Set(target, Nan::New<v8::String>("get_master_volume").ToLocalChecked(), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(player::get_master_volume)).ToLocalChecked());
Nan::Set(target, Nan::New<v8::String>("set_master_volume").ToLocalChecked(), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(player::set_master_volume)).ToLocalChecked());
}
NAN_METHOD(audio::available_devices) {
auto devices = audio::devices();
auto result = Nan::New<v8::Array>(devices.size());
for(size_t index = 0; index < devices.size(); index++) {
auto device_info = Nan::New<v8::Object>();
auto device = devices[index];
Nan::Set(device_info, Nan::New<v8::String>("name").ToLocalChecked(), Nan::New<v8::String>(device->name).ToLocalChecked());
Nan::Set(device_info, Nan::New<v8::String>("driver").ToLocalChecked(), Nan::New<v8::String>(device->driver).ToLocalChecked());
Nan::Set(device_info, Nan::New<v8::String>("device_id").ToLocalChecked(), Nan::New<v8::String>(base64::encode(digest::sha1(device->name + device->driver))).ToLocalChecked());
Nan::Set(device_info, Nan::New<v8::String>("input_supported").ToLocalChecked(), Nan::New<v8::Boolean>(device->input_supported));
Nan::Set(device_info, Nan::New<v8::String>("output_supported").ToLocalChecked(), Nan::New<v8::Boolean>(device->output_supported));
Nan::Set(device_info, Nan::New<v8::String>("input_default").ToLocalChecked(), Nan::New<v8::Boolean>(device->is_default_input));
Nan::Set(device_info, Nan::New<v8::String>("output_default").ToLocalChecked(), Nan::New<v8::Boolean>(device->is_default_output));
Nan::Set(device_info, Nan::New<v8::String>("device_index").ToLocalChecked(), Nan::New<v8::Number>(device->device_id));
result->Set(index, device_info);
}
info.GetReturnValue().Set(result);
}
NAN_METHOD(player::current_playback_device) {
if(!global_audio_output) {
info.GetReturnValue().Set(paNoDevice);
return;
}
info.GetReturnValue().Set(global_audio_output->current_device());
}
NAN_METHOD(player::set_playback_device) {
if(!global_audio_output) {
Nan::ThrowError("Global audio output hasn't been yet initialized!");
return;
}
if(info.Length() != 1 || !info[0]->IsNumber()) {
Nan::ThrowError("invalid arguments");
return;
}
std::string error;
if(!global_audio_output->open_device(error, info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0))) {
Nan::ThrowError(Nan::New<v8::String>("failed to open device (" + error + ")").ToLocalChecked());
return;
}
if(!global_audio_output->playback()) {
Nan::ThrowError("failed to open playback stream");
return;
}
}
NAN_METHOD(player::create_stream) {
if(!global_audio_output) {
Nan::ThrowError("Global audio output hasn't been yet initialized!");
return;
}
auto wrapper = new audio::AudioOutputStreamWrapper(global_audio_output->create_source(), true);
auto object = Nan::NewInstance(Nan::New(audio::AudioOutputStreamWrapper::constructor()), 0, nullptr).ToLocalChecked();
wrapper->do_wrap(object);
info.GetReturnValue().Set(object);
}
NAN_METHOD(player::get_master_volume) {
if(!global_audio_output) {
Nan::ThrowError("Global audio output hasn't been yet initialized!");
return;
}
info.GetReturnValue().Set(global_audio_output->volume());
}
NAN_METHOD(player::set_master_volume) {
if(!global_audio_output) {
Nan::ThrowError("Global audio output hasn't been yet initialized!");
return;
}
if(info.Length() != 1 || !info[0]->IsNumber()) {
Nan::ThrowError("invalid arguments");
return;
}
global_audio_output->set_volume(info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0));
}
@@ -1,33 +0,0 @@
#pragma once
#include <nan.h>
namespace tc {
namespace audio {
extern NAN_METHOD(available_devices);
namespace player {
extern NAN_MODULE_INIT(init_js);
extern NAN_METHOD(current_playback_device);
extern NAN_METHOD(set_playback_device);
extern NAN_METHOD(create_stream);
extern NAN_METHOD(get_master_volume);
extern NAN_METHOD(set_master_volume);
/*
export function get_master_volume() : number;
export function set_master_volume(volume: number);
export function set_device(device: AudioDevice) : Promise<void>;
export function current_device() : AudioDevice;
export function available_devices() : AudioDevice[];
export function create_stream() : OwnedAudioOutputStream;
export function delete_stream(stream: OwnedAudioOutputStream) : number;
*/
}
}
}
@@ -1,266 +0,0 @@
#include <utility>
#include "AudioRecorder.h"
#include "AudioConsumer.h"
#include "../AudioInput.h"
#include "../../logger.h"
using namespace std;
using namespace tc::audio;
using namespace tc::audio::recorder;
NAN_MODULE_INIT(recorder::init_js) {
Nan::Set(target, Nan::New<v8::String>("create_recorder").ToLocalChecked(), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(recorder::create_recorder)).ToLocalChecked());
}
NAN_METHOD(recorder::create_recorder) {
auto input = make_shared<AudioInput>(2, 48000);
auto wrapper = new AudioRecorderWrapper(input);
auto js_object = Nan::NewInstance(Nan::New(AudioRecorderWrapper::constructor())).ToLocalChecked();
wrapper->do_wrap(js_object);
info.GetReturnValue().Set(js_object);
}
NAN_MODULE_INIT(AudioRecorderWrapper::Init) {
auto klass = Nan::New<v8::FunctionTemplate>(AudioRecorderWrapper::NewInstance);
klass->SetClassName(Nan::New("AudioRecorder").ToLocalChecked());
klass->InstanceTemplate()->SetInternalFieldCount(1);
Nan::SetPrototypeMethod(klass, "get_device", AudioRecorderWrapper::_get_device);
Nan::SetPrototypeMethod(klass, "set_device", AudioRecorderWrapper::_set_device);
Nan::SetPrototypeMethod(klass, "start", AudioRecorderWrapper::_start);
Nan::SetPrototypeMethod(klass, "started", AudioRecorderWrapper::_started);
Nan::SetPrototypeMethod(klass, "stop", AudioRecorderWrapper::_stop);
Nan::SetPrototypeMethod(klass, "get_volume", AudioRecorderWrapper::_get_volume);
Nan::SetPrototypeMethod(klass, "set_volume", AudioRecorderWrapper::_set_volume);
Nan::SetPrototypeMethod(klass, "get_consumers", AudioRecorderWrapper::_get_consumers);
Nan::SetPrototypeMethod(klass, "create_consumer", AudioRecorderWrapper::_create_consumer);
Nan::SetPrototypeMethod(klass, "delete_consumer", AudioRecorderWrapper::_delete_consumer);
constructor().Reset(Nan::GetFunction(klass).ToLocalChecked());
}
NAN_METHOD(AudioRecorderWrapper::NewInstance) {
if(!info.IsConstructCall())
Nan::ThrowError("invalid invoke!");
}
AudioRecorderWrapper::AudioRecorderWrapper(std::shared_ptr<tc::audio::AudioInput> handle) : _input(std::move(handle)) {
log_allocate("AudioRecorderWrapper", this);
}
AudioRecorderWrapper::~AudioRecorderWrapper() {
if(this->_input) {
this->_input->stop();
this->_input->close_device();
this->_input = nullptr;
}
{
lock_guard lock(this->_consumer_lock);
this->_consumers.clear();
}
log_free("AudioRecorderWrapper", this);
}
std::shared_ptr<AudioConsumerWrapper> AudioRecorderWrapper::create_consumer() {
auto result = shared_ptr<AudioConsumerWrapper>(new AudioConsumerWrapper(this, this->_input->create_consumer(960)), [](AudioConsumerWrapper* ptr) {
assert(v8::Isolate::GetCurrent());
ptr->Unref();
});
/* wrap into object */
{
auto js_object = Nan::NewInstance(Nan::New(AudioConsumerWrapper::constructor()), 0, nullptr).ToLocalChecked();
result->do_wrap(js_object);
result->Ref();
}
{
lock_guard lock(this->_consumer_lock);
this->_consumers.push_back(result);
}
return result;
}
void AudioRecorderWrapper::delete_consumer(const AudioConsumerWrapper* consumer) {
shared_ptr<AudioConsumerWrapper> handle; /* need to keep the handle 'till everything has been finished */
{
lock_guard lock(this->_consumer_lock);
for(auto& c : this->_consumers) {
if(&*c == consumer) {
handle = c;
break;
}
}
if(!handle)
return;
{
auto it = find(this->_consumers.begin(), this->_consumers.end(), handle);
if(it != this->_consumers.end())
this->_consumers.erase(it);
}
}
{
lock_guard lock(handle->execute_lock); /* if we delete the consumer while executing strange stuff could happen */
handle->unbind();
this->_input->delete_consumer(handle->_handle);
}
}
void AudioRecorderWrapper::do_wrap(const v8::Local<v8::Object> &obj) {
this->Wrap(obj);
}
NAN_METHOD(AudioRecorderWrapper::_get_device) {
auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder());
auto input = handle->_input;
info.GetReturnValue().Set(input->current_device());
}
NAN_METHOD(AudioRecorderWrapper::_set_device) {
auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder());
auto input = handle->_input;
if(info.Length() != 2 || !info[0]->IsNumber() || !info[1]->IsFunction()) {
Nan::ThrowError("invalid arguments");
return;
}
auto device_id = info[0]->Int32Value(Nan::GetCurrentContext()).FromMaybe(0);
unique_ptr<Nan::Persistent<v8::Function>> _callback = make_unique<Nan::Persistent<v8::Function>>(info[1].As<v8::Function>());
unique_ptr<Nan::Persistent<v8::Object>> _recorder = make_unique<Nan::Persistent<v8::Object>>(info.Holder());
auto _async_callback = Nan::async_callback([call = std::move(_callback), recorder = move(_recorder)](bool result, std::string error) mutable {
Nan::HandleScope scope;
auto callback_function = call->Get(Nan::GetCurrentContext()->GetIsolate());
v8::Local<v8::Value> argv[1];
if(result)
argv[0] = v8::Boolean::New(Nan::GetCurrentContext()->GetIsolate(), result);
else
argv[0] = Nan::NewOneByteString((uint8_t*) error.data(), error.length()).ToLocalChecked();
callback_function->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, argv);
recorder->Reset();
call->Reset();
}).option_destroyed_execute(true);
std::thread([_async_callback, input, device_id]{
string error;
auto flag = input->open_device(error, device_id);
_async_callback(std::forward<bool>(flag), std::forward<std::string>(error));
}).detach();
}
NAN_METHOD(AudioRecorderWrapper::_start) {
if(info.Length() != 1) {
Nan::ThrowError("missing callback");
return;
}
if(!info[0]->IsFunction()) {
Nan::ThrowError("not a function");
return;
}
unique_ptr<Nan::Persistent<v8::Function>> _callback = make_unique<Nan::Persistent<v8::Function>>(info[0].As<v8::Function>());
unique_ptr<Nan::Persistent<v8::Object>> _recorder = make_unique<Nan::Persistent<v8::Object>>(info.Holder());
auto _async_callback = Nan::async_callback([call = std::move(_callback), recorder = move(_recorder)](bool result) mutable {
Nan::HandleScope scope;
auto callback_function = call->Get(Nan::GetCurrentContext()->GetIsolate());
v8::Local<v8::Value> argv[1];
argv[0] = v8::Boolean::New(Nan::GetCurrentContext()->GetIsolate(), result);
callback_function->Call(Nan::GetCurrentContext(), Nan::Undefined(), 1, argv);
recorder->Reset();
call->Reset();
}).option_destroyed_execute(true);
auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder());
auto input = handle->_input;
std::thread([_async_callback, input]{
_async_callback(input->record());
}).detach();
}
NAN_METHOD(AudioRecorderWrapper::_started) {
auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder());
auto input = handle->_input;
info.GetReturnValue().Set(input->recording());
}
NAN_METHOD(AudioRecorderWrapper::_stop) {
auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder());
auto input = handle->_input;
input->stop();
}
NAN_METHOD(AudioRecorderWrapper::_create_consumer) {
auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder());
auto consumer = handle->create_consumer();
if(!consumer) {
Nan::ThrowError("failed to create consumer");
return;
}
info.GetReturnValue().Set(consumer->handle());
}
NAN_METHOD(AudioRecorderWrapper::_get_consumers) {
auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder());
auto consumers = handle->consumers();
auto result = Nan::New<v8::Array>(consumers.size());
for(size_t index = 0; index < consumers.size(); index++)
result->Set(index, consumers[index]->handle());
info.GetReturnValue().Set(result);
}
NAN_METHOD(AudioRecorderWrapper::_delete_consumer) {
auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder());
if(info.Length() != 1 || !info[0]->IsObject()) {
Nan::ThrowError("invalid argument");
return;
}
if(!Nan::New(AudioConsumerWrapper::constructor_template())->HasInstance(info[0])) {
Nan::ThrowError("invalid consumer");
return;
}
auto consumer = ObjectWrap::Unwrap<AudioConsumerWrapper>(info[0]->ToObject(Nan::GetCurrentContext()).ToLocalChecked());
handle->delete_consumer(consumer);
}
NAN_METHOD(AudioRecorderWrapper::_set_volume) {
auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder());
if(info.Length() != 1 || !info[0]->IsNumber()) {
Nan::ThrowError("invalid argument");
return;
}
handle->_input->set_volume(info[0]->NumberValue(Nan::GetCurrentContext()).FromMaybe(0));
}
NAN_METHOD(AudioRecorderWrapper::_get_volume) {
auto handle = ObjectWrap::Unwrap<AudioRecorderWrapper>(info.Holder());
info.GetReturnValue().Set(handle->_input->volume());
}
@@ -1,64 +0,0 @@
#pragma once
#include <nan.h>
#include <mutex>
#include <deque>
namespace tc {
namespace audio {
class AudioInput;
namespace recorder {
class AudioConsumerWrapper;
extern NAN_MODULE_INIT(init_js);
extern NAN_METHOD(create_recorder);
class AudioRecorderWrapper : 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 AudioRecorderWrapper(std::shared_ptr<AudioInput> /* input */);
~AudioRecorderWrapper() override;
static NAN_METHOD(_get_device);
static NAN_METHOD(_set_device);
static NAN_METHOD(_start);
static NAN_METHOD(_started);
static NAN_METHOD(_stop);
static NAN_METHOD(_create_consumer);
static NAN_METHOD(_get_consumers);
static NAN_METHOD(_delete_consumer);
static NAN_METHOD(_set_volume);
static NAN_METHOD(_get_volume);
std::shared_ptr<AudioConsumerWrapper> create_consumer();
void delete_consumer(const AudioConsumerWrapper*);
inline std::deque<std::shared_ptr<AudioConsumerWrapper>> consumers() {
std::lock_guard lock(this->_consumer_lock);
return this->_consumers;
}
void do_wrap(const v8::Local<v8::Object>& /* obj */);
inline void js_ref() { this->Ref(); }
inline void js_unref() { this->Unref(); }
private:
std::shared_ptr<AudioInput> _input;
std::mutex _consumer_lock;
std::deque<std::shared_ptr<AudioConsumerWrapper>> _consumers;
};
}
}
}
-227
View File
@@ -1,227 +0,0 @@
#include <v8.h>
#include <nan.h>
#include <node.h>
#include <iostream>
#include <mutex>
#include <event2/thread.h>
#include <misc/digest.h>
#include "logger.h"
#include "NanException.h"
#include "NanEventCallback.h"
#include "connection/ServerConnection.h"
#include "connection/audio/VoiceConnection.h"
#include "connection/audio/VoiceClient.h"
#include "connection/ft/FileTransferManager.h"
#include "connection/ft/FileTransferObject.h"
#include "audio/AudioOutput.h"
#include "audio/AudioDevice.h"
#include "audio/js/AudioOutputStream.h"
#include "audio/js/AudioPlayer.h"
#include "audio/js/AudioRecorder.h"
#include "audio/js/AudioConsumer.h"
#include "audio/js/AudioFilter.h"
#include "connection/audio/AudioEventLoop.h"
#ifndef WIN32
#include <unistd.h>
#endif
extern "C" {
#include <tomcrypt_misc.h>
#include <tomcrypt.h>
#include <tommath.h>
};
using namespace std;
using namespace tc;
using namespace tc::connection;
using namespace tc::ft;
void testTomMath(){
mp_int x{};
mp_init(&x);
mp_read_radix(&x, "2280880776330203449294339386427307168808659578661428574166839717243346815923951250209099128371839254311904649344289668000305972691071196233379180504231889", 10);
mp_int n{};
mp_init(&n);
mp_read_radix(&n, "436860662135489324843442078840868871476482593772359054106809367217662215065650065606351911592188139644751920724885335056877706082800496073391354240530016", 10);
mp_int exp{};
mp_init(&exp);
mp_2expt(&exp, 1000);
mp_int r{};
mp_init(&r);
if(mp_exptmod(&x, &exp, &n, &r) == CRYPT_OK) {
log_warn(category::general, tr("TomCrypt check failed. Server connects main fail due to this mistake!"));
//Nan::ThrowError("Tomcrypt library is too modern. Use an oder one!");
//return;
}
//assert(mp_exptmod(&x, &exp, &n, &r) != CRYPT_OK); //if this method succeed than tommath failed. Unknown why but it is so
mp_clear_multi(&x, &n, &exp, &r, nullptr);
}
tc::audio::AudioOutput* global_audio_output;
#define ENUM_SET(object, key, value) \
Nan::DefineOwnProperty(object, Nan::New<v8::String>(key).ToLocalChecked(), Nan::New<v8::Number>(value), v8::DontDelete); \
Nan::Set(object, value, Nan::New<v8::String>(key).ToLocalChecked());
NAN_MODULE_INIT(init) {
logger::initialize_node();
#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. PID: {}"), _getpid());
#endif
/*
{
auto data = (uint8_t*) "Hello World";
auto hash_result = digest::sha1((const char*) data, 11);
if(hash_result.length() != 20)
Nan::ThrowError("digest::sha1 test failed");
log_error(category::connection, tr("Hash result: {}"), hash_result.length());
}
*/
{
auto data = (uint8_t*) "Hello World";
auto hash_result = digest::sha1(std::string("Hello World"));
if(hash_result.length() != 20)
Nan::ThrowError("digest::sha1 test failed");
}
{
auto data = (uint8_t*) "Hello World";
uint8_t result[SHA_DIGEST_LENGTH];
digest::tomcrypt::sha1((char*) data, 11, result);
auto hash_result = std::string((const char*) result, SHA_DIGEST_LENGTH);
log_error(category::connection, tr("Hash result: {}"), hash_result.length());
}
string error;
//TODO here
//PaJack_SetClientName("TeaClient");
Pa_Initialize();
std::thread(audio::devices).detach(); /* cache the devices */
logger::info(category::general, "Loading crypt modules");
std::string descriptors = "LTGE";
{
int crypt_init = false;
for(const auto& c : descriptors)
if((crypt_init = crypt_mp_init(&c) == CRYPT_OK))
break;
if(!crypt_init) {
Nan::ThrowError("failed to init tomcrypt");
return;
}
if(register_prng(&sprng_desc) == -1) {
Nan::ThrowError("could not setup prng");
return;
}
if (register_cipher(&rijndael_desc) == -1) {
Nan::ThrowError("could not setup rijndael");
return;
}
testTomMath();
}
logger::info(category::general, "Crypt modules loaded");
#ifdef WIN32
evthread_use_windows_threads();
#else
evthread_use_pthreads();
#endif
tc::audio::init_event_loops();
/* TODO: Test error codes and make the audi playback device configurable */
global_audio_output = new tc::audio::AudioOutput(2, 48000); //48000 44100
if(!global_audio_output->open_device(error, Pa_GetDefaultOutputDevice())) {
logger::error(category::audio, "Failed to initialize default audio playback: {}", error);
} else {
if(!global_audio_output->playback()) {
logger::error(category::audio, "Failed to start audio playback");
}
}
{
auto namespace_audio = Nan::New<v8::Object>();
Nan::Set(namespace_audio, Nan::New<v8::String>("available_devices").ToLocalChecked(), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(audio::available_devices)).ToLocalChecked());
{
auto namespace_playback = Nan::New<v8::Object>();
audio::player::init_js(namespace_playback);
audio::AudioOutputStreamWrapper::Init(namespace_playback);
Nan::Set(namespace_audio, Nan::New<v8::String>("playback").ToLocalChecked(), namespace_playback);
}
{
auto namespace_record = Nan::New<v8::Object>();
audio::recorder::init_js(namespace_record);
audio::recorder::AudioRecorderWrapper::Init(namespace_record);
audio::recorder::AudioConsumerWrapper::Init(namespace_record);
audio::recorder::AudioFilterWrapper::Init(namespace_record);
Nan::Set(namespace_audio, Nan::New<v8::String>("record").ToLocalChecked(), namespace_record);
}
Nan::Set(target, Nan::New<v8::String>("audio").ToLocalChecked(), namespace_audio);
}
VoiceClientWrap::Init(target);
VoiceConnectionWrap::Init(target);
{
auto enum_object = Nan::New<v8::Object>();
ENUM_SET(enum_object, "BUFFERING", tc::connection::VoiceClient::state::buffering);
ENUM_SET(enum_object, "PLAYING", tc::connection::VoiceClient::state::playing);
ENUM_SET(enum_object, "STOPPING", tc::connection::VoiceClient::state::stopping);
ENUM_SET(enum_object, "STOPPED", tc::connection::VoiceClient::state::stopped);
Nan::DefineOwnProperty(target, Nan::New<v8::String>("PlayerState").ToLocalChecked(), enum_object, v8::DontDelete);
}
transfer_manager = new tc::ft::FileTransferManager();
transfer_manager->initialize();
ServerConnection::Init(target);
Nan::Set(target, Nan::New<v8::String>("spawn_server_connection").ToLocalChecked(), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(ServerConnection::new_instance)).ToLocalChecked());
/* ft namespace */
{
auto ft_namespace = Nan::New<v8::Object>();
TransferObjectWrap::Init(ft_namespace);
Nan::Set(ft_namespace,
Nan::New<v8::String>("upload_transfer_object_from_buffer").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(TransferJSBufferSource::create_from_buffer)).ToLocalChecked()
);
Nan::Set(ft_namespace,
Nan::New<v8::String>("download_transfer_object_from_buffer").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(TransferJSBufferTarget::create_from_buffer)).ToLocalChecked()
);
Nan::Set(ft_namespace,
Nan::New<v8::String>("upload_transfer_object_from_file").ToLocalChecked(),
Nan::GetFunction(Nan::New<v8::FunctionTemplate>(TransferFileSource::create)).ToLocalChecked()
);
//spawn_file_connection destroy_file_connection
JSTransfer::Init(ft_namespace);
Nan::Set(ft_namespace, Nan::New<v8::String>("spawn_connection").ToLocalChecked(), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(JSTransfer::NewInstance)).ToLocalChecked());
Nan::Set(ft_namespace, Nan::New<v8::String>("destroy_connection").ToLocalChecked(), Nan::GetFunction(Nan::New<v8::FunctionTemplate>(JSTransfer::destory_transfer)).ToLocalChecked());
Nan::Set(target, Nan::New<v8::String>("ft").ToLocalChecked(), ft_namespace);
}
/* setup server types */
{
auto enum_object = Nan::New<v8::Object>();
ENUM_SET(enum_object, "UNKNOWN", tc::connection::server_type::UNKNOWN);
ENUM_SET(enum_object, "TEASPEAK", tc::connection::server_type::TEASPEAK);
ENUM_SET(enum_object, "TEAMSPEAK", tc::connection::server_type::TEAMSPEAK);
Nan::DefineOwnProperty(target, Nan::New<v8::String>("ServerType").ToLocalChecked(), enum_object, v8::DontDelete);
}
}
NODE_MODULE(MODULE_NAME, init)
@@ -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
}
}
-225
View File
@@ -1,225 +0,0 @@
#include "./hwuid.h"
#include <tomcrypt.h>
#include <array>
#include <fstream>
#include <misc/strobf.h>
#include <misc/base64.h>
#ifndef WIN32
#ifdef DARWIN
/*
#include <mach-o/arch.h>
unsigned short getCpuHash() {
const NXArchInfo* info = NXGetLocalArchInfo();
unsigned short val = 0;
val += (unsigned short)info->cputype;
val += (unsigned short)info->cpusubtype;
return val;
}
*/
#else // !DARWIN
static inline void native_cpuid(unsigned int *eax, unsigned int *ebx,
unsigned int *ecx, unsigned int *edx)
{
/* ecx is often an input as well as an output. */
asm volatile("cpuid"
: "=a" (*eax),
"=b" (*ebx),
"=c" (*ecx),
"=d" (*edx)
: "0" (*eax), "2" (*ecx));
}
uint32_t calculate_cpu_hash() {
uint32_t cpuinfo[4] = { 0, 0, 0, 0 };
native_cpuid(cpuinfo, cpuinfo + 1, cpuinfo + 2, cpuinfo + 3);
uint32_t hash = 0;
uint32_t* ptr = (&cpuinfo[0]);
for (uint32_t i = 0; i < 4; i++)
hash += ptr[i];
return hash;
}
#endif // !DARWIN
constexpr uint8_t hex_value(char c) {
if(c >= '0' && c <= '9')
return c - '0';
if(c >= 'a' && c <= 'f')
return c - 'A' + 10;
if(c >= 'A' && c <= 'F')
return c - '0' + 10;
return 0;
}
bool read_machine_id(uint8_t(&uuid)[16]) {
strobf_define(_path, "/etc/machine-id");
memset(uuid, 0, 16);
std::ifstream in(strobf_val(_path).string());
if(in) {
char buffer[32];
if(!in.read(buffer, 32)) return false;
auto it = buffer;
auto index = 0;
while(index < 16) {
uuid[index] = hex_value(*it) << 4UL;
uuid[index] = hex_value(*it);
index++;
}
}
return false;
}
inline bool generate_uuid(std::string& result, uint32_t& check_sum) {
uint8_t buffer[16];
if(!read_machine_id(buffer))
memcpy(buffer, "AAAABBBBCCCCDDDD", 16);
auto cpu_hash = calculate_cpu_hash();
auto it = (uint32_t*) buffer;
for(int i = 0; i < 4; i++)
*it++ = cpu_hash;
result = base64::encode((char*) buffer, 16);
{
crc32_state state;
crc32_init(&state);
crc32_update(&state, (u_char*) result.data(), result.length());
crc32_finish(&state, &check_sum, sizeof(check_sum));
}
return true;
}
#else
#include "./smbios.h"
#include <Windows.h>
#include <sysinfoapi.h>
#include <intrin.h>
using namespace std;
//https://www.dmtf.org/sites/default/files/standards/documents/DSP0134_2.7.1.pdf
int get_uuid(uint8_t* uuid) {
#ifdef WIN32
typedef DWORD api_size_t;
#else
typedef size_t api_size_t;
#endif
unique_ptr<SMBIOSDataHeader, decltype(::free)*> smbios_data{ nullptr, ::free };
api_size_t smbios_size = 0;
api_size_t bytes_written = 0;
// Query size of SMBIOS data.
smbios_size = GetSystemFirmwareTable('RSMB', 0, nullptr, 0);
smbios_data.reset((SMBIOSDataHeader*) malloc(smbios_size));
if (!smbios_data) return -1; //failed to allocate memory
bytes_written = GetSystemFirmwareTable('RSMB', 0, &*smbios_data, smbios_size);
if (smbios_size != bytes_written) return -2; //failed to read the firmware table
if (smbios_data->length > bytes_written - SMBIOSDataHeaderLength) return -3; //invalid length
//Lets iterate over the headers:
uint8_t* ptr = &smbios_data->table_data;
SMBIOSEntryHeader* header;
while (true) {
header = (SMBIOSEntryHeader*) ptr;
if (header->type == 127 && header->length == 4) {
break; //End marker
}
if (header->type == 1) {
//Type 1: System Information
if (header->length < 0x18) {
//If version is under 2.1 no uuid is set!
//TODO: Check before for the version?
return -4; //system information table is too short
}
memcpy(uuid, ptr + 0x08, 16);
return 0;
}
ptr = ptr + header->length; // point to struct end
while (0 != (*ptr | *(ptr + 1)) && ptr + 1 < (uint8_t*) &*smbios_data + smbios_data->length)
ptr++; // skip string area
ptr += 2;
if (ptr >= (uint8_t*) &*smbios_data + smbios_data->length - 2)
break;
}
return -6;
}
uint32_t calculate_cpu_hash() {
int cpuinfo[4] = { 0, 0, 0, 0 };
__cpuid(cpuinfo, 0);
uint32_t hash = 0;
auto ptr = (uint32_t*) (&cpuinfo[0]);
for (uint32_t i = 0; i < 4; i++)
hash += ptr[i];
return hash;
}
inline bool generate_uuid(std::string& result, uint32_t& check_sum) {
uint8_t buffer[16];
auto code = get_uuid(buffer);
if(code != 0) {
memcpy(buffer, "DDDDCCCCBBBBAAAA", 16); /* Reversed to linux (just for the foxic) */
buffer[15] = code; /* So we could get the error code */
}
auto cpu_hash = calculate_cpu_hash();
auto it = (uint32_t*) buffer;
for(int i = 0; i < 4; i++)
*it++ = cpu_hash;
result = base64::encode((char*) buffer, 16);
{
crc32_state state;
crc32_init(&state);
crc32_update(&state, (uint8_t*) result.data(), (unsigned long) result.length());
crc32_finish(&state, &check_sum, sizeof(check_sum));
}
return true;
}
#endif
inline bool check_uuid(std::string& uuid, uint32_t check_sum) {
crc32_state state;
crc32_init(&state);
crc32_update(&state, (uint8_t*) uuid.data(), (unsigned long) uuid.length());
uint32_t result;
crc32_finish(&state, &result, sizeof(result));
return result == check_sum;
}
static std::string _cached_system_uuid{};
static uint32_t _cached_system_uuid_cksm = 0;
std::string system_uuid() {
if(!_cached_system_uuid.empty() && check_uuid(_cached_system_uuid, _cached_system_uuid_cksm))
return _cached_system_uuid;
if(!generate_uuid(_cached_system_uuid, _cached_system_uuid_cksm))
_cached_system_uuid = "";
return _cached_system_uuid;
}
int main() {
std::cout << "UUID: " << system_uuid() << "\n";
return 1;
}
//ADaX2mIRrC1uv83h
//NEg5KzRIOSs0SDkrNEg5Kw==
-4
View File
@@ -1,4 +0,0 @@
#pragma once
#include <string>
extern std::string system_uuid();
-116
View File
@@ -1,116 +0,0 @@
#include <NanEventCallback.h>
#include <iostream>
#include "logger.h"
/* Basic */
void(*logger::_force_log)(logger::category::value, logger::level::level_enum /* lvl */, const std::string_view& /* message */);
/* NODE JS */
struct LogMessage {
uint8_t level;
uint8_t category;
std::string message;
LogMessage* next_message;
};
std::mutex log_messages_lock;
LogMessage* log_messages_head = nullptr;
LogMessage** log_messages_tail = &log_messages_head;
Nan::callback_t<> log_messages_callback;
void force_log_node(logger::category::value, spdlog::level::level_enum, const std::string_view &);
/* Normal */
void force_log_raw(logger::category::value, spdlog::level::level_enum level, const std::string_view &message);
struct StdExternalStringResourceBase : public v8::String::ExternalOneByteStringResource {
public:
explicit StdExternalStringResourceBase(const std::string& message) : message(message) {}
const char *data() const override {
return this->message.data();
}
size_t length() const override {
return this->message.length();
}
private:
std::string message;
};
inline v8::Local<v8::Value> get_logger_method() {
v8::Local<v8::Object> global_context = Nan::GetCurrentContext()->Global();
v8::Local<v8::Object> logger_context = global_context->Get(Nan::New<v8::String>("logger").ToLocalChecked()).As<v8::Object>();
v8::Local<v8::Value> logger_method = logger_context->Get(Nan::New<v8::String>("log").ToLocalChecked());
if(!logger_method.IsEmpty() && !logger_method->IsNullOrUndefined())
return logger_method;
logger_context = global_context->Get(Nan::New<v8::String>("console").ToLocalChecked()).As<v8::Object>();
return logger_context->Get(Nan::New<v8::String>("log").ToLocalChecked());
}
void logger::initialize_node() {
log_messages_callback = Nan::async_callback([]{
Nan::HandleScope scope;
auto isolate = Nan::GetCurrentContext()->GetIsolate();
v8::Local<v8::Value> logger_method = get_logger_method();
v8::Local<v8::Value> arguments[3];
while(true) {
std::unique_lock messages_lock(log_messages_lock);
if(!log_messages_head)
break;
auto entry = log_messages_head;
log_messages_head = entry->next_message;
if(!log_messages_head)
log_messages_tail = &log_messages_head;
messages_lock.unlock();
if(!logger_method.IsEmpty() && !logger_method->IsNullOrUndefined()) {
arguments[0] = Nan::New<v8::Number>(entry->category);
arguments[1] = Nan::New<v8::Number>(entry->level);
arguments[2] = v8::String::NewExternalOneByte(isolate, new StdExternalStringResourceBase(entry->message)).ToLocalChecked();
logger_method.As<v8::Function>()->Call(Nan::GetCurrentContext(), Nan::Undefined(), 3, arguments);
} else {
std::cout << "Failed to log message! Invalid method!" << std::endl;
}
delete entry;
}
});
logger::_force_log = force_log_node;
}
void logger::initialize_raw() {
logger::_force_log = force_log_raw;
}
void force_log_node(logger::category::value category, spdlog::level::level_enum level, const std::string_view &message) {
auto entry = new LogMessage{};
entry->level = level;
entry->category = category;
entry->message = std::string(message.data(), message.length());
entry->next_message = nullptr;
{
std::lock_guard lock(log_messages_lock);
*log_messages_tail = entry;
log_messages_tail = &(entry->next_message);
}
log_messages_callback();
}
void force_log_raw(logger::category::value category, spdlog::level::level_enum level, const std::string_view &message) {
std::cout << "[" << level << "][" << category << "] " << message << std::endl;
}
void logger::err_handler(const std::string &message) {
std::cout << "[ERROR] " << message << std::endl;
}
-107
View File
@@ -1,107 +0,0 @@
#pragma once
#include <spdlog/details/log_msg.h>
#include <memory>
#include <string>
#include <string_view>
#include <vector>
#include <functional>
namespace logger {
namespace level = spdlog::level;
namespace category {
enum value {
general,
audio,
connection,
voice_connection,
socket,
file_transfer,
memory
};
using category = value;
}
extern void initialize_raw();
extern void initialize_node();
extern void(*_force_log)(category::value /* category */, level::level_enum /* lvl */, const std::string_view& /* message */);
extern void err_handler(const std::string& /* error */);
inline bool should_log(level::level_enum lvl) {
return true;
}
template<typename... Args>
inline void force_log(category::value category, level::level_enum lvl, const char *fmt, const Args &... args) {
try {
fmt::MemoryWriter fmt_writer;
fmt_writer.write(fmt, args...);
_force_log(category, lvl, std::string_view{fmt_writer.data(), fmt_writer.size()});
}
catch (const std::exception &ex)
{
err_handler(ex.what());
}
catch(...)
{
err_handler("Unknown exception in logger");
throw;
}
}
template<typename... Args>
inline void log(category::value category, level::level_enum lvl, const char *fmt, const Args &... args) {
if (should_log(lvl))
force_log(category, lvl, fmt, args...);
}
template<typename... Args>
inline void trace(category::value category, const char *fmt, const Args &... args) {
log(category, level::trace, fmt, args...);
}
template<typename... Args>
inline void debug(category::value category, const char *fmt, const Args &... args) {
log(category, level::debug, fmt, args...);
}
template<typename... Args>
inline void info(category::value category, const char *fmt, const Args &... args) {
log(category, level::info, fmt, args...);
}
template<typename... Args>
inline void warn(category::value category, const char *fmt, const Args &... args) {
log(category, level::warn, fmt, args...);
}
template<typename... Args>
inline void error(category::value category, const char *fmt, const Args &... args) {
log(category, level::err, fmt, args...);
}
template<typename... Args>
inline void critical(category::value category, const char *fmt, const Args &... args) {
log(category, level::critical, fmt, args...);
}
}
namespace category = logger::category;
#define tr(message) message
#define log_trace(_category, message, ...) logger::trace(::logger::category::_category, message, ##__VA_ARGS__)
#define log_debug(_category, message, ...) logger::debug(::logger::category::_category, message, ##__VA_ARGS__)
#define log_info(_category, message, ...) logger::info(::logger::category::_category, message, ##__VA_ARGS__)
#define log_warn(_category, message, ...) logger::warn(::logger::category::_category, message, ##__VA_ARGS__)
#define log_error(_category, message, ...) logger::error(::logger::category::_category, message, ##__VA_ARGS__)
#define log_critical(_category, message, ...) logger::critical(::logger::category::_category, message, ##__VA_ARGS__)
#define log_allocate(class, this) log_trace(memory, "Allocated new " class ": {}", (void*) this)
#define log_free(class, this) log_trace(memory, "Deallocated " class ": {}", (void*) this)
-34
View File
@@ -1,34 +0,0 @@
#pragma once
#include <cstdint>
#pragma pack(push)
#pragma pack(1)
typedef struct _SMBIOSDataHeader
{
uint8_t _unused;
uint8_t major_version;
uint8_t minor_version;
uint8_t dmi_revision;
uint32_t length;
uint8_t table_data;
} SMBIOSDataHeader;
static constexpr size_t SMBIOSDataHeaderLength = 8;
static_assert(sizeof(SMBIOSDataHeader) == SMBIOSDataHeaderLength + 1, "invalid struct size");
typedef struct _SMBIOSEntryHeader
{
uint8_t type;
uint8_t length;
uint16_t header;
uint8_t data;
} SMBIOSEntryHeader;
static constexpr size_t SMBIOSEntryHeaderLength = 4;
static_assert(sizeof(SMBIOSEntryHeader) == SMBIOSEntryHeaderLength + 1, "invalid struct size");
#pragma pack(pop)