#include "rtc_v8_bindings.h" #include "rtc_bridge.h" #include #include #include #include #include #include using namespace v8; static Isolate *g_rtc_isolate = nullptr; static std::map> &g_desc_cbs = *new std::map>; static std::map> &g_cand_cbs = *new std::map>; static std::map> &g_state_cbs = *new std::map>; static std::map> &g_gathering_cbs = *new std::map>; static std::map> &g_dc_cbs = *new std::map>; static std::map> &g_open_cbs = *new std::map>; static std::map> &g_closed_cbs = *new std::map>; static std::map> &g_error_cbs = *new std::map>; static std::map> &g_msg_cbs = *new std::map>; static std::map> &g_pc_dcs = *new std::map>; static std::map &g_dc_to_pc = *new std::map; static Global *g_rtc_context = nullptr; static void rtc_throw(Isolate *isolate, const char *msg) { isolate->ThrowException( Exception::TypeError(String::NewFromUtf8(isolate, msg).ToLocalChecked())); } static bool rtc_store_cb(const FunctionCallbackInfo &args, std::map> &cbs, const char *name) { Isolate *isolate = args.GetIsolate(); if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsFunction()) { rtc_throw(isolate, name); return false; } int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); cbs[id].Reset(isolate, args[1].As()); return true; } static void rtc_drop_cbs(int id) { g_desc_cbs.erase(id); g_cand_cbs.erase(id); g_state_cbs.erase(id); g_gathering_cbs.erase(id); g_dc_cbs.erase(id); g_open_cbs.erase(id); g_closed_cbs.erase(id); g_error_cbs.erase(id); g_msg_cbs.erase(id); } static void rtc_track_dc(int pc, int dc) { g_pc_dcs[pc].push_back(dc); g_dc_to_pc[dc] = pc; } static void rtc_forget_dc(int dc) { auto it = g_dc_to_pc.find(dc); if (it == g_dc_to_pc.end()) return; auto &dcs = g_pc_dcs[it->second]; dcs.erase(std::remove(dcs.begin(), dcs.end(), dc), dcs.end()); g_dc_to_pc.erase(it); } static void rtc_call(Persistent &cb, int argc, Local *argv) { if (cb.IsEmpty()) return; Isolate *isolate = g_rtc_isolate; Local ctx = isolate->GetCurrentContext(); Local fn = cb.Get(isolate); TryCatch try_catch(isolate); fn->Call(ctx, Null(isolate), argc, argv).IsEmpty(); if (try_catch.HasCaught()) { String::Utf8Value err(isolate, try_catch.Exception()); kinc_log(KINC_LOG_LEVEL_WARNING, "runt_rtc callback threw: %s", *err != nullptr ? *err : "unknown"); } } static Local rtc_str(Isolate *isolate, const std::string &s) { return String::NewFromUtf8(isolate, s.data(), NewStringType::kNormal, (int)s.size()) .ToLocalChecked(); } void v8_runt_rtc_init(const FunctionCallbackInfo &args) { Isolate *isolate = args.GetIsolate(); g_rtc_isolate = isolate; args.GetReturnValue().Set(Integer::New(isolate, runt_rtc_init())); } void v8_runt_rtc_cleanup(const FunctionCallbackInfo &args) { runt_rtc_cleanup(); for (auto *m : {&g_desc_cbs, &g_cand_cbs, &g_state_cbs, &g_gathering_cbs, &g_dc_cbs, &g_open_cbs, &g_closed_cbs, &g_error_cbs, &g_msg_cbs}) { for (auto &kv : *m) kv.second.Reset(); m->clear(); } g_pc_dcs.clear(); g_dc_to_pc.clear(); RtcEvent ev; while (runt_rtc_poll_event(ev)) {} args.GetReturnValue().Set(Undefined(args.GetIsolate())); } void v8_runt_rtc_shutdown_all() { std::set dcs; for (auto &kv : g_dc_to_pc) dcs.insert(kv.first); for (auto &kv : g_msg_cbs) dcs.insert(kv.first); for (auto &kv : g_open_cbs) dcs.insert(kv.first); for (auto &kv : g_closed_cbs) dcs.insert(kv.first); for (auto &kv : g_error_cbs) dcs.insert(kv.first); for (int dc : dcs) runt_rtc_delete_data_channel(dc); std::set pcs; for (auto &kv : g_pc_dcs) pcs.insert(kv.first); for (auto &kv : g_desc_cbs) pcs.insert(kv.first); for (auto &kv : g_cand_cbs) pcs.insert(kv.first); for (auto &kv : g_state_cbs) pcs.insert(kv.first); for (auto &kv : g_gathering_cbs) pcs.insert(kv.first); for (auto &kv : g_dc_cbs) pcs.insert(kv.first); for (int pc : pcs) runt_rtc_delete_peer_connection(pc); for (auto *m : {&g_desc_cbs, &g_cand_cbs, &g_state_cbs, &g_gathering_cbs, &g_dc_cbs, &g_open_cbs, &g_closed_cbs, &g_error_cbs, &g_msg_cbs}) { for (auto &kv : *m) kv.second.Reset(); m->clear(); } g_pc_dcs.clear(); g_dc_to_pc.clear(); RtcEvent ev; while (runt_rtc_poll_event(ev)) {} runt_rtc_cleanup(); } void v8_runt_rtc_create_peer_connection(const FunctionCallbackInfo &args) { Isolate *isolate = args.GetIsolate(); std::vector iceServers; if (args.Length() >= 1 && args[0]->IsArray()) { Local arr = args[0].As(); Local ctx = isolate->GetCurrentContext(); for (uint32_t i = 0; i < arr->Length(); i++) { Local el = arr->Get(ctx, i).ToLocalChecked(); if (el->IsString()) { String::Utf8Value s(isolate, el); iceServers.push_back(*s); } } } int pc = runt_rtc_create_peer_connection(iceServers); args.GetReturnValue().Set(Integer::New(isolate, pc)); } void v8_runt_rtc_close_peer_connection(const FunctionCallbackInfo &args) { if (args.Length() < 1 || !args[0]->IsNumber()) return; int pc = args[0]->Int32Value(args.GetIsolate()->GetCurrentContext()).FromJust(); runt_rtc_close_peer_connection(pc); } void v8_runt_rtc_delete_peer_connection(const FunctionCallbackInfo &args) { if (args.Length() < 1 || !args[0]->IsNumber()) return; int pc = args[0]->Int32Value(args.GetIsolate()->GetCurrentContext()).FromJust(); runt_rtc_delete_peer_connection(pc); rtc_drop_cbs(pc); auto it = g_pc_dcs.find(pc); if (it != g_pc_dcs.end()) { for (int dc : it->second) { rtc_drop_cbs(dc); g_dc_to_pc.erase(dc); } g_pc_dcs.erase(it); } } void v8_runt_rtc_set_local_description(const FunctionCallbackInfo &args) { Isolate *isolate = args.GetIsolate(); if (args.Length() < 1 || !args[0]->IsNumber()) return; int pc = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); const char *type = nullptr; if (args.Length() >= 2 && args[1]->IsString()) { String::Utf8Value typeStr(isolate, args[1]); type = *typeStr; } runt_rtc_set_local_description(pc, type); } void v8_runt_rtc_set_remote_description(const FunctionCallbackInfo &args) { Isolate *isolate = args.GetIsolate(); if (args.Length() < 3 || !args[0]->IsNumber() || !args[1]->IsString() || !args[2]->IsString()) { rtc_throw(isolate, "runt_rtc_set_remote_description requires (pc, sdp, type)"); return; } int pc = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); String::Utf8Value sdp(isolate, args[1]); String::Utf8Value type(isolate, args[2]); int ret = runt_rtc_set_remote_description(pc, *sdp, *type); args.GetReturnValue().Set(Integer::New(isolate, ret)); } void v8_runt_rtc_add_remote_candidate(const FunctionCallbackInfo &args) { Isolate *isolate = args.GetIsolate(); if (args.Length() < 3 || !args[0]->IsNumber() || !args[1]->IsString() || !args[2]->IsString()) { rtc_throw(isolate, "runt_rtc_add_remote_candidate requires (pc, cand, mid)"); return; } int pc = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); String::Utf8Value cand(isolate, args[1]); String::Utf8Value mid(isolate, args[2]); int ret = runt_rtc_add_remote_candidate(pc, *cand, *mid); args.GetReturnValue().Set(Integer::New(isolate, ret)); } void v8_runt_rtc_get_local_description(const FunctionCallbackInfo &args) { Isolate *isolate = args.GetIsolate(); if (args.Length() < 1 || !args[0]->IsNumber()) return; int pc = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); args.GetReturnValue().Set( rtc_str(isolate, runt_rtc_get_local_description(pc))); } void v8_runt_rtc_get_local_description_type( const FunctionCallbackInfo &args) { Isolate *isolate = args.GetIsolate(); if (args.Length() < 1 || !args[0]->IsNumber()) return; int pc = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); args.GetReturnValue().Set( rtc_str(isolate, runt_rtc_get_local_description_type(pc))); } void v8_runt_rtc_create_data_channel(const FunctionCallbackInfo &args) { Isolate *isolate = args.GetIsolate(); if (args.Length() < 2 || !args[0]->IsNumber() || !args[1]->IsString()) { rtc_throw(isolate, "runt_rtc_create_data_channel requires (pc, label)"); return; } int pc = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); String::Utf8Value label(isolate, args[1]); bool unordered = false; int maxRetransmits = -1; if (args.Length() >= 3 && args[2]->IsBoolean()) { unordered = args[2]->BooleanValue(isolate); } if (args.Length() >= 4 && args[3]->IsNumber()) { maxRetransmits = args[3]->Int32Value(isolate->GetCurrentContext()).FromJust(); } int dc = runt_rtc_create_data_channel(pc, *label, unordered, maxRetransmits); if (dc >= 0) rtc_track_dc(pc, dc); args.GetReturnValue().Set(Integer::New(isolate, dc)); } void v8_runt_rtc_delete_data_channel(const FunctionCallbackInfo &args) { if (args.Length() < 1 || !args[0]->IsNumber()) return; int dc = args[0]->Int32Value(args.GetIsolate()->GetCurrentContext()).FromJust(); runt_rtc_delete_data_channel(dc); rtc_drop_cbs(dc); rtc_forget_dc(dc); } void v8_runt_rtc_send_message(const FunctionCallbackInfo &args) { Isolate *isolate = args.GetIsolate(); if (args.Length() < 2 || !args[0]->IsNumber()) { rtc_throw(isolate, "runt_rtc_send_message requires (id, data)"); return; } int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); int ret = -1; if (args[1]->IsArrayBuffer()) { Local buf = args[1].As(); ret = runt_rtc_send_message( id, static_cast(buf->GetBackingStore()->Data()), (int)buf->ByteLength()); } else if (args[1]->IsArrayBufferView()) { Local view = args[1].As(); Local buf = view->Buffer(); const char *data = static_cast(buf->GetBackingStore()->Data()) + view->ByteOffset(); ret = runt_rtc_send_message(id, data, (int)view->ByteLength()); } else if (args[1]->IsString()) { String::Utf8Value s(isolate, args[1]); ret = runt_rtc_send_message(id, *s, -1); } else { rtc_throw(isolate, "runt_rtc_send_message data must be String or ArrayBuffer"); return; } args.GetReturnValue().Set(Integer::New(isolate, ret)); } void v8_runt_rtc_close(const FunctionCallbackInfo &args) { if (args.Length() < 1 || !args[0]->IsNumber()) return; int id = args[0]->Int32Value(args.GetIsolate()->GetCurrentContext()).FromJust(); runt_rtc_close(id); } void v8_runt_rtc_delete(const FunctionCallbackInfo &args) { if (args.Length() < 1 || !args[0]->IsNumber()) return; int id = args[0]->Int32Value(args.GetIsolate()->GetCurrentContext()).FromJust(); runt_rtc_delete(id); rtc_drop_cbs(id); rtc_forget_dc(id); } void v8_runt_rtc_is_open(const FunctionCallbackInfo &args) { Isolate *isolate = args.GetIsolate(); if (args.Length() < 1 || !args[0]->IsNumber()) return; int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); args.GetReturnValue().Set(Boolean::New(isolate, runt_rtc_is_open(id))); } void v8_runt_rtc_get_buffered_amount(const FunctionCallbackInfo &args) { Isolate *isolate = args.GetIsolate(); if (args.Length() < 1 || !args[0]->IsNumber()) return; int id = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); args.GetReturnValue().Set( Integer::New(isolate, runt_rtc_get_buffered_amount(id))); } void v8_runt_rtc_set_local_description_callback( const FunctionCallbackInfo &args) { rtc_store_cb(args, g_desc_cbs, "runt_rtc_set_local_description_callback requires (pc, fn)"); } void v8_runt_rtc_set_local_candidate_callback( const FunctionCallbackInfo &args) { rtc_store_cb(args, g_cand_cbs, "runt_rtc_set_local_candidate_callback requires (pc, fn)"); } void v8_runt_rtc_set_state_change_callback( const FunctionCallbackInfo &args) { rtc_store_cb(args, g_state_cbs, "runt_rtc_set_state_change_callback requires (pc, fn)"); } void v8_runt_rtc_set_gathering_state_callback( const FunctionCallbackInfo &args) { rtc_store_cb(args, g_gathering_cbs, "runt_rtc_set_gathering_state_callback requires (pc, fn)"); } void v8_runt_rtc_set_data_channel_callback( const FunctionCallbackInfo &args) { rtc_store_cb(args, g_dc_cbs, "runt_rtc_set_data_channel_callback requires (pc, fn)"); } void v8_runt_rtc_set_open_callback(const FunctionCallbackInfo &args) { rtc_store_cb(args, g_open_cbs, "runt_rtc_set_open_callback requires (id, fn)"); } void v8_runt_rtc_set_closed_callback(const FunctionCallbackInfo &args) { rtc_store_cb(args, g_closed_cbs, "runt_rtc_set_closed_callback requires (id, fn)"); } void v8_runt_rtc_set_error_callback(const FunctionCallbackInfo &args) { rtc_store_cb(args, g_error_cbs, "runt_rtc_set_error_callback requires (id, fn)"); } void v8_runt_rtc_set_message_callback(const FunctionCallbackInfo &args) { rtc_store_cb(args, g_msg_cbs, "runt_rtc_set_message_callback requires (id, fn)"); } void v8_runt_rtc_random_bytes(const FunctionCallbackInfo &args) { Isolate *isolate = args.GetIsolate(); if (args.Length() < 1 || !args[0]->IsNumber()) { rtc_throw(isolate, "runt_rtc_random_bytes requires (len)"); return; } int len = args[0]->Int32Value(isolate->GetCurrentContext()).FromJust(); if (len <= 0) { args.GetReturnValue().Set(Null(isolate)); return; } Local buf = ArrayBuffer::New(isolate, len); if (!runt_rtc_random_bytes( static_cast(buf->GetBackingStore()->Data()), len)) { args.GetReturnValue().Set(Null(isolate)); return; } args.GetReturnValue().Set(buf); } static void rtc_drain_events() { Isolate *isolate = g_rtc_isolate; RtcEvent ev; while (runt_rtc_poll_event(ev)) { switch (ev.type) { case RtcEventType::LocalDescription: { auto it = g_desc_cbs.find(ev.id); if (it != g_desc_cbs.end()) { Local argv[2] = {rtc_str(isolate, ev.s1), rtc_str(isolate, ev.s2)}; rtc_call(it->second, 2, argv); } break; } case RtcEventType::LocalCandidate: { auto it = g_cand_cbs.find(ev.id); if (it != g_cand_cbs.end()) { Local argv[2] = {rtc_str(isolate, ev.s1), rtc_str(isolate, ev.s2)}; rtc_call(it->second, 2, argv); } break; } case RtcEventType::StateChange: { auto it = g_state_cbs.find(ev.id); if (it != g_state_cbs.end()) { Local argv[1] = {Integer::New(isolate, ev.arg)}; rtc_call(it->second, 1, argv); } break; } case RtcEventType::GatheringState: { auto it = g_gathering_cbs.find(ev.id); if (it != g_gathering_cbs.end()) { Local argv[1] = {Integer::New(isolate, ev.arg)}; rtc_call(it->second, 1, argv); } break; } case RtcEventType::DataChannel: { rtc_track_dc(ev.id, ev.arg); auto it = g_dc_cbs.find(ev.id); if (it != g_dc_cbs.end()) { Local argv[1] = {Integer::New(isolate, ev.arg)}; rtc_call(it->second, 1, argv); } break; } case RtcEventType::Open: { auto it = g_open_cbs.find(ev.id); if (it != g_open_cbs.end()) { rtc_call(it->second, 0, nullptr); } break; } case RtcEventType::Closed: { auto it = g_closed_cbs.find(ev.id); if (it != g_closed_cbs.end()) { rtc_call(it->second, 0, nullptr); } rtc_drop_cbs(ev.id); rtc_forget_dc(ev.id); break; } case RtcEventType::Error: { auto it = g_error_cbs.find(ev.id); if (it != g_error_cbs.end()) { Local argv[1] = {rtc_str(isolate, ev.s1)}; rtc_call(it->second, 1, argv); } break; } case RtcEventType::Message: { auto it = g_msg_cbs.find(ev.id); if (it != g_msg_cbs.end()) { Local arg; if (ev.isString) { arg = rtc_str(isolate, ev.s1); } else { // move the payload into the backing store (no copy) auto *payload = new std::vector(std::move(ev.data)); auto backing = ArrayBuffer::NewBackingStore( payload->data(), payload->size(), [](void *, size_t, void *deleter_data) { delete static_cast *>(deleter_data); }, payload); arg = ArrayBuffer::New(isolate, std::move(backing)); } Local argv[1] = {arg}; rtc_call(it->second, 1, argv); } break; } } } } void runt_rtc_process_events_native() { if (g_rtc_isolate == nullptr || g_rtc_context == nullptr) return; Locker locker{g_rtc_isolate}; Isolate::Scope isolate_scope(g_rtc_isolate); HandleScope handle_scope(g_rtc_isolate); Local ctx = Local::New(g_rtc_isolate, *g_rtc_context); Context::Scope context_scope(ctx); rtc_drain_events(); } void bind_rtc_bridge(Isolate *isolate, const Global &context) { Locker locker{isolate}; Isolate::Scope isolate_scope(isolate); HandleScope handle_scope(isolate); Local current_context = Local::New(isolate, context); Context::Scope context_scope(current_context); Local global = current_context->Global(); g_rtc_isolate = isolate; if (g_rtc_context == nullptr) { g_rtc_context = new Global(isolate, context); } Local runt_obj; Local existing_runt = global->Get(current_context, String::NewFromUtf8(isolate, "RunT").ToLocalChecked()) .ToLocalChecked(); if (existing_runt->IsObject()) { runt_obj = existing_runt->ToObject(current_context).ToLocalChecked(); } else { runt_obj = Object::New(isolate); global->Set(current_context, String::NewFromUtf8(isolate, "RunT").ToLocalChecked(), runt_obj); } struct RtcBinding { const char *global_name; const char *runt_name; FunctionCallback fn; }; static const RtcBinding bindings[] = { {"runt_rtc_init", "rtcInit", v8_runt_rtc_init}, {"runt_rtc_cleanup", "rtcCleanup", v8_runt_rtc_cleanup}, {"runt_rtc_create_peer_connection", "rtcCreatePeerConnection", v8_runt_rtc_create_peer_connection}, {"runt_rtc_close_peer_connection", "rtcClosePeerConnection", v8_runt_rtc_close_peer_connection}, {"runt_rtc_delete_peer_connection", "rtcDeletePeerConnection", v8_runt_rtc_delete_peer_connection}, {"runt_rtc_set_local_description", "rtcSetLocalDescription", v8_runt_rtc_set_local_description}, {"runt_rtc_set_remote_description", "rtcSetRemoteDescription", v8_runt_rtc_set_remote_description}, {"runt_rtc_add_remote_candidate", "rtcAddRemoteCandidate", v8_runt_rtc_add_remote_candidate}, {"runt_rtc_get_local_description", "rtcGetLocalDescription", v8_runt_rtc_get_local_description}, {"runt_rtc_get_local_description_type", "rtcGetLocalDescriptionType", v8_runt_rtc_get_local_description_type}, {"runt_rtc_create_data_channel", "rtcCreateDataChannel", v8_runt_rtc_create_data_channel}, {"runt_rtc_delete_data_channel", "rtcDeleteDataChannel", v8_runt_rtc_delete_data_channel}, {"runt_rtc_send_message", "rtcSendMessage", v8_runt_rtc_send_message}, {"runt_rtc_close", "rtcClose", v8_runt_rtc_close}, {"runt_rtc_delete", "rtcDelete", v8_runt_rtc_delete}, {"runt_rtc_is_open", "rtcIsOpen", v8_runt_rtc_is_open}, {"runt_rtc_get_buffered_amount", "rtcGetBufferedAmount", v8_runt_rtc_get_buffered_amount}, {"runt_rtc_set_local_description_callback", "rtcSetLocalDescriptionCallback", v8_runt_rtc_set_local_description_callback}, {"runt_rtc_set_local_candidate_callback", "rtcSetLocalCandidateCallback", v8_runt_rtc_set_local_candidate_callback}, {"runt_rtc_set_state_change_callback", "rtcSetStateChangeCallback", v8_runt_rtc_set_state_change_callback}, {"runt_rtc_set_gathering_state_callback", "rtcSetGatheringStateCallback", v8_runt_rtc_set_gathering_state_callback}, {"runt_rtc_set_data_channel_callback", "rtcSetDataChannelCallback", v8_runt_rtc_set_data_channel_callback}, {"runt_rtc_set_open_callback", "rtcSetOpenCallback", v8_runt_rtc_set_open_callback}, {"runt_rtc_set_closed_callback", "rtcSetClosedCallback", v8_runt_rtc_set_closed_callback}, {"runt_rtc_set_error_callback", "rtcSetErrorCallback", v8_runt_rtc_set_error_callback}, {"runt_rtc_set_message_callback", "rtcSetMessageCallback", v8_runt_rtc_set_message_callback}, {"runt_rtc_random_bytes", "rtcRandomBytes", v8_runt_rtc_random_bytes}, }; for (const RtcBinding &b : bindings) { Local fn = Function::New(current_context, b.fn).ToLocalChecked(); global->Set(current_context, String::NewFromUtf8(isolate, b.global_name).ToLocalChecked(), fn); runt_obj->Set(current_context, String::NewFromUtf8(isolate, b.runt_name).ToLocalChecked(), fn); } }