forked from cory/tildefriends
Merge branches/quickjs to trunk. This is the way.
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@3621 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
190
src/Database.cpp
190
src/Database.cpp
@ -1,190 +0,0 @@
|
||||
#include "Database.h"
|
||||
|
||||
#include "Task.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <sstream>
|
||||
|
||||
int Database::_count = 0;
|
||||
|
||||
Database::Database(Task* task) {
|
||||
++_count;
|
||||
|
||||
_task = task;
|
||||
|
||||
v8::Handle<v8::External> data = v8::External::New(task->getIsolate(), this);
|
||||
|
||||
v8::Local<v8::ObjectTemplate> databaseTemplate = v8::ObjectTemplate::New(task->getIsolate());
|
||||
databaseTemplate->SetInternalFieldCount(1);
|
||||
databaseTemplate->Set(v8::String::NewFromUtf8(task->getIsolate(), "get"), v8::FunctionTemplate::New(task->getIsolate(), get, data));
|
||||
databaseTemplate->Set(v8::String::NewFromUtf8(task->getIsolate(), "set"), v8::FunctionTemplate::New(task->getIsolate(), set, data));
|
||||
databaseTemplate->Set(v8::String::NewFromUtf8(task->getIsolate(), "remove"), v8::FunctionTemplate::New(task->getIsolate(), remove, data));
|
||||
databaseTemplate->Set(v8::String::NewFromUtf8(task->getIsolate(), "getAll"), v8::FunctionTemplate::New(task->getIsolate(), getAll, data));
|
||||
|
||||
v8::Local<v8::Object> databaseObject = databaseTemplate->NewInstance();
|
||||
databaseObject->SetInternalField(0, v8::External::New(task->getIsolate(), this));
|
||||
_object = v8::Persistent<v8::Object, v8::CopyablePersistentTraits<v8::Object> >(task->getIsolate(), databaseObject);
|
||||
}
|
||||
|
||||
Database::~Database() {
|
||||
--_count;
|
||||
}
|
||||
|
||||
void Database::create(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::HandleScope handleScope(args.GetIsolate());
|
||||
if (Database* database = new Database(Task::get(args.GetIsolate()))) {
|
||||
if (database->open(args.GetIsolate(), *v8::String::Utf8Value(args[0].As<v8::String>()))) {
|
||||
v8::Handle<v8::Object> result = v8::Local<v8::Object>::New(args.GetIsolate(), database->_object);
|
||||
args.GetReturnValue().Set(result);
|
||||
}
|
||||
database->release();
|
||||
}
|
||||
}
|
||||
|
||||
bool Database::checkError(const char* command, int result) {
|
||||
bool isError = false;
|
||||
if (result != MDB_SUCCESS) {
|
||||
isError = true;
|
||||
|
||||
std::ostringstream buffer;
|
||||
buffer << command << " failed (" << result << "): " << mdb_strerror(result);
|
||||
_task->getIsolate()->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(_task->getIsolate(), buffer.str().c_str())));
|
||||
}
|
||||
return isError;
|
||||
}
|
||||
|
||||
bool Database::open(v8::Isolate* isolate, const char* path) {
|
||||
int result = mdb_env_create(&_environment);
|
||||
if (checkError("mdb_env_create", result)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
result = mdb_env_set_maxdbs(_environment, 10);
|
||||
checkError("mdb_env_set_maxdbs", result);
|
||||
|
||||
result = mdb_env_open(_environment, path, 0, 0644);
|
||||
if (!checkError("mdb_env_open", result)) {
|
||||
result = mdb_txn_begin(_environment, 0, 0, &_transaction);
|
||||
if (!checkError("mdb_txn_begin", result)) {
|
||||
result = mdb_dbi_open(_transaction, NULL, MDB_CREATE, &_database);
|
||||
if (!checkError("mdb_dbi_open", result)) {
|
||||
result = mdb_txn_commit(_transaction);
|
||||
checkError("mdb_txn_commit", result);
|
||||
}
|
||||
}
|
||||
|
||||
if (result != MDB_SUCCESS) {
|
||||
mdb_txn_abort(_transaction);
|
||||
}
|
||||
}
|
||||
|
||||
if (result != MDB_SUCCESS) {
|
||||
mdb_env_close(_environment);
|
||||
}
|
||||
|
||||
return result == MDB_SUCCESS;
|
||||
}
|
||||
|
||||
void Database::get(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (Database* database = Database::get(args.Data())) {
|
||||
int result = mdb_txn_begin(database->_environment, 0, MDB_RDONLY, &database->_transaction);
|
||||
if (!database->checkError("mdb_txn_begin", result)) {
|
||||
MDB_val key;
|
||||
MDB_val value;
|
||||
v8::String::Utf8Value keyString(args[0].As<v8::String>());
|
||||
key.mv_data = *keyString;
|
||||
key.mv_size = keyString.length();
|
||||
if (mdb_get(database->_transaction, database->_database, &key, &value) == MDB_SUCCESS) {
|
||||
args.GetReturnValue().Set(v8::String::NewFromUtf8(args.GetIsolate(), reinterpret_cast<const char*>(value.mv_data), v8::String::kNormalString, value.mv_size));
|
||||
}
|
||||
mdb_txn_reset(database->_transaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Database::set(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (Database* database = Database::get(args.Data())) {
|
||||
int result = mdb_txn_begin(database->_environment, 0, 0, &database->_transaction);
|
||||
if (!database->checkError("mdb_txn_begin", result)) {
|
||||
MDB_val key;
|
||||
MDB_val data;
|
||||
v8::String::Utf8Value keyString(args[0].As<v8::String>());
|
||||
key.mv_data = *keyString;
|
||||
key.mv_size = keyString.length();
|
||||
v8::String::Utf8Value valueString(args[1]->ToString(args.GetIsolate()));
|
||||
data.mv_data = *valueString;
|
||||
data.mv_size = valueString.length();
|
||||
result = mdb_put(database->_transaction, database->_database, &key, &data, 0);
|
||||
database->checkError("mdb_put", result);
|
||||
mdb_txn_commit(database->_transaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Database::remove(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (Database* database = Database::get(args.Data())) {
|
||||
int result = mdb_txn_begin(database->_environment, 0, 0, &database->_transaction);
|
||||
if (!database->checkError("mdb_txn_begin", result)) {
|
||||
MDB_val key;
|
||||
v8::String::Utf8Value keyString(args[0].As<v8::String>());
|
||||
key.mv_data = *keyString;
|
||||
key.mv_size = keyString.length();
|
||||
result = mdb_del(database->_transaction, database->_database, &key, 0);
|
||||
database->checkError("mdb_del", result);
|
||||
mdb_txn_commit(database->_transaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Database::getAll(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (Database* database = Database::get(args.Data())) {
|
||||
int result = mdb_txn_begin(database->_environment, 0, MDB_RDONLY, &database->_transaction);
|
||||
if (!database->checkError("mdb_txn_begin", result)) {
|
||||
MDB_cursor* cursor;
|
||||
result = mdb_cursor_open(database->_transaction, database->_database, &cursor);
|
||||
if (!database->checkError("mdb_cursor_open", result)) {
|
||||
int expectedCount = 0;
|
||||
MDB_stat statistics;
|
||||
if (mdb_stat(database->_transaction, database->_database, &statistics) == 0) {
|
||||
expectedCount = statistics.ms_entries;
|
||||
}
|
||||
v8::Local<v8::Array> array = v8::Array::New(args.GetIsolate(), expectedCount);
|
||||
|
||||
MDB_val key;
|
||||
int index = 0;
|
||||
while ((result = mdb_cursor_get(cursor, &key, 0, MDB_NEXT)) == 0) {
|
||||
array->Set(index++, v8::String::NewFromUtf8(args.GetIsolate(), reinterpret_cast<const char*>(key.mv_data), v8::String::kNormalString, key.mv_size));
|
||||
}
|
||||
if (result == MDB_NOTFOUND) {
|
||||
args.GetReturnValue().Set(array);
|
||||
} else {
|
||||
database->checkError("mdb_cursor_get", result);
|
||||
}
|
||||
mdb_cursor_close(cursor);
|
||||
}
|
||||
mdb_txn_reset(database->_transaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Database::onRelease(const v8::WeakCallbackInfo<Database>& data) {
|
||||
data.GetParameter()->_object.Reset();
|
||||
delete data.GetParameter();
|
||||
}
|
||||
|
||||
void Database::ref() {
|
||||
if (++_refCount == 1) {
|
||||
_object.ClearWeak();
|
||||
}
|
||||
}
|
||||
|
||||
void Database::release() {
|
||||
assert(_refCount >= 1);
|
||||
if (--_refCount == 0) {
|
||||
_object.SetWeak(this, onRelease, v8::WeakCallbackType::kParameter);
|
||||
}
|
||||
}
|
||||
|
||||
Database* Database::get(v8::Handle<v8::Value> databaseObject) {
|
||||
return reinterpret_cast<Database*>(v8::Handle<v8::External>::Cast(databaseObject)->Value());
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
#ifndef INCLUDED_Database
|
||||
#define INCLUDED_Database
|
||||
|
||||
#include <lmdb.h>
|
||||
#include <v8.h>
|
||||
|
||||
class Task;
|
||||
|
||||
class Database {
|
||||
public:
|
||||
static void create(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static int getCount() { return _count; }
|
||||
|
||||
private:
|
||||
Database(Task* task);
|
||||
~Database();
|
||||
|
||||
Task* _task;
|
||||
int _refCount = 1;
|
||||
v8::Persistent<v8::Object, v8::CopyablePersistentTraits<v8::Object> > _object;
|
||||
|
||||
MDB_env* _environment;
|
||||
MDB_dbi _database;
|
||||
MDB_txn* _transaction;
|
||||
|
||||
static int _count;
|
||||
|
||||
static Database* get(v8::Handle<v8::Value> databaseObject);
|
||||
static void onRelease(const v8::WeakCallbackInfo<Database>& data);
|
||||
|
||||
static void get(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void set(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void remove(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void getAll(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
bool open(v8::Isolate* isolate, const char* path);
|
||||
|
||||
bool checkError(const char* command, int result);
|
||||
|
||||
void ref();
|
||||
void release();
|
||||
};
|
||||
|
||||
#endif
|
191
src/File.cpp
191
src/File.cpp
@ -1,191 +0,0 @@
|
||||
#include "File.h"
|
||||
|
||||
#include "Task.h"
|
||||
#include "TaskTryCatch.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <uv.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
double timeSpecToDouble(uv_timespec_t& timeSpec);
|
||||
|
||||
struct FileStatData {
|
||||
Task* _task;
|
||||
promiseid_t _promise;
|
||||
uv_fs_t _request;
|
||||
};
|
||||
|
||||
void File::configure(v8::Isolate* isolate, v8::Handle<v8::ObjectTemplate> global) {
|
||||
v8::Local<v8::ObjectTemplate> fileTemplate = v8::ObjectTemplate::New(isolate);
|
||||
fileTemplate->Set(v8::String::NewFromUtf8(isolate, "readFile"), v8::FunctionTemplate::New(isolate, readFile));
|
||||
fileTemplate->Set(v8::String::NewFromUtf8(isolate, "readDirectory"), v8::FunctionTemplate::New(isolate, readDirectory));
|
||||
fileTemplate->Set(v8::String::NewFromUtf8(isolate, "makeDirectory"), v8::FunctionTemplate::New(isolate, makeDirectory));
|
||||
fileTemplate->Set(v8::String::NewFromUtf8(isolate, "writeFile"), v8::FunctionTemplate::New(isolate, writeFile));
|
||||
fileTemplate->Set(v8::String::NewFromUtf8(isolate, "renameFile"), v8::FunctionTemplate::New(isolate, renameFile));
|
||||
fileTemplate->Set(v8::String::NewFromUtf8(isolate, "unlinkFile"), v8::FunctionTemplate::New(isolate, unlinkFile));
|
||||
fileTemplate->Set(v8::String::NewFromUtf8(isolate, "stat"), v8::FunctionTemplate::New(isolate, stat));
|
||||
global->Set(v8::String::NewFromUtf8(isolate, "File"), fileTemplate);
|
||||
}
|
||||
|
||||
void File::readFile(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::HandleScope scope(args.GetIsolate());
|
||||
v8::Handle<v8::String> fileName = args[0]->ToString();
|
||||
|
||||
v8::String::Utf8Value utf8FileName(fileName);
|
||||
std::ifstream file(*utf8FileName, std::ios_base::in | std::ios_base::binary | std::ios_base::ate);
|
||||
std::streampos fileSize = file.tellg();
|
||||
if (fileSize >= 0 && fileSize < 4 * 1024 * 1024) {
|
||||
file.seekg(0, std::ios_base::beg);
|
||||
v8::Handle<v8::ArrayBuffer> buffer = v8::ArrayBuffer::New(args.GetIsolate(), fileSize);
|
||||
file.read(reinterpret_cast<char*>(buffer->GetContents().Data()), fileSize);
|
||||
v8::Handle<v8::Uint8Array> array = v8::Uint8Array::New(buffer, 0, fileSize);
|
||||
args.GetReturnValue().Set(array);
|
||||
}
|
||||
}
|
||||
|
||||
void File::writeFile(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::HandleScope scope(args.GetIsolate());
|
||||
v8::Handle<v8::String> fileName = args[0]->ToString();
|
||||
v8::Handle<v8::Value> value = args[1];
|
||||
|
||||
v8::String::Utf8Value utf8FileName(fileName);
|
||||
std::ofstream file(*utf8FileName, std::ios_base::out | std::ios_base::binary);
|
||||
|
||||
if (value->IsArrayBufferView()) {
|
||||
v8::Handle<v8::ArrayBufferView> array = v8::Handle<v8::ArrayBufferView>::Cast(value);
|
||||
if (!file.write(reinterpret_cast<const char*>(array->Buffer()->GetContents().Data()), array->Buffer()->GetContents().ByteLength())) {
|
||||
args.GetReturnValue().Set(v8::Integer::New(args.GetIsolate(), -1));
|
||||
}
|
||||
} else if (value->IsString()) {
|
||||
v8::Handle<v8::String> stringValue = v8::Handle<v8::String>::Cast(value);
|
||||
if (stringValue->ContainsOnlyOneByte()) {
|
||||
std::vector<uint8_t> bytes(stringValue->Length());
|
||||
stringValue->WriteOneByte(bytes.data(), 0, bytes.size(), v8::String::NO_NULL_TERMINATION);
|
||||
if (!file.write(reinterpret_cast<const char*>(bytes.data()), bytes.size())) {
|
||||
args.GetReturnValue().Set(v8::Integer::New(args.GetIsolate(), -1));
|
||||
}
|
||||
} else {
|
||||
v8::String::Utf8Value utf8Contents(stringValue);
|
||||
if (!file.write(*utf8Contents, utf8Contents.length())) {
|
||||
args.GetReturnValue().Set(v8::Integer::New(args.GetIsolate(), -1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void File::renameFile(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
Task* task = reinterpret_cast<Task*>(args.GetIsolate()->GetData(0));
|
||||
v8::HandleScope scope(args.GetIsolate());
|
||||
|
||||
v8::String::Utf8Value oldName(args[0]->ToString());
|
||||
v8::String::Utf8Value newName(args[1]->ToString());
|
||||
|
||||
uv_fs_t req;
|
||||
int result = uv_fs_rename(task->getLoop(), &req, *oldName, *newName, 0);
|
||||
args.GetReturnValue().Set(v8::Integer::New(args.GetIsolate(), result));
|
||||
}
|
||||
|
||||
void File::unlinkFile(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
Task* task = reinterpret_cast<Task*>(args.GetIsolate()->GetData(0));
|
||||
v8::HandleScope scope(args.GetIsolate());
|
||||
|
||||
v8::String::Utf8Value fileName(args[0]->ToString());
|
||||
|
||||
uv_fs_t req;
|
||||
int result = uv_fs_unlink(task->getLoop(), &req, *fileName, 0);
|
||||
args.GetReturnValue().Set(v8::Integer::New(args.GetIsolate(), result));
|
||||
}
|
||||
|
||||
void File::readDirectory(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::HandleScope scope(args.GetIsolate());
|
||||
v8::Handle<v8::String> directory = args[0]->ToString();
|
||||
|
||||
v8::Handle<v8::Array> array = v8::Array::New(args.GetIsolate(), 0);
|
||||
|
||||
#ifdef _WIN32
|
||||
WIN32_FIND_DATA find;
|
||||
std::string pattern = *v8::String::Utf8Value(directory);
|
||||
pattern += "\\*";
|
||||
HANDLE handle = FindFirstFile(pattern.c_str(), &find);
|
||||
if (handle != INVALID_HANDLE_VALUE) {
|
||||
int index = 0;
|
||||
do {
|
||||
array->Set(v8::Integer::New(args.GetIsolate(), index++), v8::String::NewFromUtf8(args.GetIsolate(), find.cFileName));
|
||||
} while (FindNextFile(handle, &find) != 0);
|
||||
FindClose(handle);
|
||||
}
|
||||
#else
|
||||
if (DIR* dir = opendir(*v8::String::Utf8Value(directory))) {
|
||||
int index = 0;
|
||||
while (struct dirent* entry = readdir(dir)) {
|
||||
array->Set(v8::Integer::New(args.GetIsolate(), index++), v8::String::NewFromUtf8(args.GetIsolate(), entry->d_name));
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
#endif
|
||||
|
||||
args.GetReturnValue().Set(array);
|
||||
}
|
||||
|
||||
void File::makeDirectory(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
Task* task = Task::get(args.GetIsolate());
|
||||
v8::HandleScope scope(args.GetIsolate());
|
||||
v8::Handle<v8::String> directory = args[0]->ToString();
|
||||
|
||||
uv_fs_t req;
|
||||
int result = uv_fs_mkdir(task->getLoop(), &req, *v8::String::Utf8Value(directory), 0755, 0);
|
||||
args.GetReturnValue().Set(result);
|
||||
}
|
||||
|
||||
void File::stat(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (Task* task = Task::get(args.GetIsolate())) {
|
||||
v8::HandleScope scope(args.GetIsolate());
|
||||
v8::Handle<v8::String> path = args[0]->ToString();
|
||||
|
||||
promiseid_t promise = task->allocatePromise();
|
||||
|
||||
FileStatData* data = new FileStatData;
|
||||
data->_task = task;
|
||||
data->_promise = promise;
|
||||
data->_request.data = data;
|
||||
|
||||
int result = uv_fs_stat(task->getLoop(), &data->_request, *v8::String::Utf8Value(path), onStatComplete);
|
||||
if (result) {
|
||||
task->resolvePromise(promise, v8::Number::New(args.GetIsolate(), result));
|
||||
delete data;
|
||||
}
|
||||
args.GetReturnValue().Set(task->getPromise(promise));
|
||||
}
|
||||
}
|
||||
|
||||
double timeSpecToDouble(uv_timespec_t& timeSpec) {
|
||||
return timeSpec.tv_sec + static_cast<double>(timeSpec.tv_nsec) / 1e9;
|
||||
}
|
||||
|
||||
void File::onStatComplete(uv_fs_t* request) {
|
||||
FileStatData* data = reinterpret_cast<FileStatData*>(request->data);
|
||||
v8::EscapableHandleScope scope(data->_task->getIsolate());
|
||||
TaskTryCatch tryCatch(data->_task);
|
||||
v8::Isolate* isolate = data->_task->getIsolate();
|
||||
v8::Context::Scope contextScope(v8::Local<v8::Context>::New(isolate, data->_task->getContext()));
|
||||
|
||||
if (request->result) {
|
||||
data->_task->rejectPromise(data->_promise, v8::Number::New(data->_task->getIsolate(), request->result));
|
||||
} else {
|
||||
v8::Handle<v8::Object> result = v8::Object::New(isolate);
|
||||
result->Set(v8::String::NewFromUtf8(isolate, "mtime"), v8::Number::New(isolate, timeSpecToDouble(request->statbuf.st_mtim)));
|
||||
result->Set(v8::String::NewFromUtf8(isolate, "ctime"), v8::Number::New(isolate, timeSpecToDouble(request->statbuf.st_ctim)));
|
||||
result->Set(v8::String::NewFromUtf8(isolate, "atime"), v8::Number::New(isolate, timeSpecToDouble(request->statbuf.st_atim)));
|
||||
result->Set(v8::String::NewFromUtf8(isolate, "size"), v8::Number::New(isolate, request->statbuf.st_size));
|
||||
data->_task->resolvePromise(data->_promise, result);
|
||||
}
|
||||
delete data;
|
||||
}
|
24
src/File.h
24
src/File.h
@ -1,24 +0,0 @@
|
||||
#ifndef INCLUDED_File
|
||||
#define INCLUDED_File
|
||||
|
||||
#include <v8.h>
|
||||
|
||||
typedef struct uv_fs_s uv_fs_t;
|
||||
|
||||
class File {
|
||||
public:
|
||||
static void configure(v8::Isolate* isolate, v8::Handle<v8::ObjectTemplate> global);
|
||||
|
||||
private:
|
||||
static void readFile(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void writeFile(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void readDirectory(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void makeDirectory(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void unlinkFile(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void renameFile(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void stat(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
static void onStatComplete(uv_fs_t* request);
|
||||
};
|
||||
|
||||
#endif
|
@ -1,32 +0,0 @@
|
||||
#include "Mutex.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <assert.h>
|
||||
|
||||
Mutex::Mutex() {
|
||||
int result = uv_mutex_init(&_mutex);
|
||||
if (result != 0) {
|
||||
assert("Mutex lock failed.");
|
||||
}
|
||||
}
|
||||
|
||||
Mutex::~Mutex() {
|
||||
uv_mutex_destroy(&_mutex);
|
||||
}
|
||||
|
||||
void Mutex::lock() {
|
||||
uv_mutex_lock(&_mutex);
|
||||
}
|
||||
|
||||
void Mutex::unlock() {
|
||||
uv_mutex_unlock(&_mutex);
|
||||
}
|
||||
|
||||
Lock::Lock(Mutex& mutex)
|
||||
: _mutex(mutex) {
|
||||
_mutex.lock();
|
||||
}
|
||||
|
||||
Lock::~Lock() {
|
||||
_mutex.unlock();
|
||||
}
|
26
src/Mutex.h
26
src/Mutex.h
@ -1,26 +0,0 @@
|
||||
#ifndef INCLUDED_Mutex
|
||||
#define INCLUDED_Mutex
|
||||
|
||||
#include <uv.h>
|
||||
|
||||
class Mutex {
|
||||
public:
|
||||
Mutex();
|
||||
~Mutex();
|
||||
|
||||
void lock();
|
||||
void unlock();
|
||||
|
||||
private:
|
||||
uv_mutex_t _mutex;
|
||||
};
|
||||
|
||||
class Lock {
|
||||
public:
|
||||
Lock(Mutex& mutex);
|
||||
~Lock();
|
||||
private:
|
||||
Mutex& _mutex;
|
||||
};
|
||||
|
||||
#endif
|
@ -1,85 +0,0 @@
|
||||
#include "PacketStream.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
|
||||
PacketStream::PacketStream()
|
||||
: _onReceive(0),
|
||||
_onReceiveUserData(0) {
|
||||
}
|
||||
|
||||
PacketStream::~PacketStream() {
|
||||
_onReceive = 0;
|
||||
_onReceiveUserData = 0;
|
||||
close();
|
||||
}
|
||||
|
||||
void PacketStream::close() {
|
||||
if (!uv_is_closing(reinterpret_cast<uv_handle_t*>(&_stream))) {
|
||||
uv_close(reinterpret_cast<uv_handle_t*>(&_stream), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void PacketStream::start() {
|
||||
_stream.data = this;
|
||||
uv_read_start(reinterpret_cast<uv_stream_t*>(&_stream), onAllocate, onRead);
|
||||
}
|
||||
|
||||
void PacketStream::send(int packetType, char* begin, size_t length) {
|
||||
size_t bufferLength = sizeof(uv_write_t) + sizeof(packetType) + sizeof(length) + length;
|
||||
char* buffer = new char[bufferLength];
|
||||
uv_write_t* request = reinterpret_cast<uv_write_t*>(buffer);
|
||||
buffer += sizeof(uv_write_t);
|
||||
memcpy(buffer, &packetType, sizeof(packetType));
|
||||
memcpy(buffer + sizeof(packetType), &length, sizeof(length));
|
||||
memcpy(buffer + sizeof(packetType) + sizeof(length), begin, length);
|
||||
uv_buf_t writeBuffer;
|
||||
writeBuffer.base = buffer;
|
||||
writeBuffer.len = sizeof(packetType) + sizeof(length) + length;
|
||||
uv_write(request, reinterpret_cast<uv_stream_t*>(&_stream), &writeBuffer, 1, onWrite);
|
||||
}
|
||||
|
||||
void PacketStream::setOnReceive(OnReceive* onReceiveCallback, void* userData) {
|
||||
_onReceive = onReceiveCallback;
|
||||
_onReceiveUserData = userData;
|
||||
}
|
||||
|
||||
void PacketStream::onWrite(uv_write_t* request, int status) {
|
||||
delete[] reinterpret_cast<char*>(request);
|
||||
}
|
||||
|
||||
void PacketStream::onAllocate(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buffer) {
|
||||
buffer->base = new char[suggestedSize];
|
||||
buffer->len = suggestedSize;
|
||||
}
|
||||
|
||||
void PacketStream::onRead(uv_stream_t* handle, ssize_t count, const uv_buf_t* buffer) {
|
||||
PacketStream* owner = reinterpret_cast<PacketStream*>(handle->data);
|
||||
if (count >= 0) {
|
||||
if (count > 0) {
|
||||
owner->_buffer.insert(owner->_buffer.end(), buffer->base, buffer->base + count);
|
||||
owner->processMessages();
|
||||
}
|
||||
} else {
|
||||
owner->close();
|
||||
}
|
||||
delete[] reinterpret_cast<char*>(buffer->base);
|
||||
}
|
||||
|
||||
void PacketStream::processMessages() {
|
||||
int packetType = 0;
|
||||
size_t length = 0;
|
||||
while (_buffer.size() >= sizeof(packetType) + sizeof(length)) {
|
||||
memcpy(&packetType, &*_buffer.begin(), sizeof(packetType));
|
||||
memcpy(&length, &*_buffer.begin() + sizeof(packetType), sizeof(length));
|
||||
|
||||
if (_buffer.size() >= sizeof(packetType) + sizeof(length) + length) {
|
||||
if (_onReceive) {
|
||||
_onReceive(packetType, &*_buffer.begin() + sizeof(length) + sizeof(packetType), length, _onReceiveUserData);
|
||||
}
|
||||
_buffer.erase(_buffer.begin(), _buffer.begin() + sizeof(length) + sizeof(packetType) + length);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
#ifndef INCLUDED_PacketStream
|
||||
#define INCLUDED_PacketStream
|
||||
|
||||
#include <uv.h>
|
||||
#include <vector>
|
||||
|
||||
class PacketStream {
|
||||
public:
|
||||
PacketStream();
|
||||
~PacketStream();
|
||||
|
||||
void start();
|
||||
|
||||
typedef void (OnReceive)(int packetType, const char* begin, size_t length, void* userData);
|
||||
void send(int packetType, char* begin, size_t length);
|
||||
void setOnReceive(OnReceive* onReceiveCallback, void* userData);
|
||||
void close();
|
||||
|
||||
uv_pipe_t& getStream() { return _stream; }
|
||||
|
||||
private:
|
||||
OnReceive* _onReceive;
|
||||
void* _onReceiveUserData;
|
||||
uv_pipe_t _stream;
|
||||
std::vector<char> _buffer;
|
||||
|
||||
void processMessages();
|
||||
|
||||
static void onAllocate(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buffer);
|
||||
static void onRead(uv_stream_t* handle, ssize_t count, const uv_buf_t* buffer);
|
||||
static void onWrite(uv_write_t* request, int status);
|
||||
};
|
||||
|
||||
#endif
|
@ -1,226 +0,0 @@
|
||||
#include "Serialize.h"
|
||||
|
||||
#include "Task.h"
|
||||
#include "TaskStub.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
void Serialize::writeInt8(std::vector<char>& buffer, int8_t value) {
|
||||
buffer.insert(buffer.end(), value);
|
||||
}
|
||||
|
||||
void Serialize::writeInt32(std::vector<char>& buffer, int32_t value) {
|
||||
const char* p = reinterpret_cast<char*>(&value);
|
||||
buffer.insert(buffer.end(), p, p + sizeof(value));
|
||||
}
|
||||
|
||||
void Serialize::writeUint32(std::vector<char>& buffer, uint32_t value) {
|
||||
const char* p = reinterpret_cast<char*>(&value);
|
||||
buffer.insert(buffer.end(), p, p + sizeof(value));
|
||||
}
|
||||
|
||||
void Serialize::writeDouble(std::vector<char>& buffer, double value) {
|
||||
const char* p = reinterpret_cast<char*>(&value);
|
||||
buffer.insert(buffer.end(), p, p + sizeof(value));
|
||||
}
|
||||
|
||||
int8_t Serialize::readInt8(const std::vector<char>& buffer, int& offset) {
|
||||
int8_t result;
|
||||
std::memcpy(&result, &*buffer.begin() + offset, sizeof(result));
|
||||
offset += sizeof(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
int32_t Serialize::readInt32(const std::vector<char>& buffer, int& offset) {
|
||||
int32_t result;
|
||||
std::memcpy(&result, &*buffer.begin() + offset, sizeof(result));
|
||||
offset += sizeof(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
uint32_t Serialize::readUint32(const std::vector<char>& buffer, int& offset) {
|
||||
uint32_t result;
|
||||
std::memcpy(&result, &*buffer.begin() + offset, sizeof(result));
|
||||
offset += sizeof(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
double Serialize::readDouble(const std::vector<char>& buffer, int& offset) {
|
||||
double result;
|
||||
std::memcpy(&result, &*buffer.begin() + offset, sizeof(result));
|
||||
offset += sizeof(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool Serialize::store(Task* task, std::vector<char>& buffer, v8::Handle<v8::Value> value) {
|
||||
return storeInternal(task, buffer, value, 0);
|
||||
}
|
||||
|
||||
bool Serialize::storeInternal(Task* task, std::vector<char>& buffer, v8::Handle<v8::Value> value, int depth) {
|
||||
if (value.IsEmpty()) {
|
||||
return false;
|
||||
} else if (value->IsUndefined()) {
|
||||
writeInt32(buffer, kUndefined);
|
||||
} else if (value->IsNull()) {
|
||||
writeInt32(buffer, kNull);
|
||||
} else if (value->IsBoolean()) {
|
||||
writeInt32(buffer, kBoolean);
|
||||
writeInt8(buffer, value->IsTrue() ? 1 : 0);
|
||||
} else if (value->IsInt32()) {
|
||||
writeInt32(buffer, kInt32);
|
||||
writeInt32(buffer, value->Int32Value());
|
||||
} else if (value->IsUint32()) {
|
||||
writeInt32(buffer, kUint32);
|
||||
writeInt32(buffer, value->Uint32Value());
|
||||
} else if (value->IsNumber()) {
|
||||
writeInt32(buffer, kNumber);
|
||||
writeDouble(buffer, value->NumberValue());
|
||||
} else if (value->IsString()) {
|
||||
writeInt32(buffer, kString);
|
||||
v8::String::Utf8Value utf8(value->ToString());
|
||||
writeInt32(buffer, utf8.length());
|
||||
buffer.insert(buffer.end(), *utf8, *utf8 + utf8.length());
|
||||
} else if (value->IsUint8Array()) {
|
||||
writeInt32(buffer, kUint8Array);
|
||||
v8::Handle<v8::Uint8Array> array = v8::Handle<v8::Uint8Array>::Cast(value);
|
||||
char* data = reinterpret_cast<char*>(array->Buffer()->GetContents().Data());
|
||||
size_t length = array->Buffer()->GetContents().ByteLength();
|
||||
writeInt32(buffer, length);
|
||||
buffer.insert(buffer.end(), data, data + length);
|
||||
} else if (value->IsArray()) {
|
||||
writeInt32(buffer, kArray);
|
||||
v8::Handle<v8::Array> array = v8::Handle<v8::Array>::Cast(value);
|
||||
writeInt32(buffer, array->Length());
|
||||
for (size_t i = 0; i < array->Length(); ++i) {
|
||||
storeInternal(task, buffer, array->Get(i), depth + 1);
|
||||
}
|
||||
} else if (value->IsFunction()) {
|
||||
writeInt32(buffer, kFunction);
|
||||
exportid_t exportId = task->exportFunction(v8::Handle<v8::Function>::Cast(value));
|
||||
writeInt32(buffer, exportId);
|
||||
} else if (value->IsNativeError()) {
|
||||
storeInternal(task, buffer, storeMessage(task, v8::Exception::CreateMessage(value)), depth);
|
||||
} else if (value->IsObject()) {
|
||||
writeInt32(buffer, kObject);
|
||||
v8::Handle<v8::Object> object = value->ToObject();
|
||||
|
||||
// XXX: For some reason IsNativeError isn't working reliably. Catch an
|
||||
// object that still looks like an error object and treat it as such.
|
||||
if (object->GetOwnPropertyNames()->Length() == 0
|
||||
&& !object->Get(v8::String::NewFromUtf8(task->getIsolate(), "stackTrace")).IsEmpty()) {
|
||||
object = v8::Handle<v8::Object>::Cast(storeMessage(task, v8::Exception::CreateMessage(value)));
|
||||
}
|
||||
|
||||
v8::Handle<v8::Array> keys = object->GetOwnPropertyNames();
|
||||
writeInt32(buffer, keys->Length());
|
||||
for (size_t i = 0; i < keys->Length(); ++i) {
|
||||
v8::Handle<v8::Value> key = keys->Get(i);
|
||||
storeInternal(task, buffer, key, depth + 1);
|
||||
storeInternal(task, buffer, object->Get(key), depth + 1);
|
||||
}
|
||||
} else {
|
||||
writeInt32(buffer, kString);
|
||||
v8::String::Utf8Value utf8(value->ToString());
|
||||
writeInt32(buffer, utf8.length());
|
||||
buffer.insert(buffer.end(), *utf8, *utf8 + utf8.length());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
v8::Handle<v8::Value> Serialize::store(Task* task, v8::TryCatch& tryCatch) {
|
||||
return storeMessage(task, tryCatch.Message());
|
||||
}
|
||||
|
||||
v8::Handle<v8::Object> Serialize::storeMessage(Task* task, v8::Handle<v8::Message> message) {
|
||||
v8::Handle<v8::Object> error = v8::Object::New(task->getIsolate());
|
||||
error->Set(v8::String::NewFromUtf8(task->getIsolate(), "message"), message->Get());
|
||||
error->Set(v8::String::NewFromUtf8(task->getIsolate(), "fileName"), message->GetScriptResourceName());
|
||||
error->Set(v8::String::NewFromUtf8(task->getIsolate(), "lineNumber"), v8::Integer::New(task->getIsolate(), message->GetLineNumber()));
|
||||
error->Set(v8::String::NewFromUtf8(task->getIsolate(), "sourceLine"), message->GetSourceLine());
|
||||
if (!message->GetStackTrace().IsEmpty()) {
|
||||
error->Set(v8::String::NewFromUtf8(task->getIsolate(), "stackTrace"), message->GetStackTrace()->AsArray());
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
v8::Handle<v8::Value> Serialize::load(Task* task, TaskStub* from, const std::vector<char>& buffer) {
|
||||
int offset = 0;
|
||||
return loadInternal(task, from, buffer, offset, 0);
|
||||
}
|
||||
|
||||
v8::Handle<v8::Value> Serialize::loadInternal(Task* task, TaskStub* from, const std::vector<char>& buffer, int& offset, int depth) {
|
||||
if (static_cast<size_t>(offset) >= buffer.size()) {
|
||||
return v8::Undefined(task->getIsolate());
|
||||
} else {
|
||||
int32_t type = readInt32(buffer, offset);
|
||||
v8::Handle<v8::Value> result;
|
||||
|
||||
switch (type) {
|
||||
case kUndefined:
|
||||
result = v8::Undefined(task->getIsolate());
|
||||
break;
|
||||
case kNull:
|
||||
result = v8::Null(task->getIsolate());
|
||||
break;
|
||||
case kBoolean:
|
||||
result = v8::Boolean::New(task->getIsolate(), readInt8(buffer, offset) != 0);
|
||||
break;
|
||||
case kInt32:
|
||||
result = v8::Int32::New(task->getIsolate(), readInt32(buffer, offset));
|
||||
break;
|
||||
case kUint32:
|
||||
result = v8::Uint32::New(task->getIsolate(), readUint32(buffer, offset));
|
||||
break;
|
||||
case kNumber:
|
||||
result = v8::Number::New(task->getIsolate(), readDouble(buffer, offset));
|
||||
break;
|
||||
case kString:
|
||||
{
|
||||
int32_t length = readInt32(buffer, offset);
|
||||
result = v8::String::NewFromUtf8(task->getIsolate(), &*buffer.begin() + offset, v8::String::kNormalString, length);
|
||||
offset += length;
|
||||
}
|
||||
break;
|
||||
case kUint8Array:
|
||||
{
|
||||
int32_t length = readInt32(buffer, offset);
|
||||
v8::Handle<v8::ArrayBuffer> array = v8::ArrayBuffer::New(task->getIsolate(), length);
|
||||
std::memcpy(array->GetContents().Data(), &*buffer.begin() + offset, length);
|
||||
offset += length;
|
||||
result = v8::Uint8Array::New(array, 0, length);
|
||||
}
|
||||
break;
|
||||
case kArray:
|
||||
{
|
||||
int32_t length = readInt32(buffer, offset);
|
||||
v8::Handle<v8::Array> array = v8::Array::New(task->getIsolate());
|
||||
for (int i = 0; i < length; ++i) {
|
||||
v8::Handle<v8::Value> value = loadInternal(task, from, buffer, offset, depth + 1);
|
||||
array->Set(i, value);
|
||||
}
|
||||
result = array;
|
||||
}
|
||||
break;
|
||||
case kFunction:
|
||||
{
|
||||
exportid_t exportId = readInt32(buffer, offset);
|
||||
result = task->addImport(from->getId(), exportId);
|
||||
}
|
||||
break;
|
||||
case kObject:
|
||||
{
|
||||
int32_t length = readInt32(buffer, offset);
|
||||
v8::Handle<v8::Object> object = v8::Object::New(task->getIsolate());
|
||||
for (int i = 0; i < length; ++i) {
|
||||
v8::Handle<v8::Value> key = loadInternal(task, from, buffer, offset, depth + 1);
|
||||
v8::Handle<v8::Value> value = loadInternal(task, from, buffer, offset, depth + 1);
|
||||
object->Set(key, value);
|
||||
}
|
||||
result = object;
|
||||
}
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
#ifndef INCLUDED_Serialize
|
||||
#define INCLUDED_Serialize
|
||||
|
||||
#include <v8.h>
|
||||
#include <vector>
|
||||
|
||||
class Task;
|
||||
class TaskStub;
|
||||
|
||||
class Serialize {
|
||||
public:
|
||||
static bool store(Task* task, std::vector<char>& buffer, v8::Handle<v8::Value> value);
|
||||
static v8::Handle<v8::Value> load(Task* task, TaskStub* from, const std::vector<char>& buffer);
|
||||
|
||||
static v8::Handle<v8::Value> store(Task* task, v8::TryCatch& tryCatch);
|
||||
static v8::Handle<v8::Object> storeMessage(Task* task, v8::Handle<v8::Message> message);
|
||||
|
||||
private:
|
||||
static bool storeInternal(Task* task, std::vector<char>& buffer, v8::Handle<v8::Value> value, int depth);
|
||||
static v8::Handle<v8::Value> loadInternal(Task* task, TaskStub* from, const std::vector<char>& buffer, int& offse, int deptht);
|
||||
|
||||
static void writeInt8(std::vector<char>& buffer, int8_t value);
|
||||
static void writeInt32(std::vector<char>& buffer, int32_t value);
|
||||
static void writeUint32(std::vector<char>& buffer, uint32_t value);
|
||||
static void writeDouble(std::vector<char>& buffer, double value);
|
||||
|
||||
static int8_t readInt8(const std::vector<char>& buffer, int& offset);
|
||||
static int32_t readInt32(const std::vector<char>& buffer, int& offset);
|
||||
static uint32_t readUint32(const std::vector<char>& buffer, int& offset);
|
||||
static double readDouble(const std::vector<char>& buffer, int& offset);
|
||||
|
||||
enum Types {
|
||||
kUndefined,
|
||||
kNull,
|
||||
kBoolean,
|
||||
kInt32,
|
||||
kUint32,
|
||||
kNumber,
|
||||
kString,
|
||||
kArray,
|
||||
kUint8Array,
|
||||
kObject,
|
||||
kFunction,
|
||||
kError,
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
692
src/Socket.cpp
692
src/Socket.cpp
@ -1,692 +0,0 @@
|
||||
#include "Socket.h"
|
||||
|
||||
#include "Task.h"
|
||||
#include "TaskTryCatch.h"
|
||||
#include "Tls.h"
|
||||
#include "TlsContextWrapper.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <cstring>
|
||||
#include <uv.h>
|
||||
|
||||
int Socket::_count = 0;
|
||||
int Socket::_openCount = 0;
|
||||
TlsContext* Socket::_defaultTlsContext = 0;
|
||||
|
||||
struct SocketResolveData {
|
||||
uv_getaddrinfo_t resolver;
|
||||
Socket* socket;
|
||||
promiseid_t promise;
|
||||
};
|
||||
|
||||
Socket::Socket(Task* task) {
|
||||
v8::HandleScope scope(task->getIsolate());
|
||||
++_count;
|
||||
|
||||
v8::Handle<v8::External> data = v8::External::New(task->getIsolate(), this);
|
||||
|
||||
v8::Local<v8::ObjectTemplate> socketTemplate = v8::ObjectTemplate::New(task->getIsolate());
|
||||
socketTemplate->SetInternalFieldCount(1);
|
||||
socketTemplate->Set(v8::String::NewFromUtf8(task->getIsolate(), "bind"), v8::FunctionTemplate::New(task->getIsolate(), bind, data));
|
||||
socketTemplate->Set(v8::String::NewFromUtf8(task->getIsolate(), "connect"), v8::FunctionTemplate::New(task->getIsolate(), connect, data));
|
||||
socketTemplate->Set(v8::String::NewFromUtf8(task->getIsolate(), "listen"), v8::FunctionTemplate::New(task->getIsolate(), listen, data));
|
||||
socketTemplate->Set(v8::String::NewFromUtf8(task->getIsolate(), "accept"), v8::FunctionTemplate::New(task->getIsolate(), accept, data));
|
||||
socketTemplate->Set(v8::String::NewFromUtf8(task->getIsolate(), "startTls"), v8::FunctionTemplate::New(task->getIsolate(), startTls, data));
|
||||
socketTemplate->Set(v8::String::NewFromUtf8(task->getIsolate(), "stopTls"), v8::FunctionTemplate::New(task->getIsolate(), stopTls, data));
|
||||
socketTemplate->Set(v8::String::NewFromUtf8(task->getIsolate(), "shutdown"), v8::FunctionTemplate::New(task->getIsolate(), shutdown, data));
|
||||
socketTemplate->Set(v8::String::NewFromUtf8(task->getIsolate(), "close"), v8::FunctionTemplate::New(task->getIsolate(), close, data));
|
||||
socketTemplate->Set(v8::String::NewFromUtf8(task->getIsolate(), "read"), v8::FunctionTemplate::New(task->getIsolate(), read, data));
|
||||
socketTemplate->Set(v8::String::NewFromUtf8(task->getIsolate(), "onError"), v8::FunctionTemplate::New(task->getIsolate(), onError, data));
|
||||
socketTemplate->Set(v8::String::NewFromUtf8(task->getIsolate(), "write"), v8::FunctionTemplate::New(task->getIsolate(), write, data));
|
||||
socketTemplate->SetAccessor(v8::String::NewFromUtf8(task->getIsolate(), "peerName"), getPeerName, 0, data);
|
||||
socketTemplate->SetAccessor(v8::String::NewFromUtf8(task->getIsolate(), "peerCertificate"), getPeerCertificate, 0, data);
|
||||
socketTemplate->SetAccessor(v8::String::NewFromUtf8(task->getIsolate(), "isConnected"), isConnected, 0, data);
|
||||
socketTemplate->SetAccessor(v8::String::NewFromUtf8(task->getIsolate(), "noDelay"), getNoDelay, setNoDelay, data);
|
||||
|
||||
v8::Local<v8::Object> socketObject = socketTemplate->NewInstance();
|
||||
socketObject->SetInternalField(0, v8::External::New(task->getIsolate(), this));
|
||||
_object = v8::Persistent<v8::Object, v8::CopyablePersistentTraits<v8::Object> >(task->getIsolate(), socketObject);
|
||||
|
||||
uv_tcp_init(task->getLoop(), &_socket);
|
||||
++_openCount;
|
||||
_socket.data = this;
|
||||
_task = task;
|
||||
}
|
||||
|
||||
Socket::~Socket() {
|
||||
if (_tls) {
|
||||
delete _tls;
|
||||
_tls = 0;
|
||||
}
|
||||
--_count;
|
||||
}
|
||||
|
||||
void Socket::close() {
|
||||
if (!uv_is_closing(reinterpret_cast<uv_handle_t*>(&_socket))) {
|
||||
if (!_onRead.IsEmpty()) {
|
||||
_onRead.Reset();
|
||||
}
|
||||
uv_close(reinterpret_cast<uv_handle_t*>(&_socket), onClose);
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::reportError(const char* error) {
|
||||
v8::Handle<v8::Value> exception = v8::Exception::Error(v8::String::NewFromUtf8(_task->getIsolate(), error));
|
||||
if (!_onError.IsEmpty()) {
|
||||
v8::Handle<v8::Function> callback = v8::Local<v8::Function>::New(_task->getIsolate(), _onError);
|
||||
callback->Call(callback, 1, &exception);
|
||||
} else {
|
||||
_task->getIsolate()->ThrowException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::reportTlsErrors() {
|
||||
char buffer[4096];
|
||||
while (_tls && _tls->getError(buffer, sizeof(buffer))) {
|
||||
reportError(buffer);
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::startTls(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (Socket* socket = Socket::get(args.Data())) {
|
||||
if (!socket->_tls) {
|
||||
TlsContext* context = 0;
|
||||
|
||||
if (args.Length() > 0 && !args[0].IsEmpty() && args[0]->IsObject()) {
|
||||
if (TlsContextWrapper* wrapper = TlsContextWrapper::get(args[0])) {
|
||||
context = wrapper->getContext();
|
||||
}
|
||||
} else {
|
||||
if (!_defaultTlsContext) {
|
||||
_defaultTlsContext = TlsContext::create();
|
||||
}
|
||||
context = _defaultTlsContext;
|
||||
}
|
||||
|
||||
if (context) {
|
||||
socket->_tls = context->createSession();
|
||||
}
|
||||
|
||||
if (socket->_tls) {
|
||||
socket->_tls->setHostname(socket->_peerName.c_str());
|
||||
if (socket->_direction == kAccept) {
|
||||
socket->_tls->startAccept();
|
||||
} else if (socket->_direction == kConnect) {
|
||||
socket->_tls->startConnect();
|
||||
}
|
||||
socket->_startTlsPromise = socket->_task->allocatePromise();
|
||||
socket->processOutgoingTls();
|
||||
args.GetReturnValue().Set(socket->_task->getPromise(socket->_startTlsPromise));
|
||||
} else {
|
||||
args.GetIsolate()->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(args.GetIsolate(), "Failed to get TLS context")));
|
||||
}
|
||||
} else {
|
||||
args.GetIsolate()->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(args.GetIsolate(), "startTls with TLS already started")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::stopTls(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (Socket* socket = Socket::get(args.Data())) {
|
||||
if (socket->_tls) {
|
||||
socket->processOutgoingTls();
|
||||
delete socket->_tls;
|
||||
socket->_tls = 0;
|
||||
} else {
|
||||
args.GetIsolate()->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(args.GetIsolate(), "stopTls with TLS already stopped")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Socket::processSomeOutgoingTls(promiseid_t promise, uv_write_cb callback) {
|
||||
char buffer[8192];
|
||||
int result = _tls->readEncrypted(buffer, sizeof(buffer));
|
||||
if (result > 0) {
|
||||
char* rawBuffer = new char[sizeof(uv_write_t) + result];
|
||||
uv_write_t* request = reinterpret_cast<uv_write_t*>(rawBuffer);
|
||||
std::memset(request, 0, sizeof(*request));
|
||||
request->data = reinterpret_cast<void*>(promise);
|
||||
rawBuffer += sizeof(uv_write_t);
|
||||
std::memcpy(rawBuffer, buffer, result);
|
||||
|
||||
uv_buf_t writeBuffer;
|
||||
writeBuffer.base = rawBuffer;
|
||||
writeBuffer.len = result;
|
||||
|
||||
int writeResult = uv_write(request, reinterpret_cast<uv_stream_t*>(&_socket), &writeBuffer, 1, callback);
|
||||
if (writeResult != 0) {
|
||||
std::string error = "uv_write: " + std::string(uv_strerror(writeResult));
|
||||
reportError(error.c_str());
|
||||
}
|
||||
} else {
|
||||
reportTlsErrors();
|
||||
}
|
||||
return result > 0;
|
||||
}
|
||||
|
||||
void Socket::processOutgoingTls() {
|
||||
while (processSomeOutgoingTls(-1, onWrite)) {}
|
||||
}
|
||||
|
||||
void Socket::bind(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (Socket* socket = Socket::get(args.Data())) {
|
||||
v8::String::Utf8Value node(args[0]->ToString());
|
||||
v8::String::Utf8Value port(args[1]->ToString());
|
||||
|
||||
SocketResolveData* data = new SocketResolveData();
|
||||
std::memset(data, 0, sizeof(*data));
|
||||
struct addrinfo hints;
|
||||
hints.ai_family = PF_INET;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = IPPROTO_TCP;
|
||||
hints.ai_flags = 0;
|
||||
data->resolver.data = data;
|
||||
data->socket = socket;
|
||||
data->promise = socket->_task->allocatePromise();
|
||||
|
||||
int result = uv_getaddrinfo(socket->_task->getLoop(), &data->resolver, onResolvedForBind, *node, *port, &hints);
|
||||
if (result != 0) {
|
||||
std::string error = "uv_getaddrinfo: " + std::string(uv_strerror(result));
|
||||
socket->_task->rejectPromise(data->promise, v8::Exception::Error(v8::String::NewFromUtf8(args.GetIsolate(), error.c_str())));
|
||||
delete data;
|
||||
}
|
||||
|
||||
args.GetReturnValue().Set(socket->_task->getPromise(data->promise));
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::onResolvedForBind(uv_getaddrinfo_t* resolver, int status, struct addrinfo* result) {
|
||||
SocketResolveData* data = reinterpret_cast<SocketResolveData*>(resolver->data);
|
||||
if (status != 0) {
|
||||
std::string error = "uv_getaddrinfo: " + std::string(uv_strerror(status));
|
||||
data->socket->_task->rejectPromise(data->promise, v8::Exception::Error(v8::String::NewFromUtf8(data->socket->_task->getIsolate(), error.c_str())));
|
||||
} else {
|
||||
int bindResult = uv_tcp_bind(&data->socket->_socket, result->ai_addr, 0);
|
||||
if (bindResult != 0) {
|
||||
std::string error = "uv_tcp_bind: " + std::string(uv_strerror(bindResult));
|
||||
data->socket->_task->rejectPromise(data->promise, v8::Exception::Error(v8::String::NewFromUtf8(data->socket->_task->getIsolate(), error.c_str())));
|
||||
} else {
|
||||
data->socket->_task->resolvePromise(data->promise, v8::Undefined(data->socket->_task->getIsolate()));
|
||||
}
|
||||
}
|
||||
delete data;
|
||||
}
|
||||
|
||||
void Socket::connect(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (Socket* socket = Socket::get(args.Data())) {
|
||||
socket->_direction = kConnect;
|
||||
v8::String::Utf8Value node(args[0]->ToString());
|
||||
v8::String::Utf8Value port(args[1]->ToString());
|
||||
|
||||
socket->_peerName = *node;
|
||||
|
||||
promiseid_t promise = socket->_task->allocatePromise();
|
||||
|
||||
SocketResolveData* data = new SocketResolveData();
|
||||
std::memset(data, 0, sizeof(*data));
|
||||
struct addrinfo hints;
|
||||
hints.ai_family = PF_INET;
|
||||
hints.ai_socktype = SOCK_STREAM;
|
||||
hints.ai_protocol = IPPROTO_TCP;
|
||||
hints.ai_flags = 0;
|
||||
data->resolver.data = data;
|
||||
data->socket = socket;
|
||||
data->promise = promise;
|
||||
|
||||
int result = uv_getaddrinfo(socket->_task->getLoop(), &data->resolver, onResolvedForConnect, *node, *port, &hints);
|
||||
if (result != 0) {
|
||||
std::string error = "uv_getaddrinfo: " + std::string(uv_strerror(result));
|
||||
socket->_task->rejectPromise(promise, v8::Exception::Error(v8::String::NewFromUtf8(args.GetIsolate(), error.c_str())));
|
||||
delete data;
|
||||
}
|
||||
|
||||
args.GetReturnValue().Set(socket->_task->getPromise(promise));
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::onResolvedForConnect(uv_getaddrinfo_t* resolver, int status, struct addrinfo* result) {
|
||||
SocketResolveData* data = reinterpret_cast<SocketResolveData*>(resolver->data);
|
||||
if (status != 0) {
|
||||
std::string error = "uv_getaddrinfo: " + std::string(uv_strerror(status));
|
||||
data->socket->_task->rejectPromise(data->promise, v8::Exception::Error(v8::String::NewFromUtf8(data->socket->_task->getIsolate(), error.c_str())));
|
||||
} else {
|
||||
uv_connect_t* request = new uv_connect_t();
|
||||
std::memset(request, 0, sizeof(*request));
|
||||
request->data = reinterpret_cast<void*>(data->promise);
|
||||
int connectResult = uv_tcp_connect(request, &data->socket->_socket, result->ai_addr, onConnect);
|
||||
if (connectResult != 0) {
|
||||
std::string error("uv_tcp_connect: " + std::string(uv_strerror(connectResult)));
|
||||
data->socket->_task->rejectPromise(data->promise, v8::Exception::Error(v8::String::NewFromUtf8(data->socket->_task->getIsolate(), error.c_str())));
|
||||
}
|
||||
}
|
||||
delete data;
|
||||
}
|
||||
|
||||
|
||||
void Socket::onConnect(uv_connect_t* request, int status) {
|
||||
promiseid_t promise = reinterpret_cast<intptr_t>(request->data);
|
||||
if (promise != -1) {
|
||||
Socket* socket = reinterpret_cast<Socket*>(request->handle->data);
|
||||
if (status == 0) {
|
||||
socket->_connected = true;
|
||||
socket->_task->resolvePromise(promise, v8::Integer::New(socket->_task->getIsolate(), status));
|
||||
} else {
|
||||
std::string error("uv_tcp_connect: " + std::string(uv_strerror(status)));
|
||||
socket->_task->rejectPromise(promise, v8::String::NewFromUtf8(socket->_task->getIsolate(), error.c_str()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::listen(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (Socket* socket = Socket::get(args.Data())) {
|
||||
int backlog = args[0]->ToInteger()->Value();
|
||||
if (socket->_onConnect.IsEmpty()) {
|
||||
v8::Persistent<v8::Function, v8::CopyablePersistentTraits<v8::Function> > callback(args.GetIsolate(), args[1].As<v8::Function>());
|
||||
socket->_onConnect = callback;
|
||||
int result = uv_listen(reinterpret_cast<uv_stream_t*>(&socket->_socket), backlog, onNewConnection);
|
||||
if (result != 0) {
|
||||
std::string error = "uv_listen: " + std::string(uv_strerror(result));
|
||||
args.GetIsolate()->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(args.GetIsolate(), error.c_str(), v8::String::kNormalString, error.size())));
|
||||
}
|
||||
args.GetReturnValue().Set(v8::Integer::New(args.GetIsolate(), result));
|
||||
} else {
|
||||
args.GetIsolate()->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(args.GetIsolate(), "listen: Already listening.")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::onNewConnection(uv_stream_t* server, int status) {
|
||||
if (Socket* socket = reinterpret_cast<Socket*>(server->data)) {
|
||||
v8::HandleScope handleScope(socket->_task->getIsolate());
|
||||
TaskTryCatch tryCatch(socket->_task);
|
||||
if (!socket->_onConnect.IsEmpty()) {
|
||||
v8::Handle<v8::Function> callback = v8::Local<v8::Function>::New(socket->_task->getIsolate(), socket->_onConnect);
|
||||
callback->Call(callback, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::accept(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (Socket* socket = Socket::get(args.Data())) {
|
||||
v8::HandleScope handleScope(args.GetIsolate());
|
||||
Socket* client = new Socket(socket->_task);
|
||||
client->_direction = kAccept;
|
||||
promiseid_t promise = socket->_task->allocatePromise();
|
||||
v8::Handle<v8::Value> promiseObject = socket->_task->getPromise(promise);
|
||||
v8::Handle<v8::Object> result = v8::Local<v8::Object>::New(args.GetIsolate(), client->_object);
|
||||
int status = uv_accept(reinterpret_cast<uv_stream_t*>(&socket->_socket), reinterpret_cast<uv_stream_t*>(&client->_socket));
|
||||
if (status == 0) {
|
||||
client->_connected = true;
|
||||
socket->_task->resolvePromise(promise, result);
|
||||
} else {
|
||||
std::string error = "uv_accept: " + std::string(uv_strerror(status));
|
||||
socket->_task->rejectPromise(promise, v8::String::NewFromUtf8(args.GetIsolate(), error.c_str(), v8::String::kNormalString, error.size()));
|
||||
}
|
||||
args.GetReturnValue().Set(promiseObject);
|
||||
client->release();
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::close(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (Socket* socket = Socket::get(args.Data())) {
|
||||
if (socket->_closePromise == -1) {
|
||||
socket->_closePromise = socket->_task->allocatePromise();
|
||||
args.GetReturnValue().Set(socket->_task->getPromise(socket->_closePromise));
|
||||
socket->close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::shutdown(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (Socket* socket = Socket::get(args.Data())) {
|
||||
if (socket->_tls) {
|
||||
promiseid_t promise = socket->_task->allocatePromise();
|
||||
socket->processTlsShutdown(promise);
|
||||
args.GetReturnValue().Set(socket->_task->getPromise(promise));
|
||||
} else {
|
||||
promiseid_t promise = socket->_task->allocatePromise();
|
||||
socket->shutdownInternal(promise);
|
||||
args.GetReturnValue().Set(socket->_task->getPromise(promise));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::shutdownInternal(promiseid_t promise) {
|
||||
uv_shutdown_t* request = new uv_shutdown_t();
|
||||
std::memset(request, 0, sizeof(*request));
|
||||
request->data = reinterpret_cast<void*>(promise);
|
||||
int result = uv_shutdown(request, reinterpret_cast<uv_stream_t*>(&_socket), onShutdown);
|
||||
if (result != 0) {
|
||||
std::string error = "uv_shutdown: " + std::string(uv_strerror(result));
|
||||
_task->rejectPromise(promise, v8::Exception::Error(v8::String::NewFromUtf8(_task->getIsolate(), error.c_str())));
|
||||
delete request;
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::processTlsShutdown(promiseid_t promise) {
|
||||
_tls->shutdown();
|
||||
if (!processSomeOutgoingTls(promise, onTlsShutdown)) {
|
||||
shutdownInternal(promise);
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::onTlsShutdown(uv_write_t* request, int status) {
|
||||
if (Socket* socket = reinterpret_cast<Socket*>(request->handle->data)) {
|
||||
promiseid_t promise = reinterpret_cast<intptr_t>(request->data);
|
||||
socket->processTlsShutdown(promise);
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::onError(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (Socket* socket = Socket::get(args.Data())) {
|
||||
v8::Persistent<v8::Function, v8::CopyablePersistentTraits<v8::Function> > callback(args.GetIsolate(), args[0].As<v8::Function>());
|
||||
socket->_onError = callback;
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::read(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (Socket* socket = Socket::get(args.Data())) {
|
||||
v8::Persistent<v8::Function, v8::CopyablePersistentTraits<v8::Function> > callback(args.GetIsolate(), args[0].As<v8::Function>());
|
||||
socket->_onRead = callback;
|
||||
int result = uv_read_start(reinterpret_cast<uv_stream_t*>(&socket->_socket), allocateBuffer, onRead);
|
||||
promiseid_t promise = socket->_task->allocatePromise();
|
||||
if (result != 0) {
|
||||
std::string error = "uv_read_start: " + std::string(uv_strerror(result));
|
||||
socket->_task->rejectPromise(promise, v8::String::NewFromUtf8(socket->_task->getIsolate(), error.c_str(), v8::String::kNormalString, error.size()));
|
||||
} else {
|
||||
socket->_task->resolvePromise(promise, v8::Undefined(socket->_task->getIsolate()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::allocateBuffer(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buf) {
|
||||
*buf = uv_buf_init(new char[suggestedSize], suggestedSize);
|
||||
}
|
||||
|
||||
void Socket::onRead(uv_stream_t* stream, ssize_t readSize, const uv_buf_t* buffer) {
|
||||
if (Socket* socket = reinterpret_cast<Socket*>(stream->data)) {
|
||||
v8::HandleScope handleScope(socket->_task->getIsolate());
|
||||
TaskTryCatch tryCatch(socket->_task);
|
||||
v8::Handle<v8::Value> data;
|
||||
|
||||
if (readSize <= 0) {
|
||||
socket->_connected = false;
|
||||
v8::Local<v8::Function> callback = v8::Local<v8::Function>::New(socket->_task->getIsolate(), socket->_onRead);
|
||||
if (!callback.IsEmpty()) {
|
||||
data = v8::Undefined(socket->_task->getIsolate());
|
||||
callback->Call(callback, 1, &data);
|
||||
}
|
||||
socket->close();
|
||||
} else {
|
||||
if (socket->_tls) {
|
||||
socket->reportTlsErrors();
|
||||
socket->_tls->writeEncrypted(buffer->base, readSize);
|
||||
if (socket->_startTlsPromise != -1) {
|
||||
TlsSession::HandshakeResult result = socket->_tls->handshake();
|
||||
if (result == TlsSession::kDone) {
|
||||
promiseid_t promise = socket->_startTlsPromise;
|
||||
socket->_startTlsPromise = -1;
|
||||
socket->_task->resolvePromise(promise, v8::Undefined(socket->_task->getIsolate()));
|
||||
} else if (result == TlsSession::kFailed) {
|
||||
promiseid_t promise = socket->_startTlsPromise;
|
||||
socket->_startTlsPromise = -1;
|
||||
char buffer[8192];
|
||||
if (socket->_tls->getError(buffer, sizeof(buffer))) {
|
||||
socket->_task->rejectPromise(promise, v8::String::NewFromUtf8(socket->_task->getIsolate(), buffer));
|
||||
} else {
|
||||
socket->_task->rejectPromise(promise, v8::Undefined(socket->_task->getIsolate()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
char plain[8192];
|
||||
int result = socket->_tls->readPlain(plain, sizeof(plain));
|
||||
if (result > 0) {
|
||||
socket->notifyDataRead(plain, result);
|
||||
} else if (result == TlsSession::kReadFailed) {
|
||||
socket->reportTlsErrors();
|
||||
socket->close();
|
||||
break;
|
||||
} else if (result == TlsSession::kReadZero) {
|
||||
v8::Local<v8::Function> callback = v8::Local<v8::Function>::New(socket->_task->getIsolate(), socket->_onRead);
|
||||
if (!callback.IsEmpty()) {
|
||||
data = v8::Undefined(socket->_task->getIsolate());
|
||||
callback->Call(callback, 1, &data);
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (socket->_tls) {
|
||||
socket->processOutgoingTls();
|
||||
}
|
||||
} else {
|
||||
socket->notifyDataRead(buffer->base, readSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
delete[] buffer->base;
|
||||
}
|
||||
|
||||
void Socket::notifyDataRead(const char* data, size_t length) {
|
||||
v8::Local<v8::Function> callback = v8::Local<v8::Function>::New(_task->getIsolate(), _onRead);
|
||||
if (!callback.IsEmpty()) {
|
||||
if (data && length > 0) {
|
||||
v8::Handle<v8::ArrayBuffer> buffer = v8::ArrayBuffer::New(_task->getIsolate(), length);
|
||||
std::memcpy(buffer->GetContents().Data(), data, length);
|
||||
v8::Handle<v8::Uint8Array> array = v8::Uint8Array::New(buffer, 0, length);
|
||||
|
||||
v8::Handle<v8::Value> arguments = array;
|
||||
callback->Call(callback, 1, &arguments);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int Socket::writeBytes(std::function<int(const char*, size_t)> callback, v8::Handle<v8::Value> value, int* outLength) {
|
||||
int result = -1;
|
||||
|
||||
if (value->IsArrayBufferView()) {
|
||||
v8::Handle<v8::ArrayBufferView> array = v8::Handle<v8::ArrayBufferView>::Cast(value);
|
||||
result = callback(reinterpret_cast<const char*>(array->Buffer()->GetContents().Data()), array->Buffer()->GetContents().ByteLength());
|
||||
if (outLength) {
|
||||
*outLength = array->Buffer()->GetContents().ByteLength();
|
||||
}
|
||||
} else if (value->IsString()) {
|
||||
v8::Handle<v8::String> stringValue = v8::Handle<v8::String>::Cast(value);
|
||||
if (stringValue->ContainsOnlyOneByte()) {
|
||||
std::vector<uint8_t> bytes(stringValue->Length());
|
||||
stringValue->WriteOneByte(bytes.data(), 0, bytes.size(), v8::String::NO_NULL_TERMINATION);
|
||||
result = callback(reinterpret_cast<const char*>(bytes.data()), bytes.size());
|
||||
if (outLength) {
|
||||
*outLength = stringValue->Length();
|
||||
}
|
||||
} else {
|
||||
v8::String::Utf8Value utf8(stringValue);
|
||||
result = callback(*utf8, utf8.length());
|
||||
if (outLength) {
|
||||
*outLength = utf8.length();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int Socket::writeInternal(promiseid_t promise, const char* data, size_t length) {
|
||||
char* rawBuffer = new char[sizeof(uv_write_t) + length];
|
||||
uv_write_t* request = reinterpret_cast<uv_write_t*>(rawBuffer);
|
||||
std::memcpy(rawBuffer + sizeof(uv_write_t), data, length);
|
||||
|
||||
uv_buf_t buffer;
|
||||
buffer.base = rawBuffer + sizeof(uv_write_t);
|
||||
buffer.len = length;
|
||||
|
||||
request->data = reinterpret_cast<void*>(promise);
|
||||
return uv_write(request, reinterpret_cast<uv_stream_t*>(&_socket), &buffer, 1, onWrite);
|
||||
}
|
||||
|
||||
void Socket::write(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (Socket* socket = Socket::get(args.Data())) {
|
||||
promiseid_t promise = socket->_task->allocatePromise();
|
||||
args.GetReturnValue().Set(socket->_task->getPromise(promise));
|
||||
v8::Handle<v8::Value> value = args[0];
|
||||
if (!value.IsEmpty()) {
|
||||
if (socket->_tls) {
|
||||
socket->reportTlsErrors();
|
||||
int length = 0;
|
||||
std::function<int(const char*, size_t)> writeFunction = std::bind(&TlsSession::writePlain, socket->_tls, std::placeholders::_1, std::placeholders::_2);
|
||||
int result = socket->writeBytes(writeFunction, value, &length);
|
||||
char buffer[8192];
|
||||
if (result <= 0 && socket->_tls->getError(buffer, sizeof(buffer))) {
|
||||
socket->_task->rejectPromise(promise, v8::String::NewFromUtf8(args.GetIsolate(), buffer));
|
||||
} else if (result < length) {
|
||||
socket->_task->rejectPromise(promise, v8::Integer::New(socket->_task->getIsolate(), result));
|
||||
} else {
|
||||
socket->_task->resolvePromise(promise, v8::Integer::New(socket->_task->getIsolate(), result));
|
||||
}
|
||||
socket->processOutgoingTls();
|
||||
} else {
|
||||
int length;
|
||||
|
||||
std::function<int(const char*, size_t)> writeFunction = std::bind(&Socket::writeInternal, socket, promise, std::placeholders::_1, std::placeholders::_2);
|
||||
int result = socket->writeBytes(writeFunction, value, &length);
|
||||
|
||||
if (result != 0) {
|
||||
std::string error = "uv_write: " + std::string(uv_strerror(result));
|
||||
socket->_task->rejectPromise(promise, v8::String::NewFromUtf8(args.GetIsolate(), error.c_str(), v8::String::kNormalString, error.size()));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
socket->_task->rejectPromise(promise, v8::Integer::New(args.GetIsolate(), -2));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::onWrite(uv_write_t* request, int status) {
|
||||
if (Socket* socket = reinterpret_cast<Socket*>(request->handle->data)) {
|
||||
v8::HandleScope handleScope(socket->_task->getIsolate());
|
||||
promiseid_t promise = reinterpret_cast<intptr_t>(request->data);
|
||||
if (promise != -1) {
|
||||
if (status == 0) {
|
||||
socket->_task->resolvePromise(promise, v8::Integer::New(socket->_task->getIsolate(), status));
|
||||
} else {
|
||||
std::string error = "uv_write: " + std::string(uv_strerror(status));
|
||||
socket->_task->rejectPromise(promise, v8::String::NewFromUtf8(socket->_task->getIsolate(), error.c_str(), v8::String::kNormalString, error.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
delete[] reinterpret_cast<char*>(request);
|
||||
}
|
||||
|
||||
void Socket::onClose(uv_handle_t* handle) {
|
||||
--_openCount;
|
||||
if (Socket* socket = reinterpret_cast<Socket*>(handle->data)) {
|
||||
if (socket->_closePromise != -1) {
|
||||
v8::HandleScope scope(socket->_task->getIsolate());
|
||||
promiseid_t promise = socket->_closePromise;
|
||||
socket->_closePromise = -1;
|
||||
socket->_connected = false;
|
||||
socket->_task->resolvePromise(promise, v8::Integer::New(socket->_task->getIsolate(), 0));
|
||||
}
|
||||
if (socket->_object.IsEmpty()) {
|
||||
delete socket;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::onShutdown(uv_shutdown_t* request, int status) {
|
||||
if (Socket* socket = reinterpret_cast<Socket*>(request->handle->data)) {
|
||||
promiseid_t promise = reinterpret_cast<intptr_t>(request->data);
|
||||
if (status == 0) {
|
||||
socket->_task->resolvePromise(promise, v8::Undefined(socket->_task->getIsolate()));
|
||||
} else {
|
||||
std::string error = "uv_shutdown: " + std::string(uv_strerror(status));
|
||||
socket->_task->rejectPromise(promise, v8::Exception::Error(v8::String::NewFromUtf8(socket->_task->getIsolate(), error.c_str())));
|
||||
}
|
||||
}
|
||||
delete request;
|
||||
}
|
||||
|
||||
void Socket::getPeerName(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
||||
if (Socket* socket = Socket::get(info.Data())) {
|
||||
struct sockaddr_in6 addr;
|
||||
int nameLength = sizeof(addr);
|
||||
if (uv_tcp_getpeername(&socket->_socket, reinterpret_cast<sockaddr*>(&addr), &nameLength) == 0) {
|
||||
char name[1024];
|
||||
if (static_cast<size_t>(nameLength) > sizeof(struct sockaddr_in)) {
|
||||
if (uv_ip6_name(&addr, name, sizeof(name)) == 0) {
|
||||
info.GetReturnValue().Set(v8::String::NewFromUtf8(info.GetIsolate(), name));
|
||||
}
|
||||
} else {
|
||||
if (uv_ip4_name(reinterpret_cast<struct sockaddr_in*>(&addr), name, sizeof(name)) == 0) {
|
||||
info.GetReturnValue().Set(v8::String::NewFromUtf8(info.GetIsolate(), name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::getPeerCertificate(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
||||
if (Socket* socket = Socket::get(info.Data())) {
|
||||
if (socket->_tls) {
|
||||
std::vector<char> buffer(128 * 1024);
|
||||
int result = socket->_tls->getPeerCertificate(buffer.data(), buffer.size());
|
||||
if (result > 0) {
|
||||
info.GetReturnValue().Set(v8::String::NewFromUtf8(info.GetIsolate(), buffer.data(), v8::String::kNormalString, result));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::isConnected(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
||||
if (Socket* socket = Socket::get(info.Data())) {
|
||||
info.GetReturnValue().Set(v8::Boolean::New(socket->_task->getIsolate(), socket->_connected));
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::getNoDelay(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info) {
|
||||
if (Socket* socket = Socket::get(info.Data())) {
|
||||
info.GetReturnValue().Set(v8::Boolean::New(info.GetIsolate(), socket->_noDelay));
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::setNoDelay(v8::Local<v8::String> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info) {
|
||||
v8::Maybe<bool> boolValue = value->BooleanValue(info.GetIsolate()->GetCurrentContext());
|
||||
if (Socket* socket = Socket::get(info.Data())) {
|
||||
socket->_noDelay = boolValue.IsJust() && boolValue.FromJust();
|
||||
uv_tcp_nodelay(&socket->_socket, socket->_noDelay ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::create(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::HandleScope handleScope(args.GetIsolate());
|
||||
if (Socket* socket = new Socket(Task::get(args.GetIsolate()))) {
|
||||
v8::Handle<v8::Object> result = v8::Local<v8::Object>::New(args.GetIsolate(), socket->_object);
|
||||
args.GetReturnValue().Set(result);
|
||||
socket->release();
|
||||
}
|
||||
}
|
||||
|
||||
Socket* Socket::get(v8::Handle<v8::Value> socketObject) {
|
||||
return reinterpret_cast<Socket*>(v8::Handle<v8::External>::Cast(socketObject)->Value());
|
||||
}
|
||||
|
||||
void Socket::ref() {
|
||||
if (++_refCount == 1) {
|
||||
_object.ClearWeak();
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::release() {
|
||||
assert(_refCount >= 1);
|
||||
if (--_refCount == 0) {
|
||||
_object.SetWeak(this, onRelease, v8::WeakCallbackType::kParameter);
|
||||
}
|
||||
}
|
||||
|
||||
void Socket::onRelease(const v8::WeakCallbackInfo<Socket>& data) {
|
||||
data.GetParameter()->_object.Reset();
|
||||
data.GetParameter()->close();
|
||||
}
|
97
src/Socket.h
97
src/Socket.h
@ -1,97 +0,0 @@
|
||||
#ifndef INCLUDED_Socket
|
||||
#define INCLUDED_Socket
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <uv.h>
|
||||
#include <v8.h>
|
||||
|
||||
typedef int promiseid_t;
|
||||
class Task;
|
||||
class TlsContext;
|
||||
class TlsSession;
|
||||
|
||||
class Socket {
|
||||
public:
|
||||
static void create(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
void close();
|
||||
|
||||
static int getCount() { return _count; }
|
||||
static int getOpenCount() { return _openCount; }
|
||||
|
||||
private:
|
||||
Socket(Task* task);
|
||||
~Socket();
|
||||
|
||||
Task* _task;
|
||||
uv_tcp_t _socket;
|
||||
TlsSession* _tls = 0;
|
||||
promiseid_t _startTlsPromise = -1;
|
||||
promiseid_t _closePromise = -1;
|
||||
int _refCount = 1;
|
||||
bool _connected = false;
|
||||
bool _noDelay = false;
|
||||
std::string _peerName;
|
||||
|
||||
enum Direction { kUndetermined, kAccept, kConnect };
|
||||
Direction _direction = kUndetermined;
|
||||
|
||||
static int _count;
|
||||
static int _openCount;
|
||||
|
||||
static TlsContext* _defaultTlsContext;
|
||||
|
||||
v8::Persistent<v8::Object, v8::CopyablePersistentTraits<v8::Object> > _object;
|
||||
|
||||
v8::Persistent<v8::Function, v8::CopyablePersistentTraits<v8::Function> > _onConnect;
|
||||
v8::Persistent<v8::Function, v8::CopyablePersistentTraits<v8::Function> > _onRead;
|
||||
v8::Persistent<v8::Function, v8::CopyablePersistentTraits<v8::Function> > _onError;
|
||||
|
||||
static void startTls(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void stopTls(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void bind(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void connect(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void listen(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void accept(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void close(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void shutdown(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void read(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void onError(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void write(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void getPeerName(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info);
|
||||
static void getPeerCertificate(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info);
|
||||
static void isConnected(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info);
|
||||
static void getNoDelay(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& info);
|
||||
static void setNoDelay(v8::Local<v8::String> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& info);
|
||||
|
||||
static Socket* get(v8::Handle<v8::Value> socketObject);
|
||||
static void onClose(uv_handle_t* handle);
|
||||
static void onShutdown(uv_shutdown_t* request, int status);
|
||||
static void onResolvedForBind(uv_getaddrinfo_t* resolver, int status, struct addrinfo* result);
|
||||
static void onResolvedForConnect(uv_getaddrinfo_t* resolver, int status, struct addrinfo* result);
|
||||
static void onConnect(uv_connect_t* request, int status);
|
||||
static void onNewConnection(uv_stream_t* server, int status);
|
||||
|
||||
static void allocateBuffer(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buffer);
|
||||
static void onRead(uv_stream_t* stream, ssize_t readSize, const uv_buf_t* buffer);
|
||||
static void onWrite(uv_write_t* request, int status);
|
||||
static void onRelease(const v8::WeakCallbackInfo<Socket>& data);
|
||||
|
||||
void notifyDataRead(const char* data, size_t length);
|
||||
int writeBytes(std::function<int(const char*, size_t)> callback, v8::Handle<v8::Value> value, int* outLength);
|
||||
int writeInternal(promiseid_t promise, const char* data, size_t length);
|
||||
void processTlsShutdown(promiseid_t promise);
|
||||
static void onTlsShutdown(uv_write_t* request, int status);
|
||||
void shutdownInternal(promiseid_t promise);
|
||||
|
||||
bool processSomeOutgoingTls(promiseid_t promise, uv_write_cb callback);
|
||||
void processOutgoingTls();
|
||||
void reportTlsErrors();
|
||||
void reportError(const char* error);
|
||||
|
||||
void ref();
|
||||
void release();
|
||||
};
|
||||
|
||||
#endif
|
846
src/Task.cpp
846
src/Task.cpp
@ -1,846 +0,0 @@
|
||||
#include "Task.h"
|
||||
|
||||
#include "Database.h"
|
||||
#include "File.h"
|
||||
#include "Serialize.h"
|
||||
#include "Socket.h"
|
||||
#include "TaskStub.h"
|
||||
#include "TaskTryCatch.h"
|
||||
#include "TlsContextWrapper.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <libplatform/libplatform.h>
|
||||
#include <map>
|
||||
#include <sys/types.h>
|
||||
#include <uv.h>
|
||||
#include <v8.h>
|
||||
#include <v8-platform.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
static const int STDIN_FILENO = 0;
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
extern v8::Platform* gPlatform;
|
||||
int gNextTaskId = 1;
|
||||
|
||||
int Task::_count;
|
||||
|
||||
struct ExportRecord {
|
||||
v8::Persistent<v8::Function, v8::CopyablePersistentTraits<v8::Function> > _persistent;
|
||||
int _useCount;
|
||||
|
||||
ExportRecord(v8::Isolate* isolate, v8::Handle<v8::Function> function)
|
||||
: _persistent(isolate, function),
|
||||
_useCount(0) {
|
||||
}
|
||||
|
||||
void ref() {
|
||||
++_useCount;
|
||||
}
|
||||
|
||||
bool release() {
|
||||
return --_useCount == 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct ImportRecord {
|
||||
v8::Persistent<v8::Function, v8::CopyablePersistentTraits<v8::Function> > _persistent;
|
||||
exportid_t _export;
|
||||
taskid_t _task;
|
||||
Task* _owner;
|
||||
int _useCount;
|
||||
|
||||
ImportRecord(v8::Isolate* isolate, v8::Handle<v8::Function> function, exportid_t exportId, taskid_t taskId, Task* owner)
|
||||
: _persistent(isolate, function),
|
||||
_export(exportId),
|
||||
_task(taskId),
|
||||
_owner(owner),
|
||||
_useCount(0) {
|
||||
_persistent.SetWeak(this, ImportRecord::onRelease, v8::WeakCallbackType::kParameter);
|
||||
}
|
||||
|
||||
void ref() {
|
||||
if (_useCount++ == 0) {
|
||||
// Make a strong ref again until an in-flight function call is finished.
|
||||
_persistent.ClearWeak();
|
||||
}
|
||||
}
|
||||
|
||||
void release() {
|
||||
if (--_useCount == 0) {
|
||||
// All in-flight calls are finished. Make weak.
|
||||
_persistent.SetWeak(this, ImportRecord::onRelease, v8::WeakCallbackType::kParameter);
|
||||
}
|
||||
}
|
||||
|
||||
static void onRelease(const v8::WeakCallbackInfo<ImportRecord >& data) {
|
||||
ImportRecord* import = data.GetParameter();
|
||||
import->_owner->releaseExport(import->_task, import->_export);
|
||||
for (size_t i = 0; i < import->_owner->_imports.size(); ++i) {
|
||||
if (import->_owner->_imports[i] == import) {
|
||||
import->_owner->_imports.erase(import->_owner->_imports.begin() + i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
import->_persistent.Reset();
|
||||
delete import;
|
||||
}
|
||||
};
|
||||
|
||||
Task::Task() {
|
||||
_loop = uv_loop_new();
|
||||
++_count;
|
||||
v8::Isolate::CreateParams options;
|
||||
options.array_buffer_allocator = v8::ArrayBuffer::Allocator::NewDefaultAllocator();
|
||||
_allocator = options.array_buffer_allocator;
|
||||
_isolate = v8::Isolate::New(options);
|
||||
_isolate->SetData(0, this);
|
||||
_isolate->SetCaptureStackTraceForUncaughtExceptions(true, 16);
|
||||
}
|
||||
|
||||
Task::~Task() {
|
||||
_exportObject.Reset();
|
||||
_sourceObject.Reset();
|
||||
_scriptExports.clear();
|
||||
|
||||
{
|
||||
v8::Isolate::Scope isolateScope(_isolate);
|
||||
v8::HandleScope handleScope(_isolate);
|
||||
_context.Reset();
|
||||
}
|
||||
|
||||
_isolate->Dispose();
|
||||
_isolate = 0;
|
||||
|
||||
delete _allocator;
|
||||
_allocator = nullptr;
|
||||
|
||||
uv_loop_delete(_loop);
|
||||
--_count;
|
||||
}
|
||||
|
||||
v8::Handle<v8::Context> Task::getContext() {
|
||||
return v8::Local<v8::Context>::New(_isolate, _context);
|
||||
}
|
||||
|
||||
void Task::run() {
|
||||
{
|
||||
v8::Isolate::Scope isolateScope(_isolate);
|
||||
v8::HandleScope handleScope(_isolate);
|
||||
v8::Context::Scope contextScope(v8::Local<v8::Context>::New(_isolate, _context));
|
||||
uv_run(_loop, UV_RUN_DEFAULT);
|
||||
}
|
||||
_promises.clear();
|
||||
_exports.clear();
|
||||
_imports.clear();
|
||||
}
|
||||
|
||||
v8::Handle<v8::String> Task::loadFile(v8::Isolate* isolate, const char* fileName) {
|
||||
v8::Handle<v8::String> value;
|
||||
std::ifstream file(fileName, std::ios_base::in | std::ios_base::binary | std::ios_base::ate);
|
||||
std::streampos fileSize = file.tellg();
|
||||
if (fileSize >= 0) {
|
||||
file.seekg(0, std::ios_base::beg);
|
||||
char* buffer = new char[fileSize];
|
||||
file.read(buffer, fileSize);
|
||||
std::string contents(buffer, buffer + fileSize);
|
||||
value = v8::String::NewFromUtf8(isolate, buffer, v8::String::kNormalString, fileSize);
|
||||
delete[] buffer;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void Task::activate() {
|
||||
v8::Isolate::Scope isolateScope(_isolate);
|
||||
v8::HandleScope handleScope(_isolate);
|
||||
|
||||
v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();
|
||||
|
||||
if (!_importObject.IsEmpty()) {
|
||||
v8::Local<v8::Object> imports(_importObject.Get(_isolate));
|
||||
v8::Handle<v8::Array> keys = imports->GetOwnPropertyNames();
|
||||
for (size_t i = 0; i < keys->Length(); ++i) {
|
||||
global->SetAccessor(keys->Get(i).As<v8::String>(), getImportProperty);
|
||||
}
|
||||
}
|
||||
|
||||
global->Set(v8::String::NewFromUtf8(_isolate, "print"), v8::FunctionTemplate::New(_isolate, print));
|
||||
global->Set(v8::String::NewFromUtf8(_isolate, "setTimeout"), v8::FunctionTemplate::New(_isolate, setTimeout));
|
||||
global->SetAccessor(v8::String::NewFromUtf8(_isolate, "parent"), parent);
|
||||
global->Set(v8::String::NewFromUtf8(_isolate, "exit"), v8::FunctionTemplate::New(_isolate, exit));
|
||||
global->Set(v8::String::NewFromUtf8(_isolate, "utf8Length"), v8::FunctionTemplate::New(_isolate, utf8Length));
|
||||
global->SetAccessor(v8::String::NewFromUtf8(_isolate, "exports"), getExports, setExports);
|
||||
global->SetAccessor(v8::String::NewFromUtf8(_isolate, "imports"), getImports);
|
||||
global->SetAccessor(v8::String::NewFromUtf8(_isolate, "version"), version);
|
||||
global->SetAccessor(v8::String::NewFromUtf8(_isolate, "statistics"), statistics);
|
||||
if (_trusted) {
|
||||
global->Set(v8::String::NewFromUtf8(_isolate, "require"), v8::FunctionTemplate::New(_isolate, require));
|
||||
global->Set(v8::String::NewFromUtf8(_isolate, "Database"), v8::FunctionTemplate::New(_isolate, Database::create));
|
||||
global->Set(v8::String::NewFromUtf8(_isolate, "Socket"), v8::FunctionTemplate::New(_isolate, Socket::create));
|
||||
global->Set(v8::String::NewFromUtf8(_isolate, "Task"), v8::FunctionTemplate::New(_isolate, TaskStub::create));
|
||||
global->Set(v8::String::NewFromUtf8(_isolate, "TlsContext"), v8::FunctionTemplate::New(_isolate, TlsContextWrapper::create));
|
||||
File::configure(_isolate, global);
|
||||
} else {
|
||||
global->Set(v8::String::NewFromUtf8(_isolate, "require"), v8::FunctionTemplate::New(_isolate, childRequire));
|
||||
}
|
||||
|
||||
v8::Local<v8::Context> context = v8::Context::New(_isolate, 0, global);
|
||||
_context = v8::Persistent<v8::Context, v8::CopyablePersistentTraits<v8::Context> >(_isolate, context);
|
||||
|
||||
v8::Context::Scope contextScope(v8::Local<v8::Context>::New(_isolate, _context));
|
||||
|
||||
v8::Local<v8::Object> exportObject = v8::Object::New(_isolate);
|
||||
_exportObject = v8::Persistent<v8::Object, v8::CopyablePersistentTraits<v8::Object> >(_isolate, exportObject);
|
||||
}
|
||||
|
||||
void Task::activate(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
Task* task = Task::get(args.GetIsolate());
|
||||
task->activate();
|
||||
}
|
||||
|
||||
void Task::print(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::HandleScope scope(args.GetIsolate());
|
||||
v8::Local<v8::Context> context = args.GetIsolate()->GetCurrentContext();
|
||||
v8::Handle<v8::Object> json = context->Global()->Get(v8::String::NewFromUtf8(args.GetIsolate(), "JSON"))->ToObject();
|
||||
v8::Handle<v8::Function> stringify = v8::Handle<v8::Function>::Cast(json->Get(v8::String::NewFromUtf8(args.GetIsolate(), "stringify")));
|
||||
Task* task = reinterpret_cast<Task*>(args.GetIsolate()->GetData(0));
|
||||
TaskTryCatch tryCatch(task);
|
||||
std::cout << "Task[" << task << ':' << task->_scriptName << "]>";
|
||||
for (int i = 0; i < args.Length(); i++) {
|
||||
std::cout << ' ';
|
||||
v8::Handle<v8::Value> arg = args[i];
|
||||
if (arg->IsNativeError()) {
|
||||
arg = Serialize::storeMessage(task, v8::Exception::CreateMessage(arg));
|
||||
}
|
||||
v8::String::Utf8Value value(stringify->Call(json, 1, &arg));
|
||||
std::cout << (*value ? *value : "(null)");
|
||||
}
|
||||
std::cout << '\n';
|
||||
}
|
||||
|
||||
struct TimeoutData {
|
||||
Task* _task;
|
||||
v8::Persistent<v8::Function, v8::CopyablePersistentTraits<v8::Function> > _callback;
|
||||
};
|
||||
|
||||
void Task::setTimeout(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::HandleScope scope(args.GetIsolate());
|
||||
Task* task = reinterpret_cast<Task*>(args.GetIsolate()->GetData(0));
|
||||
|
||||
TimeoutData* timeout = new TimeoutData();
|
||||
timeout->_task = task;
|
||||
|
||||
v8::Persistent<v8::Function, v8::CopyablePersistentTraits<v8::Function> > function(args.GetIsolate(), v8::Handle<v8::Function>::Cast(args[0]));
|
||||
timeout->_callback = function;
|
||||
|
||||
uv_timer_t* timer = new uv_timer_t();
|
||||
uv_timer_init(task->_loop, timer);
|
||||
timer->data = timeout;
|
||||
uv_timer_start(timer, timeoutCallback, static_cast<uint64_t>(args[1].As<v8::Number>()->Value()), 0);
|
||||
}
|
||||
|
||||
void Task::timeoutCallback(uv_timer_t* handle) {
|
||||
TimeoutData* timeout = reinterpret_cast<TimeoutData*>(handle->data);
|
||||
TaskTryCatch tryCatch(timeout->_task);
|
||||
v8::HandleScope scope(timeout->_task->_isolate);
|
||||
v8::Handle<v8::Function> function = v8::Local<v8::Function>::New(timeout->_task->_isolate, timeout->_callback);
|
||||
function->Call(v8::Undefined(timeout->_task->_isolate), 0, 0);
|
||||
delete timeout;
|
||||
}
|
||||
|
||||
void Task::utf8Length(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
Task* task = reinterpret_cast<Task*>(args.GetIsolate()->GetData(0));
|
||||
TaskTryCatch tryCatch(task);
|
||||
v8::HandleScope scope(task->_isolate);
|
||||
args.GetReturnValue().Set(v8::Integer::New(args.GetIsolate(), args[0].As<v8::String>()->Utf8Length()));
|
||||
}
|
||||
|
||||
void Task::exit(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
::exit(args[0]->Int32Value());
|
||||
}
|
||||
|
||||
void Task::kill() {
|
||||
if (!_killed && _isolate) {
|
||||
_killed = true;
|
||||
v8::V8::TerminateExecution(_isolate);
|
||||
}
|
||||
}
|
||||
|
||||
bool Task::execute(const char* fileName) {
|
||||
bool executed = false;
|
||||
v8::Isolate::Scope isolateScope(_isolate);
|
||||
v8::HandleScope handleScope(_isolate);
|
||||
v8::Context::Scope contextScope(v8::Local<v8::Context>::New(_isolate, _context));
|
||||
|
||||
v8::Handle<v8::String> name = v8::String::NewFromUtf8(_isolate, fileName);
|
||||
|
||||
v8::Handle<v8::String> source = loadFile(_isolate, fileName);
|
||||
std::cout << "Running script " << fileName << "\n";
|
||||
if (!_scriptName.size()) {
|
||||
_scriptName = fileName;
|
||||
}
|
||||
if (!_path.size()) {
|
||||
std::string path = _scriptName;
|
||||
size_t position = path.rfind('/');
|
||||
if (position != std::string::npos) {
|
||||
path.resize(position + 1);
|
||||
} else {
|
||||
path = "./";
|
||||
}
|
||||
_path.push_back(path);
|
||||
}
|
||||
if (!source.IsEmpty()) {
|
||||
v8::Handle<v8::Script> script = v8::Script::Compile(source, name);
|
||||
if (!script.IsEmpty()) {
|
||||
script->Run();
|
||||
std::cout << "Script " << fileName << " completed\n";
|
||||
executed = true;
|
||||
} else {
|
||||
std::cerr << "Failed to compile: " << fileName << ".\n";
|
||||
}
|
||||
} else {
|
||||
std::string message;
|
||||
message = "Failed to load file: ";
|
||||
message += fileName;
|
||||
_isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(_isolate, message.c_str())));
|
||||
}
|
||||
return executed;
|
||||
}
|
||||
|
||||
void Task::invokeExport(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
Task* sender = Task::get(args.GetIsolate());
|
||||
v8::Handle<v8::Object> data = v8::Handle<v8::Object>::Cast(args.Data());
|
||||
exportid_t exportId = data->Get(v8::String::NewFromUtf8(args.GetIsolate(), "export"))->Int32Value();
|
||||
taskid_t recipientId = data->Get(v8::String::NewFromUtf8(args.GetIsolate(), "task"))->Int32Value();
|
||||
|
||||
for (size_t i = 0; i < sender->_imports.size(); ++i) {
|
||||
if (sender->_imports[i]->_task == recipientId && sender->_imports[i]->_export == exportId) {
|
||||
sender->_imports[i]->ref();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
v8::Local<v8::Array> array = v8::Array::New(args.GetIsolate(), args.Length() + 1);
|
||||
for (int i = 0; i < args.Length(); ++i) {
|
||||
array->Set(i + 1, args[i]);
|
||||
}
|
||||
|
||||
if (TaskStub* recipient = sender->get(recipientId)) {
|
||||
promiseid_t promise = sender->allocatePromise();
|
||||
sendPromiseExportMessage(sender, recipient, kInvokeExport, promise, exportId, array);
|
||||
args.GetReturnValue().Set(sender->getPromise(promise));
|
||||
} else {
|
||||
args.GetReturnValue().Set(args.GetIsolate()->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(args.GetIsolate(), "Invoking a function on a nonexistant task."))));
|
||||
}
|
||||
}
|
||||
|
||||
v8::Handle<v8::Value> Task::invokeExport(TaskStub* from, Task* to, exportid_t exportId, const std::vector<char>& buffer) {
|
||||
v8::Handle<v8::Value> result;
|
||||
if (to->_exports[exportId]) {
|
||||
v8::Handle<v8::Array> arguments = v8::Handle<v8::Array>::Cast(Serialize::load(to, from, buffer));
|
||||
std::vector<v8::Handle<v8::Value> > argumentArray;
|
||||
for (size_t i = 1; i < arguments->Length(); ++i) {
|
||||
argumentArray.push_back(arguments->Get(i));
|
||||
}
|
||||
v8::Handle<v8::Function> function = v8::Local<v8::Function>::New(to->_isolate, to->_exports[exportId]->_persistent);
|
||||
v8::Handle<v8::Value>* argumentPointer = 0;
|
||||
if (argumentArray.size()) {
|
||||
argumentPointer = &*argumentArray.begin();
|
||||
}
|
||||
result = function->Call(v8::Handle<v8::Object>::Cast(arguments->Get(0)), argumentArray.size(), argumentPointer);
|
||||
} else {
|
||||
std::cout << to->_scriptName << ": That's not an export we have (exportId=" << exportId << ", exports = " << to->_exports.size() << ")\n";
|
||||
}
|
||||
from->getStream().send(kReleaseImport, reinterpret_cast<char*>(&exportId), sizeof(exportId));
|
||||
return result;
|
||||
}
|
||||
|
||||
void Task::sendPromiseResolve(Task* from, TaskStub* to, promiseid_t promise, v8::Handle<v8::Value> result) {
|
||||
if (!result.IsEmpty() && result->IsPromise()) {
|
||||
// We're not going to serialize/deserialize a promise...
|
||||
v8::Handle<v8::Object> data = v8::Object::New(from->_isolate);
|
||||
data->Set(v8::String::NewFromUtf8(from->_isolate, "task"), v8::Int32::New(from->_isolate, to->getId()));
|
||||
data->Set(v8::String::NewFromUtf8(from->_isolate, "promise"), v8::Int32::New(from->_isolate, promise));
|
||||
v8::Handle<v8::Promise> promise = v8::Handle<v8::Promise>::Cast(result);
|
||||
v8::Handle<v8::Function> then = v8::Function::New(from->_isolate, invokeThen, data);
|
||||
promise->Then(then);
|
||||
v8::Handle<v8::Function> catchCallback = v8::Function::New(from->_isolate, invokeCatch, data);
|
||||
promise->Catch(catchCallback);
|
||||
from->_isolate->RunMicrotasks();
|
||||
} else {
|
||||
sendPromiseMessage(from, to, kResolvePromise, promise, result);
|
||||
}
|
||||
}
|
||||
|
||||
void Task::sendPromiseReject(Task* from, TaskStub* to, promiseid_t promise, v8::Handle<v8::Value> result) {
|
||||
if (!result.IsEmpty() && result->IsPromise()) {
|
||||
// We're not going to serialize/deserialize a promise...
|
||||
v8::Handle<v8::Object> data = v8::Object::New(from->_isolate);
|
||||
data->Set(v8::String::NewFromUtf8(from->_isolate, "task"), v8::Int32::New(from->_isolate, to->getId()));
|
||||
data->Set(v8::String::NewFromUtf8(from->_isolate, "promise"), v8::Int32::New(from->_isolate, promise));
|
||||
v8::Handle<v8::Promise> promise = v8::Handle<v8::Promise>::Cast(result);
|
||||
v8::Handle<v8::Function> then = v8::Function::New(from->_isolate, invokeThen, data);
|
||||
promise->Then(then);
|
||||
v8::Handle<v8::Function> catchCallback = v8::Function::New(from->_isolate, invokeCatch, data);
|
||||
promise->Catch(catchCallback);
|
||||
from->_isolate->RunMicrotasks();
|
||||
} else {
|
||||
sendPromiseMessage(from, to, kRejectPromise, promise, result);
|
||||
}
|
||||
}
|
||||
|
||||
void Task::sendPromiseMessage(Task* from, TaskStub* to, MessageType messageType, promiseid_t promise, v8::Handle<v8::Value> result) {
|
||||
if (to) {
|
||||
std::vector<char> buffer;
|
||||
buffer.insert(buffer.end(), reinterpret_cast<char*>(&promise), reinterpret_cast<char*>(&promise) + sizeof(promise));
|
||||
if (!result.IsEmpty() && !result->IsUndefined() && !result->IsNull()) {
|
||||
Serialize::store(from, buffer, result);
|
||||
}
|
||||
to->getStream().send(messageType, &*buffer.begin(), buffer.size());
|
||||
} else {
|
||||
std::cerr << "Sending to a NULL task.\n";
|
||||
}
|
||||
}
|
||||
|
||||
void Task::sendPromiseExportMessage(Task* from, TaskStub* to, MessageType messageType, promiseid_t promise, exportid_t exportId, v8::Handle<v8::Value> result) {
|
||||
std::vector<char> buffer;
|
||||
buffer.insert(buffer.end(), reinterpret_cast<char*>(&promise), reinterpret_cast<char*>(&promise) + sizeof(promise));
|
||||
buffer.insert(buffer.end(), reinterpret_cast<char*>(&exportId), reinterpret_cast<char*>(&exportId) + sizeof(exportId));
|
||||
if (!result.IsEmpty() && !result->IsUndefined() && !result->IsNull()) {
|
||||
Serialize::store(from, buffer, result);
|
||||
}
|
||||
to->getStream().send(messageType, &*buffer.begin(), buffer.size());
|
||||
}
|
||||
|
||||
TaskStub* Task::get(taskid_t taskId) {
|
||||
return taskId == kParentId ? _parent : _children[taskId];
|
||||
}
|
||||
|
||||
void Task::invokeThen(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
Task* from = reinterpret_cast<Task*>(args.GetIsolate()->GetData(0));
|
||||
v8::Handle<v8::Object> data = v8::Handle<v8::Object>::Cast(args.Data());
|
||||
TaskStub* to = from->get(data->Get(v8::String::NewFromUtf8(args.GetIsolate(), "task"))->Int32Value());
|
||||
promiseid_t promise = data->Get(v8::String::NewFromUtf8(args.GetIsolate(), "promise"))->Int32Value();
|
||||
sendPromiseMessage(from, to, kResolvePromise, promise, args[0]);
|
||||
}
|
||||
|
||||
void Task::invokeCatch(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
Task* from = reinterpret_cast<Task*>(args.GetIsolate()->GetData(0));
|
||||
v8::Handle<v8::Object> data = v8::Handle<v8::Object>::Cast(args.Data());
|
||||
TaskStub* to = from->get(data->Get(v8::String::NewFromUtf8(args.GetIsolate(), "task"))->Int32Value());
|
||||
promiseid_t promise = data->Get(v8::String::NewFromUtf8(args.GetIsolate(), "promise"))->Int32Value();
|
||||
sendPromiseMessage(from, to, kRejectPromise, promise, args[0]);
|
||||
}
|
||||
|
||||
void Task::parent(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& args) {
|
||||
Task* task = reinterpret_cast<Task*>(args.GetIsolate()->GetData(0));
|
||||
if (task->_parent) {
|
||||
args.GetReturnValue().Set(task->_parent->getTaskObject());
|
||||
} else {
|
||||
args.GetReturnValue().Set(v8::Undefined(task->_isolate));
|
||||
}
|
||||
}
|
||||
|
||||
void Task::version(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& args) {
|
||||
Task* task = reinterpret_cast<Task*>(args.GetIsolate()->GetData(0));
|
||||
args.GetReturnValue().Set(v8::String::NewFromUtf8(task->_isolate, v8::V8::GetVersion()));
|
||||
}
|
||||
|
||||
void Task::getImportProperty(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& args) {
|
||||
v8::Local<v8::Object> imports = Task::get(args.GetIsolate())->_importObject.Get(args.GetIsolate());
|
||||
args.GetReturnValue().Set(imports->Get(property));
|
||||
}
|
||||
|
||||
void Task::getImports(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& args) {
|
||||
args.GetReturnValue().Set(v8::Local<v8::Object>::New(args.GetIsolate(), Task::get(args.GetIsolate())->_importObject));
|
||||
}
|
||||
|
||||
void Task::getExports(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& args) {
|
||||
args.GetReturnValue().Set(v8::Local<v8::Object>::New(args.GetIsolate(), Task::get(args.GetIsolate())->_exportObject));
|
||||
}
|
||||
|
||||
void Task::setExports(v8::Local<v8::String> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& args) {
|
||||
Task::get(args.GetIsolate())->_exportObject = v8::Persistent<v8::Object, v8::CopyablePersistentTraits<v8::Object> >(args.GetIsolate(), v8::Handle<v8::Object>::Cast(value));
|
||||
}
|
||||
|
||||
Task* Task::get(v8::Isolate* isolate) {
|
||||
return reinterpret_cast<Task*>(isolate->GetData(0));
|
||||
}
|
||||
|
||||
promiseid_t Task::allocatePromise() {
|
||||
promiseid_t promiseId;
|
||||
do {
|
||||
promiseId = _nextPromise++;
|
||||
} while (_promises.find(promiseId) != _promises.end());
|
||||
v8::Persistent<v8::Promise::Resolver, v8::NonCopyablePersistentTraits<v8::Promise::Resolver> > promise(_isolate, v8::Promise::Resolver::New(_isolate));
|
||||
_promises[promiseId] = promise;
|
||||
return promiseId;
|
||||
}
|
||||
|
||||
v8::Handle<v8::Promise::Resolver> Task::getPromise(promiseid_t promise) {
|
||||
v8::Handle<v8::Promise::Resolver> result;
|
||||
if (!_promises[promise].IsEmpty()) {
|
||||
result = v8::Local<v8::Promise::Resolver>::New(_isolate, _promises[promise]);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Task::resolvePromise(promiseid_t promise, v8::Handle<v8::Value> value) {
|
||||
TaskTryCatch tryCatch(this);
|
||||
if (!_promises[promise].IsEmpty()) {
|
||||
v8::HandleScope handleScope(_isolate);
|
||||
v8::Handle<v8::Promise::Resolver> resolver = v8::Local<v8::Promise::Resolver>::New(_isolate, _promises[promise]);
|
||||
resolver->Resolve(value);
|
||||
_isolate->RunMicrotasks();
|
||||
_promises[promise].Reset();
|
||||
_promises.erase(promise);
|
||||
}
|
||||
}
|
||||
|
||||
void Task::rejectPromise(promiseid_t promise, v8::Handle<v8::Value> value) {
|
||||
TaskTryCatch tryCatch(this);
|
||||
if (!_promises[promise].IsEmpty()) {
|
||||
v8::HandleScope handleScope(_isolate);
|
||||
v8::Handle<v8::Promise::Resolver> resolver = v8::Local<v8::Promise::Resolver>::New(_isolate, _promises[promise]);
|
||||
resolver->Reject(value);
|
||||
_isolate->RunMicrotasks();
|
||||
_promises[promise].Reset();
|
||||
_promises.erase(promise);
|
||||
}
|
||||
}
|
||||
|
||||
exportid_t Task::exportFunction(v8::Handle<v8::Function> function) {
|
||||
exportid_t exportId = -1;
|
||||
v8::Handle<v8::String> exportName = v8::String::NewFromUtf8(_isolate, "export");
|
||||
v8::Local<v8::Private> privateKey = v8::Private::ForApi(_isolate, exportName);
|
||||
|
||||
v8::MaybeLocal<v8::Value> value = function->GetPrivate(_isolate->GetCurrentContext(), privateKey);
|
||||
if (!value.IsEmpty() && value.ToLocalChecked()->IsNumber())
|
||||
{
|
||||
exportid_t foundId = value.ToLocalChecked()->ToInteger(_isolate)->Int32Value();
|
||||
if (_exports[foundId]) {
|
||||
exportId = foundId;
|
||||
}
|
||||
}
|
||||
|
||||
if (exportId == -1) {
|
||||
do {
|
||||
exportId = _nextExport++;
|
||||
} while (_exports[_nextExport]);
|
||||
ExportRecord* record = new ExportRecord(_isolate, function);
|
||||
function->SetPrivate(_isolate->GetCurrentContext(), privateKey, v8::Integer::New(_isolate, exportId));
|
||||
_exports[exportId] = record;
|
||||
}
|
||||
|
||||
if (_exports[exportId]) {
|
||||
_exports[exportId]->ref();
|
||||
}
|
||||
|
||||
return exportId;
|
||||
}
|
||||
|
||||
void Task::releaseExport(taskid_t taskId, exportid_t exportId) {
|
||||
if (TaskStub* task = get(taskId)) {
|
||||
std::vector<char> buffer;
|
||||
buffer.insert(buffer.end(), reinterpret_cast<char*>(&exportId), reinterpret_cast<char*>(&exportId) + sizeof(exportId));
|
||||
task->getStream().send(kReleaseExport, &*buffer.begin(), buffer.size());
|
||||
}
|
||||
}
|
||||
|
||||
v8::Handle<v8::Function> Task::addImport(taskid_t taskId, exportid_t exportId) {
|
||||
v8::Local<v8::Object> data = v8::Object::New(_isolate);
|
||||
data->Set(v8::String::NewFromUtf8(_isolate, "export"), v8::Int32::New(_isolate, exportId));
|
||||
data->Set(v8::String::NewFromUtf8(_isolate, "task"), v8::Int32::New(_isolate, taskId));
|
||||
v8::Local<v8::Function> function = v8::Function::New(_isolate, Task::invokeExport, data);
|
||||
_imports.push_back(new ImportRecord(_isolate, function, exportId, taskId, this));
|
||||
return function;
|
||||
}
|
||||
|
||||
void Task::statistics(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& args) {
|
||||
Task* task = reinterpret_cast<Task*>(args.GetIsolate()->GetData(0));
|
||||
args.GetReturnValue().Set(task->getStatistics());
|
||||
}
|
||||
|
||||
v8::Handle<v8::Object> Task::getStatistics() {
|
||||
v8::Handle<v8::Object> result = v8::Object::New(_isolate);
|
||||
result->Set(v8::String::NewFromUtf8(_isolate, "sockets"), v8::Integer::New(_isolate, Socket::getCount()));
|
||||
result->Set(v8::String::NewFromUtf8(_isolate, "openSockets"), v8::Integer::New(_isolate, Socket::getOpenCount()));
|
||||
result->Set(v8::String::NewFromUtf8(_isolate, "promises"), v8::Integer::New(_isolate, _promises.size()));
|
||||
result->Set(v8::String::NewFromUtf8(_isolate, "exports"), v8::Integer::New(_isolate, _exports.size()));
|
||||
result->Set(v8::String::NewFromUtf8(_isolate, "imports"), v8::Integer::New(_isolate, _imports.size()));
|
||||
result->Set(v8::String::NewFromUtf8(_isolate, "tlsContexts"), v8::Integer::New(_isolate, TlsContextWrapper::getCount()));
|
||||
|
||||
uv_rusage_t usage;
|
||||
if (uv_getrusage(&usage) == 0) {
|
||||
result->Set(v8::String::NewFromUtf8(_isolate, "utime"), v8::Number::New(_isolate, usage.ru_utime.tv_sec + usage.ru_utime.tv_usec / 1000000.0));
|
||||
result->Set(v8::String::NewFromUtf8(_isolate, "stime"), v8::Number::New(_isolate, usage.ru_stime.tv_sec + usage.ru_stime.tv_usec / 1000000.0));
|
||||
result->Set(v8::String::NewFromUtf8(_isolate, "maxrss"), v8::Number::New(_isolate, usage.ru_maxrss));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Task::onReceivePacket(int packetType, const char* begin, size_t length, void* userData) {
|
||||
TaskStub* stub = reinterpret_cast<TaskStub*>(userData);
|
||||
TaskStub* from = stub;
|
||||
Task* to = stub->getOwner();
|
||||
|
||||
TaskTryCatch tryCatch(to);
|
||||
v8::HandleScope scope(to->_isolate);
|
||||
|
||||
switch (static_cast<MessageType>(packetType)) {
|
||||
case kStatistics:
|
||||
{
|
||||
promiseid_t promise;
|
||||
std::memcpy(&promise, begin, sizeof(promise));
|
||||
v8::Handle<v8::Value> result = to->getStatistics();
|
||||
sendPromiseResolve(to, from, promise, result);
|
||||
}
|
||||
break;
|
||||
case kInvokeExport:
|
||||
{
|
||||
promiseid_t promise;
|
||||
exportid_t exportId;
|
||||
std::memcpy(&promise, begin, sizeof(promise));
|
||||
std::memcpy(&exportId, begin + sizeof(promise), sizeof(exportId));
|
||||
|
||||
v8::TryCatch tryCatch;
|
||||
v8::Handle<v8::Value> result = invokeExport(from, to, exportId, std::vector<char>(begin + sizeof(promiseid_t) + sizeof(exportid_t), begin + length));
|
||||
if (tryCatch.HasCaught()) {
|
||||
sendPromiseReject(to, from, promise, Serialize::store(to, tryCatch));
|
||||
} else {
|
||||
sendPromiseResolve(to, from, promise, result);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case kResolvePromise:
|
||||
case kRejectPromise:
|
||||
{
|
||||
v8::Handle<v8::Value> arg;
|
||||
promiseid_t promise;
|
||||
std::memcpy(&promise, begin, sizeof(promiseid_t));
|
||||
if (length > sizeof(promiseid_t)) {
|
||||
arg = Serialize::load(to, from, std::vector<char>(begin + sizeof(promiseid_t), begin + length));
|
||||
}
|
||||
else {
|
||||
arg = v8::Undefined(to->_isolate);
|
||||
}
|
||||
if (static_cast<MessageType>(packetType) == kResolvePromise) {
|
||||
to->resolvePromise(promise, arg);
|
||||
}
|
||||
else {
|
||||
to->rejectPromise(promise, arg);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case kReleaseExport:
|
||||
assert(length == sizeof(exportid_t));
|
||||
exportid_t exportId;
|
||||
memcpy(&exportId, begin, sizeof(exportId));
|
||||
if (to->_exports[exportId]) {
|
||||
if (to->_exports[exportId]->release()) {
|
||||
to->_exports.erase(exportId);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case kReleaseImport:
|
||||
{
|
||||
assert(length == sizeof(exportid_t));
|
||||
exportid_t exportId;
|
||||
memcpy(&exportId, begin, sizeof(exportId));
|
||||
for (size_t i = 0; i < to->_imports.size(); ++i) {
|
||||
if (to->_imports[i]->_task == from->getId() && to->_imports[i]->_export == exportId) {
|
||||
to->_imports[i]->release();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case kSetRequires:
|
||||
{
|
||||
v8::Handle<v8::Object> result = v8::Handle<v8::Object>::Cast(Serialize::load(to, from, std::vector<char>(begin, begin + length)));
|
||||
to->_sourceObject = v8::Persistent<v8::Object, v8::CopyablePersistentTraits<v8::Object> >(to->_isolate, result);
|
||||
}
|
||||
break;
|
||||
case kActivate:
|
||||
to->activate();
|
||||
break;
|
||||
case kExecute:
|
||||
{
|
||||
assert(length >= sizeof(promiseid_t));
|
||||
promiseid_t promise;
|
||||
std::memcpy(&promise, begin, sizeof(promiseid_t));
|
||||
v8::TryCatch tryCatch(to->_isolate);
|
||||
tryCatch.SetCaptureMessage(true);
|
||||
tryCatch.SetVerbose(true);
|
||||
v8::Handle<v8::Value> value = Serialize::load(to, from, std::vector<char>(begin + sizeof(promiseid_t), begin + length));
|
||||
v8::Handle<v8::Object> object = v8::Handle<v8::Object>::Cast(value);
|
||||
v8::Handle<v8::Value> source = v8::Handle<v8::Value>::Cast(object->Get(v8::String::NewFromUtf8(to->_isolate, "source")));
|
||||
v8::Handle<v8::String> stringSource;
|
||||
if (source->IsArrayBufferView()) {
|
||||
v8::Handle<v8::ArrayBufferView> array = v8::Handle<v8::ArrayBufferView>::Cast(source);
|
||||
stringSource = v8::String::NewFromUtf8(
|
||||
to->_isolate,
|
||||
reinterpret_cast<const char*>(array->Buffer()->GetContents().Data()),
|
||||
v8::String::kNormalString,
|
||||
array->Buffer()->GetContents().ByteLength());
|
||||
} else if (source->IsString()) {
|
||||
stringSource = v8::Handle<v8::String>::Cast(source);
|
||||
}
|
||||
v8::Handle<v8::String> name = v8::Handle<v8::String>::Cast(object->Get(v8::String::NewFromUtf8(to->_isolate, "name")));
|
||||
to->executeSource(stringSource, name);
|
||||
if (tryCatch.HasCaught()) {
|
||||
sendPromiseReject(to, from, promise, Serialize::store(to, tryCatch));
|
||||
}
|
||||
else {
|
||||
sendPromiseResolve(to, from, promise, v8::Undefined(to->_isolate));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case kKill:
|
||||
::exit(1);
|
||||
break;
|
||||
case kSetImports:
|
||||
{
|
||||
v8::Handle<v8::Object> result = v8::Handle<v8::Object>::Cast(Serialize::load(to, from, std::vector<char>(begin, begin + length)));
|
||||
to->_importObject = v8::Persistent<v8::Object, v8::CopyablePersistentTraits<v8::Object> >(to->_isolate, result);
|
||||
}
|
||||
break;
|
||||
case kGetExports:
|
||||
promiseid_t promise;
|
||||
assert(length == sizeof(promise));
|
||||
std::memcpy(&promise, begin, sizeof(promiseid_t));
|
||||
v8::Handle<v8::Object> result = v8::Local<v8::Object>::New(to->_isolate, to->_exportObject);
|
||||
sendPromiseResolve(to, from, promise, result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Task::configureFromStdin() {
|
||||
_parent = TaskStub::createParent(this, STDIN_FILENO);
|
||||
}
|
||||
|
||||
std::string Task::resolveRequire(const std::string& require) {
|
||||
std::string result;
|
||||
for (size_t i = 0; i < _path.size(); ++i) {
|
||||
std::string& path = _path[i];
|
||||
std::cout << "Looking in " << path << " for " << require << "\n";
|
||||
std::string test;
|
||||
if (require.find("..") == std::string::npos && require.find('/') == std::string::npos) {
|
||||
test = path + require;
|
||||
}
|
||||
if (test.size() && (require.size() < 3 || require.rfind(".js") != require.size() - 3)) {
|
||||
test += ".js";
|
||||
}
|
||||
std::cout << "Testing " << test << "\n";
|
||||
uv_fs_t request;
|
||||
if (uv_fs_access(_loop, &request, test.c_str(), R_OK, 0) == 0) {
|
||||
result = test;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Task::require(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::HandleScope scope(args.GetIsolate());
|
||||
Task* task = Task::get(args.GetIsolate());
|
||||
v8::String::Utf8Value pathValue(args[0]);
|
||||
if (*pathValue) {
|
||||
std::string unresolved(*pathValue, *pathValue + pathValue.length());
|
||||
std::string path = task->resolveRequire(unresolved);
|
||||
if (!path.size()) {
|
||||
args.GetReturnValue().Set(args.GetIsolate()->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(args.GetIsolate(), ("require(): Unable to resolve module: " + unresolved).c_str()))));
|
||||
} else {
|
||||
ScriptExportMap::iterator it = task->_scriptExports.find(path);
|
||||
if (it != task->_scriptExports.end()) {
|
||||
v8::Handle<v8::Object> exports = v8::Local<v8::Object>::New(args.GetIsolate(), it->second);
|
||||
args.GetReturnValue().Set(exports);
|
||||
} else {
|
||||
v8::Handle<v8::Object> exports = v8::Object::New(args.GetIsolate());
|
||||
task->_scriptExports[path] = v8::Persistent<v8::Object, v8::CopyablePersistentTraits<v8::Object> >(args.GetIsolate(), exports);
|
||||
|
||||
v8::Handle<v8::String> name = v8::String::NewFromUtf8(args.GetIsolate(), path.c_str());
|
||||
v8::Handle<v8::String> source = loadFile(args.GetIsolate(), path.c_str());
|
||||
std::cout << "Requiring script " << path << "\n";
|
||||
if (!source.IsEmpty()) {
|
||||
v8::Handle<v8::Object> global = args.GetIsolate()->GetCurrentContext()->Global();
|
||||
v8::Handle<v8::Value> oldExports = global->Get(v8::String::NewFromUtf8(args.GetIsolate(), "exports"));
|
||||
global->Set(v8::String::NewFromUtf8(args.GetIsolate(), "exports"), exports);
|
||||
v8::Handle<v8::Script> script = v8::Script::Compile(source, name);
|
||||
if (!script.IsEmpty()) {
|
||||
script->Run();
|
||||
std::cout << "Script " << path << " completed\n";
|
||||
} else {
|
||||
std::cerr << "Failed to compile " << path << ".\n";
|
||||
}
|
||||
global->Set(v8::String::NewFromUtf8(args.GetIsolate(), "exports"), oldExports);
|
||||
args.GetReturnValue().Set(exports);
|
||||
} else {
|
||||
std::cerr << "Failed to load " << path << ".\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
args.GetReturnValue().Set(args.GetIsolate()->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(args.GetIsolate(), "require(): No module specified."))));
|
||||
}
|
||||
}
|
||||
|
||||
v8::Handle<v8::Value> Task::executeSource(v8::Handle<v8::String>& source, v8::Handle<v8::String>& name) {
|
||||
v8::Isolate::Scope isolateScope(_isolate);
|
||||
v8::HandleScope handleScope(_isolate);
|
||||
v8::Context::Scope contextScope(v8::Local<v8::Context>::New(_isolate, _context));
|
||||
|
||||
v8::Handle<v8::Value> result;
|
||||
v8::String::Utf8Value nameValue(name);
|
||||
if (!source.IsEmpty()) {
|
||||
v8::Handle<v8::Script> script = v8::Script::Compile(source, name);
|
||||
if (!script.IsEmpty()) {
|
||||
script->Run();
|
||||
} else {
|
||||
std::cerr << "Failed to compile " << *nameValue << ".\n";
|
||||
}
|
||||
} else {
|
||||
result = _isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(_isolate, (std::string("Failed to load ") + *nameValue + ".").c_str())));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Task::childRequire(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::HandleScope scope(args.GetIsolate());
|
||||
Task* task = Task::get(args.GetIsolate());
|
||||
|
||||
v8::Handle<v8::Object> requiresObject = v8::Local<v8::Object>::New(args.GetIsolate(), task->_sourceObject);
|
||||
if (!requiresObject.IsEmpty()) {
|
||||
v8::Handle<v8::String> name = args[0]->ToString();
|
||||
v8::String::Utf8Value nameValue(name);
|
||||
ScriptExportMap::iterator it = task->_scriptExports.find(*nameValue);
|
||||
if (it != task->_scriptExports.end()) {
|
||||
v8::Handle<v8::Object> exports = v8::Local<v8::Object>::New(args.GetIsolate(), it->second);
|
||||
args.GetReturnValue().Set(exports);
|
||||
} else {
|
||||
v8::Handle<v8::Object> exports = v8::Object::New(args.GetIsolate());
|
||||
v8::Handle<v8::String> source = v8::Handle<v8::String>::Cast(requiresObject->Get(args[0]));
|
||||
if (!source.IsEmpty()) {
|
||||
v8::Handle<v8::Object> global = args.GetIsolate()->GetCurrentContext()->Global();
|
||||
v8::Handle<v8::Value> oldExports = global->Get(v8::String::NewFromUtf8(args.GetIsolate(), "exports"));
|
||||
global->Set(v8::String::NewFromUtf8(args.GetIsolate(), "exports"), exports);
|
||||
v8::Handle<v8::Script> script = v8::Script::Compile(source, name);
|
||||
if (!script.IsEmpty()) {
|
||||
script->Run();
|
||||
} else {
|
||||
std::cerr << "Failed to compile " << *nameValue << ".\n";
|
||||
}
|
||||
global->Set(v8::String::NewFromUtf8(args.GetIsolate(), "exports"), oldExports);
|
||||
args.GetReturnValue().Set(exports);
|
||||
} else {
|
||||
args.GetReturnValue().Set(args.GetIsolate()->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8(args.GetIsolate(), (std::string("Failed to load ") + *nameValue + ".").c_str()))));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
151
src/Task.h
151
src/Task.h
@ -1,151 +0,0 @@
|
||||
#ifndef INCLUDED_Task
|
||||
#define INCLUDED_Task
|
||||
|
||||
#include "PacketStream.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <v8.h>
|
||||
#include <v8-platform.h>
|
||||
#include <vector>
|
||||
|
||||
struct ExportRecord;
|
||||
struct ImportRecord;
|
||||
class Task;
|
||||
class TaskStub;
|
||||
|
||||
struct uv_loop_s;
|
||||
typedef struct uv_loop_s uv_loop_t;
|
||||
|
||||
typedef int taskid_t;
|
||||
typedef int promiseid_t;
|
||||
typedef int exportid_t;
|
||||
|
||||
enum MessageType {
|
||||
kResolvePromise,
|
||||
kRejectPromise,
|
||||
kInvokeExport,
|
||||
kReleaseExport,
|
||||
kReleaseImport,
|
||||
kSetRequires,
|
||||
kActivate,
|
||||
kExecute,
|
||||
kKill,
|
||||
kStatistics,
|
||||
kSetImports,
|
||||
kGetExports,
|
||||
};
|
||||
|
||||
class Task {
|
||||
public:
|
||||
Task();
|
||||
~Task();
|
||||
|
||||
const std::string& getName() const { return _scriptName; }
|
||||
v8::Isolate* getIsolate() { return _isolate; }
|
||||
uv_loop_t* getLoop() { return _loop; }
|
||||
v8::Handle<v8::Context> getContext();
|
||||
void kill();
|
||||
|
||||
promiseid_t allocatePromise();
|
||||
v8::Handle<v8::Promise::Resolver> getPromise(promiseid_t promise);
|
||||
void resolvePromise(promiseid_t promise, v8::Handle<v8::Value> value);
|
||||
void rejectPromise(promiseid_t promise, v8::Handle<v8::Value> value);
|
||||
|
||||
void configureFromStdin();
|
||||
void setTrusted(bool trusted) { _trusted = trusted; }
|
||||
bool execute(const char* fileName);
|
||||
void activate();
|
||||
void run();
|
||||
|
||||
static int getCount() { return _count; }
|
||||
static Task* get(v8::Isolate* isolate);
|
||||
TaskStub* get(taskid_t taskId);
|
||||
|
||||
exportid_t exportFunction(v8::Handle<v8::Function> function);
|
||||
static void invokeExport(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
v8::Handle<v8::Function> addImport(taskid_t taskId, exportid_t exportId);
|
||||
void releaseExport(taskid_t taskId, exportid_t exportId);
|
||||
|
||||
private:
|
||||
static int _count;
|
||||
|
||||
TaskStub* _stub = 0;
|
||||
TaskStub* _parent = 0;
|
||||
taskid_t _nextTask = 1;
|
||||
static const taskid_t kParentId = 0;
|
||||
std::map<taskid_t, TaskStub*> _children;
|
||||
std::vector<std::string> _path;
|
||||
|
||||
typedef std::map<std::string, v8::Persistent<v8::Object, v8::CopyablePersistentTraits<v8::Object> > > ScriptExportMap;
|
||||
ScriptExportMap _scriptExports;
|
||||
|
||||
bool _trusted = false;
|
||||
bool _killed = false;
|
||||
std::string _scriptName;
|
||||
v8::Isolate* _isolate = 0;
|
||||
|
||||
std::map<promiseid_t, v8::Persistent<v8::Promise::Resolver, v8::CopyablePersistentTraits<v8::Promise::Resolver> > > _promises;
|
||||
promiseid_t _nextPromise = 0;
|
||||
uv_loop_t* _loop = 0;
|
||||
|
||||
std::map<exportid_t, ExportRecord*> _exports;
|
||||
exportid_t _nextExport = 0;
|
||||
|
||||
v8::Persistent<v8::Context, v8::CopyablePersistentTraits<v8::Context> > _context;
|
||||
|
||||
std::vector<ImportRecord*> _imports;
|
||||
|
||||
v8::Persistent<v8::Object, v8::CopyablePersistentTraits<v8::Object> > _importObject;
|
||||
v8::Persistent<v8::Object, v8::CopyablePersistentTraits<v8::Object> > _exportObject;
|
||||
v8::Persistent<v8::Object, v8::CopyablePersistentTraits<v8::Object> > _sourceObject;
|
||||
|
||||
v8::ArrayBuffer::Allocator* _allocator;
|
||||
|
||||
v8::Handle<v8::Object> getStatistics();
|
||||
|
||||
std::string resolveRequire(const std::string& require);
|
||||
|
||||
static void activate(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void exit(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void print(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void require(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void childRequire(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
static void setTimeout(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void timeoutCallback(uv_timer_t* handle);
|
||||
|
||||
static void invokeThen(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void invokeCatch(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
static void parent(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& args);
|
||||
static void version(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& args);
|
||||
static void statistics(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& args);
|
||||
|
||||
static void utf8Length(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
static void getImportProperty(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& args);
|
||||
static void getImports(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& args);
|
||||
static void getExports(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& args);
|
||||
static void setExports(v8::Local<v8::String> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& args);
|
||||
|
||||
static v8::Handle<v8::Value> invokeExport(TaskStub* from, Task* to, exportid_t exportId, const std::vector<char>& buffer);
|
||||
static void sendPromiseResolve(Task* from, TaskStub* to, promiseid_t promise, v8::Handle<v8::Value> result);
|
||||
static void sendPromiseReject(Task* from, TaskStub* to, promiseid_t promise, v8::Handle<v8::Value> result);
|
||||
|
||||
static void onReceivePacket(int packetType, const char* begin, size_t length, void* userData);
|
||||
|
||||
static void sendPromiseMessage(Task* from, TaskStub* to, MessageType messageType, promiseid_t promise, v8::Handle<v8::Value> result);
|
||||
static void sendPromiseExportMessage(Task* from, TaskStub* to, MessageType messageType, promiseid_t promiseId, exportid_t exportId, v8::Handle<v8::Value> result);
|
||||
|
||||
static v8::Handle<v8::String> loadFile(v8::Isolate* isolate, const char* fileName);
|
||||
v8::Handle<v8::Value> executeSource(v8::Handle<v8::String>& source, v8::Handle<v8::String>& name);
|
||||
|
||||
friend struct ImportRecord;
|
||||
friend class TaskStub;
|
||||
};
|
||||
|
||||
#endif
|
252
src/TaskStub.cpp
252
src/TaskStub.cpp
@ -1,252 +0,0 @@
|
||||
#include "TaskStub.h"
|
||||
|
||||
#include "PacketStream.h"
|
||||
#include "Serialize.h"
|
||||
#include "Task.h"
|
||||
#include "TaskTryCatch.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#include <windows.h>
|
||||
#include <ws2tcpip.h>
|
||||
static const int STDIN_FILENO = 0;
|
||||
static const int STDOUT_FILENO = 1;
|
||||
static const int STDERR_FILENO = 2;
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
bool TaskStub::_determinedExecutable = false;
|
||||
char TaskStub::_executable[1024];
|
||||
|
||||
void TaskStub::initialize() {
|
||||
if (!_determinedExecutable) {
|
||||
size_t size = sizeof(_executable);
|
||||
uv_exepath(_executable, &size);
|
||||
_determinedExecutable = true;
|
||||
}
|
||||
}
|
||||
|
||||
TaskStub::TaskStub() {
|
||||
initialize();
|
||||
std::memset(&_process, 0, sizeof(_process));
|
||||
}
|
||||
|
||||
void TaskStub::ref() {
|
||||
if (++_refCount == 1) {
|
||||
_taskObject.ClearWeak();
|
||||
}
|
||||
}
|
||||
|
||||
void TaskStub::release() {
|
||||
if (--_refCount == 0) {
|
||||
_taskObject.SetWeak(this, onRelease, v8::WeakCallbackType::kParameter);
|
||||
}
|
||||
}
|
||||
|
||||
TaskStub* TaskStub::createParent(Task* task, uv_file file) {
|
||||
v8::Isolate::Scope isolateScope(task->_isolate);
|
||||
v8::HandleScope scope(task->_isolate);
|
||||
|
||||
v8::Local<v8::Context> context = v8::Context::New(task->_isolate, 0);
|
||||
context->Enter();
|
||||
|
||||
v8::Handle<v8::ObjectTemplate> parentTemplate = v8::ObjectTemplate::New(task->_isolate);
|
||||
parentTemplate->SetInternalFieldCount(1);
|
||||
|
||||
v8::Handle<v8::Object> parentObject = parentTemplate->NewInstance();
|
||||
TaskStub* parentStub = new TaskStub();
|
||||
parentStub->_taskObject.Reset(task->_isolate, v8::Local<v8::Object>::New(task->_isolate, parentObject));
|
||||
parentObject->SetInternalField(0, v8::External::New(task->_isolate, parentStub));
|
||||
parentStub->_owner = task;
|
||||
parentStub->_id = Task::kParentId;
|
||||
|
||||
if (uv_pipe_init(task->_loop, &parentStub->_stream.getStream(), 1) != 0) {
|
||||
std::cerr << "uv_pipe_init failed\n";
|
||||
}
|
||||
parentStub->_stream.setOnReceive(Task::onReceivePacket, parentStub);
|
||||
if (uv_pipe_open(&parentStub->_stream.getStream(), file) != 0) {
|
||||
std::cerr << "uv_pipe_open failed\n";
|
||||
}
|
||||
parentStub->_stream.start();
|
||||
|
||||
return parentStub;
|
||||
}
|
||||
|
||||
void TaskStub::create(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
Task* parent = Task::get(args.GetIsolate());
|
||||
v8::HandleScope scope(args.GetIsolate());
|
||||
|
||||
TaskStub* stub = new TaskStub();
|
||||
v8::Handle<v8::External> data = v8::External::New(args.GetIsolate(), stub);
|
||||
|
||||
v8::Handle<v8::ObjectTemplate> taskTemplate = v8::ObjectTemplate::New(args.GetIsolate());
|
||||
taskTemplate->Set(v8::String::NewFromUtf8(args.GetIsolate(), "setImports"), v8::FunctionTemplate::New(args.GetIsolate(), setImports, data));
|
||||
taskTemplate->Set(v8::String::NewFromUtf8(args.GetIsolate(), "getExports"), v8::FunctionTemplate::New(args.GetIsolate(), getExports, data));
|
||||
taskTemplate->SetAccessor(v8::String::NewFromUtf8(args.GetIsolate(), "onExit"), getOnExit, setOnExit, data);
|
||||
taskTemplate->Set(v8::String::NewFromUtf8(args.GetIsolate(), "activate"), v8::FunctionTemplate::New(args.GetIsolate(), TaskStub::activate, data));
|
||||
taskTemplate->Set(v8::String::NewFromUtf8(args.GetIsolate(), "execute"), v8::FunctionTemplate::New(args.GetIsolate(), TaskStub::execute, data));
|
||||
taskTemplate->Set(v8::String::NewFromUtf8(args.GetIsolate(), "kill"), v8::FunctionTemplate::New(args.GetIsolate(), TaskStub::kill, data));
|
||||
taskTemplate->Set(v8::String::NewFromUtf8(args.GetIsolate(), "statistics"), v8::FunctionTemplate::New(args.GetIsolate(), TaskStub::statistics, data));
|
||||
taskTemplate->Set(v8::String::NewFromUtf8(args.GetIsolate(), "setRequires"), v8::FunctionTemplate::New(args.GetIsolate(), setRequires, data));
|
||||
taskTemplate->SetInternalFieldCount(1);
|
||||
|
||||
v8::Handle<v8::Object> taskObject = taskTemplate->NewInstance();
|
||||
stub->_taskObject.Reset(args.GetIsolate(), taskObject);
|
||||
taskObject->SetInternalField(0, v8::External::New(args.GetIsolate(), stub));
|
||||
stub->_owner = parent;
|
||||
|
||||
taskid_t id = 0;
|
||||
if (parent) {
|
||||
do {
|
||||
id = parent->_nextTask++;
|
||||
if (parent->_nextTask == Task::kParentId) {
|
||||
++parent->_nextTask;
|
||||
}
|
||||
} while (parent->_children.find(id) != parent->_children.end());
|
||||
parent->_children[id] = stub;
|
||||
}
|
||||
stub->_id = id;
|
||||
|
||||
char arg1[] = "--child";
|
||||
char* argv[] = { _executable, arg1, 0 };
|
||||
|
||||
uv_pipe_t* pipe = reinterpret_cast<uv_pipe_t*>(&stub->_stream.getStream());
|
||||
std::memset(pipe, 0, sizeof(*pipe));
|
||||
if (uv_pipe_init(parent->getLoop(), pipe, 1) != 0) {
|
||||
std::cerr << "uv_pipe_init failed\n";
|
||||
}
|
||||
|
||||
uv_stdio_container_t io[3];
|
||||
io[0].flags = static_cast<uv_stdio_flags>(UV_CREATE_PIPE | UV_READABLE_PIPE | UV_WRITABLE_PIPE);
|
||||
io[0].data.stream = reinterpret_cast<uv_stream_t*>(pipe);
|
||||
io[1].flags = UV_INHERIT_FD;
|
||||
io[1].data.fd = STDOUT_FILENO;
|
||||
io[2].flags = UV_INHERIT_FD;
|
||||
io[2].data.fd = STDERR_FILENO;
|
||||
|
||||
uv_process_options_t options = {0};
|
||||
options.args = argv;
|
||||
options.exit_cb = onProcessExit;
|
||||
options.stdio = io;
|
||||
options.stdio_count = sizeof(io) / sizeof(*io);
|
||||
options.file = argv[0];
|
||||
|
||||
stub->_process.data = stub;
|
||||
int result = uv_spawn(parent->getLoop(), &stub->_process, &options);
|
||||
if (result == 0) {
|
||||
stub->_stream.setOnReceive(Task::onReceivePacket, stub);
|
||||
stub->_stream.start();
|
||||
|
||||
args.GetReturnValue().Set(taskObject);
|
||||
} else {
|
||||
std::cerr << "uv_spawn failed: " << uv_strerror(result) << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void TaskStub::onProcessExit(uv_process_t* process, int64_t status, int terminationSignal) {
|
||||
TaskStub* stub = reinterpret_cast<TaskStub*>(process->data);
|
||||
if (!stub->_onExit.IsEmpty()) {
|
||||
TaskTryCatch tryCatch(stub->_owner);
|
||||
v8::HandleScope scope(stub->_owner->_isolate);
|
||||
v8::Handle<v8::Function> callback = v8::Local<v8::Function>::New(stub->_owner->_isolate, stub->_onExit);
|
||||
v8::Handle<v8::Value> args[2];
|
||||
args[0] = v8::Integer::New(stub->_owner->_isolate, status);
|
||||
args[1] = v8::Integer::New(stub->_owner->_isolate, terminationSignal);
|
||||
callback->Call(callback, 2, &args[0]);
|
||||
}
|
||||
stub->_stream.close();
|
||||
stub->_owner->_children.erase(stub->_id);
|
||||
uv_close(reinterpret_cast<uv_handle_t*>(process), 0);
|
||||
}
|
||||
|
||||
void TaskStub::onRelease(const v8::WeakCallbackInfo<TaskStub>& data) {
|
||||
// XXX?
|
||||
}
|
||||
|
||||
void TaskStub::getExports(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (TaskStub* stub = TaskStub::get(args.Data())) {
|
||||
TaskTryCatch tryCatch(stub->_owner);
|
||||
v8::HandleScope scope(args.GetIsolate());
|
||||
|
||||
promiseid_t promise = stub->_owner->allocatePromise();
|
||||
Task::sendPromiseMessage(stub->_owner, stub, kGetExports, promise, v8::Undefined(args.GetIsolate()));
|
||||
args.GetReturnValue().Set(stub->_owner->getPromise(promise));
|
||||
}
|
||||
}
|
||||
|
||||
void TaskStub::setImports(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (TaskStub* stub = TaskStub::get(args.Data())) {
|
||||
std::vector<char> buffer;
|
||||
Serialize::store(Task::get(args.GetIsolate()), buffer, args[0]);
|
||||
stub->_stream.send(kSetImports, &*buffer.begin(), buffer.size());
|
||||
}
|
||||
}
|
||||
|
||||
void TaskStub::setRequires(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (TaskStub* stub = TaskStub::get(args.Data())) {
|
||||
std::vector<char> buffer;
|
||||
Serialize::store(Task::get(args.GetIsolate()), buffer, args[0]);
|
||||
stub->_stream.send(kSetRequires, &*buffer.begin(), buffer.size());
|
||||
}
|
||||
}
|
||||
|
||||
void TaskStub::getOnExit(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& args) {
|
||||
TaskTryCatch tryCatch(TaskStub::get(args.Data())->_owner);
|
||||
v8::HandleScope scope(args.GetIsolate());
|
||||
args.GetReturnValue().Set(v8::Local<v8::Function>::New(args.GetIsolate(), TaskStub::get(args.Data())->_onExit));
|
||||
}
|
||||
|
||||
void TaskStub::setOnExit(v8::Local<v8::String> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& args) {
|
||||
TaskTryCatch tryCatch(TaskStub::get(args.Data())->_owner);
|
||||
v8::HandleScope scope(args.GetIsolate());
|
||||
v8::Persistent<v8::Function, v8::CopyablePersistentTraits<v8::Function> > function(args.GetIsolate(), v8::Handle<v8::Function>::Cast(value));
|
||||
TaskStub::get(args.Data())->_onExit = function;
|
||||
}
|
||||
|
||||
TaskStub* TaskStub::get(v8::Handle<v8::Value> object) {
|
||||
return reinterpret_cast<TaskStub*>(v8::Handle<v8::External>::Cast(object)->Value());
|
||||
}
|
||||
|
||||
v8::Handle<v8::Object> TaskStub::getTaskObject() {
|
||||
return v8::Local<v8::Object>::New(_owner->getIsolate(), _taskObject);
|
||||
}
|
||||
|
||||
void TaskStub::activate(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (TaskStub* stub = TaskStub::get(args.Data())) {
|
||||
TaskTryCatch tryCatch(stub->_owner);
|
||||
v8::HandleScope scope(args.GetIsolate());
|
||||
v8::String::Utf8Value fileName(args[0]->ToString(args.GetIsolate()));
|
||||
stub->_stream.send(kActivate, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void TaskStub::execute(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (TaskStub* stub = TaskStub::get(args.Data())) {
|
||||
TaskTryCatch tryCatch(stub->_owner);
|
||||
v8::HandleScope scope(args.GetIsolate());
|
||||
|
||||
promiseid_t promise = stub->_owner->allocatePromise();
|
||||
Task::sendPromiseMessage(stub->_owner, stub, kExecute, promise, args[0]);
|
||||
args.GetReturnValue().Set(stub->_owner->getPromise(promise));
|
||||
}
|
||||
}
|
||||
|
||||
void TaskStub::kill(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (TaskStub* stub = TaskStub::get(args.Data())) {
|
||||
uv_process_kill(&stub->_process, SIGTERM);
|
||||
}
|
||||
}
|
||||
|
||||
void TaskStub::statistics(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (TaskStub* stub = TaskStub::get(args.Data())) {
|
||||
TaskTryCatch tryCatch(stub->_owner);
|
||||
v8::HandleScope scope(args.GetIsolate());
|
||||
|
||||
promiseid_t promise = stub->_owner->allocatePromise();
|
||||
Task::sendPromiseMessage(stub->_owner, stub, kStatistics, promise, v8::Undefined(args.GetIsolate()));
|
||||
args.GetReturnValue().Set(stub->_owner->getPromise(promise));
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
#ifndef INCLUDED_TaskStub
|
||||
#define INCLUDED_TaskStub
|
||||
|
||||
#include "PacketStream.h"
|
||||
|
||||
#include <v8.h>
|
||||
|
||||
class Task;
|
||||
|
||||
typedef int taskid_t;
|
||||
|
||||
class TaskStub {
|
||||
public:
|
||||
void ref();
|
||||
void release();
|
||||
|
||||
static void create(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static TaskStub* createParent(Task* task, uv_file file);
|
||||
static void initialize();
|
||||
|
||||
taskid_t getId() { return _id; }
|
||||
Task* getOwner() { return _owner; }
|
||||
v8::Handle<v8::Object> getTaskObject();
|
||||
PacketStream& getStream() { return _stream; }
|
||||
|
||||
private:
|
||||
v8::Persistent<v8::Object> _taskObject;
|
||||
int _refCount = 1;
|
||||
|
||||
Task* _owner = 0;
|
||||
PacketStream _stream;
|
||||
taskid_t _id = -1;
|
||||
uv_process_t _process;
|
||||
|
||||
v8::Persistent<v8::Function, v8::CopyablePersistentTraits<v8::Function> > _onExit;
|
||||
|
||||
static bool _determinedExecutable;
|
||||
static char _executable[1024];
|
||||
|
||||
TaskStub();
|
||||
|
||||
static TaskStub* get(v8::Handle<v8::Value> object);
|
||||
|
||||
static void getExports(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void setImports(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
static void setRequires(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
static void getOnExit(v8::Local<v8::String> property, const v8::PropertyCallbackInfo<v8::Value>& args);
|
||||
static void setOnExit(v8::Local<v8::String> property, v8::Local<v8::Value> value, const v8::PropertyCallbackInfo<void>& args);
|
||||
|
||||
static void activate(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void execute(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void kill(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void statistics(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
static void onRelease(const v8::WeakCallbackInfo<TaskStub>& data);
|
||||
|
||||
static void onProcessExit(uv_process_t* process, int64_t status, int terminationSignal);
|
||||
};
|
||||
|
||||
#endif
|
@ -1,62 +0,0 @@
|
||||
#include "TaskTryCatch.h"
|
||||
|
||||
#include "Task.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
const char* TaskTryCatch::toString(const v8::String::Utf8Value& value) {
|
||||
return *value ? *value : "(null)";
|
||||
}
|
||||
|
||||
TaskTryCatch::TaskTryCatch(Task* task) {
|
||||
_tryCatch.SetCaptureMessage(true);
|
||||
_tryCatch.SetVerbose(true);
|
||||
}
|
||||
|
||||
TaskTryCatch::~TaskTryCatch() {
|
||||
if (_tryCatch.HasCaught()) {
|
||||
if (v8::Isolate* isolate = v8::Isolate::GetCurrent()) {
|
||||
if (Task* task = reinterpret_cast<Task*>(isolate->GetData(0))) {
|
||||
std::cerr << "Task[" << task << ':' << task->getName() << "] ";
|
||||
}
|
||||
}
|
||||
std::cerr << "Exception:\n";
|
||||
|
||||
v8::Handle<v8::Message> message(_tryCatch.Message());
|
||||
if (!message.IsEmpty()) {
|
||||
std::cerr
|
||||
<< toString(v8::String::Utf8Value(message->GetScriptResourceName()))
|
||||
<< ':'
|
||||
<< message->GetLineNumber()
|
||||
<< ": "
|
||||
<< toString(v8::String::Utf8Value(_tryCatch.Exception()))
|
||||
<< '\n';
|
||||
std::cerr << toString(v8::String::Utf8Value(message->GetSourceLine())) << '\n';
|
||||
|
||||
for (int i = 0; i < message->GetStartColumn(); ++i) {
|
||||
std::cerr << ' ';
|
||||
}
|
||||
for (int i = message->GetStartColumn(); i < message->GetEndColumn(); ++i) {
|
||||
std::cerr << '^';
|
||||
}
|
||||
if (!message->GetStackTrace().IsEmpty()) {
|
||||
for (int i = 0; i < message->GetStackTrace()->GetFrameCount(); ++i) {
|
||||
std::cerr << "oops " << i << "\n";
|
||||
}
|
||||
}
|
||||
std::cerr << '\n';
|
||||
} else {
|
||||
std::cerr << toString(v8::String::Utf8Value(_tryCatch.Exception())) << '\n';
|
||||
}
|
||||
|
||||
v8::String::Utf8Value stackTrace(_tryCatch.StackTrace());
|
||||
if (stackTrace.length() > 0) {
|
||||
std::cerr << *stackTrace << '\n';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool TaskTryCatch::hasCaught()
|
||||
{
|
||||
return _tryCatch.HasCaught();
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
#ifndef INCLUDED_TaskTryCatch
|
||||
#define INCLUDED_TaskTryCatch
|
||||
|
||||
#include <v8.h>
|
||||
|
||||
class Task;
|
||||
|
||||
class TaskTryCatch {
|
||||
public:
|
||||
TaskTryCatch(Task* task);
|
||||
~TaskTryCatch();
|
||||
bool hasCaught();
|
||||
|
||||
private:
|
||||
v8::TryCatch _tryCatch;
|
||||
static const char* toString(const v8::String::Utf8Value& value);
|
||||
};
|
||||
|
||||
#endif
|
50
src/Tls.h
50
src/Tls.h
@ -1,50 +0,0 @@
|
||||
#ifndef INCLUDED_Tls
|
||||
#define INCLUDED_Tls
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
class TlsSession;
|
||||
|
||||
class TlsContext {
|
||||
public:
|
||||
static TlsContext* create();
|
||||
virtual ~TlsContext() {}
|
||||
|
||||
virtual TlsSession* createSession() { return 0; }
|
||||
virtual bool setCertificate(const char* certificate) { return false; }
|
||||
virtual bool setPrivateKey(const char* privateKey) { return false; }
|
||||
virtual bool addTrustedCertificate(const char* certificate) { return false; }
|
||||
};
|
||||
|
||||
class TlsSession {
|
||||
public:
|
||||
virtual ~TlsSession() {}
|
||||
|
||||
virtual void setHostname(const char* hostname) {}
|
||||
virtual void startAccept() = 0;
|
||||
virtual void startConnect() = 0;
|
||||
virtual void shutdown() = 0;
|
||||
|
||||
virtual int getPeerCertificate(char* buffer, size_t bytes) { return -1; }
|
||||
|
||||
enum HandshakeResult {
|
||||
kDone,
|
||||
kMore,
|
||||
kFailed,
|
||||
};
|
||||
virtual HandshakeResult handshake() = 0;
|
||||
|
||||
enum ReadResult {
|
||||
kReadZero = -1,
|
||||
kReadFailed = -2,
|
||||
};
|
||||
virtual int readPlain(char* buffer, size_t bytes) = 0;
|
||||
virtual int writePlain(const char* buffer, size_t bytes) = 0;
|
||||
|
||||
virtual int readEncrypted(char* buffer, size_t bytes) = 0;
|
||||
virtual int writeEncrypted(const char* buffer, size_t bytes) = 0;
|
||||
|
||||
virtual bool getError(char* buffer, size_t bytes) { return false; }
|
||||
};
|
||||
|
||||
#endif
|
@ -1,115 +0,0 @@
|
||||
#include "TlsContextWrapper.h"
|
||||
|
||||
#include "Task.h"
|
||||
#include "Tls.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
int TlsContextWrapper::_count = 0;
|
||||
|
||||
void TlsContextWrapper::create(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
v8::HandleScope handleScope(args.GetIsolate());
|
||||
if (TlsContextWrapper* wrapper = new TlsContextWrapper(Task::get(args.GetIsolate()))) {
|
||||
v8::Handle<v8::Object> result = v8::Local<v8::Object>::New(args.GetIsolate(), wrapper->_object);
|
||||
args.GetReturnValue().Set(result);
|
||||
wrapper->release();
|
||||
}
|
||||
}
|
||||
|
||||
TlsContextWrapper::TlsContextWrapper(Task* task) {
|
||||
++_count;
|
||||
v8::HandleScope scope(task->getIsolate());
|
||||
v8::Handle<v8::External> identifier = v8::External::New(task->getIsolate(), reinterpret_cast<void*>(&TlsContextWrapper::create));
|
||||
v8::Handle<v8::External> data = v8::External::New(task->getIsolate(), this);
|
||||
|
||||
v8::Local<v8::ObjectTemplate> wrapperTemplate = v8::ObjectTemplate::New(task->getIsolate());
|
||||
wrapperTemplate->SetInternalFieldCount(2);
|
||||
wrapperTemplate->Set(v8::String::NewFromUtf8(task->getIsolate(), "setCertificate"), v8::FunctionTemplate::New(task->getIsolate(), setCertificate, data));
|
||||
wrapperTemplate->Set(v8::String::NewFromUtf8(task->getIsolate(), "setPrivateKey"), v8::FunctionTemplate::New(task->getIsolate(), setPrivateKey, data));
|
||||
wrapperTemplate->Set(v8::String::NewFromUtf8(task->getIsolate(), "addTrustedCertificate"), v8::FunctionTemplate::New(task->getIsolate(), addTrustedCertificate, data));
|
||||
|
||||
v8::Local<v8::Object> wrapperObject = wrapperTemplate->NewInstance();
|
||||
wrapperObject->SetInternalField(0, identifier);
|
||||
wrapperObject->SetInternalField(1, data);
|
||||
_object.Reset(task->getIsolate(), wrapperObject);
|
||||
|
||||
_context = TlsContext::create();
|
||||
_task = task;
|
||||
}
|
||||
|
||||
TlsContextWrapper::~TlsContextWrapper() {
|
||||
close();
|
||||
--_count;
|
||||
}
|
||||
|
||||
void TlsContextWrapper::close() {
|
||||
if (_context) {
|
||||
delete _context;
|
||||
_context = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void TlsContextWrapper::onRelease(const v8::WeakCallbackInfo<TlsContextWrapper>& data) {
|
||||
data.GetParameter()->_object.Reset();
|
||||
delete data.GetParameter();
|
||||
}
|
||||
|
||||
TlsContextWrapper* TlsContextWrapper::get(v8::Handle<v8::Value> value) {
|
||||
TlsContextWrapper* result = 0;
|
||||
|
||||
if (!value.IsEmpty()
|
||||
&& value->IsObject())
|
||||
{
|
||||
v8::Handle<v8::Object> object = v8::Handle<v8::Object>::Cast(value);
|
||||
if (object->InternalFieldCount() == 2
|
||||
&& v8::Handle<v8::External>::Cast(object->GetInternalField(0))->Value() == &TlsContextWrapper::create)
|
||||
{
|
||||
result = reinterpret_cast<TlsContextWrapper*>(v8::Handle<v8::External>::Cast(object->GetInternalField(1))->Value());
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TlsContextWrapper* TlsContextWrapper::get(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
return reinterpret_cast<TlsContextWrapper*>(v8::Handle<v8::External>::Cast(args.Data())->Value());
|
||||
}
|
||||
|
||||
void TlsContextWrapper::ref() {
|
||||
if (++_refCount == 1) {
|
||||
_object.ClearWeak();
|
||||
}
|
||||
}
|
||||
|
||||
void TlsContextWrapper::release() {
|
||||
assert(_refCount >= 1);
|
||||
if (--_refCount == 0) {
|
||||
_object.SetWeak(this, onRelease, v8::WeakCallbackType::kParameter);
|
||||
}
|
||||
}
|
||||
|
||||
void TlsContextWrapper::setCertificate(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (TlsContextWrapper* wrapper = TlsContextWrapper::get(args)) {
|
||||
v8::String::Utf8Value value(args[0]->ToString(args.GetIsolate()));
|
||||
wrapper->_context->setCertificate(*value);
|
||||
}
|
||||
}
|
||||
|
||||
void TlsContextWrapper::setPrivateKey(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (TlsContextWrapper* wrapper = TlsContextWrapper::get(args)) {
|
||||
v8::String::Utf8Value value(args[0]->ToString(args.GetIsolate()));
|
||||
wrapper->_context->setPrivateKey(*value);
|
||||
}
|
||||
}
|
||||
|
||||
void TlsContextWrapper::addTrustedCertificate(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
||||
if (TlsContextWrapper* wrapper = TlsContextWrapper::get(args)) {
|
||||
v8::String::Utf8Value value(args[0]->ToString(args.GetIsolate()));
|
||||
wrapper->_context->addTrustedCertificate(*value);
|
||||
}
|
||||
}
|
||||
|
||||
int TlsContextWrapper::getCount()
|
||||
{
|
||||
return _count;
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
#ifndef INCLUDED_TlsContextWrapper
|
||||
#define INCLUDED_TlsContextWrapper
|
||||
|
||||
#include <v8.h>
|
||||
|
||||
class Task;
|
||||
class TlsContext;
|
||||
|
||||
class TlsContextWrapper {
|
||||
public:
|
||||
static void create(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
void close();
|
||||
|
||||
static TlsContextWrapper* get(v8::Handle<v8::Value> value);
|
||||
|
||||
static void setCertificate(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void setPrivateKey(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void addTrustedCertificate(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
static void onRelease(const v8::WeakCallbackInfo<TlsContextWrapper>& data);
|
||||
|
||||
TlsContext* getContext() { return _context; }
|
||||
|
||||
static int getCount();
|
||||
|
||||
private:
|
||||
TlsContextWrapper(Task* task);
|
||||
~TlsContextWrapper();
|
||||
|
||||
static TlsContextWrapper* get(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
TlsContext* _context = 0;
|
||||
Task* _task = 0;
|
||||
v8::Persistent<v8::Object> _object;
|
||||
int _refCount = 1;
|
||||
static int _count;
|
||||
|
||||
void ref();
|
||||
void release();
|
||||
};
|
||||
|
||||
#endif
|
39
src/bcrypt.c
Normal file
39
src/bcrypt.c
Normal file
@ -0,0 +1,39 @@
|
||||
#include "bcrypt.h"
|
||||
|
||||
#include "ow-crypt.h"
|
||||
|
||||
#include <sys/random.h>
|
||||
|
||||
JSValue _crypt_hashpw(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
JSValue _crypt_gensalt(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
|
||||
void tf_bcrypt_init(JSContext* context) {
|
||||
JSValue global = JS_GetGlobalObject(context);
|
||||
JSValue bcrypt = JS_NewObject(context);
|
||||
JS_SetPropertyStr(context, global, "bCrypt", bcrypt);
|
||||
JS_SetPropertyStr(context, bcrypt, "hashpw", JS_NewCFunction(context, _crypt_hashpw, "hashpw", 2));
|
||||
JS_SetPropertyStr(context, bcrypt, "gensalt", JS_NewCFunction(context, _crypt_gensalt, "gensalt", 0));
|
||||
JS_FreeValue(context, global);
|
||||
}
|
||||
|
||||
JSValue _crypt_hashpw(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
const char* key = JS_ToCString(context, argv[0]);
|
||||
const char* salt = JS_ToCString(context, argv[1]);
|
||||
char output[7 + 22 + 31 + 1];
|
||||
char* hash = crypt_rn(key, salt, output, sizeof(output));
|
||||
JSValue result = JS_NewString(context, hash);
|
||||
JS_FreeCString(context, key);
|
||||
JS_FreeCString(context, salt);
|
||||
return result;
|
||||
}
|
||||
|
||||
JSValue _crypt_gensalt(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
int length;
|
||||
JS_ToInt32(context, &length, argv[0]);
|
||||
char buffer[16];
|
||||
ssize_t bytes = getrandom(buffer, sizeof(buffer), 0);
|
||||
char output[7 + 22 + 1];
|
||||
char* salt = crypt_gensalt_rn("$2b$", length, buffer, bytes, output, sizeof(output));
|
||||
JSValue result = JS_NewString(context, salt);
|
||||
return result;
|
||||
}
|
5
src/bcrypt.h
Normal file
5
src/bcrypt.h
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "quickjs.h"
|
||||
|
||||
void tf_bcrypt_init(JSContext* context);
|
154
src/database.c
Normal file
154
src/database.c
Normal file
@ -0,0 +1,154 @@
|
||||
#include "database.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <malloc.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
static JSClassID _database_class_id;
|
||||
static int _database_count;
|
||||
|
||||
typedef struct _database_t {
|
||||
JSContext* context;
|
||||
JSValue object;
|
||||
void* task;
|
||||
sqlite3* db;
|
||||
const char* id;
|
||||
} database_t;
|
||||
|
||||
static JSValue _database_create(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data);
|
||||
static void _database_finalizer(JSRuntime *runtime, JSValue value);
|
||||
|
||||
static JSValue _database_get(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _database_set(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _database_remove(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _database_get_all(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
|
||||
void tf_database_init(JSContext* context, sqlite3* sqlite) {
|
||||
JS_NewClassID(&_database_class_id);
|
||||
JSClassDef def = {
|
||||
.class_name = "Database",
|
||||
.finalizer = &_database_finalizer,
|
||||
};
|
||||
if (JS_NewClass(JS_GetRuntime(context), _database_class_id, &def) != 0) {
|
||||
printf("Failed to register database.\n");
|
||||
}
|
||||
|
||||
JSValue global = JS_GetGlobalObject(context);
|
||||
JSValue data[] = { JS_NewInt64(context, (int64_t)(intptr_t)sqlite) };
|
||||
JSValue constructor = JS_NewCFunctionData(context, _database_create, 0, 0, 1, data);
|
||||
JS_SetConstructorBit(context, constructor, true);
|
||||
JS_SetPropertyStr(context, global, "Database", constructor);
|
||||
JS_FreeValue(context, global);
|
||||
}
|
||||
|
||||
static JSValue _database_create(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv, int magic, JSValue* data) {
|
||||
++_database_count;
|
||||
JSValue object = JS_NewObjectClass(context, _database_class_id);
|
||||
sqlite3* db = NULL;
|
||||
JS_ToInt64(context, (int64_t*)&db, data[0]);
|
||||
|
||||
database_t* database = malloc(sizeof(database_t));
|
||||
*database = (database_t) {
|
||||
.task = JS_GetContextOpaque(context),
|
||||
.context = context,
|
||||
.object = object,
|
||||
.db = db,
|
||||
};
|
||||
const char* id = JS_ToCString(context, argv[0]);
|
||||
database->id = strdup(id);
|
||||
JS_FreeCString(context, id);
|
||||
JS_SetOpaque(object, database);
|
||||
|
||||
JS_SetPropertyStr(context, object, "get", JS_NewCFunction(context, _database_get, "get", 1));
|
||||
JS_SetPropertyStr(context, object, "set", JS_NewCFunction(context, _database_set, "set", 2));
|
||||
JS_SetPropertyStr(context, object, "remove", JS_NewCFunction(context, _database_remove, "remove", 1));
|
||||
JS_SetPropertyStr(context, object, "getAll", JS_NewCFunction(context, _database_get_all, "getAll", 0));
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
static void _database_finalizer(JSRuntime *runtime, JSValue value) {
|
||||
database_t* database = JS_GetOpaque(value, _database_class_id);
|
||||
if (database) {
|
||||
free((void*)database->id);
|
||||
free(database);
|
||||
}
|
||||
--_database_count;
|
||||
}
|
||||
|
||||
static JSValue _database_get(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
JSValue entry = JS_UNDEFINED;
|
||||
database_t* database = JS_GetOpaque(this_val, _database_class_id);
|
||||
if (database) {
|
||||
sqlite3_stmt* statement;
|
||||
if (sqlite3_prepare(database->db, "SELECT value FROM properties WHERE id = $1 AND key = $2", -1, &statement, NULL) == SQLITE_OK) {
|
||||
size_t length;
|
||||
const char* keyString = JS_ToCStringLen(context, &length, argv[0]);
|
||||
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 2, keyString, length, NULL) == SQLITE_OK &&
|
||||
sqlite3_step(statement) == SQLITE_ROW) {
|
||||
entry = JS_NewStringLen(context, (const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0));
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
JSValue _database_set(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
database_t* database = JS_GetOpaque(this_val, _database_class_id);
|
||||
if (database) {
|
||||
sqlite3_stmt* statement;
|
||||
if (sqlite3_prepare(database->db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES ($1, $2, $3)", -1, &statement, NULL) == SQLITE_OK) {
|
||||
size_t keyLength;
|
||||
const char* keyString = JS_ToCStringLen(context, &keyLength, argv[0]);
|
||||
size_t valueLength;
|
||||
const char* valueString = JS_ToCStringLen(context, &valueLength, argv[1]);
|
||||
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 2, keyString, keyLength, NULL) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 3, valueString, valueLength, NULL) == SQLITE_OK &&
|
||||
sqlite3_step(statement) == SQLITE_OK) {
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
}
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
JSValue _database_remove(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
database_t* database = JS_GetOpaque(this_val, _database_class_id);
|
||||
if (database) {
|
||||
sqlite3_stmt* statement;
|
||||
if (sqlite3_prepare(database->db, "DELETE FROM properties WHERE id = $1 AND key = $2", -1, &statement, NULL) == SQLITE_OK) {
|
||||
size_t keyLength;
|
||||
const char* keyString = JS_ToCStringLen(context, &keyLength, argv[0]);
|
||||
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 2, keyString, keyLength, NULL) == SQLITE_OK &&
|
||||
sqlite3_step(statement) == SQLITE_OK) {
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
}
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
JSValue _database_get_all(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
JSValue array = JS_UNDEFINED;
|
||||
database_t* database = JS_GetOpaque(this_val, _database_class_id);
|
||||
if (database) {
|
||||
sqlite3_stmt* statement;
|
||||
if (sqlite3_prepare(database->db, "SELECT key, value FROM properties WHERE id = $1", -1, &statement, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_bind_text(statement, 1, database->id, -1, NULL) == SQLITE_OK) {
|
||||
array = JS_NewArray(context);
|
||||
uint32_t index = 0;
|
||||
while (sqlite3_step(statement) == SQLITE_ROW) {
|
||||
JS_SetPropertyUint32(context, array, index++, JS_NewStringLen(context, (const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0)));
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
}
|
||||
return array;
|
||||
}
|
7
src/database.h
Normal file
7
src/database.h
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "quickjs.h"
|
||||
|
||||
typedef struct sqlite3 sqlite3;
|
||||
|
||||
void tf_database_init(JSContext* context, sqlite3* sqlite);
|
212
src/file.c
Normal file
212
src/file.c
Normal file
@ -0,0 +1,212 @@
|
||||
#include "file.h"
|
||||
|
||||
#include "task.h"
|
||||
|
||||
#include <malloc.h>
|
||||
#include <stdbool.h>
|
||||
#include <uv.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
static JSValue _file_make_directory(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _file_read_directory(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _file_read_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _file_rename_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _file_stat(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _file_unlink_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _file_write_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
|
||||
static double _time_spec_to_double(const uv_timespec_t* time_spec);
|
||||
static void _file_on_stat_complete(uv_fs_t* request);
|
||||
|
||||
typedef struct file_stat_t {
|
||||
void* _task;
|
||||
JSContext* _context;
|
||||
promiseid_t _promise;
|
||||
uv_fs_t _request;
|
||||
} file_stat_t;
|
||||
|
||||
void tf_file_init(JSContext* context) {
|
||||
JSValue global = JS_GetGlobalObject(context);
|
||||
JSValue file = JS_NewObject(context);
|
||||
JS_SetPropertyStr(context, global, "File", file);
|
||||
JS_SetPropertyStr(context, file, "makeDirectory", JS_NewCFunction(context, _file_make_directory, "makeDirectory", 1));
|
||||
JS_SetPropertyStr(context, file, "readDirectory", JS_NewCFunction(context, _file_read_directory, "readDirectory", 1));
|
||||
JS_SetPropertyStr(context, file, "readFile", JS_NewCFunction(context, _file_read_file, "readFile", 1));
|
||||
JS_SetPropertyStr(context, file, "renameFile", JS_NewCFunction(context, _file_rename_file, "renameFile", 2));
|
||||
JS_SetPropertyStr(context, file, "stat", JS_NewCFunction(context, _file_stat, "stat", 1));
|
||||
JS_SetPropertyStr(context, file, "unlinkFile", JS_NewCFunction(context, _file_unlink_file, "unlinkFile", 1));
|
||||
JS_SetPropertyStr(context, file, "writeFile", JS_NewCFunction(context, _file_write_file, "writeFile", 2));
|
||||
JS_FreeValue(context, global);
|
||||
}
|
||||
|
||||
static void _free_array_buffer_data(JSRuntime* runtime, void* opaque, void* ptr) {
|
||||
free(ptr);
|
||||
}
|
||||
|
||||
static JSValue _file_read_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
const char* file_name = JS_ToCString(context, argv[0]);
|
||||
FILE* file = fopen(file_name, "rb");
|
||||
JS_FreeCString(context, file_name);
|
||||
if (!file) {
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
long size = 0;
|
||||
if (fseek(file, 0, SEEK_END) == 0) {
|
||||
size = ftell(file);
|
||||
}
|
||||
if (size >= 0 &&
|
||||
size < 4 * 1024 * 1024 &&
|
||||
fseek(file, 0, SEEK_SET) == 0) {
|
||||
uint8_t* data = malloc(size);
|
||||
if (data &&
|
||||
fread(data, 1, size, file) == (size_t)size) {
|
||||
JSValue arrayBuffer = JS_NewArrayBuffer(context, data, size, _free_array_buffer_data, NULL, false);
|
||||
JSValue global = JS_GetGlobalObject(context);
|
||||
JSValue constructor = JS_GetPropertyStr(context, global, "Uint8Array");
|
||||
JSValue typedArray = JS_CallConstructor(context, constructor, 1, &arrayBuffer);
|
||||
JS_FreeValue(context, constructor);
|
||||
JS_FreeValue(context, global);
|
||||
JS_FreeValue(context, arrayBuffer);
|
||||
return typedArray;
|
||||
} else if (data) {
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
static JSValue _file_write_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
JSValue result = JS_NULL;
|
||||
const char* fileName = JS_ToCString(context, argv[0]);
|
||||
FILE* file = fopen(fileName, "wb");
|
||||
JS_FreeCString(context, fileName);
|
||||
|
||||
if (file) {
|
||||
size_t size;
|
||||
uint8_t* buffer = tf_try_get_array_buffer(context, &size, argv[1]);
|
||||
if (buffer) {
|
||||
int written = fwrite((const char*)buffer, 1, size, file);
|
||||
result = JS_NewInt32(context, (size_t)written == size ? 0 : written);
|
||||
} else {
|
||||
const char* data = JS_ToCStringLen(context, &size, argv[1]);
|
||||
int written = fwrite((const char*)data, 1, size, file);
|
||||
result = JS_NewInt32(context, (size_t)written == size ? 0 : written);
|
||||
JS_FreeCString(context, data);
|
||||
}
|
||||
fclose(file);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static JSValue _file_rename_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
void* task = JS_GetContextOpaque(context);
|
||||
const char* oldName = JS_ToCString(context, argv[0]);
|
||||
const char* newName = JS_ToCString(context, argv[1]);
|
||||
uv_fs_t req;
|
||||
int result = uv_fs_rename(tf_task_get_loop(task), &req, oldName, newName, 0);
|
||||
JS_FreeCString(context, oldName);
|
||||
JS_FreeCString(context, newName);
|
||||
return JS_NewInt32(context, result);
|
||||
}
|
||||
|
||||
static JSValue _file_unlink_file(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
void* task = JS_GetContextOpaque(context);
|
||||
const char* fileName = JS_ToCString(context, argv[0]);
|
||||
uv_fs_t req;
|
||||
int result = uv_fs_unlink(tf_task_get_loop(task), &req, fileName, 0);
|
||||
JS_FreeCString(context, fileName);
|
||||
return JS_NewInt32(context, result);
|
||||
}
|
||||
|
||||
static JSValue _file_read_directory(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
const char* directory = JS_ToCString(context, argv[0]);
|
||||
JSValue array = JS_NewArray(context);
|
||||
|
||||
#ifdef _WIN32
|
||||
WIN32_FIND_DATA find;
|
||||
std::string pattern = directory;
|
||||
pattern += "\\*";
|
||||
HANDLE handle = FindFirstFile(pattern.c_str(), &find);
|
||||
if (handle != INVALID_HANDLE_VALUE) {
|
||||
int index = 0;
|
||||
do {
|
||||
JS_SetPropertyUint32(context, array, index++, JS_NewString(context, find.cFileName));
|
||||
} while (FindNextFile(handle, &find) != 0);
|
||||
FindClose(handle);
|
||||
}
|
||||
#else
|
||||
DIR* dir = opendir(directory);
|
||||
if (dir) {
|
||||
uint32_t index = 0;
|
||||
struct dirent* entry = readdir(dir);
|
||||
while (entry) {
|
||||
JS_SetPropertyUint32(context, array, index++, JS_NewString(context, entry->d_name));
|
||||
entry = readdir(dir);
|
||||
}
|
||||
closedir(dir);
|
||||
}
|
||||
#endif
|
||||
|
||||
JS_FreeCString(context, directory);
|
||||
return array;
|
||||
}
|
||||
|
||||
JSValue _file_make_directory(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
void* task = JS_GetContextOpaque(context);
|
||||
const char* directory = JS_ToCString(context, argv[0]);
|
||||
|
||||
uv_fs_t req;
|
||||
int result = uv_fs_mkdir(tf_task_get_loop(task), &req, directory, 0755, 0);
|
||||
JS_FreeCString(context, directory);
|
||||
return JS_NewInt32(context, result);
|
||||
}
|
||||
|
||||
JSValue _file_stat(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
void* task = JS_GetContextOpaque(context);
|
||||
const char* path = JS_ToCString(context, argv[0]);
|
||||
promiseid_t promise = tf_task_allocate_promise(task);
|
||||
|
||||
file_stat_t* data = malloc(sizeof(file_stat_t));
|
||||
data->_task = task;
|
||||
data->_promise = promise;
|
||||
data->_request.data = data;
|
||||
data->_context = context;
|
||||
|
||||
int result = uv_fs_stat(tf_task_get_loop(task), &data->_request, path, _file_on_stat_complete);
|
||||
if (result) {
|
||||
tf_task_reject_promise(task, promise, JS_NewInt32(context, result));
|
||||
free(data);
|
||||
}
|
||||
JS_FreeCString(context, path);
|
||||
return tf_task_get_promise(task, promise);
|
||||
}
|
||||
|
||||
static double _time_spec_to_double(const uv_timespec_t* time_spec) {
|
||||
return time_spec->tv_sec + (double)(time_spec->tv_nsec) / 1e9;
|
||||
}
|
||||
|
||||
static void _file_on_stat_complete(uv_fs_t* request) {
|
||||
file_stat_t* data = (file_stat_t*)(request->data);
|
||||
JSContext* context = data->_context;
|
||||
|
||||
if (request->result) {
|
||||
tf_task_reject_promise(data->_task, data->_promise, JS_NewInt32(context, request->result));
|
||||
} else {
|
||||
JSValue result = JS_NewObject(context);
|
||||
JS_SetPropertyStr(context, result, "mtime", JS_NewFloat64(context, _time_spec_to_double(&request->statbuf.st_mtim)));
|
||||
JS_SetPropertyStr(context, result, "ctime", JS_NewFloat64(context, _time_spec_to_double(&request->statbuf.st_ctim)));
|
||||
JS_SetPropertyStr(context, result, "atime", JS_NewFloat64(context, _time_spec_to_double(&request->statbuf.st_atim)));
|
||||
JS_SetPropertyStr(context, result, "size", JS_NewFloat64(context, request->statbuf.st_size));
|
||||
tf_task_resolve_promise(data->_task, data->_promise, result);
|
||||
JS_FreeValue(context, result);
|
||||
}
|
||||
uv_fs_req_cleanup(request);
|
||||
free(data);
|
||||
}
|
5
src/file.h
Normal file
5
src/file.h
Normal file
@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include "quickjs.h"
|
||||
|
||||
void tf_file_init(JSContext* context);
|
457
src/main.c
Normal file
457
src/main.c
Normal file
@ -0,0 +1,457 @@
|
||||
#include "ssb.h"
|
||||
#include "task.h"
|
||||
#include "taskstub.h"
|
||||
|
||||
#include <quickjs-libc.h>
|
||||
#include <quickjs.h>
|
||||
#include <sqlite3.h>
|
||||
#include <xopt.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#if !defined (_WIN32) && !defined (__MACH__)
|
||||
#include <signal.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/resource.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#if !defined(_countof)
|
||||
#define _countof(a) ((int)(sizeof((a)) / sizeof(*(a))))
|
||||
#endif
|
||||
|
||||
#define XOPT_PARSE(name, flags, options, config_ptr, argc, argv, extrac_ptr, extrav_ptr, err_ptr, autohelp_file, autohelp_usage, autohelp_prefix, autohelp_suffix, autohelp_spacer) do { \
|
||||
xoptContext *_xopt_ctx; \
|
||||
*(err_ptr) = NULL; \
|
||||
_xopt_ctx = xopt_context((name), (options), ((flags) ^ XOPT_CTX_POSIXMEHARDER), (err_ptr)); \
|
||||
if (*(err_ptr)) break; \
|
||||
*extrac_ptr = xopt_parse(_xopt_ctx, (argc), (argv), (config_ptr), (extrav_ptr), (err_ptr)); \
|
||||
if ((config_ptr)->help) { \
|
||||
xoptAutohelpOptions __xopt_autohelp_opts; \
|
||||
__xopt_autohelp_opts.usage = (autohelp_usage); \
|
||||
__xopt_autohelp_opts.prefix = (autohelp_prefix); \
|
||||
__xopt_autohelp_opts.suffix = (autohelp_suffix); \
|
||||
__xopt_autohelp_opts.spacer = (autohelp_spacer); \
|
||||
xopt_autohelp(_xopt_ctx, (autohelp_file), &__xopt_autohelp_opts, (err_ptr)); \
|
||||
if (*(err_ptr)) goto __xopt_end_free_extrav; \
|
||||
free(_xopt_ctx); \
|
||||
goto xopt_help; \
|
||||
} \
|
||||
if (*(err_ptr)) goto __xopt_end_free_ctx; \
|
||||
__xopt_end_free_ctx: \
|
||||
free(_xopt_ctx); \
|
||||
break; \
|
||||
__xopt_end_free_extrav: \
|
||||
free(*(extrav_ptr)); \
|
||||
free(_xopt_ctx); \
|
||||
break; \
|
||||
} while (false)
|
||||
|
||||
static int _tf_command_test(const char* file, int argc, char* argv[]);
|
||||
static int _tf_command_import(const char* file, int argc, char* argv[]);
|
||||
static int _tf_command_export(const char* file, int argc, char* argv[]);
|
||||
static int _tf_command_run(const char* file, int argc, char* argv[]);
|
||||
static int _tf_command_sandbox(const char* file, int argc, char* argv[]);
|
||||
static int _tf_command_post(const char* file, int argc, char* argv[]);
|
||||
static int _tf_command_usage(const char* file, int argc, char* argv[]);
|
||||
|
||||
typedef struct _command_t {
|
||||
const char* name;
|
||||
int (*callback)(const char* file, int argc, char* argv[]);
|
||||
const char* description;
|
||||
} command_t;
|
||||
|
||||
const command_t k_commands[] = {
|
||||
{ "run", _tf_command_run, "Run tildefriends (default)." },
|
||||
{ "sandbox", _tf_command_sandbox, "Run a sandboxed tildefriends sandbox process (used internally)." },
|
||||
{ "post", _tf_command_post, "Create an SSB post." },
|
||||
{ "import", _tf_command_import, "Import apps to SSB." },
|
||||
{ "export", _tf_command_export, "Export apps from SSB." },
|
||||
{ "test", _tf_command_test, "Test SSB." },
|
||||
};
|
||||
|
||||
void shedPrivileges()
|
||||
{
|
||||
#if !defined (_WIN32)
|
||||
struct rlimit zeroLimit;
|
||||
zeroLimit.rlim_cur = 0;
|
||||
zeroLimit.rlim_max = 0;
|
||||
|
||||
// RLIMIT_AS
|
||||
// RLIMIT_CORE
|
||||
// RLIMIT_CPU
|
||||
// RLIMIT_DATA
|
||||
// RLIMIT_FSIZE
|
||||
// RLIMIT_RSS
|
||||
// RLIMIT_RTPRIO
|
||||
// RLIMIT_RTTIME
|
||||
// RLIMIT_SIGPENDING
|
||||
// RLIMIT_STACK
|
||||
|
||||
if (setrlimit(RLIMIT_FSIZE, &zeroLimit) != 0) {
|
||||
perror("setrlimit(RLIMIT_FSIZE, {0, 0})");
|
||||
exit(-1);
|
||||
}
|
||||
if (setrlimit(RLIMIT_NOFILE, &zeroLimit) != 0) {
|
||||
perror("setrlimit(RLIMIT_NOFILE, {0, 0})");
|
||||
exit(-1);
|
||||
}
|
||||
if (setrlimit(RLIMIT_NPROC, &zeroLimit) != 0) {
|
||||
perror("setrlimit(RLIMIT_NPROC, {0, 0})");
|
||||
exit(-1);
|
||||
}
|
||||
#if !defined (__MACH__)
|
||||
if (setrlimit(RLIMIT_LOCKS, &zeroLimit) != 0) {
|
||||
perror("setrlimit(RLIMIT_LOCKS, {0, 0})");
|
||||
exit(-1);
|
||||
}
|
||||
if (setrlimit(RLIMIT_MSGQUEUE, &zeroLimit) != 0) {
|
||||
perror("setrlimit(RLIMIT_MSGQUEUE, {0, 0})");
|
||||
exit(-1);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
static int _tf_command_test(const char* file, int argc, char* argv[])
|
||||
{
|
||||
typedef struct args_t {
|
||||
bool help;
|
||||
} args_t;
|
||||
|
||||
xoptOption options[] = {
|
||||
{ "help", 'h', offsetof(args_t, help), NULL, XOPT_TYPE_BOOL, NULL, "Shows this help message." },
|
||||
XOPT_NULLOPTION,
|
||||
};
|
||||
|
||||
args_t args = { 0 };
|
||||
const char** extras = NULL;
|
||||
int extra_count = 0;
|
||||
const char *err = NULL;
|
||||
XOPT_PARSE(file, XOPT_CTX_KEEPFIRST, options, &args, argc, (const char**)argv, &extra_count, &extras, &err, stderr, "test [options]", "options:", NULL, 15);
|
||||
if (extras) {
|
||||
free((void*)extras);
|
||||
}
|
||||
if (err) {
|
||||
fprintf(stderr, "Error: %s\n", err);
|
||||
return 2;
|
||||
}
|
||||
|
||||
tf_ssb_test();
|
||||
return 0;
|
||||
xopt_help:
|
||||
if (extras) {
|
||||
free((void*)extras);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int _tf_command_import(const char* file, int argc, char* argv[])
|
||||
{
|
||||
typedef struct args_t {
|
||||
const char* user;
|
||||
const char* db_path;
|
||||
bool help;
|
||||
} args_t;
|
||||
|
||||
xoptOption options[] = {
|
||||
{ "user", 'u', offsetof(args_t, user), NULL, XOPT_TYPE_STRING, NULL, "User into whose account apps will be imported (default: \"import\")." },
|
||||
{ "db-path", 'd', offsetof(args_t, db_path), NULL, XOPT_TYPE_STRING, NULL, "Sqlite database path (default: db.sqlite)." },
|
||||
{ "help", 'h', offsetof(args_t, help), NULL, XOPT_TYPE_BOOL, NULL, "Shows this help message." },
|
||||
XOPT_NULLOPTION,
|
||||
};
|
||||
|
||||
args_t args = { .user = "import" };
|
||||
const char** extras = NULL;
|
||||
int extra_count = 0;
|
||||
const char *err = NULL;
|
||||
XOPT_PARSE(file, XOPT_CTX_KEEPFIRST | XOPT_CTX_POSIXMEHARDER, options, &args, argc, (const char**)argv, &extra_count, &extras, &err, stderr, "import [options] [paths] ...", "options:", NULL, 15);
|
||||
|
||||
if (err) {
|
||||
fprintf(stderr, "Error: %s\n", err);
|
||||
if (extras) {
|
||||
free((void*)extras);
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
|
||||
sqlite3* db = NULL;
|
||||
if (args.db_path) {
|
||||
sqlite3_open(args.db_path, &db);
|
||||
}
|
||||
tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, db, NULL);
|
||||
if (extra_count) {
|
||||
for (int i = 0; i < extra_count; i++) {
|
||||
printf("Importing %s...\n", extras[i]);
|
||||
tf_ssb_import(ssb, args.user, extras[i]);
|
||||
}
|
||||
} else {
|
||||
printf("Importing %s...\n", "apps");
|
||||
tf_ssb_import(ssb, args.user, "apps");
|
||||
}
|
||||
tf_ssb_destroy(ssb);
|
||||
if (db) {
|
||||
sqlite3_close(db);
|
||||
}
|
||||
|
||||
if (extras) {
|
||||
free((void*)extras);
|
||||
}
|
||||
return 0;
|
||||
|
||||
xopt_help:
|
||||
if (extras) {
|
||||
free((void*)extras);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int _tf_command_export(const char* file, int argc, char* argv[])
|
||||
{
|
||||
typedef struct args_t {
|
||||
bool help;
|
||||
} args_t;
|
||||
|
||||
xoptOption options[] = {
|
||||
{ "help", 'h', offsetof(args_t, help), NULL, XOPT_TYPE_BOOL, NULL, "Shows this help message." },
|
||||
XOPT_NULLOPTION,
|
||||
};
|
||||
|
||||
args_t args = { 0 };
|
||||
const char** extras = NULL;
|
||||
int extra_count = 0;
|
||||
const char *err = NULL;
|
||||
XOPT_PARSE(file, XOPT_CTX_KEEPFIRST | XOPT_CTX_POSIXMEHARDER, options, &args, argc, (const char**)argv, &extra_count, &extras, &err, stderr, "export [options] [paths] ...", "options:", NULL, 15);
|
||||
|
||||
if (err) {
|
||||
fprintf(stderr, "Error: %s\n", err);
|
||||
if (extras) {
|
||||
free((void*)extras);
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, NULL, NULL);
|
||||
if (extra_count) {
|
||||
for (int i = 0; i < extra_count; i++) {
|
||||
printf("Exporting %s...\n", extras[i]);
|
||||
tf_ssb_export(ssb, extras[i]);
|
||||
}
|
||||
} else {
|
||||
const char* k_export[] = {
|
||||
"/~cory/index",
|
||||
"/~cory/docs",
|
||||
};
|
||||
for (int i = 0; i < _countof(k_export); i++) {
|
||||
printf("Exporting %s...\n", k_export[i]);
|
||||
tf_ssb_export(ssb, k_export[i]);
|
||||
}
|
||||
}
|
||||
tf_ssb_destroy(ssb);
|
||||
|
||||
if (extras) {
|
||||
free((void*)extras);
|
||||
}
|
||||
return 0;
|
||||
|
||||
xopt_help:
|
||||
if (extras) {
|
||||
free((void*)extras);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int _tf_command_run(const char* file, int argc, char* argv[])
|
||||
{
|
||||
typedef struct args_t {
|
||||
const char* script;
|
||||
int ssb_port;
|
||||
int http_port;
|
||||
int https_port;
|
||||
const char* db_path;
|
||||
const char* secrets_path;
|
||||
bool help;
|
||||
} args_t;
|
||||
|
||||
xoptOption options[] = {
|
||||
{ "script", 's', offsetof(args_t, script), NULL, XOPT_TYPE_STRING, NULL, "Script to run (default: core/core.js)." },
|
||||
{ "ssb-port", 'b', offsetof(args_t, ssb_port), NULL, XOPT_TYPE_INT, NULL, "Port on which to run SSB (default: 8009)." },
|
||||
{ "http-port", 'p', offsetof(args_t, http_port), NULL, XOPT_TYPE_INT, NULL, "Port on which to run Tilde Friends web server (default: 12345)." },
|
||||
{ "https-port", 'q', offsetof(args_t, https_port), NULL, XOPT_TYPE_INT, NULL, "Port on which to run secure Tilde Friends web server (default: 12346)." },
|
||||
{ "db-path", 'd', offsetof(args_t, db_path), NULL, XOPT_TYPE_STRING, NULL, "Sqlite database path (default: db.sqlite)." },
|
||||
{ "secrets-path", 'i', offsetof(args_t, secrets_path), NULL, XOPT_TYPE_STRING, NULL, "Secrets/identity path." },
|
||||
{ "help", 'h', offsetof(args_t, help), NULL, XOPT_TYPE_BOOL, NULL, "Shows this help message." },
|
||||
XOPT_NULLOPTION,
|
||||
};
|
||||
|
||||
args_t args = {
|
||||
.script = "core/core.js",
|
||||
.http_port = 12345,
|
||||
.ssb_port = 8009,
|
||||
.db_path = "db.sqlite",
|
||||
};
|
||||
const char** extras = NULL;
|
||||
int extra_count = 0;
|
||||
const char *err = NULL;
|
||||
XOPT_PARSE(file, XOPT_CTX_KEEPFIRST | XOPT_CTX_POSIXMEHARDER, options, &args, argc, (const char**)argv, &extra_count, &extras, &err, stderr, "run [options] [paths] ...", "options:", NULL, 15);
|
||||
|
||||
if (err) {
|
||||
fprintf(stderr, "Error: %s\n", err);
|
||||
if (extras) {
|
||||
free((void*)extras);
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
if (extras) {
|
||||
free((void*)extras);
|
||||
}
|
||||
|
||||
int result = 0;
|
||||
#if !defined (_WIN32) && !defined (__MACH__)
|
||||
setpgid(0, 0);
|
||||
#endif
|
||||
tf_task_t* task = tf_task_create();
|
||||
tf_task_set_trusted(task, true);
|
||||
tf_task_set_ssb_port(task, args.ssb_port);
|
||||
tf_task_set_http_port(task, args.http_port);
|
||||
tf_task_set_https_port(task, args.https_port);
|
||||
tf_task_set_db_path(task, args.db_path);
|
||||
tf_task_set_secrets_path(task, args.secrets_path);
|
||||
tf_task_activate(task);
|
||||
if (!tf_task_execute(task, args.script))
|
||||
{
|
||||
result = -1;
|
||||
}
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
tf_task_run(task);
|
||||
}
|
||||
tf_task_destroy(task);
|
||||
return result;
|
||||
|
||||
xopt_help:
|
||||
if (extras) {
|
||||
free((void*)extras);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int _tf_command_sandbox(const char* file, int argc, char* argv[])
|
||||
{
|
||||
typedef struct args_t {
|
||||
const char* script;
|
||||
bool help;
|
||||
} args_t;
|
||||
|
||||
xoptOption options[] = {
|
||||
{ "help", 'h', offsetof(args_t, help), NULL, XOPT_TYPE_BOOL, NULL, "Shows this help message." },
|
||||
XOPT_NULLOPTION,
|
||||
};
|
||||
|
||||
args_t args = { 0 };
|
||||
const char** extras = NULL;
|
||||
int extra_count = 0;
|
||||
const char *err = NULL;
|
||||
XOPT_PARSE(file, XOPT_CTX_KEEPFIRST | XOPT_CTX_POSIXMEHARDER, options, &args, argc, (const char**)argv, &extra_count, &extras, &err, stderr, "sandbox [options]", "options:", NULL, 15);
|
||||
|
||||
if (err) {
|
||||
fprintf(stderr, "Error: %s\n", err);
|
||||
if (extras) {
|
||||
free((void*)extras);
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
if (extras) {
|
||||
free((void*)extras);
|
||||
}
|
||||
|
||||
#if !defined(_WIN32) && !defined(__MACH__)
|
||||
prctl(PR_SET_PDEATHSIG, SIGHUP);
|
||||
#endif
|
||||
tf_task_t* task = tf_task_create();
|
||||
tf_task_configure_from_stdin(task);
|
||||
shedPrivileges();
|
||||
tf_task_activate(task);
|
||||
tf_task_run(task);
|
||||
tf_task_destroy(task);
|
||||
return 0;
|
||||
|
||||
xopt_help:
|
||||
if (extras) {
|
||||
free((void*)extras);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int _tf_command_post(const char* file, int argc, char* argv[])
|
||||
{
|
||||
typedef struct args_t {
|
||||
char* message;
|
||||
bool help;
|
||||
} args_t;
|
||||
|
||||
xoptOption options[] = {
|
||||
{ "message", 'm', offsetof(args_t, message), NULL, XOPT_REQUIRED | XOPT_TYPE_STRING, "TEXT", "Text to post." },
|
||||
{ "help", 'h', offsetof(args_t, help), NULL, XOPT_TYPE_BOOL, NULL, "Shows this help message." },
|
||||
XOPT_NULLOPTION,
|
||||
};
|
||||
|
||||
args_t args = { 0 };
|
||||
const char** extras = NULL;
|
||||
int extra_count = 0;
|
||||
const char *err = NULL;
|
||||
XOPT_PARSE(file, XOPT_CTX_KEEPFIRST, options, &args, argc, (const char**)argv, &extra_count, &extras, &err, stderr, "post [options]", "options:", NULL, 15);
|
||||
if (extras) {
|
||||
free((void*)extras);
|
||||
}
|
||||
if (err) {
|
||||
fprintf(stderr, "Error: %s\n", err);
|
||||
return 2;
|
||||
}
|
||||
|
||||
tf_ssb_t* ssb = tf_ssb_create(NULL, NULL, NULL, NULL);
|
||||
tf_ssb_broadcast_listener_start(ssb, false);
|
||||
tf_ssb_append_post(ssb, args.message);
|
||||
tf_ssb_destroy(ssb);
|
||||
return 0;
|
||||
|
||||
xopt_help:
|
||||
if (extras) {
|
||||
free((void*)extras);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int _tf_command_usage(const char* file, int argc, char* argv[])
|
||||
{
|
||||
printf("Usage: %s command [command-options]\n", file);
|
||||
printf("commands:\n");
|
||||
for (int i = 0; i < _countof(k_commands); i++) {
|
||||
printf(" %s - %s\n", k_commands[i].name, k_commands[i].description);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
uv_setup_args(argc, argv);
|
||||
tf_taskstub_startup();
|
||||
|
||||
#if !defined (_WIN32)
|
||||
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
|
||||
perror("signal");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (argc >= 2) {
|
||||
for (int i = 0; i < _countof(k_commands); i++) {
|
||||
const command_t* command = &k_commands[i];
|
||||
if (strcmp(argv[1], command->name) == 0) {
|
||||
return command->callback(argv[0], argc - 2, argv + 2);
|
||||
}
|
||||
}
|
||||
return _tf_command_usage(argv[0], argc, argv);
|
||||
}
|
||||
|
||||
return _tf_command_run(argv[0], argc - 1, argv + 1);
|
||||
}
|
131
src/main.cpp
131
src/main.cpp
@ -1,131 +0,0 @@
|
||||
#include "Task.h"
|
||||
#include "TaskStub.h"
|
||||
#include "TaskTryCatch.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <libplatform/libplatform.h>
|
||||
#include <uv.h>
|
||||
#include <v8.h>
|
||||
#include <v8-platform.h>
|
||||
|
||||
#if !defined (_WIN32) && !defined (__MACH__)
|
||||
#include <signal.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/resource.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
v8::Platform* gPlatform = 0;
|
||||
|
||||
void shedPrivileges() {
|
||||
#if !defined (_WIN32)
|
||||
struct rlimit zeroLimit;
|
||||
zeroLimit.rlim_cur = 0;
|
||||
zeroLimit.rlim_max = 0;
|
||||
|
||||
// RLIMIT_AS
|
||||
// RLIMIT_CORE
|
||||
// RLIMIT_CPU
|
||||
// RLIMIT_DATA
|
||||
// RLIMIT_FSIZE
|
||||
// RLIMIT_RSS
|
||||
// RLIMIT_RTPRIO
|
||||
// RLIMIT_RTTIME
|
||||
// RLIMIT_SIGPENDING
|
||||
// RLIMIT_STACK
|
||||
|
||||
if (setrlimit(RLIMIT_FSIZE, &zeroLimit) != 0) {
|
||||
perror("setrlimit(RLIMIT_FSIZE, {0, 0})");
|
||||
exit(-1);
|
||||
}
|
||||
if (setrlimit(RLIMIT_NOFILE, &zeroLimit) != 0) {
|
||||
perror("setrlimit(RLIMIT_NOFILE, {0, 0})");
|
||||
exit(-1);
|
||||
}
|
||||
if (setrlimit(RLIMIT_NPROC, &zeroLimit) != 0) {
|
||||
perror("setrlimit(RLIMIT_NPROC, {0, 0})");
|
||||
exit(-1);
|
||||
}
|
||||
#if !defined (__MACH__)
|
||||
if (setrlimit(RLIMIT_LOCKS, &zeroLimit) != 0) {
|
||||
perror("setrlimit(RLIMIT_LOCKS, {0, 0})");
|
||||
exit(-1);
|
||||
}
|
||||
if (setrlimit(RLIMIT_MSGQUEUE, &zeroLimit) != 0) {
|
||||
perror("setrlimit(RLIMIT_MSGQUEUE, {0, 0})");
|
||||
exit(-1);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
int result = 0;
|
||||
uv_setup_args(argc, argv);
|
||||
TaskStub::initialize();
|
||||
v8::V8::InitializeICUDefaultLocation(argv[0]);
|
||||
gPlatform = v8::platform::CreateDefaultPlatform();
|
||||
v8::V8::InitializePlatform(gPlatform);
|
||||
v8::V8::Initialize();
|
||||
|
||||
v8::V8::SetFlagsFromCommandLine(&argc, argv, true);
|
||||
|
||||
bool isChild = false;
|
||||
const char* coreTask = "core/core.js";
|
||||
|
||||
for (int i = 1; i < argc; ++i) {
|
||||
if (!std::strcmp(argv[i], "--child")) {
|
||||
isChild = true;
|
||||
} else {
|
||||
coreTask = argv[i];
|
||||
}
|
||||
}
|
||||
|
||||
#if !defined (_WIN32)
|
||||
if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
|
||||
perror("signal");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (isChild) {
|
||||
#if !defined (_WIN32) && !defined (__MACH__)
|
||||
prctl(PR_SET_PDEATHSIG, SIGHUP);
|
||||
#endif
|
||||
Task task;
|
||||
task.configureFromStdin();
|
||||
shedPrivileges();
|
||||
task.activate();
|
||||
task.run();
|
||||
} else {
|
||||
#if !defined (_WIN32) && !defined (__MACH__)
|
||||
setpgid(0, 0);
|
||||
#endif
|
||||
Task task;
|
||||
task.setTrusted(true);
|
||||
task.activate();
|
||||
|
||||
{
|
||||
v8::Isolate::Scope isolateScope(task.getIsolate());
|
||||
v8::HandleScope handleScope(task.getIsolate());
|
||||
v8::Context::Scope contextScope(task.getContext());
|
||||
TaskTryCatch tryCatch(&task);
|
||||
if (!task.execute(coreTask))
|
||||
{
|
||||
result = -1;
|
||||
}
|
||||
if (tryCatch.hasCaught())
|
||||
{
|
||||
result = -2;
|
||||
}
|
||||
}
|
||||
|
||||
if (result == 0)
|
||||
{
|
||||
task.run();
|
||||
}
|
||||
}
|
||||
|
||||
v8::V8::Dispose();
|
||||
|
||||
return result;
|
||||
}
|
126
src/packetstream.c
Normal file
126
src/packetstream.c
Normal file
@ -0,0 +1,126 @@
|
||||
#include "packetstream.h"
|
||||
|
||||
#include <uv.h>
|
||||
|
||||
#include <malloc.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct _tf_packetstream_t {
|
||||
tf_packetstream_onreceive_t* onreceive;
|
||||
void* onreceive_user_data;
|
||||
uv_pipe_t stream;
|
||||
char* buffer;
|
||||
size_t buffer_size;
|
||||
bool destroyed;
|
||||
} tf_packetstream_t;
|
||||
|
||||
tf_packetstream_t* tf_packetstream_create() {
|
||||
tf_packetstream_t* impl = malloc(sizeof(tf_packetstream_t));
|
||||
*impl = (tf_packetstream_t) { 0 };
|
||||
return impl;
|
||||
}
|
||||
|
||||
void tf_packetstream_destroy(tf_packetstream_t* stream) {
|
||||
stream->onreceive = NULL;
|
||||
stream->onreceive_user_data = NULL;
|
||||
stream->destroyed = true;
|
||||
if (stream->stream.data) {
|
||||
tf_packetstream_close(stream);
|
||||
} else {
|
||||
free(stream);
|
||||
}
|
||||
}
|
||||
|
||||
static void _packetstream_allocate(uv_handle_t* handle, size_t suggested_size, uv_buf_t* buffer) {
|
||||
buffer->base = malloc(suggested_size);
|
||||
buffer->len = suggested_size;
|
||||
}
|
||||
|
||||
static void _packetstream_process_messages(tf_packetstream_t* stream) {
|
||||
int packet_type = 0;
|
||||
size_t length = 0;
|
||||
while (stream->buffer_size >= sizeof(packet_type) + sizeof(length)) {
|
||||
memcpy(&packet_type, stream->buffer, sizeof(packet_type));
|
||||
memcpy(&length, stream->buffer + sizeof(packet_type), sizeof(length));
|
||||
|
||||
if (stream->buffer_size >= sizeof(packet_type) + sizeof(length) + length) {
|
||||
if (stream->onreceive) {
|
||||
stream->onreceive(packet_type, stream->buffer + sizeof(length) + sizeof(packet_type), length, stream->onreceive_user_data);
|
||||
}
|
||||
size_t consumed = sizeof(length) + sizeof(packet_type) + length;
|
||||
memmove(stream->buffer, stream->buffer + consumed, stream->buffer_size - consumed);
|
||||
stream->buffer_size -= consumed;
|
||||
stream->buffer = realloc(stream->buffer, stream->buffer_size);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _packetstream_on_read(uv_stream_t* handle, ssize_t count, const uv_buf_t* buffer) {
|
||||
tf_packetstream_t* stream = handle->data;
|
||||
if (count >= 0) {
|
||||
if (count > 0) {
|
||||
char* new_buffer = realloc(stream->buffer, stream->buffer_size + count);
|
||||
if (new_buffer) {
|
||||
memcpy(new_buffer + stream->buffer_size, buffer->base, count);
|
||||
stream->buffer = new_buffer;
|
||||
stream->buffer_size += count;
|
||||
}
|
||||
_packetstream_process_messages(stream);
|
||||
}
|
||||
} else {
|
||||
tf_packetstream_close(stream);
|
||||
}
|
||||
free(buffer->base);
|
||||
}
|
||||
|
||||
void tf_packetstream_start(tf_packetstream_t* stream) {
|
||||
stream->stream.data = stream;
|
||||
uv_read_start((uv_stream_t*)&stream->stream, _packetstream_allocate, _packetstream_on_read);
|
||||
}
|
||||
|
||||
static void _packetstream_on_write(uv_write_t* request, int status) {
|
||||
free(request);
|
||||
}
|
||||
|
||||
void tf_packetstream_send(tf_packetstream_t* stream, int packet_type, char* begin, size_t length) {
|
||||
size_t buffer_length = sizeof(uv_write_t) + sizeof(packet_type) + sizeof(length) + length;
|
||||
uv_write_t* request = malloc(buffer_length);
|
||||
memset(request, 0, sizeof(uv_write_t));
|
||||
char* buffer = (char*)(request + 1);
|
||||
memcpy(buffer, &packet_type, sizeof(packet_type));
|
||||
memcpy(buffer + sizeof(packet_type), &length, sizeof(length));
|
||||
if (length) {
|
||||
memcpy(buffer + sizeof(packet_type) + sizeof(length), begin, length);
|
||||
}
|
||||
uv_buf_t write_buffer;
|
||||
write_buffer.base = buffer;
|
||||
write_buffer.len = sizeof(packet_type) + sizeof(length) + length;
|
||||
uv_write(request, (uv_stream_t*)&stream->stream, &write_buffer, 1, _packetstream_on_write);
|
||||
}
|
||||
|
||||
void tf_packetstream_set_on_receive(tf_packetstream_t* stream, tf_packetstream_onreceive_t* callback, void* user_data) {
|
||||
stream->onreceive = callback;
|
||||
stream->onreceive_user_data = user_data;
|
||||
}
|
||||
|
||||
static void _tf_packetstream_handle_closed(uv_handle_t* handle)
|
||||
{
|
||||
tf_packetstream_t* packetstream = handle->data;
|
||||
handle->data = NULL;
|
||||
if (packetstream->destroyed) {
|
||||
free(packetstream);
|
||||
}
|
||||
}
|
||||
|
||||
void tf_packetstream_close(tf_packetstream_t* stream) {
|
||||
if (stream->stream.data && !uv_is_closing((uv_handle_t*)&stream->stream)) {
|
||||
uv_close((uv_handle_t*)&stream->stream, _tf_packetstream_handle_closed);
|
||||
}
|
||||
}
|
||||
|
||||
uv_pipe_t* tf_packetstream_get_pipe(tf_packetstream_t* stream) {
|
||||
return &stream->stream;
|
||||
}
|
18
src/packetstream.h
Normal file
18
src/packetstream.h
Normal file
@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct uv_pipe_s uv_pipe_t;
|
||||
|
||||
typedef struct _tf_packetstream_t tf_packetstream_t;
|
||||
|
||||
typedef void (tf_packetstream_onreceive_t)(int packet_type, const char* begin, size_t length, void* user_data);
|
||||
|
||||
tf_packetstream_t* tf_packetstream_create();
|
||||
void tf_packetstream_destroy(tf_packetstream_t* stream);
|
||||
|
||||
void tf_packetstream_start(tf_packetstream_t* stream);
|
||||
void tf_packetstream_send(tf_packetstream_t* stream, int packet_type, char* begin, size_t length);
|
||||
void tf_packetstream_set_on_receive(tf_packetstream_t* stream, tf_packetstream_onreceive_t* callback, void* user_data);
|
||||
void tf_packetstream_close(tf_packetstream_t* stream);
|
||||
uv_pipe_t* tf_packetstream_get_pipe(tf_packetstream_t* stream);
|
@ -1,39 +0,0 @@
|
||||
#include "quickjs.h"
|
||||
#include "quickjs-libc.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
JSRuntime* runtime = JS_NewRuntime();
|
||||
JSContext* context = JS_NewContext(runtime);
|
||||
js_init_module_std(context, "std");
|
||||
const char* import = "import * as std from 'std';\nglobalThis.std = std;\n";
|
||||
JS_Eval(context, import, strlen(import), "<input>", JS_EVAL_TYPE_MODULE);
|
||||
|
||||
js_std_add_helpers(context, 0, NULL);
|
||||
const char* js = "std.out.puts(\"hello\"); 5+4";
|
||||
JSValue result = JS_Eval(context, js, strlen(js), "test.js", 0);
|
||||
if (JS_IsError(context, result))
|
||||
{
|
||||
printf("got an error\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("not an error\n");
|
||||
}
|
||||
if (JS_IsException(result))
|
||||
{
|
||||
js_std_dump_error(context);
|
||||
}
|
||||
const char* c = JS_ToCString(context, JS_ToString(context, result));
|
||||
printf("c = %p\n", c);
|
||||
if (c)
|
||||
{
|
||||
printf("%s\n", c);
|
||||
}
|
||||
JS_FreeContext(context);
|
||||
JS_FreeRuntime(runtime);
|
||||
return 0;
|
||||
}
|
367
src/serialize.c
Normal file
367
src/serialize.c
Normal file
@ -0,0 +1,367 @@
|
||||
#include "serialize.h"
|
||||
#include "task.h"
|
||||
#include "taskstub.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "quickjs-libc.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
typedef enum _serialize_type_t {
|
||||
kUndefined,
|
||||
kNull,
|
||||
kUninitialized,
|
||||
kBoolean,
|
||||
kInt32,
|
||||
kInt64,
|
||||
kNumber,
|
||||
kString,
|
||||
kArray,
|
||||
kArrayBuffer,
|
||||
kObject,
|
||||
kFunction,
|
||||
kError,
|
||||
kException,
|
||||
} serialize_type_t;
|
||||
|
||||
typedef struct _buffer_t {
|
||||
char* data;
|
||||
size_t size;
|
||||
size_t capacity;
|
||||
} buffer_t;
|
||||
|
||||
static bool _serialize_store(tf_task_t* task, tf_taskstub_t* to, buffer_t* buffer, JSValue value);
|
||||
static JSValue _serialize_load(tf_task_t* task, tf_taskstub_t* from, const char* buffer, size_t size);
|
||||
|
||||
static bool _serialize_storeInternal(tf_task_t* task, tf_taskstub_t* to, buffer_t* buffer, JSValue value, int depth);
|
||||
static JSValue _serialize_loadInternal(tf_task_t* task, tf_taskstub_t* from, const char** buffer, size_t* size, int depth);
|
||||
|
||||
static void _serialize_writeInt8(buffer_t* buffer, int8_t value);
|
||||
static void _serialize_writeInt32(buffer_t* buffer, int32_t value);
|
||||
static void _serialize_writeInt64(buffer_t* buffer, int64_t value);
|
||||
static void _serialize_writeDouble(buffer_t* buffer, double value);
|
||||
|
||||
static int8_t _serialize_readInt8(const char** buffer, size_t* size);
|
||||
static int32_t _serialize_readInt32(const char** buffer, size_t* size);
|
||||
static int64_t _serialize_readInt64(const char** buffer, size_t* size);
|
||||
static double _serialize_readDouble(const char** buffer, size_t* size);
|
||||
|
||||
void tf_serialize_store(tf_task_t* task, tf_taskstub_t* to, void** out_buffer, size_t* out_size, JSValue value) {
|
||||
buffer_t tmp = { 0 };
|
||||
_serialize_store(task, to, &tmp, value);
|
||||
tmp.data = realloc(tmp.data, tmp.size);
|
||||
*out_buffer = tmp.data;
|
||||
*out_size = tmp.size;
|
||||
}
|
||||
|
||||
JSValue tf_serialize_load(tf_task_t* task, tf_taskstub_t* from, const char* buffer, size_t size) {
|
||||
return _serialize_load(task, from, buffer, size);
|
||||
}
|
||||
|
||||
static void _buffer_append(buffer_t* buffer, const void* data, size_t size) {
|
||||
if (buffer->capacity < buffer->size + size) {
|
||||
size_t new_capacity = (size + buffer->capacity) * 2;
|
||||
buffer->data = realloc(buffer->data, new_capacity);
|
||||
buffer->capacity = new_capacity;
|
||||
}
|
||||
memcpy((char*)buffer->data + buffer->size, data, size);
|
||||
buffer->size += size;
|
||||
}
|
||||
|
||||
void _serialize_writeInt8(buffer_t* buffer, int8_t value) {
|
||||
_buffer_append(buffer, &value, sizeof(value));
|
||||
}
|
||||
|
||||
void _serialize_writeInt32(buffer_t* buffer, int32_t value) {
|
||||
_buffer_append(buffer, &value, sizeof(value));
|
||||
}
|
||||
|
||||
void _serialize_writeInt64(buffer_t* buffer, int64_t value) {
|
||||
_buffer_append(buffer, &value, sizeof(value));
|
||||
}
|
||||
|
||||
void _serialize_writeDouble(buffer_t* buffer, double value) {
|
||||
_buffer_append(buffer, &value, sizeof(value));
|
||||
}
|
||||
|
||||
static void _serialize_read(const char** buffer, size_t* size, void* target, size_t target_size) {
|
||||
assert(*size >= target_size);
|
||||
memcpy(target, *buffer, target_size);
|
||||
*buffer += target_size;
|
||||
*size -= target_size;
|
||||
}
|
||||
|
||||
static int8_t _serialize_readInt8(const char** buffer, size_t* size) {
|
||||
int8_t result;
|
||||
_serialize_read(buffer, size, &result, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
int32_t _serialize_readInt32(const char** buffer, size_t* size) {
|
||||
int32_t result;
|
||||
_serialize_read(buffer, size, &result, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
int64_t _serialize_readInt64(const char** buffer, size_t* size) {
|
||||
int64_t result;
|
||||
_serialize_read(buffer, size, &result, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
double _serialize_readDouble(const char** buffer, size_t* size) {
|
||||
double result;
|
||||
_serialize_read(buffer, size, &result, sizeof(result));
|
||||
return result;
|
||||
}
|
||||
|
||||
static bool _serialize_store(tf_task_t* task, tf_taskstub_t* to, buffer_t* buffer, JSValue value) {
|
||||
return _serialize_storeInternal(task, to, buffer, value, 0);
|
||||
}
|
||||
|
||||
static bool _serialize_storeInternal(tf_task_t* task, tf_taskstub_t* to, buffer_t* buffer, JSValue value, int depth)
|
||||
{
|
||||
JSContext* context = tf_task_get_context(task);
|
||||
size_t size;
|
||||
size_t offset;
|
||||
size_t element_size;
|
||||
JSValue typed;
|
||||
uint8_t* bytes;
|
||||
if (JS_IsUndefined(value)) {
|
||||
_serialize_writeInt32(buffer, kUndefined);
|
||||
} else if (JS_IsUninitialized(value)) {
|
||||
_serialize_writeInt32(buffer, kUninitialized);
|
||||
} else if (JS_IsNull(value)) {
|
||||
_serialize_writeInt32(buffer, kNull);
|
||||
} else if (JS_IsBool(value)) {
|
||||
_serialize_writeInt32(buffer, kBoolean);
|
||||
_serialize_writeInt8(buffer, JS_ToBool(tf_task_get_context(task), value) ? 1 : 0);
|
||||
} else if (JS_IsNumber(value)) {
|
||||
int64_t result = 0;
|
||||
if (JS_ToInt64(tf_task_get_context(task), &result, value) == 0)
|
||||
{
|
||||
_serialize_writeInt32(buffer, kInt64);
|
||||
_serialize_writeInt64(buffer, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Unable to store integer.\n");
|
||||
}
|
||||
} else if (JS_IsNumber(value)) {
|
||||
double result = 0.0;
|
||||
if (JS_ToFloat64(tf_task_get_context(task), &result, value) == 0)
|
||||
{
|
||||
_serialize_writeInt32(buffer, kNumber);
|
||||
_serialize_writeDouble(buffer, result);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Unable to store number.\n");
|
||||
}
|
||||
} else if (JS_IsString(value)) {
|
||||
size_t len = 0;
|
||||
const char* result = JS_ToCStringLen(tf_task_get_context(task), &len, value);
|
||||
_serialize_writeInt32(buffer, kString);
|
||||
_serialize_writeInt32(buffer, (int32_t)len);
|
||||
_buffer_append(buffer, result, len);
|
||||
JS_FreeCString(tf_task_get_context(task), result);
|
||||
} else if ((bytes = tf_try_get_array_buffer(tf_task_get_context(task), &size, value)) != 0) {
|
||||
_serialize_writeInt32(buffer, kArrayBuffer);
|
||||
_serialize_writeInt32(buffer, (int32_t)size);
|
||||
_buffer_append(buffer, bytes, size);
|
||||
} else if (!JS_IsException((typed = tf_try_get_typed_array_buffer(tf_task_get_context(task), value, &offset, &size, &element_size)))) {
|
||||
size_t total_size;
|
||||
uint8_t* bytes = tf_try_get_array_buffer(tf_task_get_context(task), &total_size, typed);
|
||||
_serialize_writeInt32(buffer, kArrayBuffer);
|
||||
_serialize_writeInt32(buffer, (int32_t)size);
|
||||
_buffer_append(buffer, bytes, size);
|
||||
} else if (JS_IsArray(tf_task_get_context(task), value)) {
|
||||
_serialize_writeInt32(buffer, kArray);
|
||||
JSValue length_val = JS_GetPropertyStr(tf_task_get_context(task), value, "length");
|
||||
int length;
|
||||
if (JS_ToInt32(tf_task_get_context(task), &length, length_val) == 0) {
|
||||
_serialize_writeInt32(buffer, length);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
_serialize_storeInternal(task, to, buffer, JS_GetPropertyUint32(tf_task_get_context(task), value, i), depth + 1);
|
||||
}
|
||||
} else {
|
||||
_serialize_writeInt32(buffer, 0);
|
||||
}
|
||||
} else if (JS_IsFunction(tf_task_get_context(task), value)) {
|
||||
_serialize_writeInt32(buffer, kFunction);
|
||||
exportid_t exportId = tf_task_export_function(task, to, value);
|
||||
_serialize_writeInt32(buffer, exportId);
|
||||
} else if (JS_IsException(value)) {
|
||||
JSValue exception = JS_GetException(context);
|
||||
JSValue error = JS_NewObject(context);
|
||||
JSValue message = JS_GetPropertyStr(context, exception, "message");
|
||||
if (!JS_IsException(message)) {
|
||||
JS_SetPropertyStr(context, error, "message", message);
|
||||
} else {
|
||||
JS_FreeValue(context, message);
|
||||
}
|
||||
if (JS_IsError(context, exception)) {
|
||||
JSValue stack = JS_GetPropertyStr(context, exception, "stack");
|
||||
if (!JS_IsUndefined(stack)) {
|
||||
JS_SetPropertyStr(context, error, "stack", JS_DupValue(context, stack));
|
||||
}
|
||||
}
|
||||
_serialize_writeInt32(buffer, kException);
|
||||
_serialize_storeInternal(task, to, buffer, error, depth + 1);
|
||||
JS_FreeValue(context, error);
|
||||
} else if (JS_IsError(tf_task_get_context(task), value)) {
|
||||
_serialize_writeInt32(buffer, kError);
|
||||
JSPropertyEnum* ptab;
|
||||
uint32_t plen;
|
||||
JS_GetOwnPropertyNames(tf_task_get_context(task), &ptab, &plen, value, JS_GPN_STRING_MASK);
|
||||
_serialize_writeInt32(buffer, plen);
|
||||
for (uint32_t i = 0; i < plen; ++i) {
|
||||
JSValue key = JS_AtomToString(tf_task_get_context(task), ptab[i].atom);
|
||||
JSPropertyDescriptor desc;
|
||||
JSValue key_value = JS_NULL;
|
||||
if (JS_GetOwnProperty(tf_task_get_context(task), &desc, value, ptab[i].atom) == 1) {
|
||||
key_value = desc.value;
|
||||
}
|
||||
_serialize_storeInternal(task, to, buffer, key, depth + 1);
|
||||
_serialize_storeInternal(task, to, buffer, key_value, depth + 1);
|
||||
JS_FreeValue(tf_task_get_context(task), key);
|
||||
JS_FreeValue(tf_task_get_context(task), key_value);
|
||||
}
|
||||
for (uint32_t i = 0; i < plen; ++i) {
|
||||
JS_FreeAtom(tf_task_get_context(task), ptab[i].atom);
|
||||
}
|
||||
js_free(tf_task_get_context(task), ptab);
|
||||
} else if (JS_IsObject(value)) {
|
||||
_serialize_writeInt32(buffer, kObject);
|
||||
JSPropertyEnum* ptab;
|
||||
uint32_t plen;
|
||||
JS_GetOwnPropertyNames(tf_task_get_context(task), &ptab, &plen, value, JS_GPN_STRING_MASK);
|
||||
_serialize_writeInt32(buffer, plen);
|
||||
for (uint32_t i = 0; i < plen; ++i) {
|
||||
JSValue key = JS_AtomToString(tf_task_get_context(task), ptab[i].atom);
|
||||
JSPropertyDescriptor desc;
|
||||
JSValue key_value = JS_NULL;
|
||||
if (JS_GetOwnProperty(tf_task_get_context(task), &desc, value, ptab[i].atom) == 1) {
|
||||
key_value = desc.value;
|
||||
}
|
||||
_serialize_storeInternal(task, to, buffer, key, depth + 1);
|
||||
_serialize_storeInternal(task, to, buffer, key_value, depth + 1);
|
||||
JS_FreeValue(tf_task_get_context(task), key);
|
||||
JS_FreeValue(tf_task_get_context(task), key_value);
|
||||
}
|
||||
for (uint32_t i = 0; i < plen; ++i) {
|
||||
JS_FreeAtom(tf_task_get_context(task), ptab[i].atom);
|
||||
}
|
||||
js_free(tf_task_get_context(task), ptab);
|
||||
} else {
|
||||
fprintf(stderr, "Unknown JSValue type: %d.\n", JS_VALUE_GET_TAG(value));
|
||||
abort();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static JSValue _serialize_load(tf_task_t* task, tf_taskstub_t* from, const char* buffer, size_t size) {
|
||||
return _serialize_loadInternal(task, from, &buffer, &size, 0);
|
||||
}
|
||||
|
||||
static JSValue _serialize_loadInternal(tf_task_t* task, tf_taskstub_t* from, const char** buffer, size_t* size, int depth) {
|
||||
if (*size < sizeof(size)) {
|
||||
return JS_UNDEFINED;
|
||||
} else {
|
||||
int32_t type = _serialize_readInt32(buffer, size);
|
||||
JSValue result = JS_UNDEFINED;
|
||||
|
||||
switch (type) {
|
||||
case kUndefined:
|
||||
result = JS_UNDEFINED;
|
||||
break;
|
||||
case kNull:
|
||||
result = JS_NULL;
|
||||
break;
|
||||
case kUninitialized:
|
||||
result = JS_UNINITIALIZED;
|
||||
break;
|
||||
case kBoolean:
|
||||
result = JS_NewBool(tf_task_get_context(task), _serialize_readInt8(buffer, size) != 0);
|
||||
break;
|
||||
case kInt32:
|
||||
result = JS_NewInt32(tf_task_get_context(task), _serialize_readInt32(buffer, size));
|
||||
break;
|
||||
case kInt64:
|
||||
result = JS_NewInt64(tf_task_get_context(task), _serialize_readInt64(buffer, size));
|
||||
break;
|
||||
case kNumber:
|
||||
result = JS_NewFloat64(tf_task_get_context(task), _serialize_readDouble(buffer, size));
|
||||
break;
|
||||
case kString:
|
||||
{
|
||||
int32_t length = _serialize_readInt32(buffer, size);
|
||||
result = JS_NewStringLen(tf_task_get_context(task), *buffer, length);
|
||||
*buffer += length;
|
||||
*size -= length;
|
||||
}
|
||||
break;
|
||||
case kArrayBuffer:
|
||||
{
|
||||
int32_t length = _serialize_readInt32(buffer, size);
|
||||
result = JS_NewArrayBufferCopy(tf_task_get_context(task), (const uint8_t*)*buffer, length);
|
||||
*buffer += length;
|
||||
*size -= length;
|
||||
}
|
||||
break;
|
||||
case kArray:
|
||||
{
|
||||
int32_t length = _serialize_readInt32(buffer, size);
|
||||
result = JS_NewArray(tf_task_get_context(task));
|
||||
for (int i = 0; i < length; ++i) {
|
||||
JS_SetPropertyUint32(tf_task_get_context(task), result, i, _serialize_loadInternal(task, from, buffer, size, depth + 1));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case kFunction:
|
||||
{
|
||||
exportid_t exportId = _serialize_readInt32(buffer, size);
|
||||
result = tf_task_add_import(task, tf_taskstub_get_id(from), exportId);
|
||||
}
|
||||
break;
|
||||
case kException:
|
||||
{
|
||||
_serialize_readInt32(buffer, size);
|
||||
JSValue error = JS_NewError(tf_task_get_context(task));
|
||||
int32_t length = _serialize_readInt32(buffer, size);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
JSValue key = _serialize_loadInternal(task, from, buffer, size, depth + 1);
|
||||
JSValue value = _serialize_loadInternal(task, from, buffer, size, depth + 1);
|
||||
const char* key_str = JS_ToCString(tf_task_get_context(task), key);
|
||||
JS_SetPropertyStr(tf_task_get_context(task), error, key_str, value);
|
||||
JS_FreeCString(tf_task_get_context(task), key_str);
|
||||
JS_FreeValue(tf_task_get_context(task), key);
|
||||
}
|
||||
result = error;
|
||||
}
|
||||
break;
|
||||
case kError:
|
||||
case kObject:
|
||||
{
|
||||
int32_t length = _serialize_readInt32(buffer, size);
|
||||
result = JS_NewObject(tf_task_get_context(task));
|
||||
for (int i = 0; i < length; ++i) {
|
||||
JSValue key = _serialize_loadInternal(task, from, buffer, size, depth + 1);
|
||||
JSValue value = _serialize_loadInternal(task, from, buffer, size, depth + 1);
|
||||
const char* key_str = JS_ToCString(tf_task_get_context(task), key);
|
||||
JS_SetPropertyStr(tf_task_get_context(task), result, key_str, value);
|
||||
JS_FreeCString(tf_task_get_context(task), key_str);
|
||||
JS_FreeValue(tf_task_get_context(task), key);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
abort();
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
9
src/serialize.h
Normal file
9
src/serialize.h
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "quickjs.h"
|
||||
|
||||
typedef struct _tf_task_t tf_task_t;
|
||||
typedef struct _tf_taskstub_t tf_taskstub_t;
|
||||
|
||||
void tf_serialize_store(tf_task_t* task, tf_taskstub_t* to, void** out_buffer, size_t* out_size, JSValue value);
|
||||
JSValue tf_serialize_load(tf_task_t* task, tf_taskstub_t* from, const char* buffer, size_t size);
|
807
src/socket.c
Normal file
807
src/socket.c
Normal file
@ -0,0 +1,807 @@
|
||||
#include "socket.h"
|
||||
#include "task.h"
|
||||
#include "tls.h"
|
||||
#include "tlscontextwrapper.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <uv.h>
|
||||
|
||||
#include "quickjs-libc.h"
|
||||
|
||||
typedef int promiseid_t;
|
||||
|
||||
static JSClassID _classId;
|
||||
static int _count;
|
||||
static int _open_count;
|
||||
static tf_tls_context_t* _defaultTlsContext;
|
||||
|
||||
typedef enum _socket_direction_t {
|
||||
kUndetermined,
|
||||
kAccept,
|
||||
kConnect,
|
||||
} socket_direction_t;
|
||||
|
||||
typedef struct _socket_t {
|
||||
tf_task_t* _task;
|
||||
uv_tcp_t _socket;
|
||||
tf_tls_session_t* _tls;
|
||||
promiseid_t _startTlsPromise;
|
||||
promiseid_t _closePromise;
|
||||
bool _connected;
|
||||
bool _noDelay;
|
||||
char _peerName[256];
|
||||
socket_direction_t _direction;
|
||||
JSValue _object;
|
||||
JSValue _onConnect;
|
||||
JSValue _onRead;
|
||||
JSValue _onError;
|
||||
} socket_t;
|
||||
|
||||
static JSValue _socket_create(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static void _socket_finalizer(JSRuntime *runtime, JSValue value);
|
||||
|
||||
static JSValue _socket_startTls(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _socket_stopTls(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _socket_bind(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _socket_connect(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _socket_listen(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _socket_accept(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _socket_close(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _socket_shutdown(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _socket_read(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _socket_onError(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _socket_write(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _socket_isConnected(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _socket_getPeerName(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _socket_getPeerCertificate(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _socket_getNoDelay(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _socket_setNoDelay(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
|
||||
static void _socket_onClose(uv_handle_t* handle);
|
||||
static void _socket_onShutdown(uv_shutdown_t* request, int status);
|
||||
static void _socket_onResolvedForBind(uv_getaddrinfo_t* resolver, int status, struct addrinfo* result);
|
||||
static void _socket_onResolvedForConnect(uv_getaddrinfo_t* resolver, int status, struct addrinfo* result);
|
||||
static void _socket_onConnect(uv_connect_t* request, int status);
|
||||
static void _socket_onNewConnection(uv_stream_t* server, int status);
|
||||
static void _socket_allocateBuffer(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buffer);
|
||||
static void _socket_onRead(uv_stream_t* stream, ssize_t readSize, const uv_buf_t* buffer);
|
||||
static void _socket_onWrite(uv_write_t* request, int status);
|
||||
static void _socket_onTlsShutdown(uv_write_t* request, int status);
|
||||
|
||||
static void _socket_notifyDataRead(socket_t* socket, const char* data, size_t length);
|
||||
static int _socket_writeBytes(socket_t* socket, promiseid_t promise, int (*callback)(socket_t* socket, promiseid_t promise, const char*, size_t), JSValue value, int* outLength);
|
||||
static int _socket_writeInternal(socket_t* socket, promiseid_t promise, const char* data, size_t length);
|
||||
static void _socket_processTlsShutdown(socket_t* socket, promiseid_t promise);
|
||||
static void _socket_shutdownInternal(socket_t* socket, promiseid_t promise);
|
||||
static bool _socket_processSomeOutgoingTls(socket_t* socket, promiseid_t promise, uv_write_cb callback);
|
||||
static void _socket_processOutgoingTls(socket_t* socket);
|
||||
static void _socket_reportTlsErrors(socket_t* socket);
|
||||
static void _socket_reportError(socket_t* socket, const char* error);
|
||||
|
||||
/*
|
||||
class Socket {
|
||||
private:
|
||||
|
||||
};
|
||||
*/
|
||||
|
||||
JSValue tf_socket_init(JSContext* context) {
|
||||
JS_NewClassID(&_classId);
|
||||
JSClassDef def = {
|
||||
.class_name = "Socket",
|
||||
.finalizer = &_socket_finalizer,
|
||||
};
|
||||
if (JS_NewClass(JS_GetRuntime(context), _classId, &def) != 0) {
|
||||
fprintf(stderr, "Failed to register Socket.\n");
|
||||
}
|
||||
return JS_NewCFunction2(context, _socket_create, "Socket", 0, JS_CFUNC_constructor, 0);
|
||||
}
|
||||
|
||||
int tf_socket_get_count() {
|
||||
return _count;
|
||||
}
|
||||
|
||||
int tf_socket_get_open_count() {
|
||||
return _open_count;
|
||||
}
|
||||
|
||||
typedef struct _socket_resolve_data_t {
|
||||
uv_getaddrinfo_t resolver;
|
||||
socket_t* socket;
|
||||
promiseid_t promise;
|
||||
} socket_resolve_data_t;
|
||||
|
||||
socket_t* _socket_create_internal(JSContext* context) {
|
||||
socket_t* socket = malloc(sizeof(socket_t));
|
||||
memset(socket, 0, sizeof(*socket));
|
||||
|
||||
socket->_onRead = JS_UNDEFINED;
|
||||
socket->_onError = JS_UNDEFINED;
|
||||
socket->_onConnect = JS_UNDEFINED;
|
||||
socket->_closePromise = -1;
|
||||
socket->_startTlsPromise = -1;
|
||||
|
||||
++_count;
|
||||
JSValue object = JS_NewObjectClass(context, _classId);
|
||||
socket->_task = tf_task_get(context);
|
||||
socket->_object = object;
|
||||
JS_SetOpaque(object, socket);
|
||||
|
||||
JS_SetPropertyStr(context, object, "bind", JS_NewCFunction(context, _socket_bind, "bind", 2));
|
||||
JS_SetPropertyStr(context, object, "connect", JS_NewCFunction(context, _socket_connect, "connect", 2));
|
||||
JS_SetPropertyStr(context, object, "listen", JS_NewCFunction(context, _socket_listen, "listen", 2));
|
||||
JS_SetPropertyStr(context, object, "accept", JS_NewCFunction(context, _socket_accept, "accept", 0));
|
||||
JS_SetPropertyStr(context, object, "startTls", JS_NewCFunction(context, _socket_startTls, "startTls", 0));
|
||||
JS_SetPropertyStr(context, object, "stopTls", JS_NewCFunction(context, _socket_stopTls, "stopTls", 0));
|
||||
JS_SetPropertyStr(context, object, "shutdown", JS_NewCFunction(context, _socket_shutdown, "shutdown", 0));
|
||||
JS_SetPropertyStr(context, object, "close", JS_NewCFunction(context, _socket_close, "close", 0));
|
||||
JS_SetPropertyStr(context, object, "read", JS_NewCFunction(context, _socket_read, "read", 0));
|
||||
JS_SetPropertyStr(context, object, "onError", JS_NewCFunction(context, _socket_onError, "onError", 1));
|
||||
JS_SetPropertyStr(context, object, "write", JS_NewCFunction(context, _socket_write, "write", 1));
|
||||
|
||||
JSAtom atom = JS_NewAtom(context, "isConnected");
|
||||
JS_DefinePropertyGetSet(context, object, atom, JS_NewCFunction(context, _socket_isConnected, "isConnected", 0), JS_NULL, 0);
|
||||
JS_FreeAtom(context, atom);
|
||||
|
||||
atom = JS_NewAtom(context, "peerName");
|
||||
JS_DefinePropertyGetSet(context, object, atom, JS_NewCFunction(context, _socket_getPeerName, "peerName", 0), JS_NULL, 0);
|
||||
JS_FreeAtom(context, atom);
|
||||
|
||||
atom = JS_NewAtom(context, "peerCertificate");
|
||||
JS_DefinePropertyGetSet(context, object, atom, JS_NewCFunction(context, _socket_getPeerCertificate, "peerCertificate", 0), JS_NULL, 0);
|
||||
JS_FreeAtom(context, atom);
|
||||
|
||||
atom = JS_NewAtom(context, "noDelay");
|
||||
JSValue get_no_delay = JS_NewCFunction(context, _socket_getNoDelay, "getNoDelay", 0);
|
||||
JSValue set_no_delay = JS_NewCFunction(context, _socket_setNoDelay, "setNoDelay", 1);
|
||||
JS_DefinePropertyGetSet(context, object, atom, get_no_delay, set_no_delay, 0);
|
||||
JS_FreeAtom(context, atom);
|
||||
|
||||
++_open_count;
|
||||
uv_tcp_init(tf_task_get_loop(socket->_task), &socket->_socket);
|
||||
socket->_socket.data = socket;
|
||||
|
||||
return socket;
|
||||
}
|
||||
|
||||
JSValue _socket_create(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
return _socket_create_internal(context)->_object;
|
||||
}
|
||||
|
||||
void _socket_finalizer(JSRuntime *runtime, JSValue value) {
|
||||
socket_t* socket = JS_GetOpaque(value, _classId);
|
||||
--_count;
|
||||
free(socket);
|
||||
}
|
||||
|
||||
void _socket_close_internal(socket_t* socket) {
|
||||
if (!uv_is_closing((uv_handle_t*)&socket->_socket)) {
|
||||
JS_FreeValue(tf_task_get_context(socket->_task), socket->_onRead);
|
||||
socket->_onRead = JS_UNDEFINED;
|
||||
JS_FreeValue(tf_task_get_context(socket->_task), socket->_onError);
|
||||
socket->_onError = JS_UNDEFINED;
|
||||
if (socket->_tls) {
|
||||
tf_tls_session_destroy(socket->_tls);
|
||||
socket->_tls = NULL;
|
||||
}
|
||||
uv_close((uv_handle_t*)&socket->_socket, _socket_onClose);
|
||||
}
|
||||
}
|
||||
|
||||
void _socket_reportError(socket_t* socket, const char* error) {
|
||||
if (JS_IsFunction(tf_task_get_context(socket->_task),socket-> _onError)) {
|
||||
JSValue exception = JS_ThrowInternalError(tf_task_get_context(socket->_task), "%s", error);
|
||||
JSValue result = JS_Call(tf_task_get_context(socket->_task), socket->_onError, socket->_object, 1, &exception);
|
||||
if (JS_IsException(result)) {
|
||||
printf("Socket error.\n");
|
||||
js_std_dump_error(tf_task_get_context(socket->_task));
|
||||
}
|
||||
tf_task_run_jobs(socket->_task);
|
||||
JS_FreeValue(tf_task_get_context(socket->_task), exception);
|
||||
} else {
|
||||
fprintf(stderr, "Socket::reportError: %s\n", error);
|
||||
}
|
||||
}
|
||||
|
||||
void _socket_reportTlsErrors(socket_t* socket) {
|
||||
char buffer[4096];
|
||||
while (socket->_tls && tf_tls_session_get_error(socket->_tls, buffer, sizeof(buffer))) {
|
||||
_socket_reportError(socket, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
JSValue _socket_startTls(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
socket_t* socket = JS_GetOpaque(this_val, _classId);
|
||||
if (!socket->_tls) {
|
||||
tf_tls_context_t* context = 0;
|
||||
|
||||
if (argc > 0 && JS_IsObject(argv[0])) {
|
||||
context = tf_tls_context_wrapper_get(argv[0]);
|
||||
} else {
|
||||
if (!_defaultTlsContext) {
|
||||
_defaultTlsContext = tf_tls_context_create();
|
||||
}
|
||||
context = _defaultTlsContext;
|
||||
}
|
||||
|
||||
if (context) {
|
||||
socket->_tls = tf_tls_context_create_session(context);
|
||||
}
|
||||
|
||||
if (socket->_tls) {
|
||||
tf_tls_session_set_hostname(socket->_tls, socket->_peerName);
|
||||
if (socket->_direction == kAccept) {
|
||||
tf_tls_session_start_accept(socket->_tls);
|
||||
} else if (socket->_direction == kConnect) {
|
||||
tf_tls_session_start_connect(socket->_tls);
|
||||
}
|
||||
socket->_startTlsPromise = tf_task_allocate_promise(socket->_task);
|
||||
JSValue result = tf_task_get_promise(socket->_task, socket->_startTlsPromise);
|
||||
_socket_processOutgoingTls(socket);
|
||||
return result;
|
||||
} else {
|
||||
return JS_ThrowInternalError(tf_task_get_context(socket->_task), "Failed to get TLS context");
|
||||
}
|
||||
} else {
|
||||
return JS_ThrowInternalError(tf_task_get_context(socket->_task), "startTls with TLS already started");
|
||||
}
|
||||
}
|
||||
|
||||
JSValue _socket_stopTls(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
socket_t* socket = JS_GetOpaque(this_val, _classId);
|
||||
if (socket->_tls) {
|
||||
_socket_processOutgoingTls(socket);
|
||||
tf_tls_session_destroy(socket->_tls);
|
||||
socket->_tls = NULL;
|
||||
} else {
|
||||
JS_ThrowInternalError(tf_task_get_context(socket->_task), "stopTls with TLS already stopped");
|
||||
}
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
bool _socket_processSomeOutgoingTls(socket_t* socket, promiseid_t promise, uv_write_cb callback) {
|
||||
char buffer[65536];
|
||||
int result = tf_tls_session_read_encrypted(socket->_tls, buffer, sizeof(buffer));
|
||||
if (result > 0) {
|
||||
char* request_buffer = malloc(sizeof(uv_write_t) + result);
|
||||
uv_write_t* request = (uv_write_t*)request_buffer;
|
||||
memset(request, 0, sizeof(*request));
|
||||
request->data = (void*)(intptr_t)(promise);
|
||||
char* rawBuffer = request_buffer + sizeof(uv_write_t);
|
||||
memcpy(rawBuffer, buffer, result);
|
||||
|
||||
uv_buf_t writeBuffer =
|
||||
{
|
||||
.base = rawBuffer,
|
||||
.len = result,
|
||||
};
|
||||
|
||||
int writeResult = uv_write(request, (uv_stream_t*)&socket->_socket, &writeBuffer, 1, callback);
|
||||
if (writeResult != 0) {
|
||||
free(request_buffer);
|
||||
char error[256];
|
||||
snprintf(error, sizeof(error), "uv_write: %s", uv_strerror(writeResult));
|
||||
_socket_reportError(socket, error);
|
||||
}
|
||||
} else {
|
||||
_socket_reportTlsErrors(socket);
|
||||
}
|
||||
return result > 0;
|
||||
}
|
||||
|
||||
void _socket_processOutgoingTls(socket_t* socket) {
|
||||
while (_socket_processSomeOutgoingTls(socket, -1, _socket_onWrite)) {}
|
||||
}
|
||||
|
||||
JSValue _socket_bind(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
socket_t* socket = JS_GetOpaque(this_val, _classId);
|
||||
const char* node = JS_ToCString(tf_task_get_context(socket->_task), argv[0]);
|
||||
const char* port = JS_ToCString(tf_task_get_context(socket->_task), argv[1]);
|
||||
|
||||
socket_resolve_data_t* data = malloc(sizeof(socket_resolve_data_t));
|
||||
memset(data, 0, sizeof(*data));
|
||||
struct addrinfo hints = {
|
||||
.ai_family = PF_INET,
|
||||
.ai_socktype = SOCK_STREAM,
|
||||
.ai_protocol = IPPROTO_TCP,
|
||||
.ai_flags = 0,
|
||||
};
|
||||
data->resolver.data = data;
|
||||
data->socket = socket;
|
||||
data->promise = tf_task_allocate_promise(socket->_task);
|
||||
|
||||
int result = uv_getaddrinfo(tf_task_get_loop(socket->_task), &data->resolver, _socket_onResolvedForBind, node, port, &hints);
|
||||
if (result != 0) {
|
||||
char error[256];
|
||||
snprintf(error, sizeof(error), "uv_getaddrinfo: %s", uv_strerror(result));
|
||||
tf_task_reject_promise(socket->_task, data->promise, JS_ThrowInternalError(tf_task_get_context(socket->_task), error));
|
||||
free(data);
|
||||
}
|
||||
return tf_task_get_promise(socket->_task, data->promise);
|
||||
}
|
||||
|
||||
void _socket_onResolvedForBind(uv_getaddrinfo_t* resolver, int status, struct addrinfo* result) {
|
||||
socket_resolve_data_t* data = (socket_resolve_data_t*)resolver->data;
|
||||
if (status != 0) {
|
||||
char error[256];
|
||||
snprintf(error, sizeof(error), "uv_getaddrinfo: %s", uv_strerror(status));
|
||||
tf_task_reject_promise(data->socket->_task, data->promise, JS_ThrowInternalError(tf_task_get_context(data->socket->_task), error));
|
||||
} else {
|
||||
int bindResult = uv_tcp_bind(&data->socket->_socket, result->ai_addr, 0);
|
||||
if (bindResult != 0) {
|
||||
char error[256];
|
||||
snprintf(error, sizeof(error), "uv_tcp_bind: %s", uv_strerror(bindResult));
|
||||
tf_task_reject_promise(data->socket->_task, data->promise, JS_ThrowInternalError(tf_task_get_context(data->socket->_task), error));
|
||||
} else {
|
||||
tf_task_resolve_promise(data->socket->_task, data->promise, JS_UNDEFINED);
|
||||
}
|
||||
}
|
||||
free(data);
|
||||
}
|
||||
|
||||
JSValue _socket_connect(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
socket_t* socket = JS_GetOpaque(this_val, _classId);
|
||||
socket->_direction = kConnect;
|
||||
const char* node = JS_ToCString(context, argv[0]);
|
||||
const char* port = JS_ToCString(context, argv[1]);
|
||||
|
||||
strncpy(socket->_peerName, node, sizeof(socket->_peerName) - 1);
|
||||
|
||||
promiseid_t promise = tf_task_allocate_promise(socket->_task);
|
||||
|
||||
socket_resolve_data_t* data = malloc(sizeof(socket_resolve_data_t));
|
||||
memset(data, 0, sizeof(*data));
|
||||
struct addrinfo hints = {
|
||||
.ai_family = PF_INET,
|
||||
.ai_socktype = SOCK_STREAM,
|
||||
.ai_protocol = IPPROTO_TCP,
|
||||
};
|
||||
data->resolver.data = data;
|
||||
data->socket = socket;
|
||||
data->promise = promise;
|
||||
|
||||
int result = uv_getaddrinfo(tf_task_get_loop(socket->_task), &data->resolver, _socket_onResolvedForConnect, node, port, &hints);
|
||||
if (result != 0) {
|
||||
char error[256];
|
||||
snprintf(error, sizeof(error), "uv_getaddrinfo: %s", uv_strerror(result));
|
||||
tf_task_reject_promise(socket->_task, promise, JS_ThrowInternalError(context, "%s", error));
|
||||
free(data);
|
||||
}
|
||||
|
||||
JS_FreeCString(context, node);
|
||||
JS_FreeCString(context, port);
|
||||
return tf_task_get_promise(socket->_task, promise);
|
||||
}
|
||||
|
||||
void _socket_onResolvedForConnect(uv_getaddrinfo_t* resolver, int status, struct addrinfo* result) {
|
||||
socket_resolve_data_t* data = resolver->data;
|
||||
if (status != 0) {
|
||||
char error[256];
|
||||
snprintf(error, sizeof(error), "uv_getaddrinfo: %s", uv_strerror(status));
|
||||
tf_task_reject_promise(data->socket->_task, data->promise, JS_ThrowInternalError(tf_task_get_context(data->socket->_task), "%s", error));
|
||||
} else {
|
||||
uv_connect_t* request = malloc(sizeof(uv_connect_t));
|
||||
memset(request, 0, sizeof(*request));
|
||||
request->data = (void*)(intptr_t)data->promise;
|
||||
int connectResult = uv_tcp_connect(request, &data->socket->_socket, result->ai_addr, _socket_onConnect);
|
||||
if (connectResult != 0) {
|
||||
char error[256];
|
||||
snprintf(error, sizeof(error), "uv_tcp_connect: %s", uv_strerror(connectResult));
|
||||
tf_task_reject_promise(data->socket->_task, data->promise, JS_ThrowInternalError(tf_task_get_context(data->socket->_task), "%s", error));
|
||||
}
|
||||
}
|
||||
uv_freeaddrinfo(result);
|
||||
free(data);
|
||||
}
|
||||
|
||||
void _socket_onConnect(uv_connect_t* request, int status) {
|
||||
promiseid_t promise = (intptr_t)request->data;
|
||||
if (promise != -1) {
|
||||
socket_t* socket = request->handle->data;
|
||||
if (status == 0) {
|
||||
socket->_connected = true;
|
||||
tf_task_resolve_promise(socket->_task, promise, JS_NewInt32(tf_task_get_context(socket->_task), status));
|
||||
} else {
|
||||
char error[256];
|
||||
snprintf(error, sizeof(error), "uv_tcp_connect: %s", uv_strerror(status));
|
||||
tf_task_reject_promise(socket->_task, promise, JS_ThrowInternalError(tf_task_get_context(socket->_task), "%s", error));
|
||||
}
|
||||
}
|
||||
free(request);
|
||||
}
|
||||
|
||||
JSValue _socket_listen(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
socket_t* socket = JS_GetOpaque(this_val, _classId);
|
||||
int backlog = 1;
|
||||
JS_ToInt32(context, &backlog, argv[0]);
|
||||
if (JS_IsUndefined(socket->_onConnect)) {
|
||||
socket->_onConnect = JS_DupValue(context, argv[1]);
|
||||
int result = uv_listen((uv_stream_t*)&socket->_socket, backlog, _socket_onNewConnection);
|
||||
if (result != 0) {
|
||||
char error[256];
|
||||
snprintf(error, sizeof(error), "uv_listen: %s", uv_strerror(result));
|
||||
return JS_ThrowInternalError(context, error);
|
||||
}
|
||||
return JS_NewInt32(context, result);
|
||||
} else {
|
||||
return JS_ThrowInternalError(context, "listen: Already listening.");
|
||||
}
|
||||
}
|
||||
|
||||
void _socket_onNewConnection(uv_stream_t* server, int status) {
|
||||
socket_t* socket = server->data;
|
||||
if (!JS_IsUndefined(socket->_onConnect)) {
|
||||
JSValue result = JS_Call(tf_task_get_context(socket->_task), socket->_onConnect, socket->_object, 0, NULL);
|
||||
if (JS_IsException(result)) {
|
||||
printf("Socket error on connection.\n");
|
||||
js_std_dump_error(tf_task_get_context(socket->_task));
|
||||
}
|
||||
tf_task_run_jobs(socket->_task);
|
||||
}
|
||||
}
|
||||
|
||||
JSValue _socket_accept(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
socket_t* socket = JS_GetOpaque(this_val, _classId);
|
||||
|
||||
socket_t* client = _socket_create_internal(context);
|
||||
client->_direction = kAccept;
|
||||
promiseid_t promise = tf_task_allocate_promise(socket->_task);
|
||||
JSValue result = tf_task_get_promise(socket->_task, promise);
|
||||
int status = uv_accept((uv_stream_t*)&socket->_socket, (uv_stream_t*)&client->_socket);
|
||||
if (status == 0) {
|
||||
client->_connected = true;
|
||||
tf_task_resolve_promise(socket->_task, promise, client->_object);
|
||||
} else {
|
||||
char error[256];
|
||||
snprintf(error, sizeof(error), "uv_accept: %s", uv_strerror(status));
|
||||
tf_task_reject_promise(socket->_task, promise, JS_ThrowInternalError(context, error));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
JSValue _socket_close(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
socket_t* socket = JS_GetOpaque(this_val, _classId);
|
||||
if (socket->_closePromise == -1) {
|
||||
socket->_closePromise = tf_task_allocate_promise(socket->_task);
|
||||
JSValue result = tf_task_get_promise(socket->_task, socket->_closePromise);
|
||||
_socket_close_internal(socket);
|
||||
return result;
|
||||
}
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
JSValue _socket_shutdown(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
socket_t* socket = JS_GetOpaque(this_val, _classId);
|
||||
promiseid_t promise = tf_task_allocate_promise(socket->_task);
|
||||
JSValue result = tf_task_get_promise(socket->_task, promise);
|
||||
if (socket->_tls) {
|
||||
_socket_processTlsShutdown(socket, promise);
|
||||
} else {
|
||||
_socket_shutdownInternal(socket, promise);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void _socket_shutdownInternal(socket_t* socket, promiseid_t promise) {
|
||||
uv_shutdown_t* request = malloc(sizeof(uv_shutdown_t));
|
||||
memset(request, 0, sizeof(*request));
|
||||
request->data = (void*)(intptr_t)promise;
|
||||
int result = uv_shutdown(request, (uv_stream_t*)&socket->_socket, _socket_onShutdown);
|
||||
if (result != 0) {
|
||||
char error[256];
|
||||
snprintf(error, sizeof(error), "uv_shutdown: %s", uv_strerror(result));
|
||||
tf_task_reject_promise(socket->_task, promise, JS_ThrowInternalError(tf_task_get_context(socket->_task), "%s", error));
|
||||
free(request);
|
||||
}
|
||||
}
|
||||
|
||||
void _socket_processTlsShutdown(socket_t* socket, promiseid_t promise) {
|
||||
tf_tls_session_shutdown(socket->_tls);
|
||||
if (!_socket_processSomeOutgoingTls(socket, promise, _socket_onTlsShutdown)) {
|
||||
_socket_shutdownInternal(socket, promise);
|
||||
}
|
||||
}
|
||||
|
||||
void _socket_onTlsShutdown(uv_write_t* request, int status) {
|
||||
socket_t* socket = request->handle->data;
|
||||
promiseid_t promise = (intptr_t)request->data;
|
||||
_socket_processTlsShutdown(socket, promise);
|
||||
free(request);
|
||||
}
|
||||
|
||||
JSValue _socket_onError(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
socket_t* socket = JS_GetOpaque(this_val, _classId);
|
||||
if (!JS_IsUndefined(socket->_onError)) {
|
||||
JS_FreeValue(context, socket->_onError);
|
||||
socket->_onError = JS_UNDEFINED;
|
||||
}
|
||||
socket->_onError = JS_DupValue(context, argv[0]);
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
JSValue _socket_read(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
socket_t* socket = JS_GetOpaque(this_val, _classId);
|
||||
socket->_onRead = JS_DupValue(context, argv[0]);
|
||||
int result = uv_read_start((uv_stream_t*)&socket->_socket, _socket_allocateBuffer, _socket_onRead);
|
||||
promiseid_t promise = tf_task_allocate_promise(socket->_task);
|
||||
JSValue read_result = tf_task_get_promise(socket->_task, promise);
|
||||
if (result != 0) {
|
||||
char error[256];
|
||||
snprintf(error, sizeof(error), "uv_read_start: %s", uv_strerror(result));
|
||||
tf_task_reject_promise(socket->_task, promise, JS_ThrowInternalError(context, error));
|
||||
} else {
|
||||
tf_task_resolve_promise(socket->_task, promise, JS_UNDEFINED);
|
||||
}
|
||||
return read_result;
|
||||
}
|
||||
|
||||
void _socket_allocateBuffer(uv_handle_t* handle, size_t suggestedSize, uv_buf_t* buf) {
|
||||
*buf = uv_buf_init(malloc(suggestedSize), suggestedSize);
|
||||
}
|
||||
|
||||
void _socket_onRead(uv_stream_t* stream, ssize_t readSize, const uv_buf_t* buffer) {
|
||||
socket_t* socket = stream->data;
|
||||
if (readSize <= 0) {
|
||||
socket->_connected = false;
|
||||
if (!JS_IsUndefined(socket->_onRead)) {
|
||||
JSValue args[] = { JS_UNDEFINED };
|
||||
JSValue result = JS_Call(tf_task_get_context(socket->_task), socket->_onRead, socket->_object, 1, args);
|
||||
if (JS_IsException(result)) {
|
||||
printf("Socket error on read.\n");
|
||||
js_std_dump_error(tf_task_get_context(socket->_task));
|
||||
}
|
||||
tf_task_run_jobs(socket->_task);
|
||||
}
|
||||
_socket_close_internal(socket);
|
||||
} else {
|
||||
if (socket->_tls) {
|
||||
_socket_reportTlsErrors(socket);
|
||||
tf_tls_session_write_encrypted(socket->_tls, buffer->base, readSize);
|
||||
if (socket->_startTlsPromise != -1) {
|
||||
tf_tls_handshake_t result = tf_tls_session_handshake(socket->_tls);
|
||||
if (result == k_tls_handshake_done) {
|
||||
promiseid_t promise = socket->_startTlsPromise;
|
||||
socket->_startTlsPromise = -1;
|
||||
tf_task_resolve_promise(socket->_task, promise, JS_UNDEFINED);
|
||||
} else if (result == k_tls_handshake_failed) {
|
||||
promiseid_t promise = socket->_startTlsPromise;
|
||||
socket->_startTlsPromise = -1;
|
||||
char buffer[8192];
|
||||
if (tf_tls_session_get_error(socket->_tls, buffer, sizeof(buffer))) {
|
||||
tf_task_reject_promise(socket->_task, promise, JS_ThrowInternalError(tf_task_get_context(socket->_task), buffer));
|
||||
} else {
|
||||
tf_task_reject_promise(socket->_task, promise, JS_UNDEFINED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
char plain[8192];
|
||||
int result = tf_tls_session_read_plain(socket->_tls, plain, sizeof(plain));
|
||||
if (result > 0) {
|
||||
_socket_notifyDataRead(socket, plain, result);
|
||||
} else if (result == k_tls_read_failed) {
|
||||
_socket_reportTlsErrors(socket);
|
||||
_socket_close_internal(socket);
|
||||
break;
|
||||
} else if (result == k_tls_read_zero) {
|
||||
if (!JS_IsUndefined(socket->_onRead)) {
|
||||
JSValue args[] = { JS_UNDEFINED };
|
||||
JSValue result = JS_Call(tf_task_get_context(socket->_task), socket->_onRead, socket->_object, 1, args);
|
||||
if (JS_IsException(result)) {
|
||||
printf("Socket error on read plain.\n");
|
||||
js_std_dump_error(tf_task_get_context(socket->_task));
|
||||
}
|
||||
tf_task_run_jobs(socket->_task);
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (socket->_tls) {
|
||||
_socket_processOutgoingTls(socket);
|
||||
}
|
||||
} else {
|
||||
_socket_notifyDataRead(socket, buffer->base, readSize);
|
||||
}
|
||||
}
|
||||
free(buffer->base);
|
||||
}
|
||||
|
||||
static JSValue _newUint8Array(JSContext* context, const void* data, size_t length) {
|
||||
JSValue arrayBuffer = JS_NewArrayBufferCopy(context, (const uint8_t*)data, length);
|
||||
JSValue global = JS_GetGlobalObject(context);
|
||||
JSValue constructor = JS_GetPropertyStr(context, global, "Uint8Array");
|
||||
JSValue typedArray = JS_CallConstructor(context, constructor, 1, &arrayBuffer);
|
||||
JS_FreeValue(context, constructor);
|
||||
JS_FreeValue(context, global);
|
||||
JS_FreeValue(context, arrayBuffer);
|
||||
return typedArray;
|
||||
}
|
||||
|
||||
void _socket_notifyDataRead(socket_t* socket, const char* data, size_t length) {
|
||||
if (!JS_IsUndefined(socket->_onRead)) {
|
||||
if (data && length > 0) {
|
||||
JSValue typedArray = _newUint8Array(tf_task_get_context(socket->_task), data, length);
|
||||
JSValue args[] = { typedArray };
|
||||
JSValue result = JS_Call(tf_task_get_context(socket->_task), socket->_onRead, socket->_object, 1, args);
|
||||
if (JS_IsException(result)) {
|
||||
printf("Socket error on data read.\n");
|
||||
js_std_dump_error(tf_task_get_context(socket->_task));
|
||||
}
|
||||
tf_task_run_jobs(socket->_task);
|
||||
JS_FreeValue(tf_task_get_context(socket->_task), typedArray);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int _socket_writeBytes(socket_t* socket, promiseid_t promise, int (*callback)(socket_t* socket, promiseid_t promise, const char*, size_t), JSValue value, int* outLength) {
|
||||
int result = -1;
|
||||
size_t length;
|
||||
uint8_t* array = NULL;
|
||||
JSContext* context = tf_task_get_context(socket->_task);
|
||||
if (JS_IsString(value)) {
|
||||
const char* stringValue = JS_ToCStringLen(context, &length, value);
|
||||
result = callback(socket, promise, stringValue, length);
|
||||
JS_FreeCString(context, stringValue);
|
||||
} else if ((array = tf_try_get_array_buffer(context, &length, value)) != 0) {
|
||||
result = callback(socket, promise, (const char*)array, length);
|
||||
} else {
|
||||
size_t offset;
|
||||
size_t element_size;
|
||||
JSValue buffer = tf_try_get_typed_array_buffer(context, value, &offset, &length, &element_size);
|
||||
size_t size;
|
||||
if ((array = tf_try_get_array_buffer(context, &size, buffer)) != 0) {
|
||||
result = callback(socket, promise, (const char*)array, length);
|
||||
}
|
||||
JS_FreeValue(context, buffer);
|
||||
}
|
||||
|
||||
if (outLength) {
|
||||
*outLength = (int)length;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int _socket_writeInternal(socket_t* socket, promiseid_t promise, const char* data, size_t length) {
|
||||
char* rawBuffer = malloc(sizeof(uv_write_t) + length);
|
||||
uv_write_t* request = (uv_write_t*)rawBuffer;
|
||||
memcpy(rawBuffer + sizeof(uv_write_t), data, length);
|
||||
|
||||
uv_buf_t buffer = {
|
||||
.base = rawBuffer + sizeof(uv_write_t),
|
||||
.len = length,
|
||||
};
|
||||
|
||||
request->data = (void*)(intptr_t)promise;
|
||||
return uv_write(request, (uv_stream_t*)&socket->_socket, &buffer, 1, _socket_onWrite);
|
||||
}
|
||||
|
||||
static int _socket_write_tls(socket_t* socket, promiseid_t promise, const char* data, size_t size) {
|
||||
return tf_tls_session_write_plain(socket->_tls, data, size);
|
||||
}
|
||||
|
||||
JSValue _socket_write(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
socket_t* socket = JS_GetOpaque(this_val, _classId);
|
||||
promiseid_t promise = tf_task_allocate_promise(socket->_task);
|
||||
JSValue write_result = tf_task_get_promise(socket->_task, promise);
|
||||
if (!JS_IsUndefined(argv[0])) {
|
||||
if (socket->_tls) {
|
||||
_socket_reportTlsErrors(socket);
|
||||
int length = 0;
|
||||
int result = _socket_writeBytes(socket, -1, _socket_write_tls, argv[0], &length);
|
||||
char buffer[8192];
|
||||
if (result <= 0 && tf_tls_session_get_error(socket->_tls, buffer, sizeof(buffer))) {
|
||||
tf_task_reject_promise(socket->_task, promise, JS_ThrowInternalError(context, buffer));
|
||||
} else if (result < length) {
|
||||
tf_task_reject_promise(socket->_task, promise, JS_NewInt32(context, result));
|
||||
} else {
|
||||
tf_task_resolve_promise(socket->_task, promise, JS_NewInt32(context, result));
|
||||
}
|
||||
_socket_processOutgoingTls(socket);
|
||||
} else {
|
||||
int length;
|
||||
int result = _socket_writeBytes(socket, promise, _socket_writeInternal, argv[0], &length);
|
||||
|
||||
if (result != 0) {
|
||||
char error[256];
|
||||
snprintf(error, sizeof(error), "uv_write: %s", uv_strerror(result));
|
||||
tf_task_reject_promise(socket->_task, promise, JS_ThrowInternalError(context, error));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tf_task_reject_promise(socket->_task, promise, JS_NewInt32(context, -2));
|
||||
}
|
||||
return write_result;
|
||||
}
|
||||
|
||||
void _socket_onWrite(uv_write_t* request, int status) {
|
||||
socket_t* socket = request->handle->data;
|
||||
promiseid_t promise = (intptr_t)request->data;
|
||||
if (promise != -1) {
|
||||
if (status == 0) {
|
||||
tf_task_resolve_promise(socket->_task, promise, JS_NewInt32(tf_task_get_context(socket->_task), status));
|
||||
} else {
|
||||
char error[256];
|
||||
snprintf(error, sizeof(error), "uv_write: %s", uv_strerror(status));
|
||||
tf_task_reject_promise(socket->_task, promise, JS_ThrowInternalError(tf_task_get_context(socket->_task), error));
|
||||
}
|
||||
}
|
||||
free(request);
|
||||
}
|
||||
|
||||
JSValue _socket_isConnected(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
socket_t* socket = JS_GetOpaque(this_val, _classId);
|
||||
return socket->_connected ? JS_TRUE : JS_FALSE;
|
||||
}
|
||||
|
||||
void _socket_onClose(uv_handle_t* handle) {
|
||||
--_open_count;
|
||||
socket_t* socket = handle->data;
|
||||
if (socket->_closePromise != -1) {
|
||||
promiseid_t promise = socket->_closePromise;
|
||||
socket->_closePromise = -1;
|
||||
socket->_connected = false;
|
||||
tf_task_resolve_promise(socket->_task, promise, JS_UNDEFINED);
|
||||
}
|
||||
}
|
||||
|
||||
void _socket_onShutdown(uv_shutdown_t* request, int status) {
|
||||
socket_t* socket = request->handle->data;
|
||||
promiseid_t promise = (intptr_t)request->data;
|
||||
if (status == 0) {
|
||||
tf_task_resolve_promise(socket->_task, promise, JS_UNDEFINED);
|
||||
} else {
|
||||
char error[256];
|
||||
snprintf(error, sizeof(error), "uv_shutdown: %s", uv_strerror(status));
|
||||
tf_task_reject_promise(socket->_task, promise, JS_ThrowInternalError(tf_task_get_context(socket->_task), "%s", error));
|
||||
}
|
||||
free(request);
|
||||
}
|
||||
|
||||
JSValue _socket_getPeerName(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
socket_t* socket = JS_GetOpaque(this_val, _classId);
|
||||
struct sockaddr_in6 addr;
|
||||
int nameLength = sizeof(addr);
|
||||
if (uv_tcp_getpeername(&socket->_socket, (struct sockaddr*)&addr, &nameLength) == 0) {
|
||||
char name[1024];
|
||||
if ((size_t)nameLength > sizeof(struct sockaddr_in)) {
|
||||
if (uv_ip6_name(&addr, name, sizeof(name)) == 0) {
|
||||
return JS_NewString(context, name);
|
||||
}
|
||||
} else {
|
||||
if (uv_ip4_name((struct sockaddr_in*)&addr, name, sizeof(name)) == 0) {
|
||||
return JS_NewString(context, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
JSValue _socket_getPeerCertificate(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
socket_t* socket = JS_GetOpaque(this_val, _classId);
|
||||
if (socket->_tls) {
|
||||
char buffer[128 * 1024];
|
||||
int result = tf_tls_session_get_peer_certificate(socket->_tls, buffer, sizeof(buffer));
|
||||
if (result > 0) {
|
||||
return _newUint8Array(tf_task_get_context(socket->_task), buffer, sizeof(buffer));
|
||||
}
|
||||
}
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
JSValue _socket_getNoDelay(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
socket_t* socket = JS_GetOpaque(this_val, _classId);
|
||||
return JS_NewBool(context, socket->_noDelay);
|
||||
}
|
||||
|
||||
JSValue _socket_setNoDelay(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
socket_t* socket = JS_GetOpaque(this_val, _classId);
|
||||
int result = JS_ToBool(context, argv[0]);
|
||||
socket->_noDelay = result > 0;
|
||||
uv_tcp_nodelay(&socket->_socket, result > 0 ? 1 : 0);
|
||||
return JS_UNDEFINED;
|
||||
}
|
7
src/socket.h
Normal file
7
src/socket.h
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "quickjs.h"
|
||||
|
||||
JSValue tf_socket_init(JSContext* context);
|
||||
int tf_socket_get_count();
|
||||
int tf_socket_get_open_count();
|
162
src/ssb.connections.c
Normal file
162
src/ssb.connections.c
Normal file
@ -0,0 +1,162 @@
|
||||
#include "ssb.connections.h"
|
||||
|
||||
#include "ssb.h"
|
||||
|
||||
#include <uv.h>
|
||||
#include <sqlite3.h>
|
||||
|
||||
#include <malloc.h>
|
||||
#include <string.h>
|
||||
|
||||
#if !defined(_countof)
|
||||
#define _countof(a) ((int)(sizeof((a)) / sizeof(*(a))))
|
||||
#endif
|
||||
|
||||
typedef struct _tf_ssb_connections_t
|
||||
{
|
||||
tf_ssb_t* ssb;
|
||||
sqlite3* db;
|
||||
uv_timer_t timer;
|
||||
} tf_ssb_connections_t;
|
||||
|
||||
static void _tf_ssb_connections_changed_callback(tf_ssb_t* ssb, tf_ssb_change_t change, tf_ssb_connection_t* connection, void* user_data)
|
||||
{
|
||||
tf_ssb_connections_t* connections = user_data;
|
||||
switch (change) {
|
||||
case k_tf_ssb_change_create:
|
||||
{
|
||||
char key[ID_BASE64_LEN];
|
||||
if (tf_ssb_connection_get_host(connection) &&
|
||||
*tf_ssb_connection_get_host(connection) &&
|
||||
tf_ssb_connection_get_port(connection) &&
|
||||
tf_ssb_connection_get_id(connection, key, sizeof(key))) {
|
||||
tf_ssb_connections_store(connections, tf_ssb_connection_get_host(connection), tf_ssb_connection_get_port(connection), key);
|
||||
tf_ssb_connections_set_attempted(connections, tf_ssb_connection_get_host(connection), tf_ssb_connection_get_port(connection), key);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case k_tf_ssb_change_connect:
|
||||
{
|
||||
char key[ID_BASE64_LEN];
|
||||
if (tf_ssb_connection_get_id(connection, key, sizeof(key))) {
|
||||
tf_ssb_connections_set_succeeded(connections, tf_ssb_connection_get_host(connection), tf_ssb_connection_get_port(connection), key);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case k_tf_ssb_change_remove:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static bool _tf_ssb_connections_get_next_connection(tf_ssb_connections_t* connections, char* host, size_t host_size, int* port, char* key, size_t key_size)
|
||||
{
|
||||
bool result = false;
|
||||
sqlite3_stmt* statement;
|
||||
if (sqlite3_prepare(connections->db, "SELECT host, port, key FROM connections WHERE last_attempt IS NULL OR (strftime('%s', 'now') - last_attempt > $1) ORDER BY last_attempt LIMIT 1", -1, &statement, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_bind_int(statement, 1, 60000) == SQLITE_OK &&
|
||||
sqlite3_step(statement) == SQLITE_ROW) {
|
||||
snprintf(host, host_size, "%s", sqlite3_column_text(statement, 0));
|
||||
*port = sqlite3_column_int(statement, 1);
|
||||
snprintf(key, key_size, "%s", sqlite3_column_text(statement, 2));
|
||||
result = true;
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
} else {
|
||||
printf("prepare: %s\n", sqlite3_errmsg(connections->db));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void _tf_ssb_connections_timer(uv_timer_t* timer)
|
||||
{
|
||||
tf_ssb_connections_t* connections = timer->data;
|
||||
tf_ssb_connection_t* active[4];
|
||||
int count = tf_ssb_get_connections(connections->ssb, active, _countof(active));
|
||||
if (count < _countof(active)) {
|
||||
char host[256];
|
||||
int port;
|
||||
char key[ID_BASE64_LEN];
|
||||
if (_tf_ssb_connections_get_next_connection(connections, host, sizeof(host), &port, key, sizeof(key))) {
|
||||
uint8_t key_bin[ID_BIN_LEN];
|
||||
if (tf_ssb_id_str_to_bin(key_bin, key)) {
|
||||
tf_ssb_connect(connections->ssb, host, port, key_bin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tf_ssb_connections_t* tf_ssb_connections_create(tf_ssb_t* ssb)
|
||||
{
|
||||
tf_ssb_connections_t* connections = malloc(sizeof(tf_ssb_connections_t));
|
||||
memset(connections, 0, sizeof(*connections));
|
||||
connections->ssb = ssb;
|
||||
connections->db = tf_ssb_get_db(ssb);
|
||||
|
||||
tf_ssb_add_connections_changed_callback(ssb, _tf_ssb_connections_changed_callback, connections);
|
||||
|
||||
uv_loop_t* loop = tf_ssb_get_loop(ssb);
|
||||
connections->timer.data = connections;
|
||||
uv_timer_init(loop, &connections->timer);
|
||||
uv_timer_start(&connections->timer, _tf_ssb_connections_timer, 2000, 2000);
|
||||
uv_unref((uv_handle_t*)&connections->timer);
|
||||
|
||||
return connections;
|
||||
}
|
||||
|
||||
static void _tf_ssb_connections_on_handle_close(uv_handle_t* handle)
|
||||
{
|
||||
tf_ssb_connections_t* connections = handle->data;
|
||||
handle->data = NULL;
|
||||
free(connections);
|
||||
}
|
||||
|
||||
void tf_ssb_connections_destroy(tf_ssb_connections_t* connections)
|
||||
{
|
||||
uv_close((uv_handle_t*)&connections->timer, _tf_ssb_connections_on_handle_close);
|
||||
}
|
||||
|
||||
void tf_ssb_connections_store(tf_ssb_connections_t* connections, const char* host, int port, const char* key)
|
||||
{
|
||||
sqlite3_stmt* statement;
|
||||
if (sqlite3_prepare(connections->db, "INSERT INTO connections (host, port, key) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING", -1, &statement, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_bind_text(statement, 1, host, -1, NULL) == SQLITE_OK &&
|
||||
sqlite3_bind_int(statement, 2, port) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 3, key, -1, NULL) == SQLITE_OK) {
|
||||
int r = sqlite3_step(statement);
|
||||
if (r != SQLITE_DONE) {
|
||||
printf("tf_ssb_connections_store: %d, %s.\n", r, sqlite3_errmsg(connections->db));
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
}
|
||||
|
||||
void tf_ssb_connections_set_attempted(tf_ssb_connections_t* connections, const char* host, int port, const char* key)
|
||||
{
|
||||
sqlite3_stmt* statement;
|
||||
if (sqlite3_prepare(connections->db, "UPDATE connections SET last_attempt = strftime('%s', 'now') WHERE host = $1 AND port = $2 AND key = $3", -1, &statement, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_bind_text(statement, 1, host, -1, NULL) == SQLITE_OK &&
|
||||
sqlite3_bind_int(statement, 2, port) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 3, key, -1, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(statement) != SQLITE_DONE) {
|
||||
printf("tf_ssb_connections_set_attempted: %s.\n", sqlite3_errmsg(connections->db));
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
}
|
||||
|
||||
void tf_ssb_connections_set_succeeded(tf_ssb_connections_t* connections, const char* host, int port, const char* key)
|
||||
{
|
||||
sqlite3_stmt* statement;
|
||||
if (sqlite3_prepare(connections->db, "UPDATE connections SET last_success = strftime('%s', 'now') WHERE host = $1 AND port = $2 AND key = $3", -1, &statement, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_bind_text(statement, 1, host, -1, NULL) == SQLITE_OK &&
|
||||
sqlite3_bind_int(statement, 2, port) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 3, key, -1, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(statement) != SQLITE_DONE) {
|
||||
printf("tf_ssb_connections_set_succeeded: %s.\n", sqlite3_errmsg(connections->db));
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
}
|
11
src/ssb.connections.h
Normal file
11
src/ssb.connections.h
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
typedef struct _tf_ssb_t tf_ssb_t;
|
||||
typedef struct _tf_ssb_connections_t tf_ssb_connections_t;
|
||||
|
||||
tf_ssb_connections_t* tf_ssb_connections_create(tf_ssb_t* ssb);
|
||||
void tf_ssb_connections_destroy(tf_ssb_connections_t* connections);
|
||||
|
||||
void tf_ssb_connections_store(tf_ssb_connections_t* connections, const char* host, int port, const char* key);
|
||||
void tf_ssb_connections_set_attempted(tf_ssb_connections_t* connections, const char* host, int port, const char* key);
|
||||
void tf_ssb_connections_set_succeeded(tf_ssb_connections_t* connections, const char* host, int port, const char* key);
|
114
src/ssb.h
Normal file
114
src/ssb.h
Normal file
@ -0,0 +1,114 @@
|
||||
#pragma once
|
||||
|
||||
#include <quickjs.h>
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define ID_BASE64_LEN 57
|
||||
#define ID_BIN_LEN 32
|
||||
#define BLOB_ID_LEN 53
|
||||
|
||||
enum
|
||||
{
|
||||
k_ssb_rpc_flag_binary = 0x0,
|
||||
k_ssb_rpc_flag_utf8 = 0x1,
|
||||
k_ssb_rpc_flag_json = 0x2,
|
||||
k_ssb_rpc_mask_type = 0x3,
|
||||
|
||||
k_ssb_rpc_flag_end_error = 0x4,
|
||||
k_ssb_rpc_flag_stream = 0x8,
|
||||
|
||||
k_ssb_blob_bytes_max = 5 * 1024 * 1024,
|
||||
};
|
||||
|
||||
typedef enum _tf_ssb_change_t
|
||||
{
|
||||
k_tf_ssb_change_create,
|
||||
k_tf_ssb_change_connect,
|
||||
k_tf_ssb_change_remove,
|
||||
} tf_ssb_change_t;
|
||||
|
||||
typedef struct _tf_ssb_t tf_ssb_t;
|
||||
typedef struct _tf_ssb_rpc_t tf_ssb_rpc_t;
|
||||
typedef struct _tf_ssb_connection_t tf_ssb_connection_t;
|
||||
typedef struct _tf_trace_t tf_trace_t;
|
||||
typedef struct sqlite3 sqlite3;
|
||||
typedef struct uv_loop_s uv_loop_t;
|
||||
struct sockaddr_in;
|
||||
|
||||
enum {
|
||||
k_id_base64_len = 57,
|
||||
k_id_bin_len = 32,
|
||||
};
|
||||
|
||||
tf_ssb_t* tf_ssb_create(uv_loop_t* loop, JSContext* context, sqlite3* db, const char* secrets_path);
|
||||
void tf_ssb_destroy(tf_ssb_t* ssb);
|
||||
|
||||
sqlite3* tf_ssb_get_db(tf_ssb_t* ssb);
|
||||
uv_loop_t* tf_ssb_get_loop(tf_ssb_t* ssb);
|
||||
tf_ssb_rpc_t* tf_ssb_get_rpc(tf_ssb_t* ssb);
|
||||
|
||||
void tf_ssb_generate_keys(tf_ssb_t* ssb);
|
||||
|
||||
void tf_ssb_set_trace(tf_ssb_t* ssb, tf_trace_t* trace);
|
||||
tf_trace_t* tf_ssb_get_trace(tf_ssb_t* ssb);
|
||||
|
||||
JSContext* tf_ssb_get_context(tf_ssb_t* ssb);
|
||||
|
||||
void tf_ssb_broadcast_listener_start(tf_ssb_t* ssb, bool linger);
|
||||
void tf_ssb_run(tf_ssb_t* ssb);
|
||||
void tf_ssb_append_message(tf_ssb_t* ssb, JSValue message);
|
||||
void tf_ssb_append_post(tf_ssb_t* ssb, const char* text);
|
||||
bool tf_ssb_whoami(tf_ssb_t* ssb, char* out_id, size_t out_id_size);
|
||||
|
||||
void tf_ssb_set_broadcasts_changed_callback(tf_ssb_t* ssb, void (*callback)(tf_ssb_t* ssb, void* user_data), void* user_data);
|
||||
void tf_ssb_visit_broadcasts(tf_ssb_t* ssb, void (*callback)(const struct sockaddr_in* addr, const uint8_t* pub, void* user_data), void* user_data);
|
||||
|
||||
bool tf_ssb_get_message_by_author_and_sequence(tf_ssb_t* ssb, const char* author, int64_t sequence, char* out_message_id, size_t out_message_id_size, int64_t* out_timestamp, char** out_content);
|
||||
|
||||
bool tf_ssb_blob_get(tf_ssb_t* ssb, const char* id, uint8_t** out_blob, size_t* out_size);
|
||||
bool tf_ssb_blob_store(tf_ssb_t* ssb, const uint8_t* blob, size_t size, char* out_id, size_t out_id_size);
|
||||
|
||||
typedef void (tf_ssb_connections_changed_callback_t)(tf_ssb_t* ssb, tf_ssb_change_t change, tf_ssb_connection_t* connection, void* user_data);
|
||||
void tf_ssb_add_connections_changed_callback(tf_ssb_t* ssb, tf_ssb_connections_changed_callback_t callback, void* user_data);
|
||||
const char** tf_ssb_get_connection_ids(tf_ssb_t* ssb);
|
||||
int tf_ssb_get_connections(tf_ssb_t* ssb, tf_ssb_connection_t** out_connections, int out_connections_count);
|
||||
void tf_ssb_connect(tf_ssb_t* ssb, const char* host, int port, const uint8_t* key);
|
||||
void tf_ssb_connect_str(tf_ssb_t* ssb, const char* address);
|
||||
void tf_ssb_server_open(tf_ssb_t* ssb, int port);
|
||||
void tf_ssb_server_close(tf_ssb_t* ssb);
|
||||
|
||||
void tf_ssb_send_createHistoryStream(tf_ssb_t* ssb, const char* id);
|
||||
|
||||
void tf_ssb_send_close(tf_ssb_t* ssb);
|
||||
|
||||
void tf_ssb_visit_query(tf_ssb_t* ssb, const char* query, const JSValue binds, void (*callback)(JSValue row, void* user_data), void* user_data);
|
||||
|
||||
bool tf_ssb_id_str_to_bin(uint8_t* bin, const char* str);
|
||||
bool tf_ssb_id_bin_to_str(char* str, size_t str_size, const uint8_t* bin);
|
||||
|
||||
void tf_ssb_test();
|
||||
|
||||
void tf_ssb_export(tf_ssb_t* ssb, const char* key);
|
||||
void tf_ssb_import(tf_ssb_t* ssb, const char* user, const char* path);
|
||||
|
||||
typedef void (tf_ssb_rpc_callback_t)(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data);
|
||||
void tf_ssb_register_rpc(tf_ssb_t* ssb, const char** name, tf_ssb_rpc_callback_t* callback, void* user_data);
|
||||
|
||||
bool tf_ssb_verify_and_strip_signature(JSContext* context, JSValue val, char* out_signature, size_t out_signature_size);
|
||||
void tf_ssb_calculate_message_id(JSContext* context, JSValue message, char* out_id, size_t out_id_size);
|
||||
bool tf_ssb_store_message(tf_ssb_t* ssb, JSContext* context, const char* id, JSValue val, const char* signature);
|
||||
bool tf_ssb_get_latest_message_by_author(tf_ssb_t* ssb, const char* author, int64_t* out_sequence, char* out_message_id, size_t out_message_id_size);
|
||||
|
||||
const char* tf_ssb_connection_get_host(tf_ssb_connection_t* connection);
|
||||
int tf_ssb_connection_get_port(tf_ssb_connection_t* connection);
|
||||
tf_ssb_t* tf_ssb_connection_get_ssb(tf_ssb_connection_t* connection);
|
||||
JSContext* tf_ssb_connection_get_context(tf_ssb_connection_t* connection);
|
||||
sqlite3* tf_ssb_connection_get_db(tf_ssb_connection_t* connection);
|
||||
void tf_ssb_connection_rpc_send(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, const uint8_t* message, size_t size, tf_ssb_rpc_callback_t* callback, void* user_data);
|
||||
|
||||
int32_t tf_ssb_connection_next_request_number(tf_ssb_connection_t* connection);
|
||||
|
||||
bool tf_ssb_connection_get_id(tf_ssb_connection_t* connection, char* out_id, size_t out_id_size);
|
||||
void tf_ssb_connection_remove_request(tf_ssb_connection_t* connection, int32_t request_number);
|
296
src/ssb.qjs.c
Normal file
296
src/ssb.qjs.c
Normal file
@ -0,0 +1,296 @@
|
||||
#include "ssb.qjs.h"
|
||||
|
||||
#include "ssb.h"
|
||||
#include "task.h"
|
||||
|
||||
#include <malloc.h>
|
||||
#include <uv.h>
|
||||
|
||||
#include "quickjs-libc.h"
|
||||
|
||||
static JSClassID _tf_ssb_classId;
|
||||
|
||||
static JSValue _tf_ssb_whoami(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
||||
if (ssb) {
|
||||
char id[512];
|
||||
if (tf_ssb_whoami(ssb, id, sizeof(id))) {
|
||||
return JS_NewString(context, id);
|
||||
}
|
||||
}
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
static JSValue _tf_ssb_getMessage(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
JSValue result = JS_NULL;
|
||||
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
||||
if (ssb) {
|
||||
const char* id = JS_ToCString(context, argv[0]);
|
||||
int64_t sequence = 0;
|
||||
JS_ToInt64(context, &sequence, argv[1]);
|
||||
int64_t timestamp = -1;
|
||||
char* contents = NULL;
|
||||
if (tf_ssb_get_message_by_author_and_sequence(ssb, id, sequence, NULL, 0, ×tamp, &contents)) {
|
||||
result = JS_NewObject(context);
|
||||
JS_SetPropertyStr(context, result, "timestamp", JS_NewInt64(context, timestamp));
|
||||
JS_SetPropertyStr(context, result, "content", JS_NewString(context, contents));
|
||||
free(contents);
|
||||
}
|
||||
JS_FreeCString(context, id);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static JSValue _tf_ssb_blobGet(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
JSValue result = JS_NULL;
|
||||
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
||||
if (ssb) {
|
||||
const char* id = JS_ToCString(context, argv[0]);
|
||||
uint8_t* blob = NULL;
|
||||
size_t size = 0;
|
||||
if (tf_ssb_blob_get(ssb, id, &blob, &size)) {
|
||||
result = JS_NewArrayBufferCopy(context, blob, size);
|
||||
free(blob);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static JSValue _tf_ssb_blobStore(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
JSValue result = JS_NULL;
|
||||
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
||||
if (ssb) {
|
||||
uint8_t* blob = NULL;
|
||||
size_t size = 0;
|
||||
char id[512];
|
||||
if (JS_IsString(argv[0])) {
|
||||
const char* text = JS_ToCStringLen(context, &size, argv[0]);
|
||||
if (tf_ssb_blob_store(ssb, (const uint8_t*)text, size, id, sizeof(id))) {
|
||||
result = JS_NewString(context, id);
|
||||
}
|
||||
JS_FreeCString(context, text);
|
||||
} else if ((blob = tf_try_get_array_buffer(context, &size, argv[0])) != 0) {
|
||||
if (tf_ssb_blob_store(ssb, blob, size, id, sizeof(id))) {
|
||||
result = JS_NewString(context, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static JSValue _tf_ssb_connections(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
JSValue result = JS_NULL;
|
||||
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
||||
if (ssb) {
|
||||
const char** connections = tf_ssb_get_connection_ids(ssb);
|
||||
if (connections) {
|
||||
result = JS_NewArray(context);
|
||||
uint32_t i = 0;
|
||||
for (const char** p = connections; *p; p++, i++) {
|
||||
JS_SetPropertyUint32(context, result, i, JS_NewString(context, *p));
|
||||
}
|
||||
free(connections);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static JSValue _tf_ssb_createHistoryStream(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
||||
if (ssb) {
|
||||
const char* id = JS_ToCString(context, argv[0]);
|
||||
tf_ssb_send_createHistoryStream(ssb, id);
|
||||
JS_FreeCString(context, id);
|
||||
}
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
typedef struct _sqlStream_callback_t
|
||||
{
|
||||
JSContext* context;
|
||||
JSValue callback;
|
||||
} sqlStream_callback_t;
|
||||
|
||||
static void _tf_ssb_sqlStream_callback(JSValue row, void* user_data) {
|
||||
sqlStream_callback_t* info = user_data;
|
||||
JSValue response = JS_Call(info->context, info->callback, JS_UNDEFINED, 1, &row);
|
||||
if (JS_IsException(response)) {
|
||||
printf("Error on SQL callback.\n");
|
||||
js_std_dump_error(info->context);
|
||||
}
|
||||
tf_task_run_jobs(tf_task_get(info->context));
|
||||
JS_FreeValue(info->context, response);
|
||||
}
|
||||
|
||||
static JSValue _tf_ssb_sqlStream(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
||||
if (ssb) {
|
||||
const char* query = JS_ToCString(context, argv[0]);
|
||||
if (query) {
|
||||
sqlStream_callback_t info = {
|
||||
.context = context,
|
||||
.callback = argv[2],
|
||||
};
|
||||
tf_ssb_visit_query(ssb, query, argv[1], _tf_ssb_sqlStream_callback, &info);
|
||||
JS_FreeCString(context, query);
|
||||
}
|
||||
}
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
static JSValue _tf_ssb_post(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
||||
if (ssb) {
|
||||
const char* post_text = JS_ToCString(context, argv[0]);
|
||||
if (post_text) {
|
||||
tf_ssb_append_post(ssb, post_text);
|
||||
JS_FreeCString(context, post_text);
|
||||
}
|
||||
}
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
static JSValue _tf_ssb_appendMessage(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
||||
if (ssb) {
|
||||
tf_ssb_append_message(ssb, argv[0]);
|
||||
}
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
typedef struct _broadcasts_t
|
||||
{
|
||||
JSContext* context;
|
||||
JSValue array;
|
||||
int length;
|
||||
} broadcasts_t;
|
||||
|
||||
static void _tf_ssb_broadcasts_visit(const struct sockaddr_in* addr, const uint8_t* pub, void* user_data)
|
||||
{
|
||||
broadcasts_t* broadcasts = user_data;
|
||||
JSValue entry = JS_NewObject(broadcasts->context);
|
||||
char address[256];
|
||||
char pubkey[k_id_base64_len];
|
||||
uv_ip4_name(addr, address, sizeof(address));
|
||||
tf_ssb_id_bin_to_str(pubkey, sizeof(pubkey), pub);
|
||||
JS_SetPropertyStr(broadcasts->context, entry, "address", JS_NewString(broadcasts->context, address));
|
||||
JS_SetPropertyStr(broadcasts->context, entry, "port", JS_NewInt32(broadcasts->context, ntohs(addr->sin_port)));
|
||||
JS_SetPropertyStr(broadcasts->context, entry, "pubkey", JS_NewString(broadcasts->context, pubkey));
|
||||
JS_SetPropertyUint32(broadcasts->context, broadcasts->array, broadcasts->length++, entry);
|
||||
}
|
||||
|
||||
static JSValue _tf_ssb_getBroadcasts(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
JSValue result = JS_UNDEFINED;
|
||||
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
||||
if (ssb) {
|
||||
result = JS_NewArray(context);
|
||||
broadcasts_t broadcasts = {
|
||||
.context = context,
|
||||
.array = result,
|
||||
.length = 0,
|
||||
};
|
||||
tf_ssb_visit_broadcasts(ssb, _tf_ssb_broadcasts_visit, &broadcasts);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static JSValue _tf_ssb_connect(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv)
|
||||
{
|
||||
JSValue args = argv[0];
|
||||
tf_ssb_t* ssb = JS_GetOpaque(this_val, _tf_ssb_classId);
|
||||
if (ssb) {
|
||||
if (JS_IsString(args)) {
|
||||
const char* address_str = JS_ToCString(context, args);
|
||||
printf("Connecting to %s\n", address_str);
|
||||
tf_ssb_connect_str(ssb, address_str);
|
||||
JS_FreeCString(context, address_str);
|
||||
} else {
|
||||
JSValue address = JS_GetPropertyStr(context, args, "address");
|
||||
JSValue port = JS_GetPropertyStr(context, args, "port");
|
||||
JSValue pubkey = JS_GetPropertyStr(context, args, "pubkey");
|
||||
const char* address_str = JS_ToCString(context, address);
|
||||
int32_t port_int = 0;
|
||||
JS_ToInt32(context, &port_int, port);
|
||||
const char* pubkey_str = JS_ToCString(context, pubkey);
|
||||
uint8_t pubkey_bin[k_id_bin_len];
|
||||
printf("Connecting to %s:%d\n", address_str, port_int);
|
||||
tf_ssb_id_str_to_bin(pubkey_bin, pubkey_str);
|
||||
tf_ssb_connect(ssb, address_str, port_int, pubkey_bin);
|
||||
JS_FreeCString(context, pubkey_str);
|
||||
JS_FreeCString(context, address_str);
|
||||
JS_FreeValue(context, address);
|
||||
JS_FreeValue(context, port);
|
||||
JS_FreeValue(context, pubkey);
|
||||
}
|
||||
}
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
static void _tf_ssb_call_callback(tf_ssb_t* ssb, const char* name, void* user_data)
|
||||
{
|
||||
JSContext* context = tf_ssb_get_context(ssb);
|
||||
JSValue global = JS_GetGlobalObject(context);
|
||||
JSValue ssbo = JS_GetPropertyStr(context, global, "ssb");
|
||||
JSValue callback = JS_GetPropertyStr(context, ssbo, name);
|
||||
JSValue args = JS_UNDEFINED;
|
||||
JSValue response = JS_Call(context, callback, JS_UNDEFINED, 0, &args);
|
||||
if (JS_IsException(response)) {
|
||||
printf("Error on callback: %s.\n", name);
|
||||
js_std_dump_error(context);
|
||||
}
|
||||
tf_task_run_jobs(tf_task_get(context));
|
||||
JS_FreeValue(context, response);
|
||||
JS_FreeValue(context, ssbo);
|
||||
JS_FreeValue(context, global);
|
||||
}
|
||||
|
||||
static void _tf_ssb_broadcasts_changed(tf_ssb_t* ssb, void* user_data)
|
||||
{
|
||||
_tf_ssb_call_callback(ssb, "onBroadcastsChanged", user_data);
|
||||
}
|
||||
|
||||
static void _tf_ssb_connections_changed(tf_ssb_t* ssb, tf_ssb_change_t change, tf_ssb_connection_t* connection, void* user_data)
|
||||
{
|
||||
_tf_ssb_call_callback(ssb, "onConnectionsChanged", user_data);
|
||||
}
|
||||
|
||||
void tf_ssb_init(JSContext* context, tf_ssb_t* ssb)
|
||||
{
|
||||
JS_NewClassID(&_tf_ssb_classId);
|
||||
JSClassDef def = {
|
||||
.class_name = "ssb",
|
||||
};
|
||||
if (JS_NewClass(JS_GetRuntime(context), _tf_ssb_classId, &def) != 0) {
|
||||
fprintf(stderr, "Failed to register ssb.\n");
|
||||
}
|
||||
|
||||
tf_ssb_set_broadcasts_changed_callback(ssb, _tf_ssb_broadcasts_changed, NULL);
|
||||
tf_ssb_add_connections_changed_callback(ssb, _tf_ssb_connections_changed, NULL);
|
||||
|
||||
JSValue global = JS_GetGlobalObject(context);
|
||||
JSValue object = JS_NewObjectClass(context, _tf_ssb_classId);
|
||||
JS_SetPropertyStr(context, global, "ssb", object);
|
||||
JS_SetOpaque(object, ssb);
|
||||
JS_SetPropertyStr(context, object, "whoami", JS_NewCFunction(context, _tf_ssb_whoami, "whoami", 0));
|
||||
JS_SetPropertyStr(context, object, "getMessage", JS_NewCFunction(context, _tf_ssb_getMessage, "getMessage", 2));
|
||||
JS_SetPropertyStr(context, object, "blobGet", JS_NewCFunction(context, _tf_ssb_blobGet, "blobGet", 1));
|
||||
JS_SetPropertyStr(context, object, "blobStore", JS_NewCFunction(context, _tf_ssb_blobStore, "blobStore", 2));
|
||||
JS_SetPropertyStr(context, object, "connections", JS_NewCFunction(context, _tf_ssb_connections, "connections", 0));
|
||||
JS_SetPropertyStr(context, object, "createHistoryStream", JS_NewCFunction(context, _tf_ssb_createHistoryStream, "createHistoryStream", 1));
|
||||
JS_SetPropertyStr(context, object, "sqlStream", JS_NewCFunction(context, _tf_ssb_sqlStream, "sqlStream", 3));
|
||||
JS_SetPropertyStr(context, object, "post", JS_NewCFunction(context, _tf_ssb_post, "post", 1));
|
||||
JS_SetPropertyStr(context, object, "appendMessage", JS_NewCFunction(context, _tf_ssb_appendMessage, "appendMessage", 1));
|
||||
JS_SetPropertyStr(context, object, "getBroadcasts", JS_NewCFunction(context, _tf_ssb_getBroadcasts, "getBroadcasts", 0));
|
||||
JS_SetPropertyStr(context, object, "connect", JS_NewCFunction(context, _tf_ssb_connect, "connect", 1));
|
||||
JS_FreeValue(context, global);
|
||||
}
|
7
src/ssb.qjs.h
Normal file
7
src/ssb.qjs.h
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "quickjs.h"
|
||||
|
||||
typedef struct _tf_ssb_t tf_ssb_t;
|
||||
|
||||
void tf_ssb_init(JSContext* context, tf_ssb_t* ssb);
|
662
src/ssb.rpc.c
Normal file
662
src/ssb.rpc.c
Normal file
@ -0,0 +1,662 @@
|
||||
#include "ssb.rpc.h"
|
||||
|
||||
#include "ssb.h"
|
||||
#include "trace.h"
|
||||
|
||||
#include <base64c.h>
|
||||
#include <sodium/crypto_hash_sha256.h>
|
||||
#include <sodium/crypto_sign.h>
|
||||
#include <sqlite3.h>
|
||||
#include <uv.h>
|
||||
|
||||
#include <string.h>
|
||||
|
||||
typedef struct _tf_ssb_blob_wants_t tf_ssb_blob_wants_t;
|
||||
|
||||
typedef struct _tf_ssb_blob_wants_t
|
||||
{
|
||||
tf_ssb_rpc_t* rpc;
|
||||
tf_ssb_connection_t* connection;
|
||||
int32_t request_number;
|
||||
int64_t rowid;
|
||||
tf_ssb_blob_wants_t* next;
|
||||
int open_count;
|
||||
} tf_ssb_blob_wants_t;
|
||||
|
||||
typedef struct _tf_ssb_rpc_t
|
||||
{
|
||||
tf_ssb_blob_wants_t* wants;
|
||||
uv_async_t wants_async;
|
||||
} tf_ssb_rpc_t;
|
||||
|
||||
const char** tf_ssb_get_following_deep(tf_ssb_t* ssb, const char** ids, int depth);
|
||||
|
||||
static void _tf_ssb_rpc_blob_has(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
|
||||
{
|
||||
JSContext* context = tf_ssb_connection_get_context(connection);
|
||||
sqlite3* db = tf_ssb_connection_get_db(connection);
|
||||
sqlite3_stmt* statement;
|
||||
JSValue args_array = JS_GetPropertyStr(context, args, "args");
|
||||
JSValue blob_id_value = JS_GetPropertyUint32(context, args_array, 0);
|
||||
const char* blob_id = JS_ToCString(context, blob_id_value);
|
||||
bool have = false;
|
||||
|
||||
if (sqlite3_prepare(db, "SELECT 1 FROM blobs WHERE id = $1", -1, &statement, NULL) == SQLITE_OK) {
|
||||
have =
|
||||
sqlite3_bind_text(statement, 1, blob_id, -1, NULL) == SQLITE_OK &&
|
||||
sqlite3_step(statement) == SQLITE_ROW;
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
|
||||
JS_FreeCString(context, blob_id);
|
||||
JS_FreeValue(context, blob_id_value);
|
||||
JS_FreeValue(context, args_array);
|
||||
|
||||
uint8_t send_flags =
|
||||
(flags & k_ssb_rpc_flag_stream) |
|
||||
k_ssb_rpc_flag_json |
|
||||
k_ssb_rpc_flag_end_error;
|
||||
const char* result = have ? "true" : "false";
|
||||
tf_ssb_connection_rpc_send(connection, send_flags, -request_number, (const uint8_t*)result, strlen(result), NULL, NULL);
|
||||
}
|
||||
|
||||
static void _tf_ssb_rpc_blob_get(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
|
||||
{
|
||||
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
|
||||
JSContext* context = tf_ssb_connection_get_context(connection);
|
||||
JSValue blob_ids = JS_GetPropertyStr(context, args, "args");
|
||||
JSValue length_value = JS_GetPropertyStr(context, blob_ids, "length");
|
||||
int32_t length = 0;
|
||||
JS_ToInt32(context, &length, length_value);
|
||||
JS_FreeValue(context, length_value);
|
||||
for (int i = 0; i < length; i++) {
|
||||
JSValue blob_id_value = JS_GetPropertyUint32(context, blob_ids, i);
|
||||
const char* blob_id = JS_ToCString(context, blob_id_value);
|
||||
|
||||
uint8_t* blob = NULL;
|
||||
size_t blob_size = 0;
|
||||
if (tf_ssb_blob_get(ssb, blob_id, &blob, &blob_size)) {
|
||||
static const size_t k_block_size = 64 * 1024;
|
||||
for (size_t offset = 0; offset < blob_size; offset += k_block_size) {
|
||||
size_t block_size = offset + k_block_size < blob_size ? k_block_size : (blob_size - offset);
|
||||
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_binary | k_ssb_rpc_flag_stream, -request_number, blob, block_size, NULL, NULL);
|
||||
}
|
||||
free(blob);
|
||||
}
|
||||
|
||||
JS_FreeCString(context, blob_id);
|
||||
JS_FreeValue(context, blob_id_value);
|
||||
}
|
||||
JS_FreeValue(context, blob_ids);
|
||||
}
|
||||
|
||||
typedef struct _tf_ssb_connection_blobs_get_t
|
||||
{
|
||||
char blob_id[BLOB_ID_LEN];
|
||||
size_t n;
|
||||
size_t size;
|
||||
crypto_hash_sha256_state hash;
|
||||
uint8_t data[];
|
||||
} tf_ssb_blobs_get_t;
|
||||
|
||||
static void _tf_ssb_connection_on_rpc_blobs_get_response(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
|
||||
{
|
||||
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
|
||||
tf_ssb_blobs_get_t* get = user_data;
|
||||
if (get) {
|
||||
if (get->n + size <= get->size) {
|
||||
memcpy(get->data + get->n, message, size);
|
||||
crypto_hash_sha256_update(&get->hash, message, size);
|
||||
get->n += size;
|
||||
}
|
||||
printf("received %zd / %zd for %s\n", get->n, get->size, get->blob_id);
|
||||
if (get->n == get->size) {
|
||||
uint8_t hash[crypto_hash_sha256_BYTES];
|
||||
crypto_hash_sha256_final(&get->hash, hash);
|
||||
|
||||
char hash64[256];
|
||||
base64c_encode(hash, sizeof(hash), (uint8_t*)hash64, sizeof(hash64));
|
||||
|
||||
char id[512];
|
||||
snprintf(id, sizeof(id), "&%s.sha256", hash64);
|
||||
|
||||
if (strcmp(id, get->blob_id) == 0) {
|
||||
if (tf_ssb_blob_store(ssb, get->data, get->size, id, sizeof(id))) {
|
||||
printf("stored blob %s\n", get->blob_id);
|
||||
} else {
|
||||
printf("failed to store %s\n", get->blob_id);
|
||||
}
|
||||
} else {
|
||||
printf("blob does not match id %s vs. %s\n", id, get->blob_id);
|
||||
}
|
||||
tf_ssb_connection_remove_request(connection, -request_number);
|
||||
free(get);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tf_ssb_rpc_send_blobs_get(tf_ssb_connection_t* connection, const char* blob_id, size_t size)
|
||||
{
|
||||
JSContext* context = tf_ssb_connection_get_context(connection);
|
||||
JSValue message = JS_NewObject(context);
|
||||
JS_SetPropertyStr(context, message, "type", JS_NewString(context, "source"));
|
||||
JSValue nameval = JS_NewArray(context);
|
||||
JS_SetPropertyUint32(context, nameval, 0, JS_NewString(context, "blobs"));
|
||||
JS_SetPropertyUint32(context, nameval, 1, JS_NewString(context, "get"));
|
||||
JS_SetPropertyStr(context, message, "name", nameval);
|
||||
JSValue argsval = JS_NewArray(context);
|
||||
JS_SetPropertyUint32(context, argsval, 0, JS_NewString(context, blob_id));
|
||||
JS_SetPropertyStr(context, message, "args", argsval);
|
||||
|
||||
JSValue str = JS_JSONStringify(context, message, JS_NULL, JS_NULL);
|
||||
size_t len;
|
||||
const char* cstr = JS_ToCStringLen(context, &len, str);
|
||||
|
||||
tf_ssb_blobs_get_t* get = malloc(sizeof(tf_ssb_blobs_get_t) + size);
|
||||
*get = (tf_ssb_blobs_get_t) { .size = size };
|
||||
snprintf(get->blob_id, sizeof(get->blob_id), "%s", blob_id);
|
||||
crypto_hash_sha256_init(&get->hash);
|
||||
|
||||
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_stream, tf_ssb_connection_next_request_number(connection), (const uint8_t*)cstr, len, _tf_ssb_connection_on_rpc_blobs_get_response, get);
|
||||
JS_FreeCString(context, cstr);
|
||||
JS_FreeValue(context, str);
|
||||
JS_FreeValue(context, message);
|
||||
}
|
||||
|
||||
static void _tf_ssb_connection_on_rpc_blobs_createWants_response(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
|
||||
{
|
||||
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
|
||||
JSContext* context = tf_ssb_connection_get_context(connection);
|
||||
JSPropertyEnum* ptab;
|
||||
uint32_t plen;
|
||||
JS_GetOwnPropertyNames(context, &ptab, &plen, args, JS_GPN_STRING_MASK);
|
||||
|
||||
for (uint32_t i = 0; i < plen; ++i) {
|
||||
JSPropertyDescriptor desc;
|
||||
JSValue key_value = JS_NULL;
|
||||
if (JS_GetOwnProperty(context, &desc, args, ptab[i].atom) == 1) {
|
||||
key_value = desc.value;
|
||||
}
|
||||
|
||||
int32_t size;
|
||||
if (JS_ToInt32(context, &size, key_value) == 0) {
|
||||
JSValue key = JS_AtomToString(context, ptab[i].atom);
|
||||
const char* blob_id = JS_ToCString(context, key);
|
||||
if (size >= 0 &&
|
||||
size < k_ssb_blob_bytes_max) {
|
||||
tf_ssb_rpc_send_blobs_get(connection, blob_id, size);
|
||||
} else if (size < 0) {
|
||||
size_t blob_size = 0;
|
||||
if (tf_ssb_blob_get(ssb, blob_id, NULL, &blob_size)) {
|
||||
JSValue size_response = JS_NewObject(context);
|
||||
JS_SetPropertyStr(context, size_response, blob_id, JS_NewInt64(context, blob_size));
|
||||
JSValue jsonval = JS_JSONStringify(context, size_response, JS_NULL, JS_NULL);
|
||||
size_t len;
|
||||
const char* json = JS_ToCStringLen(context, &len, jsonval);
|
||||
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_stream, request_number, (uint8_t*)json, len, NULL, NULL);
|
||||
JS_FreeCString(context, json);
|
||||
JS_FreeValue(context, jsonval);
|
||||
JS_FreeValue(context, size_response);
|
||||
}
|
||||
}
|
||||
JS_FreeCString(context, blob_id);
|
||||
JS_FreeValue(context, key);
|
||||
}
|
||||
|
||||
JS_FreeValue(context, key_value);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < plen; ++i) {
|
||||
JS_FreeAtom(context, ptab[i].atom);
|
||||
}
|
||||
js_free(context, ptab);
|
||||
}
|
||||
|
||||
void tf_ssb_rpc_send_blobs_createWants(tf_ssb_connection_t* connection)
|
||||
{
|
||||
const char* k_createWants = "{\"name\": [\"blobs\", \"createWants\"], \"type\": \"source\", \"args\": []}";
|
||||
tf_ssb_connection_rpc_send(connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_stream, tf_ssb_connection_next_request_number(connection), (const uint8_t*)k_createWants, strlen(k_createWants), _tf_ssb_connection_on_rpc_blobs_createWants_response, NULL);
|
||||
}
|
||||
|
||||
static void _tf_ssb_rpc_remove_wants(tf_ssb_rpc_t* rpc, tf_ssb_connection_t* connection)
|
||||
{
|
||||
for (tf_ssb_blob_wants_t** it = &rpc->wants; *it; it = &(*it)->next) {
|
||||
if ((*it)->connection == connection) {
|
||||
tf_ssb_blob_wants_t* wants = *it;
|
||||
*it = wants->next;
|
||||
free(wants);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void _tf_ssb_blob_wants_update(tf_ssb_blob_wants_t* wants)
|
||||
{
|
||||
static const int k_messages_per_query = 16;
|
||||
sqlite3* db = tf_ssb_connection_get_db(wants->connection);
|
||||
|
||||
sqlite3_stmt* statement;
|
||||
if (wants->rowid <= 0) {
|
||||
if (sqlite3_prepare(db, "SELECT MAX(messages.rowid) FROM messages", -1, &statement, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_step(statement) == SQLITE_ROW) {
|
||||
wants->rowid = sqlite3_column_int64(statement, 0);
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
}
|
||||
|
||||
if (sqlite3_prepare(db,
|
||||
"SELECT DISTINCT json.value FROM messages, json_tree(messages.content) AS json "
|
||||
"LEFT OUTER JOIN blobs ON json.value = blobs.id "
|
||||
"WHERE "
|
||||
" messages.rowid <= ?1 AND messages.rowid > ?2 AND "
|
||||
" json.value LIKE '&%%.sha256' AND "
|
||||
" length(json.value) = ?3 AND "
|
||||
" blobs.content IS NULL",
|
||||
-1, &statement, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_bind_int64(statement, 1, wants->rowid) == SQLITE_OK &&
|
||||
sqlite3_bind_int64(statement, 2, wants->rowid - k_messages_per_query) == SQLITE_OK &&
|
||||
sqlite3_bind_int64(statement, 3, BLOB_ID_LEN - 1) == SQLITE_OK)
|
||||
{
|
||||
while (sqlite3_step(statement) == SQLITE_ROW) {
|
||||
JSContext* context = tf_ssb_connection_get_context(wants->connection);
|
||||
JSValue want = JS_NewObject(context);
|
||||
JS_SetPropertyStr(context, want, (const char*)sqlite3_column_text(statement, 0), JS_NewInt32(context, -1));
|
||||
JSValue jsonval = JS_JSONStringify(context, want, JS_NULL, JS_NULL);
|
||||
size_t len;
|
||||
const char* json = JS_ToCStringLen(context, &len, jsonval);
|
||||
++wants->open_count;
|
||||
tf_ssb_connection_rpc_send(wants->connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_stream, -wants->request_number, (const uint8_t*)json, len, NULL, NULL);
|
||||
JS_FreeCString(context, json);
|
||||
JS_FreeValue(context, jsonval);
|
||||
JS_FreeValue(context, want);
|
||||
}
|
||||
wants->rowid -= k_messages_per_query;
|
||||
} else {
|
||||
printf("bind failed: %s\n", sqlite3_errmsg(db));
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
} else {
|
||||
printf("prepare failed: %s\n", sqlite3_errmsg(db));
|
||||
}
|
||||
|
||||
if (wants->rowid <= 0) {
|
||||
tf_ssb_connection_rpc_send(wants->connection, k_ssb_rpc_flag_json | k_ssb_rpc_flag_stream, -wants->request_number, (const uint8_t*)"{}", 2, NULL, NULL);
|
||||
} else {
|
||||
uv_async_send(&wants->rpc->wants_async);
|
||||
}
|
||||
}
|
||||
|
||||
static void _tf_ssb_rpc_blobs_createWants(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
|
||||
{
|
||||
tf_ssb_rpc_t* rpc = user_data;
|
||||
tf_ssb_blob_wants_t* wants = malloc(sizeof(tf_ssb_blob_wants_t));
|
||||
*wants = (tf_ssb_blob_wants_t) {
|
||||
.rpc = rpc,
|
||||
.connection = connection,
|
||||
.request_number = request_number,
|
||||
.rowid = -1,
|
||||
.next = rpc->wants,
|
||||
};
|
||||
rpc->wants = wants;
|
||||
_tf_ssb_blob_wants_update(wants);
|
||||
}
|
||||
|
||||
static void _tf_ssb_rpc_createHistoryStream(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue args, const uint8_t* message, size_t size, void* user_data)
|
||||
{
|
||||
JSContext* context = tf_ssb_connection_get_context(connection);
|
||||
sqlite3* db = tf_ssb_connection_get_db(connection);
|
||||
JSValue streamArgs = JS_GetPropertyStr(context, args, "args");
|
||||
JSValue obj = JS_GetPropertyUint32(context, streamArgs, 0);
|
||||
JSValue idval = JS_GetPropertyStr(context, obj, "id");
|
||||
const char* author = JS_ToCString(context, idval);
|
||||
int64_t seq = 0;
|
||||
sqlite3_stmt* statement;
|
||||
JSValue seqval = JS_GetPropertyStr(context, obj, "seq");
|
||||
JS_ToInt64(context, &seq, seqval);
|
||||
JS_FreeValue(context, seqval);
|
||||
|
||||
const char* query = "SELECT previous, sequence, timestamp, hash, content, signature FROM messages WHERE author = $1 AND sequence >= $2 ORDER BY sequence";
|
||||
if (sqlite3_prepare(db, query, -1, &statement, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_bind_text(statement, 1, author, -1, NULL) == SQLITE_OK &&
|
||||
sqlite3_bind_int64(statement, 2, seq) == SQLITE_OK) {
|
||||
while (sqlite3_step(statement) == SQLITE_ROW) {
|
||||
JSValue message = JS_NewObject(context);
|
||||
const char* previous = (const char*)sqlite3_column_text(statement, 0);
|
||||
JS_SetPropertyStr(context, message, "previous", previous ? JS_NewString(context, previous) : JS_NULL);
|
||||
JS_SetPropertyStr(context, message, "author", JS_NewString(context, author));
|
||||
JS_SetPropertyStr(context, message, "sequence", JS_NewInt64(context, sqlite3_column_int64(statement, 1)));
|
||||
JS_SetPropertyStr(context, message, "timestamp", JS_NewInt64(context, sqlite3_column_int64(statement, 2)));
|
||||
JS_SetPropertyStr(context, message, "hash", JS_NewString(context, (const char*)sqlite3_column_text(statement, 3)));
|
||||
const char* contentstr = (const char*)sqlite3_column_text(statement, 4);
|
||||
JSValue content = JS_ParseJSON(context, contentstr, strlen(contentstr), NULL);
|
||||
JS_SetPropertyStr(context, message, "content", content);
|
||||
JS_SetPropertyStr(context, message, "signature", JS_NewString(context, (const char*)sqlite3_column_text(statement, 5)));
|
||||
JSValue jsonval = JS_JSONStringify(context, message, JS_NULL, JS_NULL);
|
||||
size_t len;
|
||||
const char* json = JS_ToCStringLen(context, &len, jsonval);
|
||||
if (tf_ssb_verify_and_strip_signature(context, message, NULL, 0)) {
|
||||
tf_ssb_connection_rpc_send(connection, flags, -request_number, (const uint8_t*)json, len, NULL, NULL);
|
||||
} else {
|
||||
printf("message signature is invalid\n");
|
||||
}
|
||||
JS_FreeCString(context, json);
|
||||
JS_FreeValue(context, jsonval);
|
||||
JS_FreeValue(context, message);
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
} else {
|
||||
printf("prepare failed: %s\n", sqlite3_errmsg(db));
|
||||
}
|
||||
tf_ssb_connection_rpc_send(connection, flags | k_ssb_rpc_flag_end_error, -request_number, (const uint8_t*)"true", 4, NULL, NULL);
|
||||
JS_FreeValue(context, obj);
|
||||
JS_FreeCString(context, author);
|
||||
JS_FreeValue(context, idval);
|
||||
JS_FreeValue(context, streamArgs);
|
||||
}
|
||||
|
||||
static void _tf_ssb_connection_on_rpc_createHistoryStream_response(tf_ssb_connection_t* connection, uint8_t flags, int32_t request_number, JSValue val, const uint8_t* message, size_t size, void* user_data)
|
||||
{
|
||||
tf_ssb_rpc_t* rpc = user_data;
|
||||
JSContext* context = tf_ssb_connection_get_context(connection);
|
||||
char signature[crypto_sign_BYTES + 128];
|
||||
char id[crypto_hash_sha256_BYTES * 2 + 1];
|
||||
tf_ssb_calculate_message_id(context, val, id, sizeof(id));
|
||||
if (tf_ssb_verify_and_strip_signature(context, val, signature, sizeof(signature))) {
|
||||
tf_ssb_store_message(tf_ssb_connection_get_ssb(connection), context, id, val, signature);
|
||||
} else {
|
||||
printf("failed to verify message\n");
|
||||
}
|
||||
uv_async_send(&rpc->wants_async);
|
||||
}
|
||||
|
||||
void tf_ssb_rpc_send_createHistoryStream(tf_ssb_connection_t* connection, const char* id)
|
||||
{
|
||||
JSContext* context = tf_ssb_connection_get_context(connection);
|
||||
JSValue message = JS_NewObject(context);
|
||||
JS_SetPropertyStr(context, message, "type", JS_NewString(context, "source"));
|
||||
JSValue nameval = JS_NewArray(context);
|
||||
JS_SetPropertyUint32(context, nameval, 0, JS_NewString(context, "createHistoryStream"));
|
||||
JS_SetPropertyStr(context, message, "name", nameval);
|
||||
JSValue obj = JS_NewObject(context);
|
||||
JS_SetPropertyStr(context, obj, "id", JS_NewString(context, id));
|
||||
JS_SetPropertyStr(context, obj, "keys", JS_FALSE);
|
||||
int64_t sequence = 0;
|
||||
tf_ssb_t* ssb = tf_ssb_connection_get_ssb(connection);
|
||||
if (tf_ssb_get_latest_message_by_author(ssb, id, &sequence, NULL, 0)) {
|
||||
JS_SetPropertyStr(context, obj, "seq", JS_NewInt64(context, sequence));
|
||||
}
|
||||
JSValue argsval = JS_NewArray(context);
|
||||
JS_SetPropertyUint32(context, argsval, 0, obj);
|
||||
JS_SetPropertyStr(context, message, "args", argsval);
|
||||
|
||||
JSValue str = JS_JSONStringify(context, message, JS_NULL, JS_NULL);
|
||||
size_t len;
|
||||
const char* cstr = JS_ToCStringLen(context, &len, str);
|
||||
tf_ssb_rpc_t* rpc = tf_ssb_get_rpc(ssb);
|
||||
tf_ssb_connection_rpc_send(connection, 0xa, tf_ssb_connection_next_request_number(connection), (const uint8_t*)cstr, len, _tf_ssb_connection_on_rpc_createHistoryStream_response, rpc);
|
||||
JS_FreeCString(context, cstr);
|
||||
JS_FreeValue(context, str);
|
||||
JS_FreeValue(context, message);
|
||||
}
|
||||
|
||||
static void _tf_ssb_rpc_on_connections_changed(tf_ssb_t* ssb, tf_ssb_change_t change, tf_ssb_connection_t* connection, void* user_data)
|
||||
{
|
||||
if (change != k_tf_ssb_change_connect) {
|
||||
return;
|
||||
}
|
||||
|
||||
char id[k_id_base64_len];
|
||||
if (tf_ssb_connection_get_id(connection, id, sizeof(id))) {
|
||||
const char** ids = tf_ssb_get_following_deep(ssb, (const char*[]) { id, NULL }, 2);
|
||||
for (int i = 0; ids && ids[i]; i++) {
|
||||
tf_ssb_rpc_send_createHistoryStream(connection, ids[i]);
|
||||
}
|
||||
free(ids);
|
||||
}
|
||||
}
|
||||
|
||||
static void _tf_ssb_add_id(const char*** results, int* results_count, int* results_capacity, const char* id)
|
||||
{
|
||||
for (int i = 0; i < *results_count; i++) {
|
||||
if (strcmp((*results)[i], id) == 0) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (*results_count + 1 > *results_capacity) {
|
||||
int old_capacity = *results_capacity;
|
||||
*results_capacity = (*results_capacity + 1) * 2;
|
||||
*results = realloc(*results, sizeof(const char*) * (*results_capacity));
|
||||
memset(*results + *results_count, 0, sizeof(const char*) * (*results_capacity - old_capacity));
|
||||
}
|
||||
(*results)[(*results_count)++] = strdup(id);
|
||||
}
|
||||
|
||||
const char** tf_ssb_get_following(tf_ssb_t* ssb, const char* id)
|
||||
{
|
||||
sqlite3* db = tf_ssb_get_db(ssb);
|
||||
JSContext* context = tf_ssb_get_context(ssb);
|
||||
|
||||
const int k_version = 0;
|
||||
int64_t rowid = 0;
|
||||
|
||||
const char** results = NULL;
|
||||
int results_count = 0;
|
||||
int results_capacity = 0;
|
||||
|
||||
sqlite3_stmt* statement;
|
||||
if (sqlite3_prepare(db, "SELECT value FROM properties WHERE id = ? AND key = (? || ':following')", -1, &statement, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_bind_text(statement, 1, "core", -1, NULL) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 2, id, -1, NULL) == SQLITE_OK &&
|
||||
sqlite3_step(statement) == SQLITE_ROW) {
|
||||
JSValue cache = JS_ParseJSON(context, (const char*)sqlite3_column_text(statement, 0), sqlite3_column_bytes(statement, 0), NULL);
|
||||
JSValue version_value = JS_GetPropertyStr(context, cache, "version");
|
||||
int32_t version = 0;
|
||||
JS_ToInt32(context, &version, version_value);
|
||||
JS_FreeValue(context, version_value);
|
||||
|
||||
if (version == k_version) {
|
||||
JSValue rowid_value = JS_GetPropertyStr(context, cache, "rowid");
|
||||
JS_ToInt64(context, &rowid, rowid_value);
|
||||
JS_FreeValue(context, rowid_value);
|
||||
|
||||
JSValue ids = JS_GetPropertyStr(context, cache, "following");
|
||||
JSValue length_value = JS_GetPropertyStr(context, ids, "length");
|
||||
int32_t length = 0;
|
||||
JS_ToInt32(context, &length, length_value);
|
||||
for (int i = 0; i < length; i++) {
|
||||
JSValue id = JS_GetPropertyUint32(context, ids, i);
|
||||
const char* id_string = JS_ToCString(context, id);
|
||||
_tf_ssb_add_id(&results, &results_count, &results_capacity, id_string);
|
||||
JS_FreeCString(context, id_string);
|
||||
JS_FreeValue(context, id);
|
||||
}
|
||||
JS_FreeValue(context, length_value);
|
||||
JS_FreeValue(context, ids);
|
||||
}
|
||||
JS_FreeValue(context, cache);
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
int64_t loaded_rowid = rowid;
|
||||
|
||||
JSValue cache = JS_UNDEFINED;
|
||||
JS_FreeValue(context, cache);
|
||||
|
||||
if (sqlite3_prepare(db, "SELECT "
|
||||
"json_extract(content, '$.contact'), "
|
||||
"json_extract(content, '$.following'), "
|
||||
"rowid "
|
||||
"FROM messages "
|
||||
"WHERE author = ? AND "
|
||||
"rowid > ? AND "
|
||||
"json_extract(content, '$.type') = 'contact' UNION "
|
||||
"SELECT NULL, NULL, MAX(rowid) FROM messages "
|
||||
"ORDER BY rowid", -1, &statement, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_bind_text(statement, 1, id, -1, NULL) == SQLITE_OK &&
|
||||
sqlite3_bind_int64(statement, 2, rowid) == SQLITE_OK) {
|
||||
while (sqlite3_step(statement) == SQLITE_ROW) {
|
||||
const char* contact = (const char*)sqlite3_column_text(statement, 0);
|
||||
if (sqlite3_column_type(statement, 0) != SQLITE_NULL) {
|
||||
if (sqlite3_column_int(statement, 1)) {
|
||||
_tf_ssb_add_id(&results, &results_count, &results_capacity, contact);
|
||||
} else {
|
||||
for (int i = 0; i < results_count; i++) {
|
||||
if (strcmp(results[i], contact) == 0) {
|
||||
free((void*)results[i]);
|
||||
results[i] = results[--results_count];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
rowid = sqlite3_column_int64(statement, 2);
|
||||
}
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
|
||||
if (rowid != loaded_rowid) {
|
||||
JSValue cache = JS_NewObject(context);
|
||||
JS_SetPropertyStr(context, cache, "version", JS_NewInt32(context, k_version));
|
||||
JS_SetPropertyStr(context, cache, "rowid", JS_NewInt64(context, rowid));
|
||||
JSValue ids = JS_NewArray(context);
|
||||
for (int i = 0; i < results_count; i++) {
|
||||
JS_SetPropertyUint32(context, ids, i, JS_NewString(context, results[i]));
|
||||
}
|
||||
JS_SetPropertyStr(context, cache, "following", ids);
|
||||
JSValue json_value = JS_JSONStringify(context, cache, JS_NULL, JS_NULL);
|
||||
const char* json = JS_ToCString(context, json_value);
|
||||
JS_FreeValue(context, json_value);
|
||||
JS_FreeValue(context, cache);
|
||||
if (sqlite3_prepare(db, "INSERT OR REPLACE INTO properties (id, key, value) VALUES (?, ? || ':following', ?)", -1, &statement, NULL) == SQLITE_OK) {
|
||||
if (sqlite3_bind_text(statement, 1, "core", -1, NULL) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 2, id, -1, NULL) == SQLITE_OK &&
|
||||
sqlite3_bind_text(statement, 3, json, -1, NULL) == SQLITE_OK) {
|
||||
sqlite3_step(statement);
|
||||
}
|
||||
sqlite3_finalize(statement);
|
||||
}
|
||||
JS_FreeCString(context, json);
|
||||
}
|
||||
|
||||
size_t size = (results_count + 1) * sizeof(const char*);
|
||||
for (int i = 0; i < results_count; i++) {
|
||||
size += strlen(results[i]) + 1;
|
||||
}
|
||||
char** result = malloc(size);
|
||||
char* p = (char*)result + (results_count + 1) * sizeof(const char*);
|
||||
for (int i = 0; i < results_count; i++) {
|
||||
result[i] = p;
|
||||
size_t length = strlen(results[i]) + 1;
|
||||
memcpy(p, results[i], length);
|
||||
free((void*)results[i]);
|
||||
p += length;
|
||||
}
|
||||
result[results_count] = NULL;
|
||||
free((void*)results);
|
||||
|
||||
return (const char**)result;
|
||||
}
|
||||
|
||||
const char** tf_ssb_get_following_deep(tf_ssb_t* ssb, const char** ids, int depth)
|
||||
{
|
||||
const char** results = NULL;
|
||||
int results_count = 0;
|
||||
int results_capacity = 0;
|
||||
|
||||
for (int i = 0; ids[i]; i++) {
|
||||
_tf_ssb_add_id(&results, &results_count, &results_capacity, ids[i]);
|
||||
}
|
||||
|
||||
for (int i = 0; ids[i]; i++) {
|
||||
const char** following = tf_ssb_get_following(ssb, ids[i]);
|
||||
for (int j = 0; following[j]; j++) {
|
||||
_tf_ssb_add_id(&results, &results_count, &results_capacity, following[j]);
|
||||
}
|
||||
free(following);
|
||||
}
|
||||
|
||||
size_t size = (results_count + 1) * sizeof(const char*);
|
||||
for (int i = 0; i < results_count; i++) {
|
||||
size += strlen(results[i]) + 1;
|
||||
}
|
||||
char** result = malloc(size);
|
||||
char* p = (char*)result + (results_count + 1) * sizeof(const char*);
|
||||
for (int i = 0; i < results_count; i++) {
|
||||
result[i] = p;
|
||||
size_t length = strlen(results[i]) + 1;
|
||||
memcpy(p, results[i], length);
|
||||
free((void*)results[i]);
|
||||
p += length;
|
||||
}
|
||||
result[results_count] = NULL;
|
||||
free((void*)results);
|
||||
|
||||
if (depth > 1) {
|
||||
const char** r = tf_ssb_get_following_deep(ssb, (const char**)result, depth - 1);
|
||||
free(result);
|
||||
result = (char**)r;
|
||||
}
|
||||
return (const char**)result;
|
||||
}
|
||||
|
||||
static void _tf_ssb_rpc_connections_changed_callback(tf_ssb_t* ssb, tf_ssb_change_t change, tf_ssb_connection_t* connection, void* user_data)
|
||||
{
|
||||
tf_ssb_rpc_t* rpc = user_data;
|
||||
if (change == k_tf_ssb_change_connect) {
|
||||
tf_ssb_rpc_send_blobs_createWants(connection);
|
||||
} else if (change == k_tf_ssb_change_remove) {
|
||||
_tf_ssb_rpc_remove_wants(rpc, connection);
|
||||
}
|
||||
}
|
||||
|
||||
static void _tf_ssb_rpc_wants_async(uv_async_t* async)
|
||||
{
|
||||
tf_ssb_rpc_t* rpc = async->data;
|
||||
tf_ssb_blob_wants_t* it = rpc->wants;
|
||||
if (it)
|
||||
{
|
||||
rpc->wants = it->next;
|
||||
it->next = NULL;
|
||||
if (rpc->wants) {
|
||||
for (tf_ssb_blob_wants_t* tail = rpc->wants; tail; tail = tail->next) {
|
||||
if (!tail->next) {
|
||||
tail->next = it;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rpc->wants = it;
|
||||
}
|
||||
_tf_ssb_blob_wants_update(it);
|
||||
}
|
||||
}
|
||||
|
||||
tf_ssb_rpc_t* tf_ssb_rpc_create(tf_ssb_t* ssb)
|
||||
{
|
||||
tf_ssb_rpc_t* rpc = malloc(sizeof(tf_ssb_rpc_t));
|
||||
*rpc = (tf_ssb_rpc_t) {
|
||||
.wants_async.data = rpc,
|
||||
};
|
||||
tf_ssb_add_connections_changed_callback(ssb, _tf_ssb_rpc_on_connections_changed, NULL);
|
||||
tf_ssb_register_rpc(ssb, (const char*[]) { "blobs", "has", NULL }, _tf_ssb_rpc_blob_has, NULL);
|
||||
tf_ssb_register_rpc(ssb, (const char*[]) { "blobs", "get", NULL }, _tf_ssb_rpc_blob_get, NULL);
|
||||
tf_ssb_register_rpc(ssb, (const char*[]) { "blobs", "createWants", NULL }, _tf_ssb_rpc_blobs_createWants, rpc);
|
||||
tf_ssb_register_rpc(ssb, (const char*[]) { "createHistoryStream", NULL }, _tf_ssb_rpc_createHistoryStream, NULL);
|
||||
tf_ssb_add_connections_changed_callback(ssb, _tf_ssb_rpc_connections_changed_callback, rpc);
|
||||
uv_async_init(tf_ssb_get_loop(ssb), &rpc->wants_async, _tf_ssb_rpc_wants_async);
|
||||
uv_unref((uv_handle_t*)&rpc->wants_async);
|
||||
return rpc;
|
||||
}
|
||||
|
||||
static void _tf_ssb_rpc_handle_closed(uv_handle_t* handle)
|
||||
{
|
||||
free(handle->data);
|
||||
}
|
||||
|
||||
void tf_ssb_rpc_destroy(tf_ssb_rpc_t* rpc)
|
||||
{
|
||||
uv_close((uv_handle_t*)&rpc->wants_async, _tf_ssb_rpc_handle_closed);
|
||||
}
|
14
src/ssb.rpc.h
Normal file
14
src/ssb.rpc.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct _tf_ssb_t tf_ssb_t;
|
||||
typedef struct _tf_ssb_connection_t tf_ssb_connection_t;
|
||||
typedef struct _tf_ssb_rpc_t tf_ssb_rpc_t;
|
||||
|
||||
tf_ssb_rpc_t* tf_ssb_rpc_create(tf_ssb_t* ssb);
|
||||
void tf_ssb_rpc_destroy(tf_ssb_rpc_t* rpc);
|
||||
|
||||
void tf_ssb_rpc_send_blobs_get(tf_ssb_connection_t* connection, const char* blob_id, size_t size);
|
||||
void tf_ssb_rpc_send_blobs_createWants(tf_ssb_connection_t* connection);
|
||||
void tf_ssb_rpc_send_createHistoryStream(tf_ssb_connection_t* connection, const char* id);
|
238
src/ssb.tests.c
Normal file
238
src/ssb.tests.c
Normal file
@ -0,0 +1,238 @@
|
||||
#include "ssb.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sqlite3.h>
|
||||
#include <uv.h>
|
||||
|
||||
static void _tf_ssb_test_id_conversion()
|
||||
{
|
||||
printf("Testing id conversion.\n");
|
||||
uint8_t bin[k_id_bin_len] = { 0 };
|
||||
char str[k_id_base64_len] = { 0 };
|
||||
const char* k_id = "@bzRTe6hgOII2yZ1keGGoNoQgostjQc830trHc453crY=.ed25519";
|
||||
(void)bin;
|
||||
(void)str;
|
||||
(void)k_id;
|
||||
assert(tf_ssb_id_str_to_bin(bin, k_id));
|
||||
assert(tf_ssb_id_bin_to_str(str, sizeof(str), bin));
|
||||
assert(strcmp(str, k_id) == 0);
|
||||
}
|
||||
|
||||
typedef struct _test_t {
|
||||
tf_ssb_t* ssb0;
|
||||
tf_ssb_t* ssb1;
|
||||
int connection_count0;
|
||||
int connection_count1;
|
||||
} test_t;
|
||||
|
||||
static void _ssb_test_connections_changed(tf_ssb_t* ssb, tf_ssb_change_t change, tf_ssb_connection_t* connection, void* user_data)
|
||||
{
|
||||
test_t* test = user_data;
|
||||
|
||||
int count = 0;
|
||||
const char** c = tf_ssb_get_connection_ids(ssb);
|
||||
for (const char** p = c; *p; p++) {
|
||||
count++;
|
||||
}
|
||||
free(c);
|
||||
|
||||
if (ssb == test->ssb0) {
|
||||
printf("callback0 change=%d connection=%p\n", change, connection);
|
||||
test->connection_count0 = count;
|
||||
} else if (ssb == test->ssb1) {
|
||||
printf("callback1 change=%d connection=%p\n", change, connection);
|
||||
test->connection_count1 = count;
|
||||
}
|
||||
printf("conns = %d %d\n", test->connection_count0, test->connection_count1);
|
||||
}
|
||||
|
||||
static void _count_messages_callback(JSValue row, void* user_data)
|
||||
{
|
||||
int* count = user_data;
|
||||
++*count;
|
||||
}
|
||||
|
||||
static int _ssb_test_count_messages(tf_ssb_t* ssb)
|
||||
{
|
||||
int count = 0;
|
||||
tf_ssb_visit_query(ssb, "SELECT * FROM messages", JS_UNDEFINED, _count_messages_callback, &count);
|
||||
return count;
|
||||
}
|
||||
|
||||
static void _tf_ssb_test_ssb()
|
||||
{
|
||||
printf("Testing SSB.\n");
|
||||
sqlite3* db0 = NULL;
|
||||
sqlite3* db1 = NULL;
|
||||
|
||||
assert(sqlite3_open(":memory:", &db0) == SQLITE_OK);
|
||||
assert(sqlite3_open(":memory:", &db1) == SQLITE_OK);
|
||||
|
||||
uv_loop_t loop = { 0 };
|
||||
uv_loop_init(&loop);
|
||||
|
||||
tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, db0, NULL);
|
||||
tf_ssb_t* ssb1 = tf_ssb_create(&loop, NULL, db1, NULL);
|
||||
|
||||
test_t test = {
|
||||
.ssb0 = ssb0,
|
||||
.ssb1 = ssb1,
|
||||
};
|
||||
|
||||
tf_ssb_add_connections_changed_callback(ssb0, _ssb_test_connections_changed, &test);
|
||||
tf_ssb_add_connections_changed_callback(ssb1, _ssb_test_connections_changed, &test);
|
||||
|
||||
tf_ssb_generate_keys(ssb0);
|
||||
tf_ssb_generate_keys(ssb1);
|
||||
|
||||
char id0[k_id_base64_len] = { 0 };
|
||||
char id1[k_id_base64_len] = { 0 };
|
||||
bool b = tf_ssb_whoami(ssb0, id0, sizeof(id0));
|
||||
(void)b;
|
||||
assert(b);
|
||||
b = tf_ssb_whoami(ssb0, id0, sizeof(id0));
|
||||
assert(b);
|
||||
printf("ID %s and %s\n", id0, id1);
|
||||
|
||||
char blob_id[k_id_base64_len] = { 0 };
|
||||
const char* k_blob = "Hello, blob!";
|
||||
b = tf_ssb_blob_store(ssb0, (const uint8_t*)k_blob, strlen(k_blob), blob_id, sizeof(blob_id));
|
||||
assert(b);
|
||||
|
||||
tf_ssb_append_post(ssb0, "Hello, world!");
|
||||
tf_ssb_append_post(ssb0, "First post.");
|
||||
|
||||
JSContext* context = tf_ssb_get_context(ssb0);
|
||||
JSValue message = JS_NewObject(context);
|
||||
JS_SetPropertyStr(context, message, "type", JS_NewString(context, "post"));
|
||||
JS_SetPropertyStr(context, message, "text", JS_NewString(context, "First post."));
|
||||
JSValue mentions = JS_NewArray(context);
|
||||
JSValue mention = JS_NewObject(context);
|
||||
JS_SetPropertyStr(context, mention, "link", JS_NewString(context, blob_id));
|
||||
JS_SetPropertyUint32(context, mentions, 0, mention);
|
||||
JS_SetPropertyStr(context, message, "mentions", mentions);
|
||||
tf_ssb_append_message(ssb0, message);
|
||||
JS_FreeValue(context, message);
|
||||
|
||||
tf_ssb_server_open(ssb0, 12347);
|
||||
|
||||
uint8_t id0bin[k_id_bin_len];
|
||||
tf_ssb_id_str_to_bin(id0bin, id0);
|
||||
tf_ssb_connect(ssb1, "127.0.0.1", 12347, id0bin);
|
||||
|
||||
while (test.connection_count0 != 1 ||
|
||||
test.connection_count1 != 1) {
|
||||
uv_run(&loop, UV_RUN_ONCE);
|
||||
}
|
||||
tf_ssb_server_close(ssb0);
|
||||
|
||||
while (_ssb_test_count_messages(ssb1) < 3) {
|
||||
uv_run(&loop, UV_RUN_ONCE);
|
||||
}
|
||||
|
||||
while (!tf_ssb_blob_get(ssb1, blob_id, NULL, NULL)) {
|
||||
uv_run(&loop, UV_RUN_ONCE);
|
||||
}
|
||||
|
||||
tf_ssb_send_close(ssb1);
|
||||
|
||||
uv_run(&loop, UV_RUN_DEFAULT);
|
||||
|
||||
tf_ssb_destroy(ssb0);
|
||||
tf_ssb_destroy(ssb1);
|
||||
|
||||
uv_loop_close(&loop);
|
||||
|
||||
sqlite3_close(db0);
|
||||
sqlite3_close(db1);
|
||||
}
|
||||
|
||||
static void _tf_ssb_test_following()
|
||||
{
|
||||
printf("Testing following.\n");
|
||||
sqlite3* db0 = NULL;
|
||||
assert(sqlite3_open(":memory:", &db0) == SQLITE_OK);
|
||||
|
||||
uv_loop_t loop = { 0 };
|
||||
uv_loop_init(&loop);
|
||||
|
||||
tf_ssb_t* ssb0 = tf_ssb_create(&loop, NULL, db0, NULL);
|
||||
tf_ssb_generate_keys(ssb0);
|
||||
|
||||
tf_ssb_t* ssb1 = tf_ssb_create(&loop, NULL, db0, NULL);
|
||||
tf_ssb_generate_keys(ssb1);
|
||||
|
||||
tf_ssb_t* ssb2 = tf_ssb_create(&loop, NULL, db0, NULL);
|
||||
tf_ssb_generate_keys(ssb2);
|
||||
|
||||
char id0[k_id_base64_len] = { 0 };
|
||||
char id1[k_id_base64_len] = { 0 };
|
||||
char id2[k_id_base64_len] = { 0 };
|
||||
tf_ssb_whoami(ssb0, id0, sizeof(id0));
|
||||
tf_ssb_whoami(ssb1, id1, sizeof(id1));
|
||||
tf_ssb_whoami(ssb2, id2, sizeof(id2));
|
||||
|
||||
JSContext* context = NULL;
|
||||
JSValue message;
|
||||
|
||||
#define FOLLOW(ssb, id, follow) \
|
||||
context = tf_ssb_get_context(ssb); \
|
||||
message = JS_NewObject(context); \
|
||||
JS_SetPropertyStr(context, message, "type", JS_NewString(context, "contact")); \
|
||||
JS_SetPropertyStr(context, message, "contact", JS_NewString(context, id)); \
|
||||
JS_SetPropertyStr(context, message, "following", follow ? JS_TRUE : JS_FALSE); \
|
||||
tf_ssb_append_message(ssb, message); \
|
||||
JS_FreeValue(context, message); \
|
||||
context = NULL
|
||||
|
||||
#define DUMP(id, depth) \
|
||||
do { \
|
||||
printf("following %d:\n", depth); \
|
||||
const char** tf_ssb_get_following_deep(tf_ssb_t* ssb_param, const char** ids, int depth_param); \
|
||||
const char** f = tf_ssb_get_following_deep(ssb0, (const char*[]) { id, NULL }, depth); \
|
||||
for (const char** p = f; p && *p; p++) { \
|
||||
printf("* %s\n", *p); \
|
||||
} \
|
||||
printf("\n"); \
|
||||
free(f); \
|
||||
} while (0)
|
||||
|
||||
FOLLOW(ssb0, id1, true);
|
||||
FOLLOW(ssb1, id2, true);
|
||||
FOLLOW(ssb2, id0, true);
|
||||
DUMP(id0, 2);
|
||||
DUMP(id1, 2);
|
||||
DUMP(id2, 2);
|
||||
FOLLOW(ssb0, id1, false);
|
||||
//FOLLOW(ssb0, id1, true);
|
||||
//FOLLOW(ssb0, id1, true);
|
||||
DUMP(id0, 1);
|
||||
DUMP(id1, 2);
|
||||
//FOLLOW(ssb0, id1, false);
|
||||
//DUMP(1);
|
||||
//DUMP(1);
|
||||
|
||||
#undef FOLLOW
|
||||
#undef DUMP
|
||||
|
||||
uv_run(&loop, UV_RUN_DEFAULT);
|
||||
|
||||
tf_ssb_destroy(ssb0);
|
||||
tf_ssb_destroy(ssb1);
|
||||
tf_ssb_destroy(ssb2);
|
||||
|
||||
uv_loop_close(&loop);
|
||||
|
||||
sqlite3_close(db0);
|
||||
}
|
||||
|
||||
void tf_ssb_test()
|
||||
{
|
||||
printf("tf_ssb_test() starting.\n");
|
||||
_tf_ssb_test_id_conversion();
|
||||
_tf_ssb_test_ssb();
|
||||
_tf_ssb_test_following();
|
||||
printf("tf_ssb_test() completed successfully.\n");
|
||||
}
|
1347
src/task.c
Normal file
1347
src/task.c
Normal file
File diff suppressed because it is too large
Load Diff
68
src/task.h
Normal file
68
src/task.h
Normal file
@ -0,0 +1,68 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "quickjs.h"
|
||||
|
||||
struct uv_loop_s;
|
||||
typedef struct uv_loop_s uv_loop_t;
|
||||
typedef struct uv_timer_s uv_timer_t;
|
||||
|
||||
typedef int taskid_t;
|
||||
typedef int promiseid_t;
|
||||
typedef int exportid_t;
|
||||
typedef struct _tf_taskstub_t tf_taskstub_t;
|
||||
|
||||
typedef struct _tf_task_t tf_task_t;
|
||||
|
||||
static const taskid_t k_task_parent_id = 0;
|
||||
|
||||
typedef enum _tf_task_message_t {
|
||||
kResolvePromise,
|
||||
kRejectPromise,
|
||||
kInvokeExport,
|
||||
kReleaseExport,
|
||||
kReleaseImport,
|
||||
kSetRequires,
|
||||
kActivate,
|
||||
kExecute,
|
||||
kKill,
|
||||
kSetImports,
|
||||
kGetExports,
|
||||
kLoadFile,
|
||||
kTaskError,
|
||||
} tf_task_message_t;
|
||||
|
||||
tf_task_t* tf_task_create();
|
||||
void tf_task_configure_from_stdin(tf_task_t* task);
|
||||
void tf_task_set_ssb_port(tf_task_t* task, int port);
|
||||
void tf_task_set_http_port(tf_task_t* task, int port);
|
||||
void tf_task_set_https_port(tf_task_t* task, int port);
|
||||
void tf_task_set_db_path(tf_task_t* task, const char* path);
|
||||
void tf_task_set_secrets_path(tf_task_t* task, const char* path);
|
||||
void tf_task_activate(tf_task_t* task);
|
||||
void tf_task_run(tf_task_t* task);
|
||||
int tf_task_execute(tf_task_t* task, const char* file);
|
||||
void tf_task_set_trusted(tf_task_t* task, bool trusted);
|
||||
JSContext* tf_task_get_context(tf_task_t* task);
|
||||
void tf_task_destroy(tf_task_t* task);
|
||||
exportid_t tf_task_export_function(tf_task_t* task, tf_taskstub_t* to, JSValue function);
|
||||
JSValue tf_task_add_import(tf_task_t* task, taskid_t stub_id, exportid_t export_id);
|
||||
uv_loop_t* tf_task_get_loop(tf_task_t* task);
|
||||
tf_task_t* tf_task_get(JSContext* context);
|
||||
void tf_task_run_jobs(tf_task_t* task);
|
||||
|
||||
promiseid_t tf_task_allocate_promise(tf_task_t* task);
|
||||
void tf_task_reject_promise(tf_task_t* task, promiseid_t promise, JSValue error);
|
||||
void tf_task_resolve_promise(tf_task_t* task, promiseid_t promise, JSValue result);
|
||||
JSValue tf_task_get_promise(tf_task_t* task, promiseid_t promise);
|
||||
void tf_task_send_promise_message(tf_task_t* from, tf_taskstub_t* to, tf_task_message_t type, promiseid_t promise, JSValue payload);
|
||||
|
||||
void tf_task_on_receive_packet(int packetType, const char* begin, size_t length, void* userData);
|
||||
taskid_t tf_task_allocate_task_id(tf_task_t* task, tf_taskstub_t* stub);
|
||||
void tf_task_remove_child(tf_task_t* task, tf_taskstub_t* child);
|
||||
|
||||
void tf_task_report_error(tf_task_t* task, JSValue error);
|
||||
|
||||
JSValue tf_try_get_typed_array_buffer(JSContext *ctx, JSValueConst obj, size_t *pbyte_offset, size_t *pbyte_length, size_t *pbytes_per_element);
|
||||
uint8_t *tf_try_get_array_buffer(JSContext *ctx, size_t *psize, JSValueConst obj);
|
361
src/taskstub.c
Normal file
361
src/taskstub.c
Normal file
@ -0,0 +1,361 @@
|
||||
#include "taskstub.h"
|
||||
|
||||
#include "packetstream.h"
|
||||
#include "serialize.h"
|
||||
#include "task.h"
|
||||
|
||||
#include <malloc.h>
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "quickjs-libc.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#include <windows.h>
|
||||
#include <ws2tcpip.h>
|
||||
static const int STDIN_FILENO = 0;
|
||||
static const int STDOUT_FILENO = 1;
|
||||
static const int STDERR_FILENO = 2;
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
static JSClassID _classId;
|
||||
static char _executable[1024];
|
||||
|
||||
typedef struct _tf_taskstub_t {
|
||||
taskid_t _id;
|
||||
JSValue _object;
|
||||
JSValue _taskObject;
|
||||
|
||||
JSValue _on_exit;
|
||||
JSValue _on_error;
|
||||
|
||||
tf_task_t* _owner;
|
||||
tf_packetstream_t* _stream;
|
||||
uv_process_t _process;
|
||||
bool _finalized;
|
||||
} tf_taskstub_t;
|
||||
|
||||
void tf_taskstub_startup() {
|
||||
static bool initialized;
|
||||
if (!initialized) {
|
||||
JS_NewClassID(&_classId);
|
||||
size_t size = sizeof(_executable);
|
||||
uv_exepath(_executable, &size);
|
||||
initialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
static JSValue _taskstub_activate(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _taskstub_execute(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _taskstub_setImports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _taskstub_getExports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _taskstub_setRequires(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _taskstub_kill(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _taskstub_get_on_exit(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _taskstub_set_on_exit(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _taskstub_get_on_error(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _taskstub_set_on_error(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _taskstub_loadFile(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static void _taskstub_on_process_exit(uv_process_t* process, int64_t status, int terminationSignal);
|
||||
static void _taskstub_finalizer(JSRuntime *runtime, JSValue value);
|
||||
|
||||
static JSValue _taskstub_create(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
tf_task_t* parent = tf_task_get(context);
|
||||
tf_taskstub_t* stub = malloc(sizeof(tf_taskstub_t));
|
||||
memset(stub, 0, sizeof(*stub));
|
||||
stub->_stream = tf_packetstream_create();
|
||||
|
||||
JSValue taskObject = JS_NewObjectClass(context, _classId);
|
||||
JS_SetOpaque(taskObject, stub);
|
||||
stub->_owner = parent;
|
||||
stub->_on_exit = JS_UNDEFINED;
|
||||
stub->_on_error = JS_UNDEFINED;
|
||||
stub->_object = JS_DupValue(context, taskObject);
|
||||
|
||||
JSAtom atom = JS_NewAtom(context, "onExit");
|
||||
JS_DefinePropertyGetSet(
|
||||
context,
|
||||
taskObject,
|
||||
atom,
|
||||
JS_NewCFunction(context, _taskstub_get_on_exit, "getOnExit", 0),
|
||||
JS_NewCFunction(context, _taskstub_set_on_exit, "setOnExit", 0),
|
||||
0);
|
||||
JS_FreeAtom(context, atom);
|
||||
|
||||
atom = JS_NewAtom(context, "onError");
|
||||
JS_DefinePropertyGetSet(
|
||||
context,
|
||||
taskObject,
|
||||
atom,
|
||||
JS_NewCFunction(context, _taskstub_get_on_error, "getOnError", 0),
|
||||
JS_NewCFunction(context, _taskstub_set_on_error, "setOnError", 0),
|
||||
0);
|
||||
JS_FreeAtom(context, atom);
|
||||
|
||||
JS_SetPropertyStr(context, taskObject, "activate", JS_NewCFunction(context, _taskstub_activate, "activate", 0));
|
||||
JS_SetPropertyStr(context, taskObject, "execute", JS_NewCFunction(context, _taskstub_execute, "execute", 1));
|
||||
JSAtom imports = JS_NewAtom(context, "imports");
|
||||
JS_SetPropertyStr(context, taskObject, "setImports", JS_NewCFunction(context, _taskstub_setImports, "setImports", 1));
|
||||
JS_FreeAtom(context, imports);
|
||||
JS_SetPropertyStr(context, taskObject, "getExports", JS_NewCFunction(context, _taskstub_getExports, "getExports", 0));
|
||||
JS_SetPropertyStr(context, taskObject, "setRequires", JS_NewCFunction(context, _taskstub_setRequires, "setRequires", 1));
|
||||
JS_SetPropertyStr(context, taskObject, "kill", JS_NewCFunction(context, _taskstub_kill, "kill", 0));
|
||||
JS_SetPropertyStr(context, taskObject, "loadFile", JS_NewCFunction(context, _taskstub_loadFile, "loadFile", 1));
|
||||
|
||||
taskid_t id = k_task_parent_id;
|
||||
if (parent) {
|
||||
id = tf_task_allocate_task_id(parent, (tf_taskstub_t*)stub);
|
||||
}
|
||||
stub->_id = id;
|
||||
|
||||
char arg1[] = "sandbox";
|
||||
char* command_argv[] = { _executable, arg1, 0 };
|
||||
|
||||
uv_pipe_t* pipe = tf_packetstream_get_pipe(stub->_stream);
|
||||
memset(pipe, 0, sizeof(*pipe));
|
||||
if (uv_pipe_init(tf_task_get_loop(parent), pipe, 1) != 0) {
|
||||
fprintf(stderr, "uv_pipe_init failed\n");
|
||||
}
|
||||
|
||||
uv_stdio_container_t io[3];
|
||||
io[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE | UV_WRITABLE_PIPE;
|
||||
io[0].data.stream = (uv_stream_t*)pipe;
|
||||
io[1].flags = UV_INHERIT_FD;
|
||||
io[1].data.fd = STDOUT_FILENO;
|
||||
io[2].flags = UV_INHERIT_FD;
|
||||
io[2].data.fd = STDERR_FILENO;
|
||||
|
||||
uv_process_options_t options = {0};
|
||||
options.args = command_argv;
|
||||
options.exit_cb = _taskstub_on_process_exit;
|
||||
options.stdio = io;
|
||||
options.stdio_count = sizeof(io) / sizeof(*io);
|
||||
options.file = command_argv[0];
|
||||
|
||||
JSValue result = JS_NULL;
|
||||
stub->_process.data = stub;
|
||||
int spawn_result = uv_spawn(tf_task_get_loop(parent), &stub->_process, &options);
|
||||
if (spawn_result == 0) {
|
||||
tf_packetstream_set_on_receive(stub->_stream, tf_task_on_receive_packet, stub);
|
||||
tf_packetstream_start(stub->_stream);
|
||||
|
||||
result = taskObject;
|
||||
} else {
|
||||
fprintf(stderr, "uv_spawn failed: %s\n", uv_strerror(spawn_result));
|
||||
JS_FreeValue(context, taskObject);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void _taskstub_gc_mark(JSRuntime* rt, JSValueConst value, JS_MarkFunc mark_func) {
|
||||
tf_taskstub_t* stub = JS_GetOpaque(value, _classId);
|
||||
if (stub) {
|
||||
JS_MarkValue(rt, stub->_on_exit, mark_func);
|
||||
JS_MarkValue(rt, stub->_on_error, mark_func);
|
||||
}
|
||||
}
|
||||
|
||||
JSValue tf_taskstub_init(JSContext* context) {
|
||||
JSClassDef def = {
|
||||
.class_name = "TaskStub",
|
||||
.finalizer = &_taskstub_finalizer,
|
||||
.gc_mark = _taskstub_gc_mark,
|
||||
};
|
||||
if (JS_NewClass(JS_GetRuntime(context), _classId, &def) != 0) {
|
||||
fprintf(stderr, "Failed to register TaskStub class.\n");
|
||||
}
|
||||
return JS_NewCFunction2(context, _taskstub_create, "TaskStub", 0, JS_CFUNC_constructor, 0);
|
||||
}
|
||||
|
||||
taskid_t tf_taskstub_get_id(const tf_taskstub_t* stub) {
|
||||
return stub->_id;
|
||||
}
|
||||
|
||||
JSValue tf_taskstub_get_task_object(const tf_taskstub_t* stub) {
|
||||
return stub->_taskObject;
|
||||
}
|
||||
|
||||
tf_packetstream_t* tf_taskstub_get_stream(const tf_taskstub_t* stub) {
|
||||
return stub->_stream;
|
||||
}
|
||||
|
||||
tf_task_t* tf_taskstub_get_owner(const tf_taskstub_t* stub) {
|
||||
return stub->_owner;
|
||||
}
|
||||
|
||||
tf_taskstub_t* tf_taskstub_create_parent(tf_task_t* task, uv_file file) {
|
||||
JSValue parentObject = JS_NewObject(tf_task_get_context(task));
|
||||
tf_taskstub_t* parentStub = malloc(sizeof(tf_taskstub_t));
|
||||
memset(parentStub, 0, sizeof(tf_taskstub_t));
|
||||
parentStub->_stream = tf_packetstream_create();
|
||||
parentStub->_on_exit = JS_UNDEFINED;
|
||||
parentStub->_on_error = JS_UNDEFINED;
|
||||
|
||||
parentStub->_taskObject = parentObject;
|
||||
JS_SetOpaque(parentObject, parentStub);
|
||||
parentStub->_owner = task;
|
||||
parentStub->_id = k_task_parent_id;
|
||||
parentStub->_object = JS_DupValue(tf_task_get_context(task), parentObject);
|
||||
|
||||
if (uv_pipe_init(tf_task_get_loop(task), tf_packetstream_get_pipe(parentStub->_stream), 1) != 0) {
|
||||
fprintf(stderr, "uv_pipe_init failed\n");
|
||||
}
|
||||
tf_packetstream_set_on_receive(parentStub->_stream, tf_task_on_receive_packet, parentStub);
|
||||
if (uv_pipe_open(tf_packetstream_get_pipe(parentStub->_stream), file) != 0) {
|
||||
fprintf(stderr, "uv_pipe_open failed\n");
|
||||
}
|
||||
tf_packetstream_start(parentStub->_stream);
|
||||
|
||||
return parentStub;
|
||||
}
|
||||
|
||||
static void _taskstub_cleanup(tf_taskstub_t* stub)
|
||||
{
|
||||
if (!stub->_process.data &&
|
||||
JS_IsUndefined(stub->_object) &&
|
||||
stub->_finalized) {
|
||||
free(stub);
|
||||
}
|
||||
}
|
||||
|
||||
static void _taskstub_finalizer(JSRuntime* runtime, JSValue value) {
|
||||
tf_taskstub_t* stub = JS_GetOpaque(value, _classId);
|
||||
stub->_on_exit = JS_UNDEFINED;
|
||||
stub->_on_error = JS_UNDEFINED;
|
||||
tf_packetstream_destroy(stub->_stream);
|
||||
stub->_stream = NULL;
|
||||
stub->_finalized = true;
|
||||
tf_task_remove_child(stub->_owner, stub);
|
||||
_taskstub_cleanup(stub);
|
||||
}
|
||||
|
||||
static void _taskstub_on_handle_close(uv_handle_t* handle)
|
||||
{
|
||||
tf_taskstub_t* stub = handle->data;
|
||||
handle->data = NULL;
|
||||
tf_task_remove_child(stub->_owner, stub);
|
||||
_taskstub_cleanup(stub);
|
||||
}
|
||||
|
||||
static void _taskstub_on_process_exit(uv_process_t* process, int64_t status, int terminationSignal) {
|
||||
tf_taskstub_t* stub = process->data;
|
||||
JSContext* context = tf_task_get_context(stub->_owner);
|
||||
if (!JS_IsUndefined(stub->_on_exit)) {
|
||||
JSValue argv[] = { JS_NewInt32(context, status), JS_NewInt32(context, terminationSignal) };
|
||||
JSValue result = JS_Call(context, stub->_on_exit, JS_NULL, 2, argv);
|
||||
tf_task_report_error(stub->_owner, result);
|
||||
JS_FreeValue(context, result);
|
||||
tf_task_run_jobs(stub->_owner);
|
||||
JS_FreeValue(context, argv[0]);
|
||||
JS_FreeValue(context, argv[1]);
|
||||
}
|
||||
tf_packetstream_close(stub->_stream);
|
||||
uv_close((uv_handle_t*)process, _taskstub_on_handle_close);
|
||||
tf_taskstub_destroy(stub);
|
||||
JS_RunGC(JS_GetRuntime(context));
|
||||
}
|
||||
|
||||
static JSValue _taskstub_getExports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
tf_taskstub_t* stub = JS_GetOpaque(this_val, _classId);
|
||||
promiseid_t promise = tf_task_allocate_promise(stub->_owner);
|
||||
tf_task_send_promise_message(stub->_owner, (tf_taskstub_t*)stub, kGetExports, promise, JS_UNDEFINED);
|
||||
return tf_task_get_promise(stub->_owner, promise);
|
||||
}
|
||||
|
||||
static JSValue _taskstub_setImports(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
tf_taskstub_t* stub = JS_GetOpaque(this_val, _classId);
|
||||
void* buffer;
|
||||
size_t size;
|
||||
tf_serialize_store(tf_task_get(context), stub, &buffer, &size, argv[0]);
|
||||
tf_packetstream_send(stub->_stream, kSetImports, (char*)buffer, size);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
static JSValue _taskstub_setRequires(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
tf_taskstub_t* stub = JS_GetOpaque(this_val, _classId);
|
||||
void* buffer;
|
||||
size_t size;
|
||||
tf_serialize_store(tf_task_get(context), stub, &buffer, &size, argv[0]);
|
||||
tf_packetstream_send(stub->_stream, kSetRequires, (char*)buffer, size);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
static JSValue _taskstub_loadFile(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
tf_taskstub_t* stub = JS_GetOpaque(this_val, _classId);
|
||||
void* buffer;
|
||||
size_t size;
|
||||
tf_serialize_store(tf_task_get(context), stub, &buffer, &size, argv[0]);
|
||||
tf_packetstream_send(stub->_stream, kLoadFile, (char*)buffer, size);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
static JSValue _taskstub_get_on_exit(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
tf_taskstub_t* stub = JS_GetOpaque(this_val, _classId);
|
||||
return JS_DupValue(context, stub->_on_exit);
|
||||
}
|
||||
|
||||
static JSValue _taskstub_set_on_exit(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
tf_taskstub_t* stub = JS_GetOpaque(this_val, _classId);
|
||||
if (!JS_IsUndefined(stub->_on_exit)) {
|
||||
JS_FreeValue(context, stub->_on_exit);
|
||||
}
|
||||
stub->_on_exit = JS_DupValue(context, argv[0]);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
static JSValue _taskstub_get_on_error(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
tf_taskstub_t* stub = JS_GetOpaque(this_val, _classId);
|
||||
return JS_DupValue(context, stub->_on_error);
|
||||
}
|
||||
|
||||
static JSValue _taskstub_set_on_error(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
tf_taskstub_t* stub = JS_GetOpaque(this_val, _classId);
|
||||
if (!JS_IsUndefined(stub->_on_error)) {
|
||||
JS_FreeValue(context, stub->_on_error);
|
||||
}
|
||||
stub->_on_error = JS_DupValue(context, argv[0]);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
static JSValue _taskstub_activate(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
tf_taskstub_t* stub = JS_GetOpaque(this_val, _classId);
|
||||
if (stub) {
|
||||
tf_packetstream_send(stub->_stream, kActivate, 0, 0);
|
||||
}
|
||||
return JS_NULL;
|
||||
}
|
||||
|
||||
static JSValue _taskstub_execute(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
tf_taskstub_t* stub = JS_GetOpaque(this_val, _classId);
|
||||
promiseid_t promise = tf_task_allocate_promise(stub->_owner);
|
||||
tf_task_send_promise_message(stub->_owner, (tf_taskstub_t*)stub, kExecute, promise, argv[0]);
|
||||
return tf_task_get_promise(stub->_owner, promise);
|
||||
}
|
||||
|
||||
static JSValue _taskstub_kill(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
tf_taskstub_t* stub = JS_GetOpaque(this_val, _classId);
|
||||
uv_process_kill(&stub->_process, SIGTERM);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
void tf_taskstub_destroy(tf_taskstub_t* stub) {
|
||||
if (!JS_IsUndefined(stub->_object)) {
|
||||
JSValue object = stub->_object;
|
||||
stub->_object = JS_UNDEFINED;
|
||||
JS_FreeValue(tf_task_get_context(stub->_owner), object);
|
||||
}
|
||||
}
|
||||
|
||||
void tf_taskstub_on_error(tf_taskstub_t* stub, JSValue error)
|
||||
{
|
||||
JSContext* context = tf_task_get_context(stub->_owner);
|
||||
if (!JS_IsUndefined(stub->_on_error)) {
|
||||
JSValue result = JS_Call(context, stub->_on_error, JS_NULL, 1, &error);
|
||||
tf_task_report_error(stub->_owner, result);
|
||||
JS_FreeValue(context, result);
|
||||
tf_task_run_jobs(stub->_owner);
|
||||
}
|
||||
}
|
21
src/taskstub.h
Normal file
21
src/taskstub.h
Normal file
@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include "quickjs.h"
|
||||
|
||||
#include <uv.h>
|
||||
|
||||
typedef int taskid_t;
|
||||
|
||||
typedef struct _tf_packetstream_t tf_packetstream_t;
|
||||
typedef struct _tf_task_t tf_task_t;
|
||||
typedef struct _tf_taskstub_t tf_taskstub_t;
|
||||
|
||||
void tf_taskstub_startup();
|
||||
JSValue tf_taskstub_init(JSContext* context);
|
||||
taskid_t tf_taskstub_get_id(const tf_taskstub_t* stub);
|
||||
JSValue tf_taskstub_get_task_object(const tf_taskstub_t* stub);
|
||||
tf_packetstream_t* tf_taskstub_get_stream(const tf_taskstub_t* stub);
|
||||
tf_task_t* tf_taskstub_get_owner(const tf_taskstub_t* stub);
|
||||
tf_taskstub_t* tf_taskstub_create_parent(tf_task_t* task, uv_file file);
|
||||
void tf_taskstub_destroy(tf_taskstub_t* stub);
|
||||
void tf_taskstub_on_error(tf_taskstub_t* stub, JSValue error);
|
@ -1,340 +1,55 @@
|
||||
#include "Tls.h"
|
||||
#include "tls.h"
|
||||
|
||||
#if !defined (_WIN32) && !defined (__MACH__)
|
||||
#include <cstring>
|
||||
#include <locale>
|
||||
#if defined(_WIN32)
|
||||
#define TF_TLS_SCHANNEL
|
||||
#elif defined(__MACH__)
|
||||
#define TF_TLS_APPLE
|
||||
#else
|
||||
#define TF_TLS_OPENSSL
|
||||
#endif
|
||||
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(TF_TLS_OPENSSL)
|
||||
#include <openssl/bio.h>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/pem.h>
|
||||
#include <openssl/ssl.h>
|
||||
#include <openssl/x509.h>
|
||||
#include <openssl/x509v3.h>
|
||||
|
||||
class TlsContext_openssl : public TlsContext {
|
||||
public:
|
||||
TlsContext_openssl();
|
||||
~TlsContext_openssl() override;
|
||||
TlsSession* createSession() override;
|
||||
bool setCertificate(const char* certificate) override;
|
||||
bool setPrivateKey(const char* privateKey) override;
|
||||
bool addTrustedCertificate(const char* certificate) override;
|
||||
|
||||
SSL_CTX* getContext() { return _context; }
|
||||
|
||||
private:
|
||||
SSL_CTX* _context = 0;
|
||||
};
|
||||
|
||||
class TlsSession_openssl : public TlsSession {
|
||||
public:
|
||||
TlsSession_openssl(TlsContext_openssl* context);
|
||||
~TlsSession_openssl();
|
||||
|
||||
void setHostname(const char* hostname) override;
|
||||
void startConnect() override;
|
||||
void startAccept() override;
|
||||
|
||||
int getPeerCertificate(char* buffer, size_t size) override;
|
||||
|
||||
void shutdown() override;
|
||||
HandshakeResult handshake() override;
|
||||
|
||||
int readPlain(char* buffer, size_t bytes) override;
|
||||
int writePlain(const char* buffer, size_t bytes) override;
|
||||
|
||||
int readEncrypted(char* buffer, size_t bytes) override;
|
||||
int writeEncrypted(const char* buffer, size_t bytes) override;
|
||||
|
||||
bool getError(char* buffer, size_t bytes) override;
|
||||
|
||||
private:
|
||||
bool verifyPeerCertificate();
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
bool verifyHostname(X509* certificate, const char* hostname);
|
||||
bool wildcardMatch(const char* pattern, const char* name);
|
||||
#elif defined(TF_TLS_APPLE)
|
||||
#elif defined(TF_TLS_SCHANNEL)
|
||||
#endif
|
||||
|
||||
TlsContext_openssl* _context = 0;
|
||||
BIO* _bioIn = 0;
|
||||
BIO* _bioOut = 0;
|
||||
SSL* _ssl = 0;
|
||||
std::string _hostname;
|
||||
enum { kUndetermined, kAccept, kConnect } _direction = kUndetermined;
|
||||
};
|
||||
typedef enum _direction_t {
|
||||
k_direction_undetermined,
|
||||
k_direction_accept,
|
||||
k_direction_connect,
|
||||
} direction_t;
|
||||
|
||||
TlsSession* TlsContext_openssl::createSession() {
|
||||
return new TlsSession_openssl(this);
|
||||
}
|
||||
|
||||
#include <iostream>
|
||||
|
||||
TlsContext_openssl::TlsContext_openssl() {
|
||||
SSL_library_init();
|
||||
SSL_load_error_strings();
|
||||
|
||||
const SSL_METHOD* method = SSLv23_method();
|
||||
if (!method)
|
||||
{
|
||||
std::cerr << "SSLv23_method returned NULL\n";
|
||||
}
|
||||
|
||||
_context = SSL_CTX_new(method);
|
||||
if (!_context)
|
||||
{
|
||||
std::cerr << "SSL_CTX_new returned NULL\n";
|
||||
}
|
||||
SSL_CTX_set_default_verify_paths(_context);
|
||||
}
|
||||
|
||||
TlsContext_openssl::~TlsContext_openssl() {
|
||||
SSL_CTX_free(_context);
|
||||
}
|
||||
|
||||
bool TlsContext_openssl::setCertificate(const char* certificate) {
|
||||
int result = 0;
|
||||
BIO* bio = BIO_new(BIO_s_mem());
|
||||
BIO_puts(bio, certificate);
|
||||
X509* x509 = PEM_read_bio_X509(bio, 0, 0, 0);
|
||||
result = SSL_CTX_use_certificate(_context, x509);
|
||||
while (true) {
|
||||
x509 = PEM_read_bio_X509(bio, 0, 0, 0);
|
||||
if (x509) {
|
||||
SSL_CTX_add_extra_chain_cert(_context, x509);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
BIO_free(bio);
|
||||
return result == 1;
|
||||
}
|
||||
|
||||
bool TlsContext_openssl::setPrivateKey(const char* privateKey) {
|
||||
int result = 0;
|
||||
BIO* bio = BIO_new(BIO_s_mem());
|
||||
BIO_puts(bio, privateKey);
|
||||
EVP_PKEY* key = PEM_read_bio_PrivateKey(bio, 0, 0, 0);
|
||||
result = SSL_CTX_use_PrivateKey(_context, key);
|
||||
BIO_free(bio);
|
||||
return result == 1;
|
||||
}
|
||||
|
||||
bool TlsContext_openssl::addTrustedCertificate(const char* certificate) {
|
||||
bool result = false;
|
||||
BIO* bio = BIO_new_mem_buf(const_cast<char*>(certificate), -1);
|
||||
X509* x509 = PEM_read_bio_X509(bio, 0, 0, 0);
|
||||
BIO_free(bio);
|
||||
|
||||
if (x509) {
|
||||
X509_STORE* store = SSL_CTX_get_cert_store(_context);
|
||||
if (store && X509_STORE_add_cert(store, x509) == 1) {
|
||||
result = true;
|
||||
}
|
||||
X509_free(x509);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TlsContext* TlsContext::create() {
|
||||
return new TlsContext_openssl();
|
||||
}
|
||||
|
||||
TlsSession_openssl::TlsSession_openssl(TlsContext_openssl* context) {
|
||||
_context = context;
|
||||
_bioIn = BIO_new(BIO_s_mem());
|
||||
_bioOut = BIO_new(BIO_s_mem());
|
||||
}
|
||||
|
||||
TlsSession_openssl::~TlsSession_openssl() {
|
||||
if (_ssl) {
|
||||
SSL_free(_ssl);
|
||||
}
|
||||
}
|
||||
|
||||
void TlsSession_openssl::setHostname(const char* hostname) {
|
||||
_hostname = hostname;
|
||||
}
|
||||
|
||||
void TlsSession_openssl::startAccept() {
|
||||
_direction = kAccept;
|
||||
_ssl = SSL_new(_context->getContext());
|
||||
SSL_set_bio(_ssl, _bioIn, _bioOut);
|
||||
SSL_accept(_ssl);
|
||||
handshake();
|
||||
}
|
||||
|
||||
void TlsSession_openssl::startConnect() {
|
||||
_direction = kConnect;
|
||||
_ssl = SSL_new(_context->getContext());
|
||||
X509_VERIFY_PARAM* param = SSL_get0_param(_ssl);
|
||||
X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
|
||||
X509_VERIFY_PARAM_set1_host(param, _hostname.c_str(), 0);
|
||||
SSL_set_bio(_ssl, _bioIn, _bioOut);
|
||||
|
||||
SSL_connect(_ssl);
|
||||
handshake();
|
||||
}
|
||||
|
||||
void TlsSession_openssl::shutdown() {
|
||||
SSL_shutdown(_ssl);
|
||||
}
|
||||
|
||||
TlsSession::HandshakeResult TlsSession_openssl::handshake() {
|
||||
TlsSession::HandshakeResult result = kDone;
|
||||
if (!SSL_is_init_finished(_ssl)) {
|
||||
int value = SSL_do_handshake(_ssl);
|
||||
if (value <= 0) {
|
||||
int error = SSL_get_error(_ssl, value);
|
||||
if (error != SSL_ERROR_WANT_READ && error != SSL_ERROR_WANT_WRITE) {
|
||||
result = kFailed;
|
||||
} else {
|
||||
result = kMore;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result == kDone && _direction == kConnect && !verifyPeerCertificate()) {
|
||||
result = kFailed;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int TlsSession_openssl::readPlain(char* buffer, size_t bytes) {
|
||||
int result = SSL_read(_ssl, buffer, bytes);
|
||||
if (result <= 0) {
|
||||
int error = SSL_get_error(_ssl, result);
|
||||
if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) {
|
||||
result = 0;
|
||||
} else if (error == SSL_ERROR_ZERO_RETURN) {
|
||||
if ((SSL_get_shutdown(_ssl) & SSL_RECEIVED_SHUTDOWN) != 0) {
|
||||
result = kReadZero;
|
||||
} else {
|
||||
result = 0;
|
||||
}
|
||||
} else {
|
||||
result = kReadFailed;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int TlsSession_openssl::writePlain(const char* buffer, size_t bytes) {
|
||||
return SSL_write(_ssl, buffer, bytes);
|
||||
}
|
||||
|
||||
int TlsSession_openssl::readEncrypted(char* buffer, size_t bytes) {
|
||||
return BIO_read(_bioOut, buffer, bytes);
|
||||
}
|
||||
|
||||
int TlsSession_openssl::writeEncrypted(const char* buffer, size_t bytes) {
|
||||
return BIO_write(_bioIn, buffer, bytes);
|
||||
}
|
||||
|
||||
int TlsSession_openssl::getPeerCertificate(char* buffer, size_t size) {
|
||||
int result = -1;
|
||||
X509* certificate = SSL_get_peer_certificate(_ssl);
|
||||
BIO* bio = BIO_new(BIO_s_mem());
|
||||
PEM_write_bio_X509(bio, certificate);
|
||||
BUF_MEM* mem;
|
||||
BIO_get_mem_ptr(bio, &mem);
|
||||
if (mem->length <= size) {
|
||||
std::memcpy(buffer, mem->data, mem->length);
|
||||
result = mem->length;
|
||||
}
|
||||
BIO_free(bio);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool TlsSession_openssl::verifyPeerCertificate() {
|
||||
bool verified = false;
|
||||
X509* certificate = SSL_get_peer_certificate(_ssl);
|
||||
if (certificate) {
|
||||
|
||||
if (SSL_get_verify_result(_ssl) == X509_V_OK) {
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
if (verifyHostname(certificate, _hostname.c_str())) {
|
||||
verified = true;
|
||||
}
|
||||
#else
|
||||
verified = true;
|
||||
typedef struct _tf_tls_context_t {
|
||||
#if defined(TF_TLS_OPENSSL)
|
||||
SSL_CTX* context;
|
||||
#elif defined(TF_TLS_APPLE)
|
||||
#elif defined(TF_TLS_SCHANNEL)
|
||||
#endif
|
||||
}
|
||||
X509_free(certificate);
|
||||
}
|
||||
return verified;
|
||||
}
|
||||
} tf_tls_context_t;
|
||||
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
bool TlsSession_openssl::wildcardMatch(const char* pattern, const char* name) {
|
||||
while (*pattern && *name) {
|
||||
if (*pattern == '*') {
|
||||
for (const char* p = name; *p; ++p) {
|
||||
if (wildcardMatch(pattern + 1, p)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (std::tolower(*pattern) == std::tolower(*name)) {
|
||||
++pattern;
|
||||
++name;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return *pattern == 0 && *name == 0;
|
||||
}
|
||||
|
||||
bool TlsSession_openssl::verifyHostname(X509* certificate, const char* hostname) {
|
||||
bool verified = false;
|
||||
void* names = X509_get_ext_d2i(certificate, NID_subject_alt_name, 0, 0);
|
||||
if (names) {
|
||||
int count = sk_GENERAL_NAME_num(names);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const GENERAL_NAME* check = sk_GENERAL_NAME_value(names, i);
|
||||
#if OPENSSL_VERSION_NUMBER <= 0x1000211fL
|
||||
const unsigned char* name = ASN1_STRING_data(check->d.ia5);
|
||||
#else
|
||||
const char* name = ASN1_STRING_get0_data(check->d.ia5);
|
||||
typedef struct _tf_tls_session_t {
|
||||
#if defined(TF_TLS_OPENSSL)
|
||||
tf_tls_context_t* context;
|
||||
BIO* bio_in;
|
||||
BIO* bio_out;
|
||||
SSL* ssl;
|
||||
const char* hostname;
|
||||
direction_t direction;
|
||||
#elif defined(TF_TLS_APPLE)
|
||||
#elif defined(TF_TLS_SCHANNEL)
|
||||
#endif
|
||||
size_t length = ASN1_STRING_length(check->d.ia5);
|
||||
if (wildcardMatch(std::string((const char*)name, length).c_str(), hostname)) {
|
||||
verified = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} tf_tls_session_t;
|
||||
|
||||
if (!verified) {
|
||||
int index = X509_NAME_get_index_by_NID(X509_get_subject_name(certificate), NID_commonName, -1);
|
||||
if (index >= 0) {
|
||||
X509_NAME_ENTRY* entry = X509_NAME_get_entry(X509_get_subject_name(certificate), index);
|
||||
if (entry) {
|
||||
ASN1_STRING* asn1 = X509_NAME_ENTRY_get_data(entry);
|
||||
if (asn1) {
|
||||
#if OPENSSL_VERSION_NUMBER <= 0x1000211fL
|
||||
const unsigned char* commonName = ASN1_STRING_data(asn1);
|
||||
#else
|
||||
const char* commonName = ASN1_STRING_get0_data(asn1);
|
||||
#endif
|
||||
if (static_cast<size_t>(ASN1_STRING_length(asn1)) == std::strlen((const char*)commonName)) {
|
||||
verified = wildcardMatch((const char*)commonName, hostname);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return verified;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool TlsSession_openssl::getError(char* buffer, size_t bytes) {
|
||||
unsigned long error = ERR_get_error();
|
||||
if (error != 0) {
|
||||
ERR_error_string_n(error, buffer, bytes);
|
||||
}
|
||||
return error != 0;
|
||||
}
|
||||
#if !defined (_WIN32) && !defined (__MACH__)
|
||||
#elif defined (__MACH__)
|
||||
#include <Security/SecIdentity.h>
|
||||
#include <Security/SecImportExport.h>
|
||||
@ -685,7 +400,7 @@ private:
|
||||
CredHandle _credentialsHandle;
|
||||
CtxtHandle _securityContext;
|
||||
SecPkgContext_StreamSizes _sizes;
|
||||
enum { kUndetermined, kConnect, kAccept } _direction = kUndetermined;
|
||||
enum { k_direction_undetermined, k_direction_connect, k_direction_accept } _direction = k_direction_undetermined;
|
||||
bool _initial = false;
|
||||
static HRESULT _lastError;
|
||||
|
||||
@ -845,7 +560,7 @@ TlsSession_sspi::~TlsSession_sspi() {
|
||||
}
|
||||
|
||||
void TlsSession_sspi::startAccept() {
|
||||
_direction = kAccept;
|
||||
_direction = k_direction_accept;
|
||||
_initial = true;
|
||||
SCHANNEL_CRED credentials;
|
||||
ZeroMemory(&credentials, sizeof(credentials));
|
||||
@ -859,7 +574,7 @@ void TlsSession_sspi::startAccept() {
|
||||
}
|
||||
|
||||
void TlsSession_sspi::startConnect() {
|
||||
_direction = kConnect;
|
||||
_direction = k_direction_connect;
|
||||
_initial = true;
|
||||
SCHANNEL_CRED credentials;
|
||||
ZeroMemory(&credentials, sizeof(credentials));
|
||||
@ -958,7 +673,7 @@ TlsSession::HandshakeResult TlsSession_sspi::handshakeInternal(bool initial) {
|
||||
|
||||
SECURITY_STATUS status = SEC_E_OK;
|
||||
|
||||
if (_direction == kConnect) {
|
||||
if (_direction == k_direction_connect) {
|
||||
status = getSecurityLibrary()->InitializeSecurityContextA(
|
||||
&_credentialsHandle,
|
||||
initial ? 0 : &_securityContext,
|
||||
@ -972,7 +687,7 @@ TlsSession::HandshakeResult TlsSession_sspi::handshakeInternal(bool initial) {
|
||||
&outBuffer,
|
||||
&outFlags,
|
||||
0);
|
||||
} else if (_direction = kAccept) {
|
||||
} else if (_direction = k_direction_accept) {
|
||||
status = getSecurityLibrary()->AcceptSecurityContext(
|
||||
&_credentialsHandle,
|
||||
initial ? 0 : &_securityContext,
|
||||
@ -1164,3 +879,347 @@ TlsContext* TlsContext::create() {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
tf_tls_context_t* tf_tls_context_create() {
|
||||
tf_tls_context_t* context = malloc(sizeof(tf_tls_context_t));
|
||||
memset(context, 0, sizeof(*context));
|
||||
#if defined(TF_TLS_OPENSSL)
|
||||
SSL_library_init();
|
||||
SSL_load_error_strings();
|
||||
context->context = SSL_CTX_new(SSLv23_method());
|
||||
SSL_CTX_set_default_verify_paths(context->context);
|
||||
#elif defined(TF_TLS_APPLE)
|
||||
#elif defined(TF_TLS_SCHANNEL)
|
||||
#endif
|
||||
return context;
|
||||
}
|
||||
|
||||
bool tf_tls_context_set_certificate(tf_tls_context_t* context, const char* certificate) {
|
||||
#if defined(TF_TLS_OPENSSL)
|
||||
int result = 0;
|
||||
BIO* bio = BIO_new(BIO_s_mem());
|
||||
BIO_puts(bio, certificate);
|
||||
X509* x509 = PEM_read_bio_X509(bio, 0, 0, 0);
|
||||
result = SSL_CTX_use_certificate(context->context, x509);
|
||||
while (true) {
|
||||
x509 = PEM_read_bio_X509(bio, 0, 0, 0);
|
||||
if (x509) {
|
||||
SSL_CTX_add_extra_chain_cert(context->context, x509);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
BIO_free(bio);
|
||||
return result == 1;
|
||||
#elif defined(TF_TLS_APPLE)
|
||||
#elif defined(TF_TLS_SCHANNEL)
|
||||
#endif
|
||||
}
|
||||
|
||||
bool tf_tls_context_set_private_key(tf_tls_context_t* context, const char* private_key) {
|
||||
#if defined(TF_TLS_OPENSSL)
|
||||
int result = 0;
|
||||
BIO* bio = BIO_new(BIO_s_mem());
|
||||
BIO_puts(bio, private_key);
|
||||
EVP_PKEY* key = PEM_read_bio_PrivateKey(bio, 0, 0, 0);
|
||||
result = SSL_CTX_use_PrivateKey(context->context, key);
|
||||
BIO_free(bio);
|
||||
return result == 1;
|
||||
#elif defined(TF_TLS_APPLE)
|
||||
#elif defined(TF_TLS_SCHANNEL)
|
||||
#endif
|
||||
}
|
||||
|
||||
bool tf_tls_context_add_trusted_certificate(tf_tls_context_t* context, const char* certificate) {
|
||||
#if defined(TF_TLS_OPENSSL)
|
||||
bool result = false;
|
||||
BIO* bio = BIO_new_mem_buf(certificate, -1);
|
||||
X509* x509 = PEM_read_bio_X509(bio, 0, 0, 0);
|
||||
BIO_free(bio);
|
||||
|
||||
if (x509) {
|
||||
X509_STORE* store = SSL_CTX_get_cert_store(context->context);
|
||||
if (store && X509_STORE_add_cert(store, x509) == 1) {
|
||||
result = true;
|
||||
}
|
||||
X509_free(x509);
|
||||
}
|
||||
|
||||
return result;
|
||||
#elif defined(TF_TLS_APPLE)
|
||||
#elif defined(TF_TLS_SCHANNEL)
|
||||
#endif
|
||||
}
|
||||
|
||||
tf_tls_session_t* tf_tls_context_create_session(tf_tls_context_t* context) {
|
||||
#if defined(TF_TLS_OPENSSL)
|
||||
tf_tls_session_t* session = malloc(sizeof(tf_tls_session_t));
|
||||
memset(session, 0, sizeof(*session));
|
||||
session->context = context;
|
||||
session->bio_in = BIO_new(BIO_s_mem());
|
||||
session->bio_out = BIO_new(BIO_s_mem());
|
||||
return session;
|
||||
#elif defined(TF_TLS_APPLE)
|
||||
#elif defined(TF_TLS_SCHANNEL)
|
||||
#endif
|
||||
}
|
||||
|
||||
void tf_tls_context_destroy(tf_tls_context_t* context) {
|
||||
#if defined(TF_TLS_OPENSSL)
|
||||
SSL_CTX_free(context->context);
|
||||
free(context);
|
||||
#elif defined(TF_TLS_APPLE)
|
||||
#elif defined(TF_TLS_SCHANNEL)
|
||||
#endif
|
||||
}
|
||||
|
||||
void tf_tls_session_destroy(tf_tls_session_t* session) {
|
||||
#if defined(TF_TLS_OPENSSL)
|
||||
if (session->ssl) {
|
||||
SSL_free(session->ssl);
|
||||
}
|
||||
if (session->hostname) {
|
||||
free((void*)session->hostname);
|
||||
}
|
||||
free(session);
|
||||
#elif defined(TF_TLS_APPLE)
|
||||
#elif defined(TF_TLS_SCHANNEL)
|
||||
#endif
|
||||
}
|
||||
|
||||
void tf_tls_session_set_hostname(tf_tls_session_t* session, const char* hostname) {
|
||||
#if defined(TF_TLS_OPENSSL)
|
||||
if (session->hostname) {
|
||||
free((void*)session->hostname);
|
||||
session->hostname = NULL;
|
||||
}
|
||||
if (hostname) {
|
||||
session->hostname = strdup(hostname);
|
||||
}
|
||||
#elif defined(TF_TLS_APPLE)
|
||||
#elif defined(TF_TLS_SCHANNEL)
|
||||
#endif
|
||||
}
|
||||
|
||||
void tf_tls_session_start_accept(tf_tls_session_t* session) {
|
||||
#if defined(TF_TLS_OPENSSL)
|
||||
session->direction = k_direction_accept;
|
||||
session->ssl = SSL_new(session->context->context);
|
||||
SSL_set_bio(session->ssl, session->bio_in, session->bio_out);
|
||||
SSL_accept(session->ssl);
|
||||
tf_tls_session_handshake(session);
|
||||
#elif defined(TF_TLS_APPLE)
|
||||
#elif defined(TF_TLS_SCHANNEL)
|
||||
#endif
|
||||
}
|
||||
|
||||
void tf_tls_session_start_connect(tf_tls_session_t* session) {
|
||||
#if defined(TF_TLS_OPENSSL)
|
||||
session->direction = k_direction_connect;
|
||||
session->ssl = SSL_new(session->context->context);
|
||||
X509_VERIFY_PARAM* param = SSL_get0_param(session->ssl);
|
||||
X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
|
||||
X509_VERIFY_PARAM_set1_host(param, session->hostname, 0);
|
||||
SSL_set_bio(session->ssl, session->bio_in, session->bio_out);
|
||||
SSL_connect(session->ssl);
|
||||
tf_tls_session_handshake(session);
|
||||
#elif defined(TF_TLS_APPLE)
|
||||
#elif defined(TF_TLS_SCHANNEL)
|
||||
#endif
|
||||
}
|
||||
|
||||
void tf_tls_session_shutdown(tf_tls_session_t* session) {
|
||||
#if defined(TF_TLS_OPENSSL)
|
||||
SSL_shutdown(session->ssl);
|
||||
#elif defined(TF_TLS_APPLE)
|
||||
#elif defined(TF_TLS_SCHANNEL)
|
||||
#endif
|
||||
}
|
||||
|
||||
int tf_tls_session_get_peer_certificate(tf_tls_session_t* session, char* buffer, size_t bytes) {
|
||||
#if defined(TF_TLS_OPENSSL)
|
||||
int result = -1;
|
||||
X509* certificate = SSL_get_peer_certificate(session->ssl);
|
||||
BIO* bio = BIO_new(BIO_s_mem());
|
||||
PEM_write_bio_X509(bio, certificate);
|
||||
BUF_MEM* mem;
|
||||
BIO_get_mem_ptr(bio, &mem);
|
||||
if (mem->length <= bytes) {
|
||||
memcpy(buffer, mem->data, mem->length);
|
||||
result = mem->length;
|
||||
}
|
||||
BIO_free(bio);
|
||||
return result;
|
||||
#elif defined(TF_TLS_APPLE)
|
||||
#elif defined(TF_TLS_SCHANNEL)
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(TF_TLS_OPENSSL)
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
bool _tls_session_wildcard_match(const char* pattern, size_t pattern_length, const char* name) {
|
||||
const char* it = pattern;
|
||||
while (it - pattern < pattern_length && *name) {
|
||||
if (*it == '*') {
|
||||
for (const char* p = name; *p; ++p) {
|
||||
if (_tls_session_wildcard_match(it + 1, pattern_length - 1, p)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
} else if (tolower(*it) == tolower(*name)) {
|
||||
++it;
|
||||
++name;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return it - pattern <= pattern_length && *name == 0;
|
||||
}
|
||||
|
||||
static bool _tls_session_verify_hostname(X509* certificate, const char* hostname) {
|
||||
bool verified = false;
|
||||
void* names = X509_get_ext_d2i(certificate, NID_subject_alt_name, 0, 0);
|
||||
if (names) {
|
||||
int count = sk_GENERAL_NAME_num(names);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
const GENERAL_NAME* check = sk_GENERAL_NAME_value(names, i);
|
||||
if (!verified) {
|
||||
#if OPENSSL_VERSION_NUMBER <= 0x1000211fL
|
||||
const unsigned char* name = ASN1_STRING_data(check->d.ia5);
|
||||
#else
|
||||
const char* name = ASN1_STRING_get0_data(check->d.ia5);
|
||||
#endif
|
||||
size_t length = ASN1_STRING_length(check->d.ia5);
|
||||
if (_tls_session_wildcard_match((const char*)name, length, hostname)) {
|
||||
verified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
sk_GENERAL_NAMES_free(names);
|
||||
}
|
||||
|
||||
if (!verified) {
|
||||
int index = X509_NAME_get_index_by_NID(X509_get_subject_name(certificate), NID_commonName, -1);
|
||||
if (index >= 0) {
|
||||
X509_NAME_ENTRY* entry = X509_NAME_get_entry(X509_get_subject_name(certificate), index);
|
||||
if (entry) {
|
||||
ASN1_STRING* asn1 = X509_NAME_ENTRY_get_data(entry);
|
||||
if (asn1) {
|
||||
#if OPENSSL_VERSION_NUMBER <= 0x1000211fL
|
||||
const unsigned char* commonName = ASN1_STRING_data(asn1);
|
||||
#else
|
||||
const char* commonName = ASN1_STRING_get0_data(asn1);
|
||||
#endif
|
||||
if ((size_t)(ASN1_STRING_length(asn1)) == strlen((const char*)commonName)) {
|
||||
verified = _tls_session_wildcard_match((const char*)commonName, ASN1_STRING_length(asn1), hostname);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return verified;
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool _tls_session_verify_peer_certificate(tf_tls_session_t* session) {
|
||||
bool verified = false;
|
||||
X509* certificate = SSL_get_peer_certificate(session->ssl);
|
||||
if (certificate) {
|
||||
if (SSL_get_verify_result(session->ssl) == X509_V_OK) {
|
||||
#if OPENSSL_VERSION_NUMBER < 0x10100000L
|
||||
if (_tls_session_verify_hostname(certificate, session->hostname)) {
|
||||
verified = true;
|
||||
}
|
||||
#else
|
||||
verified = true;
|
||||
#endif
|
||||
}
|
||||
X509_free(certificate);
|
||||
}
|
||||
return verified;
|
||||
}
|
||||
#endif
|
||||
|
||||
tf_tls_handshake_t tf_tls_session_handshake(tf_tls_session_t* session) {
|
||||
#if defined(TF_TLS_OPENSSL)
|
||||
tf_tls_handshake_t result = k_tls_handshake_done;
|
||||
if (!SSL_is_init_finished(session->ssl)) {
|
||||
int value = SSL_do_handshake(session->ssl);
|
||||
if (value <= 0) {
|
||||
int error = SSL_get_error(session->ssl, value);
|
||||
if (error != SSL_ERROR_WANT_READ && error != SSL_ERROR_WANT_WRITE) {
|
||||
result = k_tls_handshake_failed;
|
||||
} else {
|
||||
result = k_tls_handshake_more;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (result == k_tls_handshake_done && session->direction == k_direction_connect && !_tls_session_verify_peer_certificate(session)) {
|
||||
result = k_tls_handshake_failed;
|
||||
}
|
||||
return result;
|
||||
#elif defined(TF_TLS_APPLE)
|
||||
#elif defined(TF_TLS_SCHANNEL)
|
||||
#endif
|
||||
}
|
||||
|
||||
int tf_tls_session_read_plain(tf_tls_session_t* session, char* buffer, size_t bytes) {
|
||||
#if defined(TF_TLS_OPENSSL)
|
||||
int result = SSL_read(session->ssl, buffer, bytes);
|
||||
if (result <= 0) {
|
||||
int error = SSL_get_error(session->ssl, result);
|
||||
if (error == SSL_ERROR_WANT_READ || error == SSL_ERROR_WANT_WRITE) {
|
||||
result = 0;
|
||||
} else if (error == SSL_ERROR_ZERO_RETURN) {
|
||||
if ((SSL_get_shutdown(session->ssl) & SSL_RECEIVED_SHUTDOWN) != 0) {
|
||||
result = k_tls_read_zero;
|
||||
} else {
|
||||
result = 0;
|
||||
}
|
||||
} else {
|
||||
result = k_tls_read_failed;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
#elif defined(TF_TLS_APPLE)
|
||||
#elif defined(TF_TLS_SCHANNEL)
|
||||
#endif
|
||||
}
|
||||
|
||||
int tf_tls_session_write_plain(tf_tls_session_t* session, const char* buffer, size_t bytes) {
|
||||
#if defined(TF_TLS_OPENSSL)
|
||||
return SSL_write(session->ssl, buffer, bytes);
|
||||
#elif defined(TF_TLS_APPLE)
|
||||
#elif defined(TF_TLS_SCHANNEL)
|
||||
#endif
|
||||
}
|
||||
|
||||
int tf_tls_session_read_encrypted(tf_tls_session_t* session, char* buffer, size_t bytes) {
|
||||
#if defined(TF_TLS_OPENSSL)
|
||||
return BIO_read(session->bio_out, buffer, bytes);
|
||||
#elif defined(TF_TLS_APPLE)
|
||||
#elif defined(TF_TLS_SCHANNEL)
|
||||
#endif
|
||||
}
|
||||
|
||||
int tf_tls_session_write_encrypted(tf_tls_session_t* session, const char* buffer, size_t bytes) {
|
||||
#if defined(TF_TLS_OPENSSL)
|
||||
return BIO_write(session->bio_in, buffer, bytes);
|
||||
#elif defined(TF_TLS_APPLE)
|
||||
#elif defined(TF_TLS_SCHANNEL)
|
||||
#endif
|
||||
}
|
||||
|
||||
bool tf_tls_session_get_error(tf_tls_session_t* session, char* buffer, size_t bytes) {
|
||||
#if defined(TF_TLS_OPENSSL)
|
||||
unsigned long error = ERR_get_error();
|
||||
if (error != 0) {
|
||||
ERR_error_string_n(error, buffer, bytes);
|
||||
}
|
||||
return error != 0;
|
||||
#elif defined(TF_TLS_APPLE)
|
||||
#elif defined(TF_TLS_SCHANNEL)
|
||||
#endif
|
||||
}
|
39
src/tls.h
Normal file
39
src/tls.h
Normal file
@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
|
||||
typedef struct _tf_tls_context_t tf_tls_context_t;
|
||||
typedef struct _tf_tls_session_t tf_tls_session_t;
|
||||
|
||||
typedef enum _tf_tls_handshake_t {
|
||||
k_tls_handshake_done,
|
||||
k_tls_handshake_more,
|
||||
k_tls_handshake_failed,
|
||||
} tf_tls_handshake_t;
|
||||
|
||||
typedef enum _tf_tls_read_t {
|
||||
k_tls_read_zero = -1,
|
||||
k_tls_read_failed = -2,
|
||||
} tf_tls_read_t;
|
||||
|
||||
tf_tls_context_t* tf_tls_context_create();
|
||||
bool tf_tls_context_set_certificate(tf_tls_context_t* context, const char* certificate);
|
||||
bool tf_tls_context_set_private_key(tf_tls_context_t* context, const char* private_key);
|
||||
bool tf_tls_context_add_trusted_certificate(tf_tls_context_t* context, const char* certificate);
|
||||
tf_tls_session_t* tf_tls_context_create_session(tf_tls_context_t* context);
|
||||
void tf_tls_context_destroy(tf_tls_context_t* context);
|
||||
|
||||
void tf_tls_session_destroy(tf_tls_session_t* session);
|
||||
void tf_tls_session_set_hostname(tf_tls_session_t* session, const char* hostname);
|
||||
void tf_tls_session_start_accept(tf_tls_session_t* session);
|
||||
void tf_tls_session_start_connect(tf_tls_session_t* session);
|
||||
void tf_tls_session_shutdown(tf_tls_session_t* session);
|
||||
int tf_tls_session_get_peer_certificate(tf_tls_session_t* session, char* buffer, size_t bytes);
|
||||
tf_tls_handshake_t tf_tls_session_handshake(tf_tls_session_t* session);
|
||||
|
||||
int tf_tls_session_read_plain(tf_tls_session_t* session, char* buffer, size_t bytes);
|
||||
int tf_tls_session_write_plain(tf_tls_session_t* session, const char* buffer, size_t bytes);
|
||||
int tf_tls_session_read_encrypted(tf_tls_session_t* session, char* buffer, size_t bytes);
|
||||
int tf_tls_session_write_encrypted(tf_tls_session_t* session, const char* buffer, size_t bytes);
|
||||
bool tf_tls_session_get_error(tf_tls_session_t* session, char* buffer, size_t bytes);
|
97
src/tlscontextwrapper.c
Normal file
97
src/tlscontextwrapper.c
Normal file
@ -0,0 +1,97 @@
|
||||
#include "tlscontextwrapper.h"
|
||||
|
||||
#include "task.h"
|
||||
#include "tls.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static JSClassID _classId;
|
||||
static int _count;
|
||||
|
||||
typedef struct _tf_tls_context_wrapper_t {
|
||||
tf_tls_context_t* context;
|
||||
tf_task_t* task;
|
||||
JSValue object;
|
||||
} tf_tls_context_wrapper_t;
|
||||
|
||||
static JSValue _tls_context_wrapper_set_certificate(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _tls_context_wrapper_set_private_key(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static JSValue _tls_context_wrapper_add_trusted_certificate(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
|
||||
static JSValue _tls_context_wrapper_create(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv);
|
||||
static void _tls_context_wrapper_finalizer(JSRuntime *runtime, JSValue value);
|
||||
|
||||
JSValue _tls_context_wrapper_set_certificate(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
tf_tls_context_wrapper_t* wrapper = JS_GetOpaque(this_val, _classId);
|
||||
const char* value = JS_ToCString(context, argv[0]);
|
||||
tf_tls_context_set_certificate(wrapper->context, value);
|
||||
JS_FreeCString(context, value);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
JSValue _tls_context_wrapper_set_private_key(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
tf_tls_context_wrapper_t* wrapper = JS_GetOpaque(this_val, _classId);
|
||||
const char* value = JS_ToCString(context, argv[0]);
|
||||
tf_tls_context_set_private_key(wrapper->context, value);
|
||||
JS_FreeCString(context, value);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
JSValue _tls_context_wrapper_add_trusted_certificate(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
tf_tls_context_wrapper_t* wrapper = JS_GetOpaque(this_val, _classId);
|
||||
const char* value = JS_ToCString(context, argv[0]);
|
||||
tf_tls_context_add_trusted_certificate(wrapper->context, value);
|
||||
JS_FreeCString(context, value);
|
||||
return JS_UNDEFINED;
|
||||
}
|
||||
|
||||
JSValue tf_tls_context_wrapper_init(JSContext* context) {
|
||||
JS_NewClassID(&_classId);
|
||||
JSClassDef def = {
|
||||
.class_name = "TlsContext",
|
||||
.finalizer = _tls_context_wrapper_finalizer,
|
||||
};
|
||||
if (JS_NewClass(JS_GetRuntime(context), _classId, &def) != 0) {
|
||||
fprintf(stderr, "Failed to register TlsContext.\n");
|
||||
}
|
||||
return JS_NewCFunction2(context, _tls_context_wrapper_create, "TlsContext", 0, JS_CFUNC_constructor, 0);
|
||||
}
|
||||
|
||||
tf_tls_context_t* tf_tls_context_wrapper_get(JSValue value) {
|
||||
tf_tls_context_wrapper_t* wrapper = JS_GetOpaque(value, _classId);
|
||||
return wrapper ? wrapper->context : NULL;
|
||||
}
|
||||
|
||||
int tf_tls_context_wrapper_get_count() {
|
||||
return _count;
|
||||
}
|
||||
|
||||
JSValue _tls_context_wrapper_create(JSContext* context, JSValueConst this_val, int argc, JSValueConst* argv) {
|
||||
tf_tls_context_wrapper_t* wrapper = malloc(sizeof(tf_tls_context_wrapper_t));
|
||||
memset(wrapper, 0, sizeof(*wrapper));
|
||||
|
||||
++_count;
|
||||
wrapper->object = JS_NewObjectClass(context, _classId);
|
||||
JS_SetOpaque(wrapper->object, wrapper);
|
||||
|
||||
JS_SetPropertyStr(context, wrapper->object, "setCertificate", JS_NewCFunction(context, _tls_context_wrapper_set_certificate, "setCertificate", 1));
|
||||
JS_SetPropertyStr(context, wrapper->object, "setPrivateKey", JS_NewCFunction(context, _tls_context_wrapper_set_private_key, "setPrivateKey", 1));
|
||||
JS_SetPropertyStr(context, wrapper->object, "addTrustedCertificate", JS_NewCFunction(context, _tls_context_wrapper_add_trusted_certificate, "addTrustedCertificate", 1));
|
||||
|
||||
wrapper->context = tf_tls_context_create();
|
||||
wrapper->task = tf_task_get(context);
|
||||
|
||||
return wrapper->object;
|
||||
}
|
||||
|
||||
void _tls_context_wrapper_finalizer(JSRuntime *runtime, JSValue value) {
|
||||
tf_tls_context_wrapper_t* wrapper = JS_GetOpaque(value, _classId);
|
||||
if (wrapper->context) {
|
||||
tf_tls_context_destroy(wrapper->context);
|
||||
wrapper->context = NULL;
|
||||
}
|
||||
--_count;
|
||||
free(wrapper);
|
||||
}
|
9
src/tlscontextwrapper.h
Normal file
9
src/tlscontextwrapper.h
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "quickjs.h"
|
||||
|
||||
typedef struct _tf_tls_context_t tf_tls_context_t;
|
||||
|
||||
JSValue tf_tls_context_wrapper_init(JSContext* context);
|
||||
tf_tls_context_t* tf_tls_context_wrapper_get(JSValue value);
|
||||
int tf_tls_context_wrapper_get_count();
|
166
src/trace.c
Normal file
166
src/trace.c
Normal file
@ -0,0 +1,166 @@
|
||||
#include "trace.h"
|
||||
|
||||
#include <assert.h>
|
||||
#include <malloc.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
||||
enum {
|
||||
k_buffer_size = 4 * 1024 * 1024,
|
||||
};
|
||||
|
||||
typedef struct _tf_trace_t
|
||||
{
|
||||
char buffer[k_buffer_size];
|
||||
int write_offset;
|
||||
} tf_trace_t;
|
||||
|
||||
tf_trace_t* tf_trace_create()
|
||||
{
|
||||
tf_trace_t* trace = malloc(sizeof(tf_trace_t));
|
||||
memset(trace, 0, sizeof(*trace));
|
||||
return trace;
|
||||
}
|
||||
|
||||
void tf_trace_destroy(tf_trace_t* trace)
|
||||
{
|
||||
free(trace);
|
||||
}
|
||||
|
||||
static int64_t _trace_ts()
|
||||
{
|
||||
int64_t result = 0;
|
||||
struct timespec ts;
|
||||
if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) {
|
||||
result = (ts.tv_sec * 1e9 + ts.tv_nsec) / 1e3;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void _trace_append(tf_trace_t* trace, const char* buffer, size_t size)
|
||||
{
|
||||
if (trace->write_offset + size >= k_buffer_size) {
|
||||
trace->buffer[trace->write_offset] = '\0';
|
||||
trace->write_offset = 0;
|
||||
}
|
||||
|
||||
if (trace->write_offset + size < k_buffer_size) {
|
||||
memcpy(trace->buffer + trace->write_offset, buffer, size);
|
||||
trace->write_offset += size;
|
||||
trace->buffer[trace->write_offset++] = '\n';
|
||||
}
|
||||
}
|
||||
|
||||
void tf_trace_counter(tf_trace_t* trace, const char* name, int argc, const char** arg_names, const int64_t* arg_values)
|
||||
{
|
||||
if (!trace) {
|
||||
return;
|
||||
}
|
||||
|
||||
char line[1024];
|
||||
int p = 0;
|
||||
p += snprintf(line + p, sizeof(line) - p, "{\"ph\": \"C\", \"pid\": %d, \"ts\": %" PRId64 ", \"name\": \"%s\", \"args\": {", getpid(), _trace_ts(), name);
|
||||
for (int i = 0; i < argc; i++)
|
||||
{
|
||||
p += snprintf(line + p, sizeof(line) - p, "\"%s\": %" PRId64 "%s", arg_names[i], arg_values[i], i == argc - 1 ? "}}," : ", ");
|
||||
}
|
||||
|
||||
_trace_append(trace, line, p);
|
||||
}
|
||||
|
||||
void tf_trace_begin(tf_trace_t* trace, const char* name)
|
||||
{
|
||||
if (!trace) {
|
||||
return;
|
||||
}
|
||||
|
||||
char line[1024];
|
||||
int p = snprintf(line, sizeof(line), "{\"ph\": \"B\", \"pid\": %d, \"ts\": %" PRId64 ", \"name\": \"", getpid(), _trace_ts());
|
||||
for (const char* c = name; *c && p < (int)sizeof(line); c++) {
|
||||
switch (*c) {
|
||||
case '"':
|
||||
case '\\':
|
||||
line[p++] = '\\';
|
||||
if (p < (int)sizeof(line)) {
|
||||
line[p++] = *c;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
line[p++] = *c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
p += snprintf(line + p, sizeof(line) - p, "\"},");
|
||||
_trace_append(trace, line, p);
|
||||
}
|
||||
|
||||
void tf_trace_end(tf_trace_t* trace)
|
||||
{
|
||||
if (!trace) {
|
||||
return;
|
||||
}
|
||||
|
||||
char line[1024];
|
||||
int p = snprintf(line, sizeof(line), "{\"ph\": \"E\", \"pid\": %d, \"ts\": %" PRId64 "},", getpid(), _trace_ts());
|
||||
_trace_append(trace, line, p);
|
||||
}
|
||||
|
||||
char* tf_trace_export(tf_trace_t* trace)
|
||||
{
|
||||
if (!trace) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const int k_extra_size = 1024;
|
||||
char* buffer = malloc(k_buffer_size + k_extra_size);
|
||||
const char* newline = strchr(trace->buffer + trace->write_offset, '\n');
|
||||
int begin = newline ? newline - trace->buffer : 0;
|
||||
size_t size = 0;
|
||||
size += snprintf(buffer, k_buffer_size, "{\"displayTimeUnit\": \"ns\",\n\"traceEvents\": [\n");
|
||||
if (begin) {
|
||||
size_t this_size = strlen(trace->buffer + begin);
|
||||
memcpy(buffer + size, trace->buffer + begin, this_size);
|
||||
size += this_size;
|
||||
}
|
||||
memcpy(buffer + size, trace->buffer, trace->write_offset);
|
||||
size += trace->write_offset;
|
||||
if (size > 2 && buffer[size - 1] == '\n' && buffer[size - 2] == ',') {
|
||||
buffer[size - 2] = '\n';
|
||||
size--;
|
||||
}
|
||||
size += snprintf(buffer + size, k_buffer_size - size, "]}\n");
|
||||
buffer[size] = '\0';
|
||||
assert(size < (size_t)k_buffer_size + k_extra_size);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
static int _tf_trace_sqlite_callback(unsigned int t, void* c, void* p, void* x)
|
||||
{
|
||||
tf_trace_t* trace = c;
|
||||
switch (t) {
|
||||
case SQLITE_TRACE_STMT:
|
||||
{
|
||||
const char* statement = x;
|
||||
if (statement[0] != '-' || statement[1] != '-') {
|
||||
tf_trace_begin(trace, statement);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case SQLITE_TRACE_PROFILE:
|
||||
tf_trace_end(trace);
|
||||
break;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void tf_trace_sqlite(tf_trace_t* trace, sqlite3* sqlite)
|
||||
{
|
||||
if (sqlite) {
|
||||
sqlite3_trace_v2(sqlite, SQLITE_TRACE_STMT | SQLITE_TRACE_PROFILE, _tf_trace_sqlite_callback, trace);
|
||||
} else {
|
||||
sqlite3_trace_v2(sqlite, 0, NULL, NULL);
|
||||
}
|
||||
}
|
17
src/trace.h
Normal file
17
src/trace.h
Normal file
@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
typedef struct _tf_trace_t tf_trace_t;
|
||||
typedef struct sqlite3 sqlite3;
|
||||
|
||||
tf_trace_t* tf_trace_create();
|
||||
void tf_trace_destroy(tf_trace_t* trace);
|
||||
|
||||
void tf_trace_counter(tf_trace_t* trace, const char* name, int argc, const char** arg_names, const int64_t* arg_values);
|
||||
void tf_trace_begin(tf_trace_t* trace, const char* name);
|
||||
void tf_trace_end(tf_trace_t* trace);
|
||||
|
||||
char* tf_trace_export(tf_trace_t* trace);
|
||||
|
||||
void tf_trace_sqlite(tf_trace_t* trace, sqlite3* sqlite);
|
Reference in New Issue
Block a user