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 #ifndef LOG_TAG
16 #define LOG_TAG "bt_napi_socket_spp_client"
17 #endif
18 
19 #include "bluetooth_errorcode.h"
20 #include "napi_bluetooth_spp_client.h"
21 #include "napi_bluetooth_error.h"
22 #include "napi_bluetooth_utils.h"
23 #include "securec.h"
24 #include <limits>
25 #include <unistd.h>
26 #include <uv.h>
27 
28 namespace OHOS {
29 namespace Bluetooth {
30 std::map<int, std::shared_ptr<NapiSppClient>> NapiSppClient::clientMap;
31 int NapiSppClient::count = 0;
32 const int SOCKET_BUFFER_SIZE = 1024;
33 
CheckSppConnectParams(napi_env env,napi_callback_info info,std::string & deviceId,SppConnectCallbackInfo * callbackInfo)34 static napi_status CheckSppConnectParams(
35     napi_env env, napi_callback_info info, std::string &deviceId, SppConnectCallbackInfo *callbackInfo)
36 {
37     HILOGI("enter");
38     size_t argc = ARGS_SIZE_THREE;
39     napi_value argv[ARGS_SIZE_THREE] = {0};
40 
41     NAPI_BT_CALL_RETURN(napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr));
42     NAPI_BT_RETURN_IF((argc != ARGS_SIZE_THREE && argc != ARGS_SIZE_THREE - CALLBACK_SIZE),
43         "Requires 2 or 3 arguments.", napi_invalid_arg);
44     NAPI_BT_RETURN_IF(!ParseString(env, deviceId, argv[PARAM0]),
45         "Wrong argument type. String expected.", napi_invalid_arg);
46 
47     callbackInfo->env_ = env;
48     callbackInfo->sppOption_ = GetSppOptionFromJS(env, argv[PARAM1]);
49     NAPI_BT_RETURN_IF((callbackInfo->sppOption_ == nullptr), "GetSppOptionFromJS faild.", napi_invalid_arg);
50     callbackInfo->deviceId_ = deviceId;
51 
52     napi_value promise = nullptr;
53 
54     if (argc == ARGS_SIZE_THREE) {
55         // Callback mode
56         HILOGI("callback mode");
57         napi_valuetype valueType = napi_undefined;
58         napi_typeof(env, argv[PARAM2], &valueType);
59         if (valueType != napi_function) {
60             HILOGE("Wrong argument type. Function expected.");
61             delete callbackInfo;
62             callbackInfo = nullptr;
63             return napi_invalid_arg;
64         }
65         napi_create_reference(env, argv[PARAM2], 1, &callbackInfo->callback_);
66         napi_get_undefined(env, &promise);
67     } else {
68         // Promise mode
69         HILOGI("promise mode");
70         napi_create_promise(env, &callbackInfo->deferred_, &promise);
71     }
72     return napi_ok;
73 }
74 
GetSppOptionFromJS(napi_env env,napi_value object)75 std::shared_ptr<SppOption> GetSppOptionFromJS(napi_env env, napi_value object)
76 {
77     std::shared_ptr<SppOption> sppOption = std::make_shared<SppOption>();
78     napi_value propertyNameValue = nullptr;
79     napi_value value = nullptr;
80 
81     napi_create_string_utf8(env, "uuid", NAPI_AUTO_LENGTH, &propertyNameValue);
82     napi_get_property(env, object, propertyNameValue, &value);
83     bool isSuccess = ParseString(env, sppOption->uuid_, value);
84     if (!isSuccess || (!IsValidUuid(sppOption->uuid_))) {
85         HILOGE("Parse UUID faild.");
86         return nullptr;
87     }
88     HILOGI("uuid is %{public}s", sppOption->uuid_.c_str());
89 
90     napi_create_string_utf8(env, "secure", NAPI_AUTO_LENGTH, &propertyNameValue);
91     napi_get_property(env, object, propertyNameValue, &value);
92     ParseBool(env, sppOption->secure_, value);
93     HILOGI("secure is %{public}d", sppOption->secure_);
94 
95     int type = 0;
96     napi_create_string_utf8(env, "type", NAPI_AUTO_LENGTH, &propertyNameValue);
97     napi_get_property(env, object, propertyNameValue, &value);
98     ParseInt32(env, type, value);
99     sppOption->type_ = BtSocketType(type);
100     HILOGI("uuid: %{public}s, secure: %{public}d, type: %{public}d",
101         sppOption->uuid_.c_str(), sppOption->secure_, sppOption->type_);
102     return sppOption;
103 }
104 
SppConnect(napi_env env,napi_callback_info info)105 napi_value NapiSppClient::SppConnect(napi_env env, napi_callback_info info)
106 {
107     HILOGI("enter");
108     std::string deviceId;
109     SppConnectCallbackInfo *callbackInfo = new SppConnectCallbackInfo();
110     auto status = CheckSppConnectParams(env, info, deviceId, callbackInfo);
111     NAPI_BT_ASSERT_RETURN_UNDEF(env, status == napi_ok, BT_ERR_INVALID_PARAM);
112 
113     napi_value resource = nullptr;
114     napi_create_string_utf8(env, "SppConnect", NAPI_AUTO_LENGTH, &resource);
115 
116     napi_create_async_work(
117         env, nullptr, resource,
118         [](napi_env env, void* data) {
119             HILOGI("SppConnect execute");
120             SppConnectCallbackInfo* callbackInfo = static_cast<SppConnectCallbackInfo*>(data);
121             callbackInfo->device_ = std::make_shared<BluetoothRemoteDevice>(callbackInfo->deviceId_, 0);
122             callbackInfo->client_ = std::make_shared<ClientSocket>(*callbackInfo->device_,
123                 UUID::FromString(callbackInfo->sppOption_->uuid_),
124                 callbackInfo->sppOption_->type_, callbackInfo->sppOption_->secure_);
125             HILOGI("SppConnect client_ constructed");
126             callbackInfo->errorCode_ = callbackInfo->client_->Connect(SPP_SOCKET_PSM_VALUE);
127             if (callbackInfo->errorCode_ == BtStatus::BT_SUCCESS) {
128                 HILOGI("SppConnect successfully");
129                 callbackInfo->errorCode_ = CODE_SUCCESS;
130             } else {
131                 HILOGE("SppConnect failed");
132             }
133         },
134         [](napi_env env, napi_status status, void* data) {
135             HILOGI("SppConnect execute back");
136             SppConnectCallbackInfo* callbackInfo = static_cast<SppConnectCallbackInfo*>(data);
137             napi_value result[ARGS_SIZE_TWO] = {0};
138             napi_value callback = 0;
139             napi_value undefined = 0;
140             napi_value callResult = 0;
141             napi_get_undefined(env, &undefined);
142 
143             if (callbackInfo->errorCode_ == CODE_SUCCESS) {
144                 HILOGI("SppConnect execute back success");
145                 std::shared_ptr<NapiSppClient> client =  std::make_shared<NapiSppClient>();
146                 client->device_ = callbackInfo->device_;
147                 client->id_ = NapiSppClient::count++;
148                 napi_create_int32(env, client->id_, &result[PARAM1]);
149                 client->client_ = callbackInfo->client_;
150                 clientMap.insert(std::make_pair(client->id_, client));
151                 HILOGI("SppConnect execute back successfully");
152             } else {
153                 napi_get_undefined(env, &result[PARAM1]);
154                 HILOGI("SppConnect execute back failed");
155             }
156 
157             if (callbackInfo->callback_) {
158                 // Callback mode
159                 HILOGI("SppConnect execute Callback mode");
160                 result[PARAM0] = GetCallbackErrorValue(callbackInfo->env_, callbackInfo->errorCode_);
161                 napi_get_reference_value(env, callbackInfo->callback_, &callback);
162                 napi_call_function(env, undefined, callback, ARGS_SIZE_TWO, result, &callResult);
163                 napi_delete_reference(env, callbackInfo->callback_);
164             } else {
165                 if (callbackInfo->errorCode_ == CODE_SUCCESS) {
166                 // Promise mode
167                     HILOGI("SppConnect execute Promise mode successfully");
168                     napi_resolve_deferred(env, callbackInfo->deferred_, result[PARAM1]);
169                 } else {
170                     HILOGI("SppConnect execute Promise mode failed");
171                     napi_reject_deferred(env, callbackInfo->deferred_, result[PARAM1]);
172                 }
173             }
174             napi_delete_async_work(env, callbackInfo->asyncWork_);
175             delete callbackInfo;
176             callbackInfo = nullptr;
177         },
178         static_cast<void*>(callbackInfo), &callbackInfo->asyncWork_);
179     if (napi_queue_async_work(env, callbackInfo->asyncWork_) != napi_ok) {
180         HILOGE("SppConnect napi_queue_async_work failed");
181         delete callbackInfo;
182         callbackInfo = nullptr;
183     }
184     return NapiGetUndefinedRet(env);
185 }
186 
CheckSppCloseClientSocketParams(napi_env env,napi_callback_info info,int & id)187 static napi_status CheckSppCloseClientSocketParams(napi_env env, napi_callback_info info, int &id)
188 {
189     HILOGI("enter");
190     size_t argc = ARGS_SIZE_ONE;
191     napi_value argv[ARGS_SIZE_ONE] = {0};
192     napi_value thisVar = nullptr;
193 
194     NAPI_BT_CALL_RETURN(napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
195     NAPI_BT_RETURN_IF((argc != ARGS_SIZE_ONE), "Requires 1 arguments.", napi_invalid_arg);
196     NAPI_BT_RETURN_IF(!ParseInt32(env, id, argv[PARAM0]), "Wrong argument type. int expected.", napi_invalid_arg);
197     return napi_ok;
198 }
199 
SppCloseClientSocket(napi_env env,napi_callback_info info)200 napi_value NapiSppClient::SppCloseClientSocket(napi_env env, napi_callback_info info)
201 {
202     HILOGI("enter");
203     std::shared_ptr<NapiSppClient> client = nullptr;
204     int id =  -1;
205     bool isOK = false;
206     auto status = CheckSppCloseClientSocketParams(env, info, id);
207     NAPI_BT_ASSERT_RETURN_UNDEF(env, status == napi_ok, BT_ERR_INVALID_PARAM);
208 
209     if (clientMap[id]) {
210         client = clientMap[id];
211     } else {
212         HILOGE("no such key in map.");
213         return NapiGetUndefinedRet(env);
214     }
215 
216     if (client->client_) {
217         client->client_->Close();
218         isOK = true;
219     }
220     clientMap.erase(id);
221     return NapiGetBooleanRet(env, isOK);
222 }
223 
CheckSppWriteParams(napi_env env,napi_callback_info info,int & id,uint8_t ** totalBuf,size_t & totalSize)224 static napi_status CheckSppWriteParams(
225     napi_env env, napi_callback_info info, int &id, uint8_t** totalBuf, size_t &totalSize)
226 {
227     HILOGI("enter");
228     size_t argc = ARGS_SIZE_TWO;
229     napi_value argv[ARGS_SIZE_TWO] = {0};
230     napi_value thisVar = nullptr;
231 
232     NAPI_BT_CALL_RETURN(napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
233     NAPI_BT_RETURN_IF((argc != ARGS_SIZE_TWO), "Requires 2 arguments.", napi_invalid_arg);
234     NAPI_BT_RETURN_IF(!ParseInt32(env, id, argv[PARAM0]), "Wrong argument type. int expected.", napi_invalid_arg);
235     NAPI_BT_RETURN_IF(!ParseArrayBuffer(env, totalBuf, totalSize, argv[PARAM1]),
236         "ParseArrayBuffer failed.", napi_invalid_arg);
237     return napi_ok;
238 }
239 
SppWrite(napi_env env,napi_callback_info info)240 napi_value NapiSppClient::SppWrite(napi_env env, napi_callback_info info)
241 {
242     HILOGI("enter");
243     uint8_t* totalBuf = nullptr;
244     size_t totalSize = 0;
245     bool isOK = false;
246     int id = -1;
247 
248     auto status = CheckSppWriteParams(env, info, id, &totalBuf, totalSize);
249     NAPI_BT_ASSERT_RETURN_FALSE(env, status == napi_ok, BT_ERR_INVALID_PARAM);
250     NAPI_BT_ASSERT_RETURN_FALSE(env, clientMap[id] > 0, BT_ERR_INTERNAL_ERROR);
251     std::shared_ptr<OutputStream> outputStream = clientMap[id]->client_->GetOutputStream();
252     while (totalSize) {
253         int result = outputStream->Write(totalBuf, totalSize);
254         NAPI_BT_ASSERT_RETURN_FALSE(env, result > 0, BT_ERR_SPP_IO);
255         totalSize = totalSize - static_cast<size_t>(result);
256         totalBuf += static_cast<size_t>(result);
257         isOK = true;
258     }
259     return NapiGetBooleanRet(env, isOK);
260 }
261 
NapiThreadSafeFuncCallJs(napi_env,napi_value jsCallback,void * context,void * data)262 static void NapiThreadSafeFuncCallJs(napi_env, napi_value jsCallback, void *context, void *data)
263 {
264     BufferCallbackInfo *callbackInfo = static_cast<BufferCallbackInfo *>(data);
265     std::shared_ptr<SppCallbackBuffer> buffer = callbackInfo->PopData();
266     if (buffer == nullptr) {
267         HILOGE("callbackInfo->PopData return nullptr");
268         return;
269     }
270     if (buffer->len_ < 0 || buffer->len_ > SOCKET_BUFFER_SIZE) {
271         HILOGE("buffer->len_ invalid");
272         return;
273     }
274 
275     napi_value result = nullptr;
276     uint8_t *bufferData = nullptr;
277     napi_create_arraybuffer(callbackInfo->env_, buffer->len_, (void **)&bufferData, &result);
278     if (memcpy_s(bufferData, buffer->len_, buffer->data_, buffer->len_) != EOK) {
279         HILOGE("memcpy_s failed!");
280         return;
281     }
282 
283     napi_value undefined = nullptr;
284     napi_value callResult = nullptr;
285     napi_get_undefined(callbackInfo->env_, &undefined);
286     napi_call_function(callbackInfo->env_, undefined, jsCallback, ARGS_SIZE_ONE, &result, &callResult);
287 }
288 
NapiSppCreateThreadSafeFunc(const std::shared_ptr<NapiSppClient> & client)289 static napi_status NapiSppCreateThreadSafeFunc(const std::shared_ptr<NapiSppClient> &client)
290 {
291     napi_value name;
292     napi_threadsafe_function tsfn;
293     const size_t maxQueueSize = 0;  // 0 means no limited
294     const size_t initialThreadCount = 1;
295     napi_value callback = nullptr;
296     auto callbackInfo = client->callbackInfos_[REGISTER_SPP_READ_TYPE];
297     NAPI_BT_CALL_RETURN(napi_create_string_utf8(callbackInfo->env_, "SppRead", NAPI_AUTO_LENGTH, &name));
298     NAPI_BT_CALL_RETURN(napi_get_reference_value(callbackInfo->env_, callbackInfo->callback_, &callback));
299     NAPI_BT_CALL_RETURN(napi_create_threadsafe_function(callbackInfo->env_, callback, nullptr,
300         name, maxQueueSize, initialThreadCount, nullptr, nullptr, nullptr, NapiThreadSafeFuncCallJs, &tsfn));
301 
302     client->sppReadThreadSafeFunc_ = tsfn;
303     return napi_ok;
304 }
305 
CheckSppClientOn(napi_env env,napi_callback_info info)306 napi_status CheckSppClientOn(napi_env env, napi_callback_info info)
307 {
308     HILOGI("enter");
309     size_t argc = ARGS_SIZE_THREE;
310     napi_value argv[ARGS_SIZE_THREE] = {0};
311     napi_value thisVar = nullptr;
312     int id = -1;
313     std::string type;
314 
315     NAPI_BT_CALL_RETURN(napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
316     NAPI_BT_RETURN_IF((argc != ARGS_SIZE_THREE), "Requires 3 arguments.", napi_invalid_arg);
317     NAPI_BT_RETURN_IF(!ParseString(env, type, argv[PARAM0]),
318         "Wrong argument type. String expected.", napi_invalid_arg);
319     NAPI_BT_RETURN_IF(type.c_str() != REGISTER_SPP_READ_TYPE, "Invalid type.", napi_invalid_arg);
320 
321     std::shared_ptr<BluetoothCallbackInfo> callbackInfo = std::make_shared<BufferCallbackInfo>();
322     callbackInfo->env_ = env;
323 
324     napi_valuetype valueType1 = napi_undefined;
325     napi_valuetype valueType2 = napi_undefined;
326     NAPI_BT_CALL_RETURN(napi_typeof(env, argv[PARAM1], &valueType1));
327     NAPI_BT_CALL_RETURN(napi_typeof(env, argv[PARAM2], &valueType2));
328     NAPI_BT_RETURN_IF(valueType1 != napi_number && valueType2 != napi_function,
329         "Wrong argument type. Function expected.", napi_invalid_arg);
330 
331     napi_create_reference(env, argv[PARAM2], 1, &callbackInfo->callback_);
332 
333     NAPI_BT_RETURN_IF(!ParseInt32(env, id, argv[PARAM1]), "Wrong argument type. Int expected.", napi_invalid_arg);
334 
335     std::shared_ptr<NapiSppClient> client = NapiSppClient::clientMap[id];
336     NAPI_BT_RETURN_IF(!client, "client is nullptr.", napi_invalid_arg);
337     NAPI_BT_RETURN_IF(client->sppReadFlag, "client is reading... please off first", napi_invalid_arg);
338     client->sppReadFlag = true;
339     client->callbackInfos_[type] = callbackInfo;
340     NAPI_BT_RETURN_IF(NapiSppCreateThreadSafeFunc(client) != napi_ok, "inner error", napi_invalid_arg);
341     HILOGI("sppRead begin");
342     client->thread_ = std::make_shared<std::thread>(NapiSppClient::SppRead, id);
343     client->thread_->detach();
344     return napi_ok;
345 }
346 
On(napi_env env,napi_callback_info info)347 napi_value NapiSppClient::On(napi_env env, napi_callback_info info)
348 {
349     HILOGI("enter");
350     auto status = CheckSppClientOn(env, info);
351     NAPI_BT_ASSERT_RETURN_UNDEF(env, status == napi_ok, BT_ERR_INVALID_PARAM);
352     return NapiGetUndefinedRet(env);
353 }
354 
CheckSppClientOff(napi_env env,napi_callback_info info)355 napi_status CheckSppClientOff(napi_env env, napi_callback_info info)
356 {
357     HILOGI("enter");
358     size_t argc = ARGS_SIZE_THREE;
359     napi_value argv[ARGS_SIZE_THREE] = {0};
360     napi_value thisVar = nullptr;
361     int id = -1;
362     std::string type;
363 
364     NAPI_BT_CALL_RETURN(napi_get_cb_info(env, info, &argc, argv, &thisVar, nullptr));
365     NAPI_BT_RETURN_IF(
366         (argc != ARGS_SIZE_TWO && argc != ARGS_SIZE_THREE), "Requires 2 or 3 arguments.", napi_invalid_arg);
367     NAPI_BT_RETURN_IF(!ParseString(env, type, argv[PARAM0]),
368                       "Wrong argument type. String expected.", napi_invalid_arg);
369     NAPI_BT_RETURN_IF(type.c_str() != REGISTER_SPP_READ_TYPE, "Invalid type.", napi_invalid_arg);
370 
371     NAPI_BT_RETURN_IF(!ParseInt32(env, id, argv[PARAM1]), "Wrong argument type. Int expected.", napi_invalid_arg);
372 
373     std::shared_ptr<NapiSppClient> client = NapiSppClient::clientMap[id];
374     NAPI_BT_RETURN_IF(!client, "client is nullptr.", napi_invalid_arg);
375     NAPI_BT_RETURN_IF(napi_release_threadsafe_function(client->sppReadThreadSafeFunc_, napi_tsfn_abort),
376         "innner error",
377         napi_invalid_arg);
378     client->sppReadThreadSafeFunc_ = nullptr;
379     client->callbackInfos_[type] = nullptr;
380     client->sppReadFlag = false;
381     return napi_ok;
382 }
383 
Off(napi_env env,napi_callback_info info)384 napi_value NapiSppClient::Off(napi_env env, napi_callback_info info)
385 {
386     HILOGI("enter");
387     auto status = CheckSppClientOff(env, info);
388     NAPI_BT_ASSERT_RETURN_UNDEF(env, status == napi_ok, BT_ERR_INVALID_PARAM);
389     return NapiGetUndefinedRet(env);
390 }
391 
SppRead(int id)392 void NapiSppClient::SppRead(int id)
393 {
394     auto client = clientMap[id];
395     if (client == nullptr || !client->sppReadFlag || client->callbackInfos_[REGISTER_SPP_READ_TYPE] == nullptr) {
396         HILOGE("thread start failed.");
397         return;
398     }
399     std::shared_ptr<InputStream> inputStream = client->client_->GetInputStream();
400     uint8_t buf[SOCKET_BUFFER_SIZE];
401 
402     while (true) {
403         HILOGI("thread start.");
404         (void)memset_s(buf, sizeof(buf), 0, sizeof(buf));
405         HILOGI("inputStream.Read start");
406         int ret = inputStream->Read(buf, sizeof(buf));
407         HILOGI("inputStream.Read end");
408         if (ret <= 0) {
409             HILOGI("inputStream.Read failed, ret = %{public}d", ret);
410             return;
411         } else {
412             HILOGI("callback read data to jshap begin");
413             if (client == nullptr || !client->sppReadFlag || !client->callbackInfos_[REGISTER_SPP_READ_TYPE]) {
414                 HILOGE("failed");
415                 return;
416             }
417             std::shared_ptr<BufferCallbackInfo> callbackInfo =
418                 std::static_pointer_cast<BufferCallbackInfo>(client->callbackInfos_[REGISTER_SPP_READ_TYPE]);
419             if (callbackInfo == nullptr) {
420                 HILOGE("callbackInfo nullptr");
421                 return;
422             }
423 
424             std::shared_ptr<SppCallbackBuffer> buffer = std::make_shared<SppCallbackBuffer>();
425             buffer->len_ = ret;
426             if (memcpy_s(buffer->data_, sizeof(buffer->data_), buf, ret) != EOK) {
427                 HILOGE("memcpy_s failed!");
428                 return;
429             }
430             callbackInfo->PushData(buffer);
431 
432             auto status = napi_acquire_threadsafe_function(client->sppReadThreadSafeFunc_);
433             if (status != napi_ok) {
434                 HILOGE("napi_acquire_threadsafe_function failed, status: %{public}d", status);
435                 return;
436             }
437 
438             status = napi_call_threadsafe_function(
439                 client->sppReadThreadSafeFunc_, static_cast<void *>(callbackInfo.get()), napi_tsfn_blocking);
440             if (status != napi_ok) {
441                 HILOGE("napi_call_threadsafe_function failed, status: %{public}d", status);
442                 return;
443             }
444 
445             status = napi_release_threadsafe_function(client->sppReadThreadSafeFunc_, napi_tsfn_release);
446             if (status != napi_ok) {
447                 HILOGE("napi_release_threadsafe_function failed, status: %{public}d", status);
448                 return;
449             }
450         }
451     }
452     return;
453 }
454 } // namespace Bluetooth
455 } // namespace OHOS