• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 "json_payload.h"
17 #include <securec.h>
18 
19 #include "cJSON.h"
20 #ifndef DFINDER_USE_MINI_NSTACKX
21 #include "coap_client.h"
22 #endif /* END OF DFINDER_USE_MINI_NSTACKX */
23 #include "nstackx_dfinder_log.h"
24 #include "nstackx_dfinder_mgt_msg_log.h"
25 #include "nstackx_error.h"
26 #include "nstackx_device.h"
27 #include "nstackx_statistics.h"
28 
29 #define TAG "nStackXCoAP"
30 
AddDeviceJsonData(cJSON * data,const DeviceInfo * deviceInfo)31 static int32_t AddDeviceJsonData(cJSON *data, const DeviceInfo *deviceInfo)
32 {
33     cJSON *item;
34 
35     item = cJSON_CreateString(deviceInfo->deviceId);
36     if (item == NULL || !cJSON_AddItemToObject(data, JSON_DEVICE_ID, item)) {
37         cJSON_Delete(item);
38         return NSTACKX_EFAILED;
39     }
40 
41     item = cJSON_CreateString(deviceInfo->deviceName);
42     if (item == NULL || !cJSON_AddItemToObject(data, JSON_DEVICE_NAME, item)) {
43         cJSON_Delete(item);
44         return NSTACKX_EFAILED;
45     }
46 
47     item = cJSON_CreateNumber(deviceInfo->deviceType);
48     if (item == NULL || !cJSON_AddItemToObject(data, JSON_DEVICE_TYPE, item)) {
49         cJSON_Delete(item);
50         return NSTACKX_EFAILED;
51     }
52 
53     item = cJSON_CreateString(deviceInfo->version);
54     if (item == NULL || !cJSON_AddItemToObject(data, JSON_HICOM_VERSION, item)) {
55         cJSON_Delete(item);
56         return NSTACKX_EFAILED;
57     }
58 
59     item = cJSON_CreateNumber(deviceInfo->mode);
60     if (item == NULL || !cJSON_AddItemToObject(data, JSON_REQUEST_MODE, item)) {
61         cJSON_Delete(item);
62         return NSTACKX_EFAILED;
63     }
64 
65     item = cJSON_CreateString(deviceInfo->deviceHash);
66     if (item == NULL || !cJSON_AddItemToObject(data, JSON_DEVICE_HASH, item)) {
67         cJSON_Delete(item);
68         return NSTACKX_EFAILED;
69     }
70 
71     item = cJSON_CreateString(deviceInfo->serviceData);
72     if (item == NULL || !cJSON_AddItemToObject(data, JSON_SERVICE_DATA, item)) {
73         cJSON_Delete(item);
74         DFINDER_LOGE(TAG, "cJSON_CreateString for serviceData failed");
75         return NSTACKX_EFAILED;
76     }
77 
78 #ifndef DFINDER_USE_MINI_NSTACKX
79     item = cJSON_CreateString(deviceInfo->extendServiceData);
80     if (item == NULL || !cJSON_AddItemToObject(data, JSON_EXTEND_SERVICE_DATA, item)) {
81         cJSON_Delete(item);
82         DFINDER_LOGE(TAG, "cJSON_CreateString for extendServiceData failed");
83         return NSTACKX_EFAILED;
84     }
85 #endif /* END OF DFINDER_USE_MINI_NSTACKX */
86 
87     return NSTACKX_EOK;
88 }
89 
90 #ifdef DFINDER_SUPPORT_MULTI_NIF
AddApJsonDataWithIdx(cJSON * data,uint8_t idx)91 static int32_t AddApJsonDataWithIdx(cJSON *data, uint8_t idx)
92 {
93     cJSON *item = NULL;
94     char ipString[INET_ADDRSTRLEN] = {0};
95 
96     if (GetLocalIpStringWithIdx(ipString, sizeof(ipString), idx) != NSTACKX_EOK) {
97         DFINDER_LOGE(TAG, "get local ip string failed with idx-%hhu", idx);
98         return NSTACKX_EFAILED;
99     }
100 
101     item = cJSON_CreateString(ipString);
102     if (item == NULL || !cJSON_AddItemToObject(data, JSON_DEVICE_WLAN_IP, item)) {
103         DFINDER_LOGE(TAG, "cjson create ip string failed");
104         cJSON_Delete(item);
105         return NSTACKX_EFAILED;
106     }
107 
108     return NSTACKX_EOK;
109 }
110 #else
AddWifiApJsonData(cJSON * data)111 static int32_t AddWifiApJsonData(cJSON *data)
112 {
113     cJSON *item = NULL;
114     char ipString[INET_ADDRSTRLEN] = {0};
115 
116     if (GetLocalIpString(ipString, sizeof(ipString)) != NSTACKX_EOK) {
117         return NSTACKX_EFAILED;
118     }
119 
120     item = cJSON_CreateString(ipString);
121     if (item == NULL || !cJSON_AddItemToObject(data, JSON_DEVICE_WLAN_IP, item)) {
122         cJSON_Delete(item);
123         return NSTACKX_EFAILED;
124     }
125 
126     return NSTACKX_EOK;
127 }
128 #endif
129 
AddCapabilityBitmap(cJSON * data,const DeviceInfo * deviceInfo)130 static int32_t AddCapabilityBitmap(cJSON *data, const DeviceInfo *deviceInfo)
131 {
132     cJSON *capabilityArray = NULL;
133     cJSON *capability = NULL;
134     uint32_t i;
135 
136     if (deviceInfo->capabilityBitmapNum == 0) {
137         return NSTACKX_EOK;
138     }
139 
140     capabilityArray = cJSON_CreateArray();
141     if (capabilityArray == NULL) {
142         goto L_END_JSON;
143     }
144 
145     for (i = 0; i < deviceInfo->capabilityBitmapNum; i++) {
146         capability = cJSON_CreateNumber(deviceInfo->capabilityBitmap[i]);
147         if (capability == NULL || !cJSON_AddItemToArray(capabilityArray, capability)) {
148             cJSON_Delete(capability);
149             goto L_END_JSON;
150         }
151     }
152     if (!cJSON_AddItemToObject(data, JSON_CAPABILITY_BITMAP, capabilityArray)) {
153         goto L_END_JSON;
154     }
155 
156     return NSTACKX_EOK;
157 
158 L_END_JSON:
159     cJSON_Delete(capabilityArray);
160     return NSTACKX_EFAILED;
161 }
162 
AddBusinessJsonData(cJSON * data,const DeviceInfo * deviceInfo,uint8_t isBroadcast)163 static int32_t AddBusinessJsonData(cJSON *data, const DeviceInfo *deviceInfo, uint8_t isBroadcast)
164 {
165     cJSON *item = NULL;
166 
167     item = cJSON_CreateNumber(deviceInfo->businessType);
168     if (item == NULL || !cJSON_AddItemToObject(data, JSON_BUSINESS_TYPE, item)) {
169         cJSON_Delete(item);
170         DFINDER_LOGE(TAG, "cJSON_CreateString for businessType failed");
171         return NSTACKX_EFAILED;
172     }
173     if (isBroadcast) {
174         item = cJSON_CreateString(deviceInfo->businessData.businessDataBroadcast);
175     } else {
176         item = cJSON_CreateString(deviceInfo->businessData.businessDataUnicast);
177     }
178     if (item == NULL || !cJSON_AddItemToObject(data, JSON_BUSINESS_DATA, item)) {
179         cJSON_Delete(item);
180         DFINDER_LOGE(TAG, "cJSON_CreateString for businessData failed");
181         return NSTACKX_EFAILED;
182     }
183 
184     return NSTACKX_EOK;
185 }
186 
ParseDeviceJsonData(const cJSON * data,DeviceInfo * dev)187 static int32_t ParseDeviceJsonData(const cJSON *data, DeviceInfo *dev)
188 {
189     cJSON *item = NULL;
190 
191     item = cJSON_GetObjectItemCaseSensitive(data, JSON_DEVICE_ID);
192     if (!cJSON_IsString(item) || !strlen(item->valuestring)) {
193         DFINDER_LOGE(TAG, "Cannot find device ID or invalid device ID");
194         return NSTACKX_EINVAL;
195     }
196     if (strcpy_s(dev->deviceId, sizeof(dev->deviceId), item->valuestring) != EOK) {
197         return NSTACKX_EFAILED;
198     }
199 
200     item = cJSON_GetObjectItemCaseSensitive(data, JSON_DEVICE_NAME);
201     if (!cJSON_IsString(item) || !strlen(item->valuestring)) {
202         DFINDER_LOGE(TAG, "Cannot find device name or invalid device name");
203         return NSTACKX_EINVAL;
204     }
205     if (strcpy_s(dev->deviceName, sizeof(dev->deviceName), item->valuestring) != EOK) {
206         return NSTACKX_EFAILED;
207     }
208 
209     item = cJSON_GetObjectItemCaseSensitive(data, JSON_DEVICE_TYPE);
210     if (!cJSON_IsNumber(item) || (item->valuedouble < 0) || (item->valuedouble > 0xFF)) {
211         DFINDER_LOGE(TAG, "Cannot find device type or invalid device type");
212         return NSTACKX_EINVAL;
213     }
214     dev->deviceType = (uint8_t)item->valuedouble;
215 
216     item = cJSON_GetObjectItemCaseSensitive(data, JSON_HICOM_VERSION);
217     if (!cJSON_IsString(item) || !strlen(item->valuestring)) {
218         DFINDER_LOGW(TAG, "Can't find hicom version");
219         return NSTACKX_EOK;
220     }
221     if (strcpy_s(dev->version, sizeof(dev->version), item->valuestring) != EOK) {
222         return NSTACKX_EFAILED;
223     }
224 
225     return NSTACKX_EOK;
226 }
227 
ParseWifiApJsonData(const cJSON * data,DeviceInfo * dev)228 static void ParseWifiApJsonData(const cJSON *data, DeviceInfo *dev)
229 {
230     cJSON *item = NULL;
231 
232     item = cJSON_GetObjectItemCaseSensitive(data, JSON_DEVICE_WLAN_IP);
233     if (cJSON_IsString(item)) {
234         if (inet_pton(AF_INET, item->valuestring, &(dev->netChannelInfo.wifiApInfo.ip)) != 1) {
235             DFINDER_LOGW(TAG, "Invalid ip address");
236         } else {
237             dev->netChannelInfo.wifiApInfo.state = NET_CHANNEL_STATE_CONNETED;
238         }
239     }
240 }
241 
ParseModeJsonData(const cJSON * data,DeviceInfo * dev)242 static void ParseModeJsonData(const cJSON *data, DeviceInfo *dev)
243 {
244     cJSON *item = NULL;
245     item = cJSON_GetObjectItemCaseSensitive(data, JSON_REQUEST_MODE);
246     if (item == NULL) {
247         DFINDER_LOGE(TAG, "Cannot get mode json");
248         return;
249     }
250     if (!cJSON_IsNumber(item) || (item->valuedouble < 0)) {
251         DFINDER_LOGE(TAG, "Cannot find mode or invalid mode");
252     } else {
253         if (dev == NULL) {
254             DFINDER_LOGE(TAG, "device info is null");
255             return;
256         }
257         dev->mode = (uint8_t)item->valuedouble;
258     }
259 }
260 
ParseDeviceHashData(const cJSON * data,DeviceInfo * dev)261 static void ParseDeviceHashData(const cJSON *data, DeviceInfo *dev)
262 {
263     cJSON *item = NULL;
264     item = cJSON_GetObjectItemCaseSensitive(data, JSON_DEVICE_HASH);
265     if (item == NULL) {
266         DFINDER_LOGD(TAG, "Cannot get hash json");
267         return;
268     }
269     if (item->valuestring == NULL) {
270         DFINDER_LOGD(TAG, "Cannot get valuestring");
271         return;
272     }
273     if (!cJSON_IsString(item) || !strlen(item->valuestring)) {
274         DFINDER_LOGD(TAG, "Cannot find device hash or invalid hash");
275         return;
276     }
277     if (strcpy_s(dev->deviceHash, sizeof(dev->deviceHash), item->valuestring) != EOK) {
278         DFINDER_LOGE(TAG, "parse device hash data error");
279         return;
280     }
281 }
282 
ParseServiceDataJsonData(const cJSON * data,DeviceInfo * dev)283 static void ParseServiceDataJsonData(const cJSON *data, DeviceInfo *dev)
284 {
285     cJSON *item = NULL;
286     item = cJSON_GetObjectItemCaseSensitive(data, JSON_SERVICE_DATA);
287     if (item == NULL) {
288         DFINDER_LOGE(TAG, "Cannot get service data");
289         return;
290     }
291     if (!cJSON_IsString(item)) {
292         DFINDER_LOGE(TAG, "Cannot find serviceData");
293         return;
294     }
295     if (item->valuestring == NULL) {
296         DFINDER_LOGE(TAG, "item->valuestring is null");
297         return;
298     }
299     if (strcpy_s(dev->serviceData, sizeof(dev->serviceData), item->valuestring)) {
300         DFINDER_LOGE(TAG, "parse device serviceData error");
301         return;
302     }
303 }
304 
305 #ifndef DFINDER_USE_MINI_NSTACKX
ParseExtendServiceDataJsonData(const cJSON * data,DeviceInfo * dev)306 static void ParseExtendServiceDataJsonData(const cJSON *data, DeviceInfo *dev)
307 {
308     cJSON *item = NULL;
309     item = cJSON_GetObjectItemCaseSensitive(data, JSON_EXTEND_SERVICE_DATA);
310     if (item == NULL) {
311         DFINDER_LOGE(TAG, "Cannot get service data");
312         return;
313     }
314     if (!cJSON_IsString(item)) {
315         DFINDER_LOGE(TAG, "Cannot find extendServiceData");
316         return;
317     }
318     if (item->valuestring == NULL) {
319         DFINDER_LOGE(TAG, "item->valuestring is null");
320         return;
321     }
322     if (strcpy_s(dev->extendServiceData, sizeof(dev->extendServiceData), item->valuestring)) {
323         DFINDER_LOGE(TAG, "parse device extendServiceData error");
324         return;
325     }
326 }
327 #endif /* END OF DFINDER_USE_MINI_NSTACKX */
328 
ParseCapabilityBitmap(const cJSON * data,DeviceInfo * deviceInfo)329 static void ParseCapabilityBitmap(const cJSON *data, DeviceInfo *deviceInfo)
330 {
331     cJSON *capability = NULL;
332     cJSON *item = NULL;
333     uint32_t capabilityBitmapNum = 0;
334 
335     item = cJSON_GetObjectItemCaseSensitive(data, JSON_CAPABILITY_BITMAP);
336     if (cJSON_IsArray(item)) {
337         cJSON_ArrayForEach(capability, item) {
338             if (capabilityBitmapNum >= NSTACKX_MAX_CAPABILITY_NUM) {
339                 break;
340             }
341 
342             if (!cJSON_IsNumber(capability) ||
343                 capability->valuedouble < 0 ||
344                 capability->valuedouble > 0xFFFFFFFF) {
345                 /* skip invalid capability */
346                 continue;
347             }
348             deviceInfo->capabilityBitmap[capabilityBitmapNum++] = (uint32_t)capability->valuedouble;
349         }
350     }
351     deviceInfo->capabilityBitmapNum = capabilityBitmapNum;
352 }
353 
ParseBusinessType(const cJSON * data,DeviceInfo * dev)354 static void ParseBusinessType(const cJSON *data, DeviceInfo *dev)
355 {
356     cJSON *item = NULL;
357     item = cJSON_GetObjectItemCaseSensitive(data, JSON_BUSINESS_TYPE);
358     if (item == NULL) {
359         dev->businessType = NSTACKX_BUSINESS_TYPE_NULL;
360         DFINDER_LOGW(TAG, "Cannot get businessType json");
361         return;
362     }
363     if (!cJSON_IsNumber(item) || (item->valuedouble < 0)) {
364         dev->businessType = NSTACKX_BUSINESS_TYPE_NULL;
365         DFINDER_LOGE(TAG, "Cannot find businessType or invalid Type");
366     } else {
367         dev->businessType = (uint8_t)item->valuedouble;
368     }
369 }
370 
ParseBusinessDataJsonData(const cJSON * data,DeviceInfo * dev,uint8_t isBroadcast)371 static void ParseBusinessDataJsonData(const cJSON *data, DeviceInfo *dev, uint8_t isBroadcast)
372 {
373     cJSON *item = NULL;
374     item = cJSON_GetObjectItemCaseSensitive(data, JSON_BUSINESS_DATA);
375     if (item == NULL) {
376         DFINDER_LOGE(TAG, "Cannot get businessData json");
377         return;
378     }
379     if (!cJSON_IsString(item)) {
380         DFINDER_LOGE(TAG, "Cannot find businessData");
381         return;
382     }
383     if (isBroadcast == NSTACKX_TRUE) {
384         if (strcpy_s(dev->businessData.businessDataBroadcast,
385             sizeof(dev->businessData.businessDataBroadcast), item->valuestring)) {
386             DFINDER_LOGE(TAG, "parse device businessData error");
387             return;
388         }
389     } else {
390         if (strcpy_s(dev->businessData.businessDataUnicast,
391             sizeof(dev->businessData.businessDataUnicast), item->valuestring)) {
392             DFINDER_LOGE(TAG, "parse device businessData error");
393             return;
394         }
395     }
396 }
397 
398 /*
399  * Service Discover JSON format
400  * {
401  *   "deviceId":[device ID, string],
402  *   "deviceName":[device name, string],
403  *   "type": [device type, number],
404  *   "version":[hicom version, string],
405  *   "wlanIp":[WLAN IP address, string],
406  *   "capabilityBitmap":[bitmap, bitmap, bitmap, ...]
407  *   "coapUri":[coap uri for discover, string]   <-- optional. When present, means it's broadcast request.
408  * }
409  */
410 #ifdef DFINDER_SUPPORT_MULTI_NIF
PrepareServiceDiscoverWithIdxEx(uint8_t isBroadcast,uint32_t idx)411 static char *PrepareServiceDiscoverWithIdxEx(uint8_t isBroadcast, uint32_t idx)
412 #else
413 static char *PrepareServiceDiscoverEx(uint8_t isBroadcast)
414 #endif /* #ifdef DFINDER_SUPPORT_MULTI_NIF */
415 {
416     char coapUriBuffer[NSTACKX_MAX_URI_BUFFER_LENGTH] = {0};
417     char host[NSTACKX_MAX_IP_STRING_LEN] = {0};
418     char *formatString = NULL;
419     const DeviceInfo *deviceInfo = GetLocalDeviceInfoPtr();
420     cJSON *data = NULL;
421     cJSON *localCoapString = NULL;
422 
423     data = cJSON_CreateObject();
424     if (data == NULL) {
425         goto L_END_JSON;
426     }
427 
428     /* Prepare local device info */
429     if ((AddDeviceJsonData(data, deviceInfo) != NSTACKX_EOK) ||
430 #ifdef DFINDER_SUPPORT_MULTI_NIF
431         (AddApJsonDataWithIdx(data, idx) != NSTACKX_EOK) ||
432 #else
433         (AddWifiApJsonData(data) != NSTACKX_EOK) ||
434 #endif
435         (AddCapabilityBitmap(data, deviceInfo) != NSTACKX_EOK) ||
436         (AddBusinessJsonData(data, deviceInfo, isBroadcast) != NSTACKX_EOK)) {
437         DFINDER_LOGE(TAG, "Add json data failed");
438         goto L_END_JSON;
439     }
440 
441     if (isBroadcast) {
442 #ifdef DFINDER_SUPPORT_MULTI_NIF
443         if (GetLocalIpStringWithIdx(host, sizeof(host), idx) != NSTACKX_EOK) {
444 #else
445         if (GetLocalIpString(host, sizeof(host)) != NSTACKX_EOK) {
446 #endif
447             DFINDER_LOGE(TAG, "GetLocalIpStringWithIdx failed");
448             goto L_END_JSON;
449         }
450         if (sprintf_s(coapUriBuffer, sizeof(coapUriBuffer), "coap://%s/" COAP_DEVICE_DISCOVER_URI, host) < 0) {
451             DFINDER_LOGE(TAG, "deal coap url failed");
452             goto L_END_JSON;
453         }
454         localCoapString = cJSON_CreateString(coapUriBuffer);
455         if (localCoapString == NULL || !cJSON_AddItemToObject(data, JSON_COAP_URI, localCoapString)) {
456             cJSON_Delete(localCoapString);
457             DFINDER_LOGE(TAG, "local coap string failed");
458             goto L_END_JSON;
459         }
460     }
461 
462     formatString = cJSON_PrintUnformatted(data);
463     if (formatString == NULL) {
464         DFINDER_LOGE(TAG, "cJSON_PrintUnformatted failed");
465     }
466 
467 L_END_JSON:
468     cJSON_Delete(data);
469     return formatString;
470 }
471 
472 #ifdef DFINDER_SUPPORT_MULTI_NIF
473 char *PrepareServiceDiscoverWithIdx(uint8_t isBroadcast, uint32_t idx)
474 {
475     char *str = PrepareServiceDiscoverWithIdxEx(isBroadcast, idx);
476     if (str == NULL) {
477         IncStatistics(STATS_PREPARE_SD_MSG_FAILED);
478     }
479     return str;
480 }
481 #else
482 char *PrepareServiceDiscover(uint8_t isBroadcast)
483 {
484     char *str = PrepareServiceDiscoverEx(isBroadcast);
485     if (str == NULL) {
486         IncStatistics(STATS_PREPARE_SD_MSG_FAILED);
487     }
488     return str;
489 }
490 #endif
491 
492 static int32_t ParseServiceDiscoverEx(const uint8_t *buf, DeviceInfo *deviceInfo, char **remoteUrlPtr)
493 {
494     char *remoteUrl = NULL;
495     cJSON *data = NULL;
496     cJSON *item = NULL;
497     uint8_t isBroadcast = NSTACKX_FALSE;
498 
499     if (buf == NULL || deviceInfo == NULL || remoteUrlPtr == NULL) {
500         return NSTACKX_EINVAL;
501     }
502 
503     data = cJSON_Parse((char *)buf);
504     if (data == NULL) {
505         return NSTACKX_EINVAL;
506     }
507 
508     if (ParseDeviceJsonData(data, deviceInfo) != NSTACKX_EOK) {
509         cJSON_Delete(data);
510         return NSTACKX_EINVAL;
511     }
512 
513     ParseWifiApJsonData(data, deviceInfo);
514     ParseCapabilityBitmap(data, deviceInfo);
515     ParseModeJsonData(data, deviceInfo);
516     ParseDeviceHashData(data, deviceInfo);
517     ParseServiceDataJsonData(data, deviceInfo);
518 #ifndef DFINDER_USE_MINI_NSTACKX
519     ParseExtendServiceDataJsonData(data, deviceInfo);
520 #endif /* END OF DFINDER_USE_MINI_NSTACKX */
521     ParseBusinessType(data, deviceInfo);
522 
523     item = cJSON_GetObjectItemCaseSensitive(data, JSON_COAP_URI);
524     if (item != NULL) {
525         isBroadcast = NSTACKX_TRUE;
526         if (cJSON_IsString(item)) {
527             DFINDER_LOGD(TAG, "new device join");
528             remoteUrl = strdup(item->valuestring);
529             if (remoteUrl == NULL) {
530                 DFINDER_LOGE(TAG, "remoteUrl strdup fail");
531                 cJSON_Delete(data);
532                 return NSTACKX_ENOMEM;
533             }
534         }
535     }
536     ParseBusinessDataJsonData(data, deviceInfo, isBroadcast);
537     deviceInfo->businessData.isBroadcast = isBroadcast;
538     *remoteUrlPtr = remoteUrl;
539     cJSON_Delete(data);
540     DFINDER_MGT_UNPACK_LOG(deviceInfo);
541     return NSTACKX_EOK;
542 }
543 
544 int32_t ParseServiceDiscover(const uint8_t *buf, DeviceInfo *deviceInfo, char **remoteUrlPtr)
545 {
546     int32_t ret = ParseServiceDiscoverEx(buf, deviceInfo, remoteUrlPtr);
547     if (ret != NSTACKX_EOK) {
548         IncStatistics(STATS_PARSE_SD_MSG_FAILED);
549     }
550     return ret;
551 }
552