1 /*
2 * Copyright (c) 2022-2025 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 #include <cstdint>
16 #include <cstring>
17 #include <vector>
18 #include <string>
19
20 #include "interop-logging.h"
21
22 #ifdef KOALA_INTEROP_MODULE
23 #undef KOALA_INTEROP_MODULE
24 #endif
25 #define KOALA_INTEROP_MODULE InteropNativeModule
26 #include "convertors-napi.h"
27
28
29 // Adapter for NAPI_MODULE
30 #define NODE_API_MODULE_ADAPTER(modname, regfunc) \
31 static napi_value __napi_##regfunc(napi_env env, napi_value exports) { \
32 return Napi::RegisterModule(env, exports, regfunc); \
33 } \
34 NAPI_MODULE(modname, __napi_##regfunc)
35
getValueTypeChecked(napi_env env,napi_value value)36 napi_valuetype getValueTypeChecked(napi_env env, napi_value value) {
37 napi_valuetype type;
38 napi_status status = napi_typeof(env, value, &type);
39 KOALA_NAPI_THROW_IF_FAILED(env, status, napi_undefined);
40 return type;
41 }
42
isTypedArray(napi_env env,napi_value value)43 bool isTypedArray(napi_env env, napi_value value) {
44 bool result = false;
45 napi_status status = napi_is_typedarray(env, value, &result);
46 KOALA_NAPI_THROW_IF_FAILED(env, status, false);
47 return result;
48 }
49
getBoolean(napi_env env,napi_value value)50 KBoolean getBoolean(napi_env env, napi_value value) {
51 if (getValueTypeChecked(env, value) == napi_valuetype::napi_boolean) {
52 bool result = false;
53 napi_get_value_bool(env, value, &result);
54 return static_cast<KBoolean>(result);
55 }
56 return static_cast<KBoolean>(getInt32(env, value) != 0);
57 }
58
getInt32(napi_env env,napi_value value)59 KInt getInt32(napi_env env, napi_value value) {
60 if (getValueTypeChecked(env, value) != napi_valuetype::napi_number) {
61 napi_throw_error(env, nullptr, "Expected Number");
62 return 0;
63 }
64 int32_t result = false;
65 napi_get_value_int32(env, value, &result);
66 return static_cast<KInt>(result);
67 }
68
getUInt32(napi_env env,napi_value value)69 KUInt getUInt32(napi_env env, napi_value value) {
70 if (getValueTypeChecked(env, value) != napi_valuetype::napi_number) {
71 napi_throw_error(env, nullptr, "Expected Number");
72 return 0;
73 }
74 uint32_t result = false;
75 napi_get_value_uint32(env, value, &result);
76 return static_cast<KUInt>(result);
77 }
78
getFloat32(napi_env env,napi_value value)79 KFloat getFloat32(napi_env env, napi_value value) {
80 if (getValueTypeChecked(env, value) != napi_valuetype::napi_number) {
81 napi_throw_error(env, nullptr, "Expected Number");
82 return 0.0f;
83 }
84 double result = false;
85 napi_get_value_double(env, value, &result);
86 return static_cast<KFloat>(static_cast<float>(result));
87 }
88
getFloat64(napi_env env,napi_value value)89 KDouble getFloat64(napi_env env, napi_value value) {
90 if (getValueTypeChecked(env, value) != napi_valuetype::napi_number) {
91 napi_throw_error(env, nullptr, "Expected Number");
92 return 0.0;
93 }
94 double result = false;
95 napi_get_value_double(env, value, &result);
96 return static_cast<KDouble>(result);
97 }
98
getString(napi_env env,napi_value value)99 KStringPtr getString(napi_env env, napi_value value) {
100 KStringPtr result {};
101 napi_valuetype valueType = getValueTypeChecked(env, value);
102 if (valueType == napi_valuetype::napi_null || valueType == napi_valuetype::napi_undefined) {
103 return result;
104 }
105
106 if (valueType != napi_valuetype::napi_string) {
107 napi_throw_error(env, nullptr, "Expected String");
108 return result;
109 }
110
111 size_t length = 0;
112 napi_status status = napi_get_value_string_utf8(env, value, nullptr, 0, &length);
113 if (status != 0) return result;
114 result.resize(length);
115 status = napi_get_value_string_utf8(env, value, result.data(), length + 1, nullptr);
116 return result;
117 }
118
getPointer(napi_env env,napi_value value)119 KNativePointer getPointer(napi_env env, napi_value value) {
120 napi_valuetype valueType = getValueTypeChecked(env, value);
121 if (valueType == napi_valuetype::napi_external) {
122 KNativePointer result = nullptr;
123 napi_status status = napi_get_value_external(env, value, &result);
124 KOALA_NAPI_THROW_IF_FAILED(env, status, nullptr);
125 return result;
126 }
127
128 if (valueType != napi_valuetype::napi_bigint) {
129 napi_throw_error(env, nullptr, "cannot be coerced to pointer");
130 return nullptr;
131 }
132
133 bool isWithinRange = true;
134 uint64_t ptrU64 = 0;
135 napi_status status = napi_get_value_bigint_uint64(env, value, &ptrU64, &isWithinRange);
136 KOALA_NAPI_THROW_IF_FAILED(env, status, nullptr);
137 if (!isWithinRange) {
138 napi_throw_error(env, nullptr, "cannot be coerced to uint64, value is too large");
139 return nullptr;
140 }
141 return reinterpret_cast<KNativePointer>(ptrU64);
142 }
143
getInt64(napi_env env,napi_value value)144 KLong getInt64(napi_env env, napi_value value) {
145 if (getValueTypeChecked(env, value) != napi_valuetype::napi_bigint) {
146 napi_throw_error(env, nullptr, "cannot be coerced to int64");
147 return -1;
148 }
149
150 bool isWithinRange = true;
151 int64_t ptr64 = 0;
152 napi_get_value_bigint_int64(env, value, &ptr64, &isWithinRange);
153 if (!isWithinRange) {
154 napi_throw_error(env, nullptr, "cannot be coerced to int64, value is too large");
155 return -1;
156 }
157 return static_cast<KLong>(ptr64);
158 }
159
getUInt64(napi_env env,napi_value value)160 KULong getUInt64(napi_env env, napi_value value) {
161 if (getValueTypeChecked(env, value) != napi_valuetype::napi_bigint) {
162 napi_throw_error(env, nullptr, "cannot be coerced to uint64");
163 return -1;
164 }
165
166 bool isWithinRange = true;
167 uint64_t ptr64 = 0;
168 napi_get_value_bigint_uint64(env, value, &ptr64, &isWithinRange);
169 if (!isWithinRange) {
170 napi_throw_error(env, nullptr, "cannot be coerced to uint64, value is too large");
171 return -1;
172 }
173 return static_cast<KLong>(ptr64);
174 }
175
makeString(napi_env env,const KStringPtr & value)176 napi_value makeString(napi_env env, const KStringPtr& value) {
177 napi_value result;
178 napi_status status;
179 status = napi_create_string_utf8(env, value.isNull() ? "" : value.data(), value.length(), &result);
180 KOALA_NAPI_THROW_IF_FAILED(env, status, result);
181 return result;
182 }
183
makeString(napi_env env,const std::string & value)184 napi_value makeString(napi_env env, const std::string& value) {
185 napi_value result;
186 napi_status status;
187 status = napi_create_string_utf8(env, value.c_str(), value.length(), &result);
188 KOALA_NAPI_THROW_IF_FAILED(env, status, result);
189 return result;
190 }
191
makeBoolean(napi_env env,int8_t value)192 napi_value makeBoolean(napi_env env, int8_t value) {
193 napi_value result;
194 napi_status status;
195 status = napi_get_boolean(env, value != 0, &result);
196 KOALA_NAPI_THROW_IF_FAILED(env, status, result);
197 return result;
198 }
199
makeInt32(napi_env env,int32_t value)200 napi_value makeInt32(napi_env env, int32_t value) {
201 napi_value result;
202 napi_status status;
203 status = napi_create_int32(env, value, &result);
204 KOALA_NAPI_THROW_IF_FAILED(env, status, result);
205 return result;
206 }
207
makeUInt32(napi_env env,uint32_t value)208 napi_value makeUInt32(napi_env env, uint32_t value) {
209 napi_value result;
210 napi_status status;
211 status = napi_create_uint32(env, value, &result);
212 KOALA_NAPI_THROW_IF_FAILED(env, status, result);
213 return result;
214 }
215
makeInt64(napi_env env,int64_t value)216 napi_value makeInt64(napi_env env, int64_t value) {
217 napi_value result;
218 napi_status status;
219 status = napi_create_bigint_int64(env, value, &result);
220 KOALA_NAPI_THROW_IF_FAILED(env, status, result);
221 return result;
222 }
223
makeUInt64(napi_env env,uint64_t value)224 napi_value makeUInt64(napi_env env, uint64_t value) {
225 napi_value result;
226 napi_status status;
227 status = napi_create_bigint_uint64(env, value, &result);
228 KOALA_NAPI_THROW_IF_FAILED(env, status, result);
229 return result;
230 }
231
makeFloat32(napi_env env,float value)232 napi_value makeFloat32(napi_env env, float value) {
233 napi_value result;
234 napi_status status;
235 status = napi_create_double(env, value, &result);
236 KOALA_NAPI_THROW_IF_FAILED(env, status, result);
237 return result;
238 }
239
makePointer(napi_env env,void * value)240 napi_value makePointer(napi_env env, void* value) {
241 napi_value result;
242 napi_status status;
243 status = napi_create_bigint_uint64(env, static_cast<uint64_t>(reinterpret_cast<uintptr_t>(value)), &result);
244 KOALA_NAPI_THROW_IF_FAILED(env, status, result);
245 return result;
246 }
247
makeVoid(napi_env env)248 napi_value makeVoid(napi_env env) {
249 napi_value result;
250 napi_status status;
251 status = napi_get_undefined(env, &result);
252 KOALA_NAPI_THROW_IF_FAILED(env, status, result);
253 return result;
254 }
255
makeObject(napi_env env,napi_value object)256 napi_value makeObject(napi_env env, napi_value object) {
257 napi_value result;
258 napi_status status;
259 status = napi_create_object(env, &result);
260 KOALA_NAPI_THROW_IF_FAILED(env, status, result);
261 return result;
262 }
263
264 #if _MSC_VER >= 1932 // Visual Studio 2022 version 17.2+
265 # pragma comment(linker, "/alternatename:__imp___std_init_once_complete=__imp_InitOnceComplete")
266 # pragma comment(linker, "/alternatename:__imp___std_init_once_begin_initialize=__imp_InitOnceBeginInitialize")
267 #endif
268
getInstance()269 Exports* Exports::getInstance() {
270 static Exports *instance = nullptr;
271 if (instance == nullptr) {
272 instance = new Exports();
273 }
274 return instance;
275 }
276
getModules()277 std::vector<std::string> Exports::getModules() {
278 std::vector<std::string> result;
279 for (auto it = implementations.begin(); it != implementations.end(); ++it) {
280 result.push_back(it->first);
281 }
282 return result;
283 }
284
addMethod(const char * module,const char * name,napi_type_t impl)285 void Exports::addMethod(const char* module, const char* name, napi_type_t impl) {
286 auto it = implementations.find(module);
287 if (it == implementations.end()) {
288 it = implementations.insert(std::make_pair(module, std::vector<std::pair<std::string, napi_type_t>>())).first;
289 }
290 it->second.push_back(std::make_pair(name, impl));
291
292 }
293
getMethods(const std::string & module)294 const std::vector<std::pair<std::string, napi_type_t>>& Exports::getMethods(const std::string& module) {
295 auto it = implementations.find(module);
296 if (it == implementations.end()) {
297 LOGE("Module %s is not registered", module.c_str());
298 INTEROP_FATAL("Fatal error");
299 }
300 return it->second;
301 }
302
303 //
304 // Callback dispatcher
305 //
306 // TODO Should we get rid of explicit Node_* declrations and hide the naming convention behind the macro definitions?
307
308 static napi_ref g_koalaNapiCallbackDispatcher = nullptr;
309
310 // TODO: shall we pass name in globalThis instead of object reference?
Node_SetCallbackDispatcher(napi_env env,napi_callback_info cbinfo)311 napi_value Node_SetCallbackDispatcher(napi_env env, napi_callback_info cbinfo) {
312 fprintf(stderr, "Node_SetCallbackDispatcher!\n");
313
314 CallbackInfo info(env, cbinfo);
315 napi_value dispatcher = info[0];
316 napi_value result = makeVoid(env);
317 napi_status status = napi_create_reference(env, dispatcher, 1, &g_koalaNapiCallbackDispatcher);
318 KOALA_NAPI_THROW_IF_FAILED(env, status, result);
319
320 return result;
321 }
MAKE_NODE_EXPORT(KOALA_INTEROP_MODULE,SetCallbackDispatcher)322 MAKE_NODE_EXPORT(KOALA_INTEROP_MODULE, SetCallbackDispatcher)
323
324 napi_value Node_CleanCallbackDispatcher(napi_env env, napi_callback_info cbinfo) {
325 napi_value result = makeVoid(env);
326 if (g_koalaNapiCallbackDispatcher) {
327 napi_status status = napi_delete_reference(env, g_koalaNapiCallbackDispatcher);
328 g_koalaNapiCallbackDispatcher = nullptr;
329 KOALA_NAPI_THROW_IF_FAILED(env, status, result);
330 }
331 return result;
332 }
MAKE_NODE_EXPORT(KOALA_INTEROP_MODULE,CleanCallbackDispatcher)333 MAKE_NODE_EXPORT(KOALA_INTEROP_MODULE, CleanCallbackDispatcher)
334
335 napi_value getKoalaNapiCallbackDispatcher(napi_env env) {
336 if (!g_koalaNapiCallbackDispatcher) {
337 abort();
338 }
339 napi_value value;
340 napi_status status = napi_get_reference_value(env, g_koalaNapiCallbackDispatcher, &value);
341 KOALA_NAPI_THROW_IF_FAILED(env, status, makeVoid(env));
342 return value;
343 }
344
345 //
346 // Module initialization
347 //
348
349 using ModuleRegisterCallback = napi_value (*)(napi_env env, napi_value exports);
350
351 /**
352 * Sets a new callback and returns its previous value.
353 */
ProvideModuleRegisterCallback(ModuleRegisterCallback value=nullptr)354 ModuleRegisterCallback ProvideModuleRegisterCallback(ModuleRegisterCallback value = nullptr) {
355 static const ModuleRegisterCallback DEFAULT_CB = [](napi_env env, napi_value exports) { return exports; };
356 static ModuleRegisterCallback curCallback = DEFAULT_CB;
357
358 ModuleRegisterCallback prevCallback = curCallback;
359 curCallback = value ? value : DEFAULT_CB;
360 return prevCallback;
361 }
362
363 static constexpr bool splitModules = true;
364
InitModule(napi_env env,napi_value exports)365 static napi_value InitModule(napi_env env, napi_value exports) {
366 LOG("InitModule: " QUOTE(INTEROP_LIBRARY_NAME) "\n");
367 Exports* inst = Exports::getInstance();
368 napi_status status;
369 napi_value target = exports;
370 for (const auto &module : inst->getModules()) {
371 if (splitModules) {
372 status = napi_create_object(env, &target);
373 KOALA_NAPI_THROW_IF_FAILED(env, status, exports);
374 status = napi_set_named_property(env, exports, module.c_str(), target);
375 KOALA_NAPI_THROW_IF_FAILED(env, status, exports);
376 }
377
378 for (const auto &impl : inst->getMethods(module)) {
379 napi_value implFunc;
380 status = napi_create_function(env, impl.first.c_str(), NAPI_AUTO_LENGTH, impl.second, nullptr, &implFunc);
381 KOALA_NAPI_THROW_IF_FAILED(env, status, exports);
382 status = napi_set_named_property(env, target, impl.first.c_str(), implFunc);
383 KOALA_NAPI_THROW_IF_FAILED(env, status, exports);
384 }
385 }
386 return ProvideModuleRegisterCallback()(env, exports);
387 }
388
389 NAPI_MODULE(INTEROP_LIBRARY_NAME, InitModule)
390