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:
2021-01-02 18:10:00 +00:00
parent d293637741
commit 79022e1e1f
703 changed files with 419987 additions and 30640 deletions

View File

@ -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());
}

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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();
}

View File

@ -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

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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

View File

@ -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();
}

View File

@ -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

View File

@ -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()))));
}
}
}
}

View File

@ -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

View File

@ -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));
}
}

View File

@ -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

View File

@ -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();
}

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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
View 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
View File

@ -0,0 +1,5 @@
#pragma once
#include "quickjs.h"
void tf_bcrypt_init(JSContext* context);

154
src/database.c Normal file
View 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
View 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
View 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
View File

@ -0,0 +1,5 @@
#pragma once
#include "quickjs.h"
void tf_file_init(JSContext* context);

457
src/main.c Normal file
View 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);
}

View File

@ -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
View 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
View 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);

View File

@ -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
View 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
View 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
View 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
View 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();

2344
src/ssb.c Normal file

File diff suppressed because it is too large Load Diff

162
src/ssb.connections.c Normal file
View 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
View 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
View 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
View 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, &timestamp, &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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

68
src/task.h Normal file
View 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
View 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
View 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);

View File

@ -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
View 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
View 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
View 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
View 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
View 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);