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