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 "ohos_bt_socket.h"
17 
18 #include <iostream>
19 #include <cstring>
20 #include <vector>
21 
22 #include "ohos_bt_adapter_utils.h"
23 #include "bluetooth_def.h"
24 #include "bluetooth_gatt_client.h"
25 #include "bluetooth_socket.h"
26 #include "bluetooth_host.h"
27 #include "bluetooth_log.h"
28 #include "bluetooth_utils.h"
29 #include "bluetooth_object_map.h"
30 
31 #ifdef __cplusplus
32 extern "C" {
33 #endif
34 
35 using namespace std;
36 
37 namespace OHOS {
38 namespace Bluetooth {
39 
40 const int MAX_OBJECT_NUM = 10000;
41 
42 static BluetoothObjectMap<std::shared_ptr<ServerSocket>, MAX_OBJECT_NUM> g_serverMap;
43 static BluetoothObjectMap<std::shared_ptr<ClientSocket>, MAX_OBJECT_NUM> g_clientMap;
44 
45 class BluetoothConnectionObserverWapper : public BluetoothConnectionObserver {
46 public:
BluetoothConnectionObserverWapper(BtSocketConnectionCallback &callback)47     explicit BluetoothConnectionObserverWapper(BtSocketConnectionCallback &callback)
48     {
49         socektConnectCallback = callback;
50     }
51 
52     __attribute__((no_sanitize("cfi")))
53     void OnConnectionStateChanged(const CallbackConnectParam &param) override
54     {
55         if (socektConnectCallback.connStateCb == nullptr && socektConnectCallback.bleConnStateCb == nullptr) {
56             HILOGE("callback is null");
57             return;
58         }
59         BdAddr addr;
60         GetAddrFromString(param.addr.GetDeviceAddr(), addr.addr);
61         BtUuid btUuid;
62         string strUuid = param.uuid.ToString();
63         btUuid.uuid = (char *)strUuid.c_str();
64         btUuid.uuidLen = strUuid.size();
65         if (param.type == OHOS_SOCKET_SPP_RFCOMM && socektConnectCallback.connStateCb != nullptr) {
66             socektConnectCallback.connStateCb(&addr, btUuid, param.status, param.result);
67         }
68         if (param.type == OHOS_SOCKET_L2CAP_LE && socektConnectCallback.bleConnStateCb != nullptr) {
69             socektConnectCallback.bleConnStateCb(&addr, param.psm, param.status, param.result);
70         }
71     }
72 
73     BtSocketConnectionCallback socektConnectCallback;
74 };
75 
76 using ClientCbIterator = std::map<int, std::shared_ptr<BluetoothConnectionObserverWapper>>::iterator;
77 
GetSocketUuidPara(const BluetoothCreateSocketPara *socketPara, UUID &serverUuid)78 static bool GetSocketUuidPara(const BluetoothCreateSocketPara *socketPara, UUID &serverUuid)
79 {
80     if (socketPara->socketType == OHOS_SOCKET_SPP_RFCOMM) {
81         if (socketPara->uuid.uuid == nullptr || strlen(socketPara->uuid.uuid) != socketPara->uuid.uuidLen) {
82             HILOGE("param uuid invalid!");
83             return false;
84         }
85         string tmpUuid(socketPara->uuid.uuid);
86         if (!IsValidUuid(tmpUuid)) {
87             HILOGE("match the UUID faild.");
88             return false;
89         }
90         serverUuid = UUID::FromString(tmpUuid);
91     } else if (socketPara->socketType == OHOS_SOCKET_L2CAP_LE) {
92         serverUuid = UUID::RandomUUID();
93     } else {
94         HILOGE("param socketType invalid. socketType: %{public}d", socketPara->socketType);
95         return false;
96     }
97     return true;
98 }
99 
100 /**
101  * @brief Creates an server listening socket based on the service record.
102  *
103  * @param socketPara The parameters to create a server socket.
104  * @param name The service's name.
105  * @return Returns a server ID, if create fail return {@link BT_SOCKET_INVALID_ID}.
106  */
SocketServerCreate(const BluetoothCreateSocketPara *socketPara, const char *name)107 int SocketServerCreate(const BluetoothCreateSocketPara *socketPara, const char *name)
108 {
109     HILOGD("SocketServerCreate start!");
110     if (socketPara == nullptr || name == nullptr) {
111         HILOGE("socketPara or name is null.");
112         return BT_SOCKET_INVALID_ID;
113     }
114 
115     if (socketPara->socketType == OHOS_SOCKET_L2CAP_LE) {
116         CHECK_AND_RETURN_LOG_RET(IS_BLE_ENABLED(), BT_SOCKET_INVALID_ID, "BLE is not TURN_ON");
117     } else {
118         CHECK_AND_RETURN_LOG_RET(IS_BT_ENABLED(), BT_SOCKET_INVALID_ID, "BR is not TURN_ON");
119     }
120 
121     UUID serverUuid;
122     if (!GetSocketUuidPara(socketPara, serverUuid)) {
123         return BT_SOCKET_INVALID_ID;
124     }
125 
126     string serverName(name);
127     std::shared_ptr<ServerSocket> server = std::make_shared<ServerSocket>(serverName, serverUuid,
128         BtSocketType(socketPara->socketType), socketPara->isEncrypt);
129     int result = server->Listen();
130     if (result != BT_NO_ERROR) {
131         HILOGE("SocketServerCreate fail, result: %{public}d", result);
132         server->Close();
133         HILOGE("SocketServerCreate closed.");
134         return BT_SOCKET_INVALID_ID;
135     }
136     int serverId = g_serverMap.AddObject(server);
137     HILOGI("success, serverId: %{public}d, socketType: %{public}d, isEncrypt: %{public}d", serverId,
138         socketPara->socketType, socketPara->isEncrypt);
139     return serverId;
140 }
141 
142 /**
143  * @brief Waits for a remote device to connect to this server socket.
144  *
145  * This method return a client ID indicates a client socket
146  * can be used to read data from and write data to remote device.
147  * This method will block until a connection is established.
148  *
149  * @param serverId The relative ID used to identify the current server socket, obtain the value by calling
150  * {@link SocketServerCreate}.
151  * @return Returns a client ID, if accept fail return {@link BT_SOCKET_INVALID_ID}.
152  */
SocketServerAccept(int serverId)153 int SocketServerAccept(int serverId)
154 {
155     HILOGI("SocketServerAccept start, serverId: %{public}d", serverId);
156     std::shared_ptr<ServerSocket> server = g_serverMap.GetObject(serverId);
157     if (server == nullptr) {
158         HILOGD("server is null!");
159         return BT_SOCKET_INVALID_ID;
160     }
161 
162     std::shared_ptr<ClientSocket> client = server->Accept(0);
163     if (client == nullptr) {
164         HILOGE("client is null!");
165         return BT_SOCKET_INVALID_ID;
166     }
167     int clientId = g_clientMap.AddObject(client);
168     HILOGI("success, clientId: %{public}d", clientId);
169     return clientId;
170 }
171 
172 /**
173  * @brief Disables an socket server socket and releases related resources.
174  *
175  * @param serverId The relative ID used to identify the current server socket, obtain the value by calling
176  * {@link SocketServerCreate}.
177  * @return Returns the operation result status {@link BtStatus}.
178  */
SocketServerClose(int serverId)179 int SocketServerClose(int serverId)
180 {
181     HILOGI("SocketServerClose start, serverId: %{public}d", serverId);
182     std::shared_ptr<ServerSocket> server = g_serverMap.GetObject(serverId);
183     if (server == nullptr) {
184         HILOGE("server is null!");
185         return OHOS_BT_STATUS_FAIL;
186     }
187     server->Close();
188     g_serverMap.RemoveObject(serverId);
189     return OHOS_BT_STATUS_SUCCESS;
190 }
191 
192 /**
193  * @brief Set fast connection flag
194  *
195  * @param bdAddr The remote device address to connect.
196  * @return Returns the operation result status {@link BtStatus}.
197  */
SocketSetFastConnection(const BdAddr *bdAddr)198 int SocketSetFastConnection(const BdAddr *bdAddr)
199 {
200     string strAddress;
201     int leType = 1;
202     if (bdAddr == nullptr) {
203         HILOGE("bdAddr is nullptr.");
204         return OHOS_BT_STATUS_PARM_INVALID;
205     }
206     ConvertAddr(bdAddr->addr, strAddress);
207     // create a client to reuse requestfastestconn.
208     std::shared_ptr<GattClient> client = nullptr;
209     BluetoothRemoteDevice device(strAddress, leType);
210     client = std::make_shared<GattClient>(device);
211     client->Init();
212     int result = client->RequestFastestConn();
213     if (result != OHOS_BT_STATUS_SUCCESS) {
214         HILOGE("request fastest connect fail.");
215         return OHOS_BT_STATUS_FAIL;
216     }
217     return OHOS_BT_STATUS_SUCCESS;
218 }
219 
220 /**
221  * @brief Connects to a remote device over the socket.
222  * This method will block until a connection is made or the connection fails.
223  *
224  * @param socketPara The param to create a client socket and connect to a remote device.
225  * @param bdAddr The remote device address to connect.
226  * @param psm BluetoothSocketType is {@link OHOS_SOCKET_L2CAP_LE} use dynamic PSM value from remote device.
227  * BluetoothSocketType is {@link OHOS_SOCKET_SPP_RFCOMM} use -1.
228  * @return Returns a client ID, if connect fail return {@link BT_SOCKET_INVALID_ID}.
229  */
SocketConnect(const BluetoothCreateSocketPara *socketPara, const BdAddr *bdAddr, int psm)230 int SocketConnect(const BluetoothCreateSocketPara *socketPara, const BdAddr *bdAddr, int psm)
231 {
232     HILOGI("SocketConnect start.");
233     if (socketPara == nullptr || bdAddr == nullptr) {
234         HILOGE("socketPara is nullptr, or bdAddr is nullptr");
235         return BT_SOCKET_INVALID_ID;
236     }
237 
238     string strAddress;
239     ConvertAddr(bdAddr->addr, strAddress);
240     std::shared_ptr<BluetoothRemoteDevice> device = std::make_shared<BluetoothRemoteDevice>(strAddress, 0);
241 
242     UUID serverUuid;
243     if (!GetSocketUuidPara(socketPara, serverUuid)) {
244         return BT_SOCKET_INVALID_ID;
245     }
246 
247     std::shared_ptr<ClientSocket> client = std::make_shared<ClientSocket>(*device, serverUuid,
248         BtSocketType(socketPara->socketType), socketPara->isEncrypt);
249     HILOGI("socketType: %{public}d, isEncrypt: %{public}d", socketPara->socketType, socketPara->isEncrypt);
250     client->Init();
251     int result = client->Connect(psm);
252     if (result != OHOS_BT_STATUS_SUCCESS) {
253         HILOGE("SocketConnect fail, result: %{public}d", result);
254         client->Close();
255         HILOGE("SocketConnect closed.");
256         return BT_SOCKET_INVALID_ID;
257     }
258     int clientId = g_clientMap.AddObject(client);
259     HILOGI("SocketConnect success, clientId: %{public}d", clientId);
260     return clientId;
261 }
262 
263 /**
264  * @brief Connects to a remote device over the socket.
265  * This method will block until a connection is made or the connection fails.
266  * @param socketPara The param to create a client socket and connect to a remote device.
267  * @param bdAddr The remote device address to connect.
268  * @param psm BluetoothSocketType is {@link OHOS_SOCKET_L2CAP_LE} use dynamic PSM value from remote device.
269  * BluetoothSocketType is {@link OHOS_SOCKET_SPP_RFCOMM} use -1.
270  * @param callback Reference to the socket state observer
271  * @return Returns a client ID, if connect fail return {@link BT_SOCKET_INVALID_ID}.
272  */
SocketConnectEx(const BluetoothCreateSocketPara *socketPara, const BdAddr *bdAddr, int psm, BtSocketConnectionCallback *callback)273 int SocketConnectEx(const BluetoothCreateSocketPara *socketPara, const BdAddr *bdAddr, int psm,
274     BtSocketConnectionCallback *callback)
275 {
276     HILOGI("SocketConnect start.");
277     if (socketPara == nullptr || bdAddr == nullptr || callback == nullptr) {
278         HILOGE("socketPara is nullptr, or bdAddr is nullptr, or callback is nullptr");
279         return BT_SOCKET_INVALID_ID;
280     }
281 
282     string strAddress;
283     ConvertAddr(bdAddr->addr, strAddress);
284     std::shared_ptr<BluetoothRemoteDevice> device = std::make_shared<BluetoothRemoteDevice>(strAddress, 0);
285 
286     UUID serverUuid;
287     if (!GetSocketUuidPara(socketPara, serverUuid)) {
288         return BT_SOCKET_INVALID_ID;
289     }
290 
291     if (socketPara->socketType != OHOS_SOCKET_SPP_RFCOMM && socketPara->socketType != OHOS_SOCKET_L2CAP_LE) {
292         HILOGE("SocketType is not support");
293         return BT_SOCKET_INVALID_TYPE;
294     }
295 
296     std::shared_ptr<BluetoothConnectionObserverWapper> connWrapper =
297         std::make_shared<BluetoothConnectionObserverWapper>(*callback);
298     std::shared_ptr<ClientSocket> client = std::make_shared<ClientSocket>(*device, serverUuid,
299         BtSocketType(socketPara->socketType), socketPara->isEncrypt, connWrapper);
300     HILOGI("socketType: %{public}d, isEncrypt: %{public}d", socketPara->socketType, socketPara->isEncrypt);
301     client->Init();
302     int result = client->Connect(psm);
303     if (result != OHOS_BT_STATUS_SUCCESS) {
304         HILOGE("SocketConnect fail, result: %{public}d", result);
305         client->Close();
306         HILOGE("SocketConnect closed.");
307         return BT_SOCKET_INVALID_ID;
308     }
309     int clientId = g_clientMap.AddObject(client);
310     HILOGI("SocketConnect success, clientId: %{public}d", clientId);
311     return clientId;
312 }
313 
314 /**
315  * @brief Disables a connection and releases related resources.
316  *
317  * @param clientId The relative ID used to identify the current client socket.
318  * @return Returns the operation result status {@link BtStatus}.
319  */
SocketDisconnect(int clientId)320 int SocketDisconnect(int clientId)
321 {
322     HILOGI("SocketDisconnect start, clientId: %{public}d", clientId);
323     std::shared_ptr<ClientSocket> client = g_clientMap.GetObject(clientId);
324     if (client == nullptr) {
325         HILOGE("client is null, clientId: %{public}d", clientId);
326         return OHOS_BT_STATUS_FAIL;
327     }
328     client->Close();
329     g_clientMap.RemoveObject(clientId);
330     HILOGI("SocketDisConnect success, clientId: %{public}d", clientId);
331     return OHOS_BT_STATUS_SUCCESS;
332 }
333 
334 /**
335  * @brief Socket get remote device's address.
336  *
337  * @param clientId The relative ID used to identify the current client socket.
338  * @param remoteAddr Remote device's address, memory allocated by caller.
339  * @return Returns the operation result status {@link BtStatus}.
340  */
SocketGetRemoteAddr(int clientId, BdAddr *remoteAddr)341 int SocketGetRemoteAddr(int clientId, BdAddr *remoteAddr)
342 {
343     HILOGI("SocketGetRemoteAddr clientId: %{public}d", clientId);
344     if (remoteAddr == nullptr) {
345         HILOGE("remoteAddr is null, clientId: %{public}d", clientId);
346         return OHOS_BT_STATUS_PARM_INVALID;
347     }
348     std::shared_ptr<ClientSocket> client = g_clientMap.GetObject(clientId);
349     if (client == nullptr) {
350         HILOGE("client is null, clientId: %{public}d", clientId);
351         return OHOS_BT_STATUS_FAIL;
352     }
353     string tmpAddr = client->GetRemoteDevice().GetDeviceAddr();
354     GetAddrFromString(tmpAddr, remoteAddr->addr);
355     HILOGI("device: %{public}s", GetEncryptAddr(tmpAddr).c_str());
356     return OHOS_BT_STATUS_SUCCESS;
357 }
358 
359 /**
360  * @brief Get the connection status of this socket.
361  *
362  * @param clientId The relative ID used to identify the current client socket.
363  * @return Returns true is connected or false is not connected.
364  */
IsSocketConnected(int clientId)365 bool IsSocketConnected(int clientId)
366 {
367     HILOGI("IsSocketConnected clientId: %{public}d", clientId);
368     std::shared_ptr<ClientSocket> client = g_clientMap.GetObject(clientId);
369     if (client == nullptr) {
370         HILOGE("client is null, clientId: %{public}d", clientId);
371         return false;
372     }
373     bool isConnected = client->IsConnected();
374     HILOGI("clientId: %{public}d, isConnected: %{public}d", clientId, isConnected);
375     return isConnected;
376 }
377 
378 /**
379  * @brief Read data from socket.
380  * This method blocks until input data is available.
381  *
382  * @param clientId The relative ID used to identify the current client socket.
383  * @param buf Indicate the buffer which read in, memory allocated by caller.
384  * @param bufLen Indicate the buffer length.
385  * @return Returns the length greater than 0 as read the actual length.
386  * Returns {@link BT_SOCKET_READ_SOCKET_CLOSED} if the socket is closed.
387  * Returns {@link BT_SOCKET_READ_FAILED} if the operation failed.
388  */
SocketRead(int clientId, uint8_t *buf, uint32_t bufLen)389 int SocketRead(int clientId, uint8_t *buf, uint32_t bufLen)
390 {
391     HILOGD("SocketRead start, clientId: %{public}d, bufLen: %{public}d", clientId, bufLen);
392     if (buf == nullptr || bufLen == 0) {
393         HILOGE("buf is null or bufLen is 0! clientId: %{public}d", clientId);
394         return BT_SOCKET_READ_FAILED;
395     }
396     std::shared_ptr<ClientSocket> client = g_clientMap.GetObject(clientId);
397     if (client == nullptr) {
398         HILOGE("client is null, clientId: %{public}d", clientId);
399         return BT_SOCKET_READ_FAILED;
400     }
401     if (client->GetInputStream() == nullptr) {
402         HILOGE("inputStream is null, clientId: %{public}d", clientId);
403         return BT_SOCKET_READ_FAILED;
404     }
405     int readLen = client->GetInputStream()->Read(buf, bufLen);
406     HILOGD("SocketRead ret, clientId: %{public}d, readLen: %{public}d", clientId, readLen);
407     return readLen;
408 }
409 
410 /**
411  * @brief Client write data to socket.
412  *
413  * @param clientId The relative ID used to identify the current client socket.
414  * @param data Indicate the data to be written.
415  * @return Returns the actual write length.
416  * Returns {@link BT_SOCKET_WRITE_FAILED} if the operation failed.
417  */
SocketWrite(int clientId, const uint8_t *data, uint32_t len)418 int SocketWrite(int clientId, const uint8_t *data, uint32_t len)
419 {
420     HILOGD("SocketWrite start, clientId: %{public}d, len: %{public}d", clientId, len);
421     if (data == nullptr || len == 0) {
422         HILOGE("data is null or len is 0! clientId: %{public}d", clientId);
423         return BT_SOCKET_WRITE_FAILED;
424     }
425     std::shared_ptr<ClientSocket> client = g_clientMap.GetObject(clientId);
426     if (client == nullptr) {
427         HILOGE("client is null! clientId: %{public}d", clientId);
428         return BT_SOCKET_WRITE_FAILED;
429     }
430     if (client->GetOutputStream() == nullptr) {
431         HILOGE("outputStream is null, clientId: %{public}d", clientId);
432         return BT_SOCKET_READ_FAILED;
433     }
434     int writeLen = client->GetOutputStream()->Write(data, len);
435     HILOGD("end, writeLen: %{public}d", writeLen);
436     return writeLen;
437 }
438 
439 /**
440  * @brief Get dynamic PSM value for OHOS_SOCKET_L2CAP.
441  *
442  * @param serverId The relative ID used to identify the current server socket, obtain the value by calling
443  * {@link SocketServerCreate}.
444  * @return Returns the PSM value.
445  * Returns {@link BT_SOCKET_INVALID_PSM} if the operation failed.
446  */
SocketGetPsm(int serverId)447 int SocketGetPsm(int serverId)
448 {
449     HILOGI("serverId: %{public}d", serverId);
450     std::shared_ptr<ServerSocket> server = g_serverMap.GetObject(serverId);
451     CHECK_AND_RETURN_LOG_RET(server, BT_SOCKET_INVALID_PSM, "server is null!");
452     return server->GetL2capPsm();
453 }
454 
455 /**
456  * @brief Get server channel number for OHOS_SOCKET_RFCOMM.
457  *
458  * @param serverId The relative ID used to identify the current server socket, obtain the value by calling
459  * {@link SocketServerCreate}.
460  * @return Returns the scn.
461  * Returns {@link BT_SOCKET_INVALID_PSM} if the operation failed.
462  */
SocketGetScn(int serverId)463 int SocketGetScn(int serverId)
464 {
465     HILOGI("serverId: %{public}d", serverId);
466     std::shared_ptr<ServerSocket> server = g_serverMap.GetObject(serverId);
467     CHECK_AND_RETURN_LOG_RET(server, BT_SOCKET_INVALID_SCN, "server is null!");
468     return server->GetRfcommScn();
469 }
470 
471 /**
472  * @brief Adjust the socket send and recv buffer size, limit range is 4KB to 50KB
473  *
474  * @param clientId The relative ID used to identify the current client socket.
475  * @param bufferSize The buffer size want to set, unit is byte.
476  * @return  Returns the operation result status {@link BtStatus}.
477  */
SetSocketBufferSize(int clientId, uint32_t bufferSize)478 int SetSocketBufferSize(int clientId, uint32_t bufferSize)
479 {
480     HILOGI("start, clientId: %{public}d, bufferSize: %{public}d", clientId, bufferSize);
481     std::shared_ptr<ClientSocket> client = g_clientMap.GetObject(clientId);
482     if (client == nullptr) {
483         HILOGE("client is null! clientId: %{public}d", clientId);
484         return OHOS_BT_STATUS_FAIL;
485     }
486     int ret = client->SetBufferSize(bufferSize);
487     if (ret == RET_BAD_PARAM) {
488         return OHOS_BT_STATUS_PARM_INVALID;
489     } else if (ret == RET_BAD_STATUS) {
490         return OHOS_BT_STATUS_FAIL;
491     }
492     return OHOS_BT_STATUS_SUCCESS;
493 }
494 /**
495  * @brief Update the coc connection params
496  *
497  * @param param CocUpdateSocketParam instance for carry params.
498  * @param bdAddr The remote device address to connect.
499  * @return Returns the operation result status {@link BtStatus}.
500  */
SocketUpdateCocConnectionParams(BluetoothCocUpdateSocketParam* param, const BdAddr *bdAddr)501 int SocketUpdateCocConnectionParams(BluetoothCocUpdateSocketParam* param, const BdAddr *bdAddr)
502 {
503     CocUpdateSocketParam params;
504 
505     HILOGI("Socket update coc params start");
506     CHECK_AND_RETURN_LOG_RET(param, OHOS_BT_STATUS_FAIL, "param is null");
507     CHECK_AND_RETURN_LOG_RET(bdAddr, OHOS_BT_STATUS_FAIL, "bdAddr is null");
508     ConvertAddr(bdAddr->addr, params.addr);
509     params.minInterval = param->minInterval;
510     params.maxInterval = param->maxInterval;
511     params.peripheralLatency = param->peripheralLatency;
512     params.supervisionTimeout = param->supervisionTimeout;
513     params.minConnEventLen = param->minConnEventLen;
514     params.maxConnEventLen = param->maxConnEventLen;
515 
516     std::shared_ptr<BluetoothRemoteDevice> device = std::make_shared<BluetoothRemoteDevice>(params.addr,
517         OHOS_SOCKET_SPP_RFCOMM);
518     std::shared_ptr<ClientSocket> client = std::make_shared<ClientSocket>(*device, UUID::RandomUUID(),
519         TYPE_L2CAP_LE, false);
520     CHECK_AND_RETURN_LOG_RET(client, OHOS_BT_STATUS_FAIL, "client is null");
521     return client->UpdateCocConnectionParams(params);
522 }
523 
524 }  // namespace Bluetooth
525 }  // namespace OHOS
526 #ifdef __cplusplus
527 }
528 #endif