ZaStoGram/TMessagesProj/jni/voip/tgcalls/v2wasm/WamrCoreBackend.cpp
2026-09-24 16:17:30 +04:00

223 lines
8 KiB
C++

#include "v2wasm/WamrCoreBackend.h"
#include <cstdio>
#include <cstring>
#include <fcntl.h>
#include <unistd.h>
#include "v2wasm/CallCoreABI.h"
#include "rtc_base/logging.h"
namespace tgcalls {
namespace {
bool ensureWamrRuntime() {
static bool initialized = []() {
RuntimeInitArgs initArgs;
memset(&initArgs, 0, sizeof(initArgs));
initArgs.mem_alloc_type = Alloc_With_System_Allocator;
// "(*~)" / "(*~*~)i": WAMR validates each (ptr, len) buffer range
// inside module memory and passes native pointers — the
// trust-boundary check. Registered directly against the class's
// public static trampolines (see WamrCoreBackend.h) — no separate
// extern "C" thunks are needed since NativeSymbol::func_ptr is a
// plain void*.
static NativeSymbol nativeSymbols[] = {
{ "host_emit", (void *)WamrCoreBackend::hostEmitNative, "(*~)", nullptr },
{ "host_deflate", (void *)WamrCoreBackend::hostDeflateNative, "(*~*~)i", nullptr },
{ "host_inflate", (void *)WamrCoreBackend::hostInflateNative, "(*~*~)i", nullptr },
};
initArgs.native_module_name = "env";
initArgs.native_symbols = nativeSymbols;
initArgs.n_native_symbols = 3;
return wasm_runtime_full_init(&initArgs);
}();
return initialized;
}
} // namespace
void WamrCoreBackend::hostEmitNative(wasm_exec_env_t execEnv, uint8_t *data, uint32_t len) {
auto instance = wasm_runtime_get_module_inst(execEnv);
auto backend = static_cast<WamrCoreBackend *>(wasm_runtime_get_custom_data(instance));
if (backend && backend->_emit) {
backend->_emit(data, len);
}
}
int32_t WamrCoreBackend::hostDeflateNative(wasm_exec_env_t execEnv, uint8_t *in, uint32_t inLen, uint8_t *out, uint32_t outCap) {
(void)execEnv; // stateless service; buffers already range-validated by WAMR
return tgcalls_host_deflate(in, inLen, out, outCap);
}
int32_t WamrCoreBackend::hostInflateNative(wasm_exec_env_t execEnv, uint8_t *in, uint32_t inLen, uint8_t *out, uint32_t outCap) {
(void)execEnv;
return tgcalls_host_inflate(in, inLen, out, outCap);
}
#if TGCALLS_ALLOW_EXTERNAL_WASM_CORE
WamrCoreBackend::WamrCoreBackend(std::string modulePath) :
_modulePath(std::move(modulePath)) {
}
#endif
WamrCoreBackend::WamrCoreBackend(const uint8_t *moduleBytes, size_t moduleSize) :
_moduleBytes(moduleBytes, moduleBytes + moduleSize) {
}
WamrCoreBackend::~WamrCoreBackend() {
if (_execEnv) {
wasm_runtime_destroy_exec_env(_execEnv);
}
if (_instance) {
wasm_runtime_deinstantiate(_instance);
}
if (_module) {
wasm_runtime_unload(_module);
}
if (_devNullFd >= 0) {
close(_devNullFd);
}
// The process-wide runtime stays initialized (shared across calls).
}
#if TGCALLS_ALLOW_EXTERNAL_WASM_CORE
bool WamrCoreBackend::readModuleFromFile() {
FILE *file = fopen(_modulePath.c_str(), "rb");
if (!file) {
RTC_LOG(LS_ERROR) << "WamrCoreBackend: cannot open module: " << _modulePath;
return false;
}
fseek(file, 0, SEEK_END);
const long size = ftell(file);
if (size <= 0) {
fclose(file);
RTC_LOG(LS_ERROR) << "WamrCoreBackend: cannot determine module size: " << _modulePath;
return false;
}
fseek(file, 0, SEEK_SET);
_moduleBytes.resize((size_t)size);
const size_t read = fread(_moduleBytes.data(), 1, (size_t)size, file);
fclose(file);
if (read != (size_t)size) {
RTC_LOG(LS_ERROR) << "WamrCoreBackend: short read of module";
return false;
}
return true;
}
#endif
bool WamrCoreBackend::create(std::string const &configJson, EmitFn emit) {
_emit = std::move(emit);
if (!ensureWamrRuntime()) {
RTC_LOG(LS_ERROR) << "WamrCoreBackend: runtime init failed";
return false;
}
#if TGCALLS_ALLOW_EXTERNAL_WASM_CORE
if (!_modulePath.empty() && !readModuleFromFile()) {
return false;
}
#endif
if (_moduleBytes.empty()) {
RTC_LOG(LS_ERROR) << "WamrCoreBackend: no module bytes";
return false;
}
char error[128] = {0};
_module = wasm_runtime_load(_moduleBytes.data(), (uint32_t)_moduleBytes.size(), error, sizeof(error));
if (!_module) {
RTC_LOG(LS_ERROR) << "WamrCoreBackend: load failed: " << error;
return false;
}
// Sandbox the module's WASI stdio to /dev/null: without this the loader
// maps the module's stdio to the REAL process stdin/stdout/stderr
// (fd_read on stdin could even block the media thread). No preopened
// dirs, no env, no args — the module gets no other WASI capabilities.
_devNullFd = open("/dev/null", O_RDWR);
if (_devNullFd < 0) {
RTC_LOG(LS_ERROR) << "WamrCoreBackend: cannot open /dev/null";
return false;
}
wasm_runtime_set_wasi_args_ex(_module, nullptr, 0, nullptr, 0, nullptr, 0, nullptr, 0, _devNullFd, _devNullFd, _devNullFd);
_instance = wasm_runtime_instantiate(_module, 512 * 1024, 0, error, sizeof(error));
if (!_instance) {
RTC_LOG(LS_ERROR) << "WamrCoreBackend: instantiate failed: " << error;
return false;
}
wasm_runtime_set_custom_data(_instance, this);
_execEnv = wasm_runtime_create_exec_env(_instance, 512 * 1024);
if (!_execEnv) {
RTC_LOG(LS_ERROR) << "WamrCoreBackend: exec env failed";
return false;
}
// WASI reactor protocol: DO NOT call "_initialize" here. WAMR's
// wasm_runtime_instantiate() already invokes it internally (once,
// before returning) for any module that imports the WASI API — see
// execute_post_instantiate_functions() in
// core/iwasm/interpreter/wasm_runtime.c. wasi-libc's crt1-reactor.c
// guards "_initialize" with a "called more than once" trap
// (__builtin_trap() -> an "unreachable" instruction), so calling it
// again here traps every time and create() always fails.
_coreInit = wasm_runtime_lookup_function(_instance, "core_init");
_coreOnEvent = wasm_runtime_lookup_function(_instance, "core_on_event");
_rtAlloc = wasm_runtime_lookup_function(_instance, "rt_alloc");
_rtFree = wasm_runtime_lookup_function(_instance, "rt_free");
if (!_coreInit || !_coreOnEvent || !_rtAlloc || !_rtFree) {
RTC_LOG(LS_ERROR) << "WamrCoreBackend: missing required export";
return false;
}
return callCoreFunction(_coreInit, (const uint8_t *)configJson.data(), configJson.size());
}
bool WamrCoreBackend::onEvent(const uint8_t *data, size_t len) {
if (!_instance) {
return false;
}
return callCoreFunction(_coreOnEvent, data, len);
}
bool WamrCoreBackend::callCoreFunction(wasm_function_inst_t function, const uint8_t *data, size_t len) {
// rt_alloc -> copy in -> call -> rt_free (ABI module-form buffer contract).
uint32_t args[2] = { (uint32_t)len, 0 };
if (!wasm_runtime_call_wasm(_execEnv, _rtAlloc, 1, args)) {
RTC_LOG(LS_ERROR) << "WamrCoreBackend: rt_alloc trapped: " << wasm_runtime_get_exception(_instance);
return false;
}
const uint32_t appOffset = args[0];
if (appOffset == 0) {
RTC_LOG(LS_ERROR) << "WamrCoreBackend: rt_alloc returned null";
return false;
}
if (!wasm_runtime_validate_app_addr(_instance, (uint64_t)appOffset, (uint64_t)len)) {
RTC_LOG(LS_ERROR) << "WamrCoreBackend: rt_alloc returned invalid range";
return false;
}
void *native = wasm_runtime_addr_app_to_native(_instance, (uint64_t)appOffset);
memcpy(native, data, len);
uint32_t callArgs[2] = { appOffset, (uint32_t)len };
const bool ok = wasm_runtime_call_wasm(_execEnv, function, 2, callArgs);
if (!ok) {
RTC_LOG(LS_ERROR) << "WamrCoreBackend: core call trapped: " << wasm_runtime_get_exception(_instance);
}
uint32_t freeArgs[1] = { appOffset };
if (!wasm_runtime_call_wasm(_execEnv, _rtFree, 1, freeArgs)) {
RTC_LOG(LS_ERROR) << "WamrCoreBackend: rt_free trapped: " << wasm_runtime_get_exception(_instance);
return false;
}
return ok;
}
} // namespace tgcalls