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