1 /*
2  * Copyright (c) 2023 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 
16 #include <vector>
17 
18 #include "securec.h"
19 
20 #include "asset_log.h"
21 #include "asset_mem.h"
22 #include "asset_system_api.h"
23 #include "asset_system_type.h"
24 
25 #include "asset_napi_common.h"
26 #include "asset_napi_error_code.h"
27 
28 namespace OHOS {
29 namespace Security {
30 namespace Asset {
31 namespace {
32 
33 #define MAX_BUFFER_LEN 2048
34 
35 #define NAPI_THROW_RETURN_ERR(env, condition, code, message)            \
36     NAPI_THROW_BASE(env, condition, napi_generic_failure, code, message)
37 
38 #define NAPI_CALL_BREAK(env, theCall)   \
39 if ((theCall) != napi_ok) {             \
40     GET_AND_THROW_LAST_ERROR((env));    \
41     break;                              \
42 }
43 
44 #define NAPI_CALL_RETURN_ERR(env, theCall)  \
45 if ((theCall) != napi_ok) {                 \
46     GET_AND_THROW_LAST_ERROR((env));        \
47     return napi_generic_failure;            \
48 }
49 
50 #define CHECK_ASSET_TAG(env, condition, tag, message)                                   \
51 if ((condition)) {                                                                      \
52     char msg[MAX_MESSAGE_LEN] = { 0 };                                                  \
53     (void)sprintf_s(msg, MAX_MESSAGE_LEN, "AssetTag(0x%08x) " message, tag);            \
54     LOGE("[FATAL][NAPI]%{public}s", (msg));                                             \
55     napi_throw((env), CreateJsError((env), SEC_ASSET_INVALID_ARGUMENT, (msg)));         \
56     return napi_invalid_arg;                                                            \
57 }
58 
IsBlobValid(const AssetBlob & blob)59 bool IsBlobValid(const AssetBlob &blob)
60 {
61     return blob.data != nullptr && blob.size != 0;
62 }
63 
ParseByteArray(const napi_env env,napi_value value,uint32_t tag,AssetBlob & blob)64 napi_status ParseByteArray(const napi_env env, napi_value value, uint32_t tag, AssetBlob &blob)
65 {
66     napi_typedarray_type arrayType;
67     size_t length = 0;
68     void *rawData = nullptr;
69 
70     bool result = false;
71     NAPI_CALL_RETURN_ERR(env, napi_is_typedarray(env, value, &result));
72     CHECK_ASSET_TAG(env, !result, tag, "Expect type napi_typedarray.");
73     NAPI_CALL_RETURN_ERR(env, napi_get_typedarray_info(env, value, &arrayType, &length, &rawData, nullptr, nullptr));
74     CHECK_ASSET_TAG(env, arrayType != napi_uint8_array, tag, "Expect type napi_uint8_array.");
75     CHECK_ASSET_TAG(env, length == 0 || length > MAX_BUFFER_LEN, tag, "Invalid array length.");
76 
77     blob.data = static_cast<uint8_t *>(AssetMalloc(length));
78     NAPI_THROW_RETURN_ERR(
79         env, blob.data == nullptr, SEC_ASSET_OUT_OF_MEMORY, "Unable to allocate memory for AssetBlob.");
80 
81     (void)memcpy_s(blob.data, length, rawData, length);
82     blob.size = static_cast<uint32_t>(length);
83     return napi_ok;
84 }
85 
ParseAssetAttribute(const napi_env env,napi_value tag,napi_value value,AssetAttr & attr)86 napi_status ParseAssetAttribute(const napi_env env, napi_value tag, napi_value value, AssetAttr &attr)
87 {
88     // parse tag
89     napi_valuetype type = napi_undefined;
90     NAPI_CALL_RETURN_ERR(env, napi_typeof(env, tag, &type));
91     NAPI_THROW_RETURN_ERR(
92         env, type != napi_number, SEC_ASSET_INVALID_ARGUMENT, "The tag type of map should be number.");
93     NAPI_CALL_RETURN_ERR(env, napi_get_value_uint32(env, tag, &attr.tag));
94 
95     // parse value
96     NAPI_CALL_RETURN_ERR(env, napi_typeof(env, value, &type));
97     switch (attr.tag & SEC_ASSET_TAG_TYPE_MASK) {
98         case SEC_ASSET_TYPE_BOOL:
99             CHECK_ASSET_TAG(env, type != napi_boolean, attr.tag, "Expect type napi_boolean.");
100             NAPI_CALL_RETURN_ERR(env, napi_get_value_bool(env, value, &attr.value.boolean));
101             break;
102         case SEC_ASSET_TYPE_NUMBER:
103             CHECK_ASSET_TAG(env, type != napi_number, attr.tag, "Expect type napi_number.");
104             NAPI_CALL_RETURN_ERR(env, napi_get_value_uint32(env, value, &attr.value.u32));
105             break;
106         case SEC_ASSET_TYPE_BYTES:
107             CHECK_ASSET_TAG(env, type != napi_object, attr.tag, "Expect type napi_object.");
108             NAPI_CALL_RETURN_ERR(env, ParseByteArray(env, value, attr.tag, attr.value.blob));
109             break;
110         default:
111             CHECK_ASSET_TAG(env, true, attr.tag, "Invalid tag argument.");
112     }
113     return napi_ok;
114 }
115 
GetIteratorNext(const napi_env env,napi_value iterator,napi_value func,bool * done)116 napi_value GetIteratorNext(const napi_env env, napi_value iterator, napi_value func, bool *done)
117 {
118     napi_value next = nullptr;
119     NAPI_CALL(env, napi_call_function(env, iterator, func, 0, nullptr, &next));
120 
121     napi_value doneValue = nullptr;
122     NAPI_CALL(env, napi_get_named_property(env, next, "done", &doneValue));
123     NAPI_CALL(env, napi_get_value_bool(env, doneValue, done));
124     return next;
125 }
126 
GetUndefinedValue(const napi_env env)127 napi_value GetUndefinedValue(const napi_env env)
128 {
129     napi_value value = nullptr;
130     NAPI_CALL(env, napi_get_undefined(env, &value));
131     return value;
132 }
133 
CreateJsMap(const napi_env env,const AssetResult & result)134 napi_value CreateJsMap(const napi_env env, const AssetResult &result)
135 {
136     napi_value global = nullptr;
137     napi_value mapFunc = nullptr;
138     napi_value map = nullptr;
139     NAPI_CALL(env, napi_get_global(env, &global));
140     NAPI_CALL(env, napi_get_named_property(env, global, "Map", &mapFunc));
141     NAPI_CALL(env, napi_new_instance(env, mapFunc, 0, nullptr, &map));
142     napi_value setFunc = nullptr;
143     NAPI_CALL(env, napi_get_named_property(env, map, "set", &setFunc));
144     for (uint32_t i = 0; i < result.count; i++) {
145         napi_value key = nullptr;
146         napi_value value = nullptr;
147         NAPI_CALL(env, napi_create_uint32(env, result.attrs[i].tag, &key));
148         switch (result.attrs[i].tag & SEC_ASSET_TAG_TYPE_MASK) {
149             case SEC_ASSET_TYPE_BOOL:
150                 NAPI_CALL(env, napi_get_boolean(env, result.attrs[i].value.boolean, &value));
151                 break;
152             case SEC_ASSET_TYPE_NUMBER:
153                 NAPI_CALL(env, napi_create_uint32(env, result.attrs[i].value.u32, &value));
154                 break;
155             case SEC_ASSET_TYPE_BYTES:
156                 value = CreateJsUint8Array(env, result.attrs[i].value.blob);
157                 break;
158             default:
159                 return nullptr;
160         }
161 
162         napi_value setArgs[] = { key, value };
163         NAPI_CALL(env, napi_call_function(env, map, setFunc, sizeof(setArgs) / sizeof(setArgs[0]), setArgs, nullptr));
164     }
165     return map;
166 }
167 
GetBusinessValue(const napi_env env,AsyncContext * context)168 napi_value GetBusinessValue(const napi_env env, AsyncContext *context)
169 {
170     // Processing the return value of the PreQueryAsset function.
171     if (IsBlobValid(context->challenge)) {
172         return CreateJsUint8Array(env, context->challenge);
173     }
174 
175     // Processing the return value of the QueryAsset function.
176     if (context->resultSet.results != nullptr && context->resultSet.count != 0) {
177         return CreateJsMapArray(env, context->resultSet);
178     }
179 
180     return GetUndefinedValue(env);
181 }
182 
ResolvePromise(const napi_env env,AsyncContext * context)183 void ResolvePromise(const napi_env env, AsyncContext *context)
184 {
185     napi_value result = nullptr;
186     if (context->result == SEC_ASSET_SUCCESS) {
187         result = GetBusinessValue(env, context);
188         NAPI_CALL_RETURN_VOID(env, napi_resolve_deferred(env, context->deferred, result));
189     } else {
190         result = CreateJsError(env, context->result);
191         NAPI_CALL_RETURN_VOID(env, napi_reject_deferred(env, context->deferred, result));
192     }
193 }
194 
ParseMapParam(const napi_env env,napi_value arg,std::vector<AssetAttr> & attrs)195 napi_status ParseMapParam(const napi_env env, napi_value arg, std::vector<AssetAttr> &attrs)
196 {
197     // check map type
198     bool isMap = false;
199     NAPI_CALL_RETURN_ERR(env, napi_is_map(env, arg, &isMap));
200     NAPI_THROW_RETURN_ERR(env, !isMap, SEC_ASSET_INVALID_ARGUMENT, "Expect Map type.");
201 
202     // parse map object
203     napi_value entriesFunc = nullptr;
204     napi_value iterator = nullptr;
205     napi_value nextFunc = nullptr;
206     NAPI_CALL_RETURN_ERR(env, napi_get_named_property(env, arg, "entries", &entriesFunc));
207     NAPI_CALL_RETURN_ERR(env, napi_call_function(env, arg, entriesFunc, 0, nullptr, &iterator));
208     NAPI_CALL_RETURN_ERR(env, napi_get_named_property(env, iterator, "next", &nextFunc));
209 
210     bool done = false;
211     napi_value next = nullptr;
212     while ((next = GetIteratorNext(env, iterator, nextFunc, &done)) != nullptr && !done) {
213         napi_value entry = nullptr;
214         napi_value key = nullptr;
215         napi_value value = nullptr;
216         NAPI_CALL_BREAK(env, napi_get_named_property(env, next, "value", &entry));
217         NAPI_CALL_BREAK(env, napi_get_element(env, entry, 0, &key));
218         NAPI_CALL_BREAK(env, napi_get_element(env, entry, 1, &value));
219 
220         AssetAttr param = { 0 };
221         NAPI_CALL_BREAK(env, ParseAssetAttribute(env, key, value, param));
222         attrs.push_back(param);
223     }
224 
225     NAPI_THROW_RETURN_ERR(env, !done, SEC_ASSET_INVALID_ARGUMENT, "Parse entry of map failed.");
226     return napi_ok;
227 }
228 
ParseJsArgs(const napi_env env,napi_callback_info info,napi_value * value,size_t valueSize)229 napi_status ParseJsArgs(const napi_env env, napi_callback_info info, napi_value *value, size_t valueSize)
230 {
231     size_t argc = valueSize;
232     NAPI_CALL_RETURN_ERR(env, napi_get_cb_info(env, info, &argc, value, nullptr, nullptr));
233     NAPI_THROW_RETURN_ERR(env, argc < valueSize, SEC_ASSET_INVALID_ARGUMENT,
234         "The number of arguments is insufficient.");
235     return napi_ok;
236 }
237 
ParseJsUserId(const napi_env env,napi_value arg,std::vector<AssetAttr> & attrs)238 napi_status ParseJsUserId(const napi_env env, napi_value arg, std::vector<AssetAttr> &attrs)
239 {
240     napi_valuetype type = napi_undefined;
241     NAPI_CALL_RETURN_ERR(env, napi_typeof(env, arg, &type));
242     NAPI_THROW_RETURN_ERR(env, type != napi_number, SEC_ASSET_INVALID_ARGUMENT, "The type of userId should be number.");
243 
244     AssetAttr param = { 0 };
245     param.tag = SEC_ASSET_TAG_USER_ID;
246     NAPI_CALL_RETURN_ERR(env, napi_get_value_uint32(env, arg, &param.value.u32));
247     attrs.push_back(param);
248     return napi_ok;
249 }
250 
251 } // anonymous namespace
252 
CreateAsyncContext()253 AsyncContext *CreateAsyncContext()
254 {
255     return static_cast<AsyncContext *>(AssetMalloc(sizeof(AsyncContext)));
256 }
257 
DestroyAsyncContext(const napi_env env,AsyncContext * context)258 void DestroyAsyncContext(const napi_env env, AsyncContext *context)
259 {
260     if (context == nullptr) {
261         return;
262     }
263     if (context->work != nullptr) {
264         napi_delete_async_work(env, context->work);
265         context->work = nullptr;
266     }
267 
268     AssetFreeResultSet(&context->resultSet);
269     AssetFreeBlob(&context->challenge);
270     FreeAssetAttrs(context->updateAttrs);
271     FreeAssetAttrs(context->attrs);
272     AssetFree(context);
273 }
274 
CreateAsyncWork(const napi_env env,AsyncContext * context,const char * funcName,napi_async_execute_callback execute)275 napi_value CreateAsyncWork(const napi_env env, AsyncContext *context, const char *funcName,
276     napi_async_execute_callback execute)
277 {
278     napi_value result = nullptr;
279     NAPI_CALL(env, napi_create_promise(env, &context->deferred, &result));
280 
281     napi_value resource = nullptr;
282     NAPI_CALL(env, napi_create_string_utf8(env, funcName, NAPI_AUTO_LENGTH, &resource));
283     NAPI_CALL(env, napi_create_async_work(
284         env, nullptr, resource, execute,
285         [](napi_env env, napi_status status, void *data) {
286             AsyncContext *asyncContext = static_cast<AsyncContext *>(data);
287             ResolvePromise(env, asyncContext);
288             DestroyAsyncContext(env, asyncContext);
289         },
290         static_cast<void *>(context), &context->work));
291     NAPI_CALL(env, napi_queue_async_work(env, context->work));
292     return result;
293 }
294 
FreeAssetAttrs(std::vector<AssetAttr> & attrs)295 void FreeAssetAttrs(std::vector<AssetAttr> &attrs)
296 {
297     for (auto attr : attrs) {
298         if ((attr.tag & SEC_ASSET_TAG_TYPE_MASK) == SEC_ASSET_TYPE_BYTES) {
299             AssetFreeBlob(&attr.value.blob);
300         }
301     }
302     attrs.clear();
303 }
304 
CreateJsError(const napi_env env,int32_t errCode)305 napi_value CreateJsError(const napi_env env, int32_t errCode)
306 {
307     return CreateJsError(env, errCode, GetErrorMessage(errCode));
308 }
309 
CreateJsError(const napi_env env,int32_t errCode,const char * errorMsg)310 napi_value CreateJsError(const napi_env env, int32_t errCode, const char *errorMsg)
311 {
312     napi_value code = nullptr;
313     NAPI_CALL(env, napi_create_int32(env, errCode, &code));
314 
315     napi_value message = nullptr;
316     NAPI_CALL(env, napi_create_string_utf8(env, errorMsg, strlen(errorMsg), &message));
317 
318     napi_value result = nullptr;
319     NAPI_CALL(env, napi_create_error(env, code, message, &result));
320     return result;
321 }
322 
CreateJsMapArray(const napi_env env,const AssetResultSet & resultSet)323 napi_value CreateJsMapArray(const napi_env env, const AssetResultSet &resultSet)
324 {
325     napi_value array = nullptr;
326     NAPI_CALL(env, napi_create_array(env, &array));
327     for (uint32_t i = 0; i < resultSet.count; i++) {
328         if (resultSet.results[i].attrs == nullptr || resultSet.results[i].count == 0) {
329             return nullptr;
330         }
331         napi_value map = CreateJsMap(env, resultSet.results[i]);
332         NAPI_CALL(env, napi_set_element(env, array, i, map));
333     }
334     return array;
335 }
336 
CreateJsUint8Array(const napi_env env,const AssetBlob & blob)337 napi_value CreateJsUint8Array(const napi_env env, const AssetBlob &blob)
338 {
339     if (!IsBlobValid(blob) || blob.size > MAX_BUFFER_LEN) {
340         return nullptr;
341     }
342 
343     void *data = nullptr;
344     napi_value buffer = nullptr;
345     NAPI_CALL(env, napi_create_arraybuffer(env, blob.size, &data, &buffer));
346     (void)memcpy_s(data, blob.size, blob.data, blob.size);
347 
348     napi_value result = nullptr;
349     NAPI_CALL(env, napi_create_typedarray(env, napi_uint8_array, blob.size, buffer, 0, &result));
350     return result;
351 }
352 
ParseParam(const napi_env env,napi_callback_info info,const NapiCallerArgs & args,std::vector<AssetAttr> & attrs)353 napi_status ParseParam(const napi_env env, napi_callback_info info, const NapiCallerArgs &args,
354     std::vector<AssetAttr> &attrs)
355 {
356     std::vector<AssetAttr> updateAttrs;
357     return ParseParam(env, info, args, attrs, updateAttrs);
358 }
359 
ParseParam(const napi_env env,napi_callback_info info,const NapiCallerArgs & args,std::vector<AssetAttr> & attrs,std::vector<AssetAttr> & updateAttrs)360 napi_status ParseParam(const napi_env env, napi_callback_info info, const NapiCallerArgs &args,
361     std::vector<AssetAttr> &attrs, std::vector<AssetAttr> &updateAttrs)
362 {
363     napi_value argv[MAX_ARGS_NUM] = { 0 };
364     napi_status ret = ParseJsArgs(env, info, argv, args.expectArgNum);
365     if (ret != napi_ok) {
366         return ret;
367     }
368 
369     size_t index = 0;
370     if (args.isAsUser) {
371         ret = ParseJsUserId(env, argv[index++], attrs);
372         if (ret != napi_ok) {
373             return ret;
374         }
375     }
376 
377     ret = ParseMapParam(env, argv[index++], attrs);
378     if (ret != napi_ok) {
379         LOGE("Parse first map parameter failed.");
380         return ret;
381     }
382 
383     if (args.isUpdate) {
384         ret = ParseMapParam(env, argv[index++], updateAttrs);
385         if (ret != napi_ok) {
386             LOGE("Parse second map parameter failed.");
387             return ret;
388         }
389     }
390     return napi_ok;
391 }
392 
NapiAsync(const napi_env env,napi_callback_info info,napi_async_execute_callback execute,const NapiCallerArgs & args,CheckFuncPtr checkFunc)393 napi_value NapiAsync(const napi_env env, napi_callback_info info, napi_async_execute_callback execute,
394     const NapiCallerArgs &args, CheckFuncPtr checkFunc)
395 {
396     AsyncContext *context = CreateAsyncContext();
397     NAPI_THROW(env, context == nullptr, SEC_ASSET_OUT_OF_MEMORY, "Unable to allocate memory for AsyncContext.");
398 
399     do {
400         if (ParseParam(env, info, args, context->attrs, context->updateAttrs) != napi_ok) {
401             break;
402         }
403 
404         if (checkFunc(env, context->attrs) != napi_ok) {
405             break;
406         }
407 
408         napi_value promise = CreateAsyncWork(env, context, __func__, execute);
409         if (promise == nullptr) {
410             LOGE("Create async work failed.");
411             break;
412         }
413         return promise;
414     } while (0);
415     DestroyAsyncContext(env, context);
416     return nullptr;
417 }
418 
419 } // Asset
420 } // Security
421 } // OHOS