2bfb853a783916f8f3b58c860d07d307e4d7579c
[platform/core/connectivity/zigbee-manager.git] / zigbee-daemon / zigbee-lib / src / zblib_service.c
1 /*
2  * Copyright (c) 2016 Samsung Electronics Co., Ltd. All rights reserved.
3  *
4  * Contact: Suresh Kumar N (suresh.n@samsung.com)
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  */
18
19 #include <dlfcn.h>
20 #include <sys/stat.h>
21
22 #include <zblib.h>
23 #include <zblib_service.h>
24 #include <zblib_plugin.h>
25 #include <zblib_service_interface.h>
26 #include <zblib_request.h>
27 #include <zblib_driver.h>
28 #include <zblib_driver_manager.h>
29
30 struct _zblib_async_init_info {
31         gboolean is_initialized; /**< Does initialization succeeded? */
32         gboolean is_finished; /**< Does initialization finished? */
33
34         ZigBeePlugin *plugin; /**< ZigBee plugin */
35         void *user_data; /**< User data */
36 };
37
38 /**< ZigBee Service object */
39 struct zblib_service_type {
40         GMainLoop *main_loop; /**< Service main-loop */
41
42         GSList *interface_objs; /**< ZigBee Service interface objects */
43         GSList *plugins; /**< ZigBee plug-ins */
44
45         GHashTable *request_table; /**< Request Hash table */
46         guint request_id; /**< Request ID */
47
48         GSList *async_initializer; /**< Hash table for asynchronous initializer */
49         zblib_plugin_init_finished_cb callback; /**< Callback */
50 };
51
52 static GSList *__zblib_service_ref_async_initializer(ZigBeeService* service)
53 {
54         zblib_check_null_ret_error("service", service, NULL);
55
56         return service->async_initializer;
57 }
58
59 static void __zblib_service_set_async_initializer(ZigBeeService* service, GSList* list)
60 {
61         zblib_check_null_ret("service", service);
62
63         service->async_initializer = list;
64 }
65
66 static void __on_zblib_init_async_finished(gboolean result, void *user_data)
67 {
68         GSList *list_async = NULL;
69         ZigBeeService *service = NULL;
70         ZigBeePlugin *plugin = NULL;
71         struct _zblib_async_init_info *info = (struct _zblib_async_init_info*)user_data;
72         char *plugin_name = NULL;
73
74         int i = 0;
75         int _finished_count = 0;
76         int _succeeded_count = 0;
77
78         zblib_check_null_ret("info", info);
79
80         plugin = info->plugin;
81         service = zblib_plugin_ref_service(plugin);
82         zblib_check_null_ret("service", service);
83
84         plugin_name = zblib_plugin_get_plugin_name(plugin);
85
86         info->is_finished = TRUE;
87         if (result) {
88                 Z_LOGD("Plugin [%s] succeeded async init", plugin_name);
89                 info->is_initialized = TRUE;
90         } else {
91                 Z_LOGE("Plugin [%s] failed to init !", plugin_name);
92         }
93         g_free(plugin_name);
94
95         /* Check all async initializer list */
96         list_async = __zblib_service_ref_async_initializer(service);
97         while (list_async != NULL) {
98                 /* Initialize each plug-in */
99                 struct _zblib_async_init_info *in =
100                                 (struct _zblib_async_init_info *)(list_async->data);
101                 i++;
102                 if (G_UNLIKELY(NULL == in)) {
103                         Z_LOGE("Invalid parameter !");
104                         list_async = g_slist_next(list_async);
105                         continue;
106                 }
107
108                 if (in->is_finished)
109                         _finished_count++;
110                 if (in->is_initialized)
111                         _succeeded_count++;
112
113                 list_async = g_slist_next(list_async);
114         }
115
116         if (i == _finished_count) {
117                 gboolean ret = TRUE;
118                 guint driver_noti_id = (ZBLIB_DRIVER_TYPE_MANAGER << 24)
119                                 | ZBLIB_MANAGER_NOTI_ZIGBEE_ENABLED;
120
121                 if (_finished_count == _succeeded_count) {
122                         /* All initializer finished successfully */
123                         Z_LOGD("All async init finished !");
124                         ret = TRUE;
125                 } else {
126                         /* Some initializers are failed */
127                         Z_LOGE("There are some failed plugin initializer !");
128                         ret = FALSE;
129                 }
130                 /* Notify whether zigbee service is enabled or not */
131                 zblib_plugin_send_notification(plugin,
132                         driver_noti_id, &ret, sizeof(ret));
133         }
134 }
135
136 static void *__zblib_service_load_plugin(gchar *filename,
137         ZblibPluginDescriptor_t **descriptor_out)
138 {
139         ZblibPluginDescriptor_t *descriptor = NULL;
140         void *handle = NULL;
141         struct stat stat_buf;
142         char file_date[27];
143
144         zblib_check_null_ret_error("descriptor_out", descriptor_out, NULL);
145
146         /* Open .so */
147         handle = dlopen(filename, RTLD_LAZY);
148         if (G_UNLIKELY(NULL == handle)) {
149                 Z_LOGE("dlopen() failed:[%s]", filename);
150                 return NULL;
151         }
152
153         /* Get symbol - "zigbee_plugin_descriptor" */
154         descriptor = dlsym(handle, "zigbee_plugin_descriptor");
155         if (G_UNLIKELY(NULL == descriptor)) {
156                 Z_LOGE("dlsym() failed:[%s]", "plugin_define_desc");
157                 dlclose(handle);
158                 return NULL;
159         }
160
161         Z_LOGD("%s plugin", descriptor->name);
162         Z_LOGD(" - path = %s", filename);
163         Z_LOGD(" - version = %d", descriptor->version);
164
165         memset(&stat_buf, 0x00, sizeof(stat_buf));
166         memset(&file_date, '\0', sizeof(file_date));
167
168         if (0 == stat(filename, &stat_buf)) {
169                 if (NULL != ctime_r(&stat_buf.st_mtime, file_date)) {
170                         if (1 < strlen(file_date))
171                                 file_date[strlen(file_date)-1] = '\0';
172                         Z_LOGD(" - date = %s", file_date);
173                 }
174         }
175
176         /* Load plug-in */
177         if (G_LIKELY(descriptor->load)) {
178                 if (G_UNLIKELY(FALSE == descriptor->load())) {
179                         Z_LOGW("load() failed... Skip this plugin!");
180                         dlclose(handle);
181                         return NULL;
182                 } else {
183                         Z_LOGI("Plug-in (%s) loaded!", descriptor->name);
184                 }
185         }
186
187         *descriptor_out = descriptor;
188
189         return handle;
190 }
191
192 static gboolean __zblib_service_init_plugin(ZigBeePlugin *plugin)
193 {
194         const ZblibPluginDescriptor_t *descriptor = zblib_plugin_get_descriptor(plugin);
195
196         zblib_check_null_ret_error("descriptor", descriptor, FALSE);
197         if (NULL == descriptor->init) {
198                 Z_LOGD("descriptor->init is NULL, trying async init");
199                 return FALSE;
200         }
201
202         if (G_UNLIKELY(FALSE == descriptor->init(plugin))) {
203                 char *plugin_name = zblib_plugin_get_plugin_name(plugin);
204                 if (G_UNLIKELY(NULL != plugin_name)) {
205                         Z_LOGE("plugin(%s) init failed!", plugin_name);
206                         g_free(plugin_name);
207                 }
208                 return FALSE;
209         }
210
211         return TRUE;
212 }
213
214 static gboolean __zblib_service_init_async_plugin(ZigBeePlugin *plugin,
215                 zblib_plugin_init_finished_cb callback, void *user_data)
216 {
217         const ZblibPluginDescriptor_t *descriptor = zblib_plugin_get_descriptor(plugin);
218         char *plugin_name = NULL;
219
220         zblib_check_null_ret_error("descriptor", descriptor, FALSE);
221         zblib_check_null_ret_error("descriptor->init_async", descriptor->init_async, FALSE);
222
223         plugin_name = zblib_plugin_get_plugin_name(plugin);
224         if (G_UNLIKELY(NULL != plugin_name))
225                 plugin_name = g_strdup("NONAME");
226
227         if (G_UNLIKELY(FALSE == descriptor->init_async(plugin, callback, user_data))) {
228                 Z_LOGE("plugin(%s) init failed!", plugin_name);
229                 g_free(plugin_name);
230                 return FALSE;
231         }
232
233         g_free(plugin_name);
234         return TRUE;
235 }
236
237 static gboolean __zblib_service_unload_plugin(ZigBeePlugin *plugin)
238 {
239         const ZblibPluginDescriptor_t *descriptor = zblib_plugin_get_descriptor(plugin);
240         char *plugin_name = NULL;
241
242         zblib_check_null_ret_error("descriptor", descriptor, FALSE);
243         zblib_check_null_ret_error("descriptor->unload", descriptor->unload, FALSE);
244
245         plugin_name = zblib_plugin_get_plugin_name(plugin);
246
247         descriptor->unload(plugin);
248         Z_LOGI("plugin(%s) unloaded!", plugin_name);
249
250         g_free(plugin_name);
251
252         return TRUE;
253 }
254
255 static void __zblib_service_remove_request_table_iter(gpointer key,
256         gpointer value, gpointer user_data)
257 {
258         ZigBeeServiceInterface *service_interface = (ZigBeeServiceInterface *)user_data;
259         gint request_id = GPOINTER_TO_INT(key);
260
261         NOT_USED(value);
262
263         zblib_check_null_ret("service_interface", service_interface);
264
265         Z_LOGD("Removing request id [%d]", request_id);
266         zblib_request_free(service_interface, request_id);
267 }
268
269 static void __zblib_service_remove_async_list(GSList *list_async)
270 {
271         GSList *iter = NULL;
272         if (0 < g_slist_length(list_async)) {
273                 iter = g_slist_nth(list_async, 0);
274                 while (iter) {
275                         if (iter) {
276                                 struct _zblib_async_init_info *async_plugin =
277                                         (struct _zblib_async_init_info *)(list_async->data);
278                                 list_async = g_slist_remove(list_async, async_plugin);
279                         }
280                         iter = g_slist_nth(list_async, 0);
281                 }
282         }
283 }
284
285 ZigBeeService *zblib_service_new()
286 {
287         ZigBeeService *service;
288
289         service = g_malloc0(sizeof(struct zblib_service_type));
290
291         /* Create g-main loop */
292         service->main_loop = g_main_loop_new(NULL, FALSE);
293         if (G_UNLIKELY(NULL == service->main_loop)) {
294                 Z_LOGE("g-main loop creation failed!!!");
295                 g_free(service);
296                 return NULL;
297         }
298
299         /* Create request hash table */
300         service->request_table = g_hash_table_new_full(g_direct_hash,
301                 g_direct_equal, NULL, NULL);
302
303         return service;
304 }
305
306 void zblib_service_free(ZigBeeService *service)
307 {
308         zblib_check_null_ret("service", service);
309
310         if (service->request_table) {
311                 GSList *interface_objs = NULL;
312                 ZigBeeServiceInterface *service_interface = NULL;
313
314                 interface_objs = service->interface_objs;
315                 if (NULL == interface_objs) {
316                         Z_LOGE("interface_objs is NULL");
317                 } else {
318                         while (interface_objs) {
319                                 service_interface = (ZigBeeServiceInterface *)interface_objs->data;
320
321                                 /* Remove left request */
322                                 g_hash_table_foreach(service->request_table,
323                                                 __zblib_service_remove_request_table_iter,
324                                                 service_interface);
325
326                                 /* Move to next service interface */
327                                 interface_objs = g_slist_next(interface_objs);
328                         }
329                         g_hash_table_remove_all(service->request_table);
330                         g_hash_table_destroy(service->request_table);
331                         service->request_table = NULL;
332                 }
333         }
334
335         /* Free async initializer */
336         if (service->async_initializer) {
337                 g_slist_free(service->async_initializer);
338                 service->async_initializer = NULL;
339         }
340
341         /* Free plug-ins */
342         if (service->plugins) {
343                 g_slist_free(service->plugins);
344                 service->plugins = NULL;
345         }
346
347         /* Unref 'g-main loop' */
348         if (service->main_loop)
349                 g_main_loop_unref(service->main_loop);
350
351         g_free(service);
352 }
353
354 gboolean zblib_service_run(ZigBeeService *service)
355 {
356         zblib_check_null_ret_error("service", service, FALSE);
357         zblib_check_null_ret_error("service->main_loop", service->main_loop, FALSE);
358
359         g_main_loop_run(service->main_loop);
360
361         return TRUE;
362 }
363
364 gboolean zblib_service_exit(ZigBeeService *service)
365 {
366         zblib_check_null_ret_error("service", service, FALSE);
367         zblib_check_null_ret_error("service->main_loop", service->main_loop, FALSE);
368
369         g_main_loop_quit(service->main_loop);
370
371         return TRUE;
372 }
373
374 gboolean zblib_service_add_plugin(ZigBeeService *service, ZigBeePlugin *plugin)
375 {
376         gchar *plugin_name;
377
378         zblib_check_null_ret_error("service", service, FALSE);
379         zblib_check_null_ret_error("plugin", plugin, FALSE);
380
381         plugin_name = zblib_plugin_get_plugin_name(plugin);
382
383         /* All plug-ins would be appended */
384         service->plugins = g_slist_append(service->plugins, plugin);
385         Z_LOGD("%s added", plugin_name);
386         g_free(plugin_name);
387
388         return TRUE;
389 }
390
391 gboolean zblib_service_remove_plugin(ZigBeeService *service, ZigBeePlugin *plugin)
392 {
393         zblib_check_null_ret_error("service", service, FALSE);
394         zblib_check_null_ret_error("plugin", plugin, FALSE);
395
396         /* Specific vendor plug-in would be removed */
397         service->plugins = g_slist_remove(service->plugins, plugin);
398
399         /* Deinitialize plugin */
400         zblib_plugin_free(plugin);
401
402         return TRUE;
403 }
404
405 gboolean zblib_service_load_plugins(ZigBeeService *service, const char *plugin_path)
406 {
407         const gchar *file = NULL;
408         gchar *filename = NULL;
409         GDir *dir = NULL;
410         void *handle = NULL;
411         ZblibPluginDescriptor_t *descriptor = NULL;
412         ZigBeePlugin *plugin = NULL;
413         gboolean ret;
414
415         zblib_check_null_ret_error("service", service, FALSE);
416         zblib_check_null_ret_error("plugin_path", plugin_path, FALSE);
417
418         /* Open plug-in directory */
419         dir = g_dir_open(plugin_path, 0, NULL);
420         if (G_UNLIKELY(dir == NULL)) {
421                 Z_LOGE("Directory open failed!");
422                 return FALSE;
423         }
424
425         /* Scan through all libraries in plug-in directory */
426         while ((file = g_dir_read_name(dir)) != NULL) {
427                 if ((g_str_has_prefix(file, "lib") == TRUE)
428                                 || (g_str_has_suffix(file, ".so") == FALSE))
429                         continue;
430
431                 filename = g_build_filename(plugin_path, file, NULL);
432
433                 /* Load plug-in */
434                 handle = __zblib_service_load_plugin(filename, &descriptor);
435                 if (G_UNLIKELY(NULL == handle)) {
436                         g_free(filename);
437                         continue;
438                 }
439
440                 /* Create new plug-in */
441                 plugin = zblib_plugin_new(service, filename, descriptor, handle);
442                 if (G_UNLIKELY(NULL == plugin)) {
443                         dlclose(handle);
444                         g_free(filename);
445                         continue;
446                 }
447
448                 /* Add new plug-in */
449                 ret = zblib_service_add_plugin(service, plugin);
450                 if (G_UNLIKELY(FALSE == ret))
451                         zblib_plugin_free(plugin);
452
453                 g_free(filename);
454         }
455         g_dir_close(dir);
456
457         return TRUE;
458 }
459
460 gboolean zblib_service_initialize_plugins(ZigBeeService *service)
461 {
462         GSList *list;
463         GSList *list_async;
464
465         zblib_check_null_ret_error("service", service, FALSE);
466
467         /* Refer plug-in list */
468         list = zblib_service_ref_plugins(service);
469         list_async = __zblib_service_ref_async_initializer(service);
470         while (list != NULL) {
471                 /* Initialize each plug-in */
472                 ZigBeePlugin *plugin = (ZigBeePlugin *)(list->data);
473                 if (FALSE == __zblib_service_init_plugin(plugin)) {
474                         /* If there is no initializer, it should have asynchronous one */
475                         const ZblibPluginDescriptor_t *descriptor =
476                                         zblib_plugin_get_descriptor(plugin);
477                         if (NULL != descriptor && NULL != descriptor->init_async) {
478                                 /* Register async initializer */
479                                 struct _zblib_async_init_info *info =
480                                         g_try_new0(struct _zblib_async_init_info, 1);
481                                 if (NULL == info) {
482                                         Z_LOGE("Fatal : Failed to allocate memory");
483                                         return FALSE;
484                                 }
485                                 info->is_finished = FALSE;
486                                 info->is_initialized = FALSE;
487                                 info->plugin = plugin;
488                                 info->user_data = NULL;
489
490                                 list_async = g_slist_append(list_async, info);
491                         } else {
492                                 __zblib_service_remove_async_list(list_async);
493                                 Z_LOGE("Fatal : Failed to initialize plugin");
494                                 return FALSE;
495                         }
496                 }
497                 list = g_slist_next(list);
498         }
499
500         __zblib_service_set_async_initializer(service, list_async);
501
502         return TRUE;
503 }
504
505 gboolean zblib_service_initialize_async_plugins(ZigBeeService *service)
506 {
507         GSList *list_async;
508
509         zblib_check_null_ret_error("service", service, FALSE);
510
511         /* Refer async initializer list */
512         list_async = __zblib_service_ref_async_initializer(service);
513         while (list_async != NULL) {
514                 /* Initialize each plug-in */
515                 struct _zblib_async_init_info *info =
516                                 (struct _zblib_async_init_info *)(list_async->data);
517
518                 if (G_UNLIKELY(NULL == info)) {
519                         Z_LOGE("Invalid parameter !");
520                         list_async = g_slist_next(list_async);
521                         continue;
522                 }
523
524                 if (G_UNLIKELY(FALSE == __zblib_service_init_async_plugin(
525                                         info->plugin, __on_zblib_init_async_finished, info))) {
526                         Z_LOGE("Fatal : Failed to initialize mandatory plugin");
527                         return FALSE;
528                 }
529                 list_async = g_slist_next(list_async);
530         }
531
532         return TRUE;
533 }
534
535 gboolean zblib_service_unload_plugins(ZigBeeService *service)
536 {
537         GSList *list = NULL;
538         GSList *list_async = NULL;
539
540         zblib_check_null_ret_error("service", service, FALSE);
541
542         /* Unload everyy async_plugin */
543         list_async = __zblib_service_ref_async_initializer(service);
544         __zblib_service_remove_async_list(list_async);
545
546         list = zblib_service_ref_plugins(service);
547         while (list != NULL) {
548                 ZigBeePlugin *plugin = list->data;
549
550                 /* Unload each plug-in */
551                 if (G_UNLIKELY(FALSE == __zblib_service_unload_plugin(plugin))) {
552                         list = g_slist_next(list);
553                         continue;
554                 }
555
556                 list = g_slist_next(list);
557                 zblib_service_remove_plugin(service, plugin);
558         }
559
560         return TRUE;
561 }
562
563 GSList *zblib_service_ref_plugins(ZigBeeService *service)
564 {
565         zblib_check_null_ret_error("service", service, NULL);
566
567         return service->plugins;
568 }
569
570 gboolean zblib_service_add_service_interface(ZigBeeService *service,
571         ZigBeeServiceInterface *service_interface)
572 {
573         gchar *object_name;
574
575         zblib_check_null_ret_error("service", service, FALSE);
576         zblib_check_null_ret_error("service_interface_name", service_interface, FALSE);
577
578         object_name = zblib_service_interface_get_name(service_interface);
579
580         /*
581          * All service interface objects would be appended
582          */
583         service->interface_objs = g_slist_append(service->interface_objs, service_interface);
584         Z_LOGD("%s added", object_name);
585         g_free(object_name);
586
587         return TRUE;
588 }
589
590 gboolean zblib_service_remove_service_interface(ZigBeeService *service,
591         ZigBeeServiceInterface *service_interface)
592 {
593         zblib_check_null_ret_error("service", service, FALSE);
594         zblib_check_null_ret_error("service_interface_name", service_interface, FALSE);
595
596         /*
597          * service interface object would be removed
598          */
599         service->interface_objs = g_slist_remove(service->interface_objs, service_interface);
600
601         return TRUE;
602 }
603
604 ZigBeeServiceInterface *zblib_service_ref_service_interface(ZigBeeService *service,
605         const gchar *service_interface_name)
606 {
607         ZigBeeServiceInterface *service_interface = NULL;
608         ZigBeeServiceInterface *tmp_service_interface = NULL;
609         gchar *object_name;
610
611         GSList *list;
612
613         zblib_check_null_ret_error("service", service, NULL);
614         zblib_check_null_ret_error("service_interface_name", service_interface_name, NULL);
615
616         list = service->interface_objs;
617         while (list) {
618                 tmp_service_interface = (ZigBeeServiceInterface *)list->data;
619
620                 /* Get object name of service_interface */
621                 object_name = zblib_service_interface_get_name(tmp_service_interface);
622                 if (g_strcmp0(service_interface_name, object_name) == 0) {
623                         g_free(object_name);
624
625                         service_interface = tmp_service_interface;
626                         break;
627                 }
628                 g_free(object_name);
629
630                 list = g_slist_next(list);
631         }
632
633         if (NULL == service_interface) {
634                 Z_LOGE("Service interface object of name '%s' not found!",
635                         service_interface_name);
636                 return NULL;
637         }
638
639         Z_LOGD("'%s' found", service_interface_name);
640
641         return service_interface;
642 }
643
644 GHashTable *zblib_service_ref_request_hash_table(ZigBeeService *service)
645 {
646         zblib_check_null_ret_error("service", service, NULL);
647
648         return service->request_table;
649 }
650
651 gint zblib_service_generate_request_id(ZigBeeService *service)
652 {
653         zblib_check_null_ret_error("service", service, -1);
654
655         /* Increment request ID */
656         service->request_id++;
657
658         return (gint)service->request_id;
659 }
660
661 gboolean zblib_service_dispatch_request(ZigBeeService *service,
662         guint request_id)
663 {
664         ZigBeePlugin *plugin = NULL;
665         GSList *plugin_list = NULL;
666         gboolean ret = FALSE;
667
668         zblib_check_null_ret_error("service", service, FALSE);
669
670         /* Fetch plugin_list */
671         plugin_list = service->plugins;
672         zblib_check_null_ret_error("plugin_list", plugin_list, FALSE);
673
674         while (plugin_list) {
675                 plugin = (ZigBeePlugin *)plugin_list->data;
676
677                 /* Dispatch request to plugin */
678                 ret = zblib_plugin_dispatch_request(plugin, request_id);
679
680                 /* Move to next plugin */
681                 plugin_list = g_slist_next(plugin_list);
682         }
683
684         return ret;
685 }
686
687 void zblib_service_send_response(ZigBeeService *service,
688         guint request_id, gpointer resp_data, guint resp_data_len)
689 {
690         ZigBeeServiceInterface *service_interface = NULL;
691
692         zblib_check_null_ret("service", service);
693
694         service_interface = zblib_request_ref_service_interface(service, request_id);
695         zblib_check_null_ret("service_interface", service_interface);
696
697         /* Send response to service interface */
698         zblib_service_interface_send_response(service_interface,
699                 request_id, resp_data, resp_data_len);
700 }
701
702 void zblib_service_send_notification(ZigBeeService *service,
703         guint noti_id, gpointer noti_data, guint noti_data_len)
704 {
705         GSList *interface_objs = NULL;
706         ZigBeeServiceInterface *service_interface = NULL;
707
708         zblib_check_null_ret("service", service);
709
710         interface_objs = service->interface_objs;
711         zblib_check_null_ret("interface_objs", interface_objs);
712
713         while (interface_objs) {
714                 service_interface = (ZigBeeServiceInterface *)interface_objs->data;
715
716                 /* Send notification to service interface */
717                 zblib_service_interface_send_notification(service_interface,
718                         noti_id, noti_data, noti_data_len);
719
720                 /* Move to next service interface */
721                 interface_objs = g_slist_next(interface_objs);
722         }
723 }