diff --git a/examples/delete.c b/examples/delete.c new file mode 100644 index 0000000..b01dae9 --- /dev/null +++ b/examples/delete.c @@ -0,0 +1,115 @@ +/* + * Example fingerprint delete finger program, which delete the right index + * finger which has been previously enrolled to disk. + * Copyright (C) 2019 Synaptics Inc + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include + +#include + +struct fp_dscv_dev *discover_device(struct fp_dscv_dev **discovered_devs) +{ + struct fp_dscv_dev *ddev = discovered_devs[0]; + struct fp_driver *drv; + if (!ddev) + return NULL; + + drv = fp_dscv_dev_get_driver(ddev); + printf("Found device claimed by %s driver\n", fp_driver_get_full_name(drv)); + return ddev; +} + + +int main(void) +{ + int r = 1; + struct fp_dscv_dev *ddev; + struct fp_dscv_dev **discovered_devs; + struct fp_dev *dev; + struct fp_print_data *data; + + setenv ("G_MESSAGES_DEBUG", "all", 0); + setenv ("LIBUSB_DEBUG", "3", 0); + + r = fp_init(); + if (r < 0) { + fprintf(stderr, "Failed to initialize libfprint\n"); + exit(1); + } + + discovered_devs = fp_discover_devs(); + if (!discovered_devs) { + fprintf(stderr, "Could not discover devices\n"); + goto out; + } + + ddev = discover_device(discovered_devs); + if (!ddev) { + fprintf(stderr, "No devices detected.\n"); + goto out; + } + + dev = fp_dev_open(ddev); + fp_dscv_devs_free(discovered_devs); + if (!dev) { + fprintf(stderr, "Could not open device.\n"); + goto out; + } + + printf("Opened device. Loading previously enrolled right index finger " + "data...\n"); + + r = fp_print_data_load(dev, RIGHT_INDEX, &data); + if (r != 0) { + fprintf(stderr, "Failed to load fingerprint, error %d\n", r); + fprintf(stderr, "Did you remember to enroll your right index finger " + "first?\n"); + goto out_close; + } + + printf("Print loaded. delete data in sensor.\n"); + if(!fp_dev_supports_data_in_sensor(dev)) + { + printf("This driver doesn't support to store data in sensor.\n"); + goto out_close; + } + + r = fp_delete_finger(dev, data); + fp_print_data_free(data); + if (r) { + printf("delete finger failed with error %d :(\n", r); + } + else + { + printf("sensor data deleted. now delete host data"); + r = fp_print_data_delete(dev, RIGHT_INDEX); + if (r < 0) { + printf("Delete sensor data successfully but delete host data failed. %d :(\n", r); + } + } + +out_close: + fp_dev_close(dev); +out: + fp_exit(); + return r; +} + + diff --git a/examples/meson.build b/examples/meson.build index cba326f..ca9bbb9 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -1,5 +1,5 @@ -examples = [ 'verify_live', 'enroll', 'verify', 'img_capture' ] +examples = [ 'verify_live', 'enroll', 'verify', 'img_capture', 'delete' ] foreach example: examples executable(example, example + '.c', diff --git a/libfprint/drivers/synaptics/include/synaptics.h b/libfprint/drivers/synaptics/include/synaptics.h index 883cfc2..45d8638 100644 --- a/libfprint/drivers/synaptics/include/synaptics.h +++ b/libfprint/drivers/synaptics/include/synaptics.h @@ -43,6 +43,7 @@ typedef enum syna_state SYNA_STATE_IDENTIFY_DELAY_RESULT , SYNA_STATE_VERIFY , SYNA_STATE_VERIFY_DELAY_RESULT , + SYNA_STATE_DELETE , } syna_state_t; typedef struct synaptics_dev_s @@ -57,11 +58,6 @@ typedef struct synaptics_dev_s struct syna_enroll_resp_data enroll_resp_data; gboolean isFingerOnSensor; syna_state_t state; - pthread_mutex_t op_mutex; - pthread_cond_t op_cond; - int op_finished; - GSList* file_gslist; - GSList* sensor_gslist; }synaptics_dev; #endif //__synaptics_h__ diff --git a/libfprint/drivers/synaptics/src/synaptics.c b/libfprint/drivers/synaptics/src/synaptics.c index 4122560..2c63715 100644 --- a/libfprint/drivers/synaptics/src/synaptics.c +++ b/libfprint/drivers/synaptics/src/synaptics.c @@ -190,9 +190,6 @@ static int dev_init(struct fp_dev *dev, unsigned long driver_data) return -1; } sdev->usb_config->product_id = dsc.idProduct; - - pthread_mutex_init(&sdev->op_mutex, NULL); - pthread_cond_init(&sdev->op_cond, NULL); result = bmkt_init(&(sdev->ctx)); if (result != BMKT_SUCCESS) @@ -263,18 +260,6 @@ static void dev_exit(struct fp_dev *dev) } bmkt_exit(sdev->ctx); - - ret = pthread_mutex_destroy(&sdev->op_mutex); - if (ret) - { - fp_err("failed to destroy mutex"); - } - - ret = pthread_cond_destroy(&sdev->op_cond); - if (ret) - { - fp_err("failed to destroy cond "); - } g_free(sdev); fpi_drvcb_close_complete(dev); @@ -299,121 +284,6 @@ static gboolean rand_string(char *str, size_t size) #define TEMPLATE_ID_SIZE 20 -void get_file_data(struct fp_dev *dev, const char *basepath) -{ - char path[PATH_MAX]; - struct dirent *dp; - DIR *dir = opendir(basepath); - synaptics_dev *sdev = FP_INSTANCE_DATA(dev); - if(!dir) - return; - while( (dp = readdir(dir)) != NULL) - { - if(strcmp(dp->d_name, ".")!=0 && strcmp(dp->d_name,"..")!=0) - { - strcpy(path,basepath); - strcat(path,"/"); - strcat(path,dp->d_name); - fp_info("%s type is %d ",dp->d_name,dp->d_type); - if(dp->d_type == DT_REG) - { - gsize length; - gchar *contents; - GError *err = NULL; - struct fp_print_data *fdata; - fp_info(" load file %s ", path); - g_file_get_contents(path, &contents, &length, &err); - if (err) { - fp_err("load file failed "); - } - else - { - fdata = fp_print_data_from_data(contents, length); - struct fp_print_data_item *item = fdata->prints->data; - struct syna_mis_print_data *print_data =(struct syna_mis_print_data *) item -> data; - - char *data_node=(char *)g_malloc0(strlen(print_data->user_id) + 1); - memcpy(data_node, print_data->user_id, strlen(print_data->user_id) + 1); - sdev->file_gslist = g_slist_append(sdev->file_gslist, data_node); - g_free(contents); - } - } - else - get_file_data(dev, path); - } - } -} - -static int get_enrolled_users_resp(bmkt_response_t *resp, void *ctx) -{ - - bmkt_enroll_templates_resp_t *get_enroll_templates_resp = &resp->response.enroll_templates_resp; - struct fp_dev *dev=(struct fp_dev *)ctx; - synaptics_dev *sdev = FP_INSTANCE_DATA(dev); - - switch (resp->response_id) - { - case BMKT_RSP_QUERY_FAIL: - fp_info("Failed to query enrolled users: %d", resp->result); - pthread_mutex_lock(&sdev->op_mutex); - sdev->op_finished = 1; - pthread_cond_signal(&sdev->op_cond); - pthread_mutex_unlock(&sdev->op_mutex); - break; - case BMKT_RSP_QUERY_RESPONSE_COMPLETE: - pthread_mutex_lock(&sdev->op_mutex); - sdev->op_finished = 1; - pthread_cond_signal(&sdev->op_cond); - pthread_mutex_unlock(&sdev->op_mutex); - fp_info("Query complete!"); - break; - case BMKT_RSP_TEMPLATE_RECORDS_REPORT: - - for (int n = 0; n < BMKT_MAX_NUM_TEMPLATES_INTERNAL_FLASH; n++) - { - if (get_enroll_templates_resp->templates[n].user_id_len == 0) - continue; - - fp_info("![query %d of %d] template %d: status=0x%x, userId=%s, fingerId=%d", - get_enroll_templates_resp->query_sequence, - get_enroll_templates_resp->total_query_messages, - n, - get_enroll_templates_resp->templates[n].template_status, - get_enroll_templates_resp->templates[n].user_id, - get_enroll_templates_resp->templates[n].finger_id); - char *data_node = (char *)g_malloc0(strlen(get_enroll_templates_resp->templates[n].user_id) + 1); - memcpy(data_node, get_enroll_templates_resp->templates[n].user_id, - strlen(get_enroll_templates_resp->templates[n].user_id) + 1 ); - sdev->sensor_gslist = g_slist_prepend(sdev->sensor_gslist, data_node); - } - break; - } - - return 0; -} - -void get_sensor_data(struct fp_dev *dev) -{ - synaptics_dev *sdev = FP_INSTANCE_DATA(dev); - int result; - sdev->op_finished = 0; - result = bmkt_get_enrolled_users(sdev->sensor, get_enrolled_users_resp, dev); - if (result != BMKT_SUCCESS) - { - fp_err("Failed to get enrolled users: %d", result); - } - else - { - fp_info("get enrolled data started."); - } - pthread_mutex_lock(&sdev->op_mutex); - if(sdev->op_finished == 0) - { - pthread_cond_wait(&sdev->op_cond, &sdev->op_mutex); - } - pthread_mutex_unlock(&sdev->op_mutex); -} - static int del_enrolled_user_resp(bmkt_response_t *resp, void *ctx) { bmkt_del_user_resp_t *del_user_resp = &resp->response.del_user_resp; @@ -428,84 +298,33 @@ static int del_enrolled_user_resp(bmkt_response_t *resp, void *ctx) break; case BMKT_RSP_DEL_USER_FP_FAIL: fp_info("Failed to delete enrolled user: %d", resp->result); - pthread_mutex_lock(&sdev->op_mutex); - sdev->op_finished = 1; - pthread_cond_signal(&sdev->op_cond); - pthread_mutex_unlock(&sdev->op_mutex); + if(sdev->state == SYNA_STATE_DELETE) + { + /* Return result complete when record doesn't exist, otherwise host data + won't be deleted. */ + if(resp->result == BMKT_FP_DATABASE_NO_RECORD_EXISTS) + fpi_drvcb_delete_complete(dev, FP_DELETE_COMPLETE); + else + fpi_drvcb_delete_complete(dev, FP_DELETE_FAIL); + } break; case BMKT_RSP_DEL_USER_FP_OK: fp_info("Successfully deleted enrolled user"); - pthread_mutex_lock(&sdev->op_mutex); - sdev->op_finished = 1; - pthread_cond_signal(&sdev->op_cond); - pthread_mutex_unlock(&sdev->op_mutex); + if(sdev->state == SYNA_STATE_DELETE) + { + fpi_drvcb_delete_complete(dev, FP_DELETE_COMPLETE); + } break; } return 0; } -const char FPRINTD_DATAPATH[]="/usr/local/var/lib/fprint"; -/* - * Delete the data which doesn't exist in fprintd folder from sensor database, - * otherwise, new finger may have problem to be recognized if it - * already exists in sensor. -*/ -void sync_database(struct fp_dev *dev) -{ - synaptics_dev *sdev = FP_INSTANCE_DATA(dev); - int result = 0; - int sindex, findex; - GSList* snode; - GSList* fnode; - - get_file_data(dev, FPRINTD_DATAPATH); - get_sensor_data(dev); - - for(sindex = 0; (snode = g_slist_nth(sdev->sensor_gslist, sindex)); sindex++) - { - for(findex = 0; (fnode = g_slist_nth(sdev->file_gslist, findex)); findex++) - { - if(strlen(snode->data) == strlen(fnode->data) && - strncmp(snode->data, fnode->data, strlen(snode->data)) == 0) - { - break; - } - } - if(!fnode) - { - sdev->op_finished = 0; - result = bmkt_delete_enrolled_user(sdev->sensor, 1, snode->data, - strlen(snode->data), del_enrolled_user_resp, dev); - if (result != BMKT_SUCCESS) - { - fp_err("Failed to delete enrolled user: %d", result); - } - else - { - - pthread_mutex_lock(&sdev->op_mutex); - if(sdev->op_finished == 0) - { - pthread_cond_wait(&sdev->op_cond, &sdev->op_mutex); - } - pthread_mutex_unlock(&sdev->op_mutex); - - } - - } - } - - g_slist_free(sdev->file_gslist); - g_slist_free(sdev->sensor_gslist); -} static int enroll_start(struct fp_dev *dev) { synaptics_dev *sdev = FP_INSTANCE_DATA(dev); int result = 0; char userid[TEMPLATE_ID_SIZE + 1]; - - sync_database(dev); fp_info("enroll_start"); @@ -587,7 +406,49 @@ static int verify_response(bmkt_response_t *resp, void *ctx) return 0; } +static int delete_finger(struct fp_dev *dev) +{ + synaptics_dev *sdev = FP_INSTANCE_DATA(dev); + int result = 0; + struct fp_print_data *print = fpi_dev_get_delete_data(dev);; + struct fp_print_data_item *item = print->prints->data; + struct syna_mis_print_data *print_data; + bmkt_user_id_t user; + if(item->length != sizeof(struct syna_mis_print_data)) + { + fp_err("print data is incorrect !"); + goto cleanup; + } + + print_data = (struct syna_mis_print_data *)item->data; + + memset(&user, 0, sizeof(bmkt_user_id_t)); + memcpy(user.user_id, print_data->user_id, sizeof(print_data->user_id)); + + fp_info("delete finger !"); + + user.user_id_len = strlen(user.user_id); + if (user.user_id_len <= 0 || user.user_id[0] == ' ') + { + fp_err("Invalid user name."); + goto cleanup; + } + + sdev->state = SYNA_STATE_DELETE; + result = bmkt_delete_enrolled_user(sdev->sensor, 1, print_data->user_id, + user.user_id_len, del_enrolled_user_resp, dev); + if (result != BMKT_SUCCESS) + { + fp_err("Failed to delete enrolled user: %d", result); + goto cleanup; + } + + return 0; + +cleanup: + return -1; +} static int verify_start(struct fp_dev *dev) { synaptics_dev *sdev = FP_INSTANCE_DATA(dev); @@ -658,6 +519,7 @@ struct fp_driver synaptics_driver = { .enroll_stop = enroll_stop, .verify_start = verify_start, .verify_stop = verify_stop, + .delete_finger = delete_finger, }; diff --git a/libfprint/fp_internal.h b/libfprint/fp_internal.h index 5c89e18..7a062d4 100644 --- a/libfprint/fp_internal.h +++ b/libfprint/fp_internal.h @@ -77,6 +77,9 @@ enum fp_dev_state { DEV_STATE_CAPTURING, DEV_STATE_CAPTURE_DONE, DEV_STATE_CAPTURE_STOPPING, + DEV_STATE_DELETING, + DEV_STATE_DELETE_DONE, + DEV_STATE_DELETE_STOPPING, }; struct fp_dev { @@ -96,6 +99,9 @@ struct fp_dev { /* read-only to drivers */ struct fp_print_data *verify_data; + + struct fp_print_data *delete_data; + /* drivers should not mess with any of the below */ enum fp_dev_state state; int __enroll_stage; @@ -123,6 +129,8 @@ struct fp_dev { void *capture_cb_data; fp_operation_stop_cb capture_stop_cb; void *capture_stop_cb_data; + fp_delete_cb delete_cb; + void *delete_cb_data; /* FIXME: better place to put this? */ struct fp_print_data **identify_gallery; diff --git a/libfprint/fpi-async.c b/libfprint/fpi-async.c index ddf5230..f333227 100644 --- a/libfprint/fpi-async.c +++ b/libfprint/fpi-async.c @@ -680,3 +680,58 @@ API_EXPORTED int fp_async_capture_stop(struct fp_dev *dev, } return r; } + +/** + * fp_async_delete_finger: + * @dev: the struct #fp_dev device + * @data: data to delete. Must have been previously enrolled. + * @callback: the callback to call when data deleted. + * @user_data: user data to pass to the callback + * + * Request to delete data in sensor. + * + * Returns: 0 on success, non-zero on error + */ + +API_EXPORTED int fp_async_delete_finger(struct fp_dev *dev, + struct fp_print_data *data, fp_img_operation_cb callback, void *user_data) +{ + struct fp_driver *drv; + int r; + + g_return_val_if_fail(dev != NULL, -ENODEV); + g_return_val_if_fail (callback != NULL, -EINVAL); + + drv = dev->drv; + + G_DEBUG_HERE(); + if (!drv->delete_finger) + return -ENOTSUP; + + dev->state = DEV_STATE_DELETING; + dev->delete_cb = callback; + dev->delete_cb_data = user_data; + dev->delete_data = data; + + r = drv->delete_finger(dev); + if (r < 0) { + dev->delete_cb = NULL; + dev->state = DEV_STATE_ERROR; + fp_err("failed to delete data, error %d", r); + } + return r; +} +/* Drivers call this when delete done */ +void fpi_drvcb_delete_complete(struct fp_dev *dev, int status) +{ + fp_dbg("status %d", status); + BUG_ON(dev->state != DEV_STATE_DELETING); + dev->state = (status) ? DEV_STATE_ERROR : DEV_STATE_DELETE_DONE; + if (dev->delete_cb) + dev->delete_cb(dev, status, dev->delete_cb_data); + +} + + + + diff --git a/libfprint/fpi-async.h b/libfprint/fpi-async.h index cf6fa32..46fa111 100644 --- a/libfprint/fpi-async.h +++ b/libfprint/fpi-async.h @@ -36,4 +36,7 @@ void fpi_drvcb_report_verify_result(struct fp_dev *dev, int result, struct fp_img *img); void fpi_drvcb_verify_stopped(struct fp_dev *dev); +void fpi_drvcb_delete_complete(struct fp_dev *dev, int status); + + #endif diff --git a/libfprint/fpi-core.c b/libfprint/fpi-core.c index 6009c82..1f4b95e 100644 --- a/libfprint/fpi-core.c +++ b/libfprint/fpi-core.c @@ -703,6 +703,21 @@ API_EXPORTED int fp_dev_supports_identification(struct fp_dev *dev) return dev->drv->identify_start != NULL; } +/** + * fp_dev_supports_data_in_sensor: + * @dev: the struct #fp_dev device + * + * Determines if a device is capable of storing print data in sensor. + * Not all devices support this functionality. + * + * Returns: 1 if the device is capable of storing data in sensor, 0 otherwise. + */ +API_EXPORTED int fp_dev_supports_data_in_sensor(struct fp_dev *dev) +{ + return dev->drv->delete_finger != NULL; +} + + /** * fp_dev_get_img_width: * @dev: the struct #fp_dev device diff --git a/libfprint/fpi-core.h b/libfprint/fpi-core.h index a5a0a44..156dbf5 100644 --- a/libfprint/fpi-core.h +++ b/libfprint/fpi-core.h @@ -87,6 +87,7 @@ struct fp_driver { int (*identify_stop)(struct fp_dev *dev, gboolean iterating); int (*capture_start)(struct fp_dev *dev); int (*capture_stop)(struct fp_dev *dev); + int (*delete_finger)(struct fp_dev *dev); }; /** diff --git a/libfprint/fpi-dev.c b/libfprint/fpi-dev.c index 219a2a1..19500cc 100644 --- a/libfprint/fpi-dev.c +++ b/libfprint/fpi-dev.c @@ -148,3 +148,17 @@ fpi_dev_get_verify_data(struct fp_dev *dev) { return dev->verify_data; } + +/** + * fpi_dev_get_delete_data: + * @dev: a struct #fp_dev + * + * Returns the delete data associated with @dev. + * Returns: a struct #fp_print_data pointer or %NULL + */ +struct fp_print_data * +fpi_dev_get_delete_data(struct fp_dev *dev) +{ + return dev->delete_data; +} + diff --git a/libfprint/fpi-dev.h b/libfprint/fpi-dev.h index 9843391..cd0fb3c 100644 --- a/libfprint/fpi-dev.h +++ b/libfprint/fpi-dev.h @@ -43,5 +43,7 @@ libusb_device_handle *fpi_dev_get_usb_dev(struct fp_dev *dev); void fpi_dev_set_nr_enroll_stages(struct fp_dev *dev, int nr_enroll_stages); struct fp_print_data *fpi_dev_get_verify_data(struct fp_dev *dev); +struct fp_print_data *fpi_dev_get_delete_data(struct fp_dev *dev); + #endif diff --git a/libfprint/fpi-sync.c b/libfprint/fpi-sync.c index e5a64f8..587a26d 100644 --- a/libfprint/fpi-sync.c +++ b/libfprint/fpi-sync.c @@ -441,6 +441,72 @@ API_EXPORTED int fp_verify_finger(struct fp_dev *dev, return fp_verify_finger_img(dev, enrolled_print, NULL); } +struct sync_delete_data { + gboolean populated; + int result; +}; +static void sync_delete_cb(struct fp_dev *dev, int result, void *user_data) +{ + struct sync_delete_data *ddata = user_data; + ddata->result = result; + ddata->populated = TRUE; +} + +/** + * fp_delete_finger: + * @dev: the struct #fp_dev device to perform the operation. + * @enrolled_data: the id need to delete on sensor. This id is + * returned in previously enrolled with a MIS device. + * + * Perform a delete data operation on sensor. When print data is stored on sensor, + * this function is needed when host deletes enrolled finger. + * + * Returns: negative code on error, otherwise a code from #fp_delete_result + */ + +API_EXPORTED int fp_delete_finger(struct fp_dev *dev, + struct fp_print_data *enrolled_data) +{ + struct sync_delete_data *ddata; + gboolean stopped = FALSE; + int r; + + if (!enrolled_data) { + fp_err("no print given"); + return -EINVAL; + } + + if (!fp_dev_supports_print_data(dev, enrolled_data)) { + fp_err("print is not compatible with device"); + return -EINVAL; + } + + fp_dbg("to be handled by %s", dev->drv->name); + ddata = g_malloc0(sizeof(struct sync_delete_data)); + r = fp_async_delete_finger(dev, enrolled_data, sync_delete_cb, ddata); + if (r < 0) { + fp_dbg("delete_finger error %d", r); + g_free(ddata); + return r; + } + + while (!ddata->populated) { + r = fp_handle_events(); + if (r < 0) + goto out; + } + + r = ddata->result; + fp_dbg("delete_finger result %d", r); + +out: + g_free(ddata); + return r; +} + + + + struct sync_identify_data { gboolean populated; int result; diff --git a/libfprint/fprint.h b/libfprint/fprint.h index 4b68e4a..6ccf610 100644 --- a/libfprint/fprint.h +++ b/libfprint/fprint.h @@ -267,12 +267,17 @@ int fp_verify_finger(struct fp_dev *dev, struct fp_print_data *enrolled_print); int fp_dev_supports_identification(struct fp_dev *dev); +int fp_dev_supports_data_in_sensor(struct fp_dev *dev); + int fp_identify_finger_img(struct fp_dev *dev, struct fp_print_data **print_gallery, size_t *match_offset, struct fp_img **img); int fp_identify_finger(struct fp_dev *dev, struct fp_print_data **print_gallery, size_t *match_offset); +int fp_delete_finger(struct fp_dev *dev, + struct fp_print_data *enrolled_print); + /* Data handling */ int fp_print_data_load(struct fp_dev *dev, enum fp_finger finger, struct fp_print_data **data) LIBFPRINT_DEPRECATED; @@ -451,6 +456,22 @@ int fp_async_capture_start(struct fp_dev *dev, int unconditional, fp_img_operati int fp_async_capture_stop(struct fp_dev *dev, fp_operation_stop_cb callback, void *user_data); +/** + * fp_delete_result: + * @FP_DELETE_COMPLETE: Delete completed successfully. + * @FP_DELETE_FAIL: Delete failed + * + */ +enum fp_delete_result { + FP_DELETE_COMPLETE = 0, + FP_DELETE_FAIL = 1, +}; + +typedef void (*fp_delete_cb)(struct fp_dev *dev, int status, void *user_data); + +int fp_async_delete_finger(struct fp_dev *dev, struct fp_print_data *data, fp_img_operation_cb callback, void *user_data); + + #ifdef __cplusplus } #endif