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