Improve print_data structure and on-disk format
Drivers now have an ID number. These will be assigned by me and documented on the wiki. 0 cannot be used. Drivers now define a devtype for each device they initialise. This is to cope with the situation where a driver can support varying devices where their print data is incompatible (i.e. image scaling is totally changed). This doesn't apply to any existing supported devices. Print data no longer includes driver name, and includes driver ID instead. Paths to saved print data now include driver ID and devtype, and no longer include driver name. APIs exposed for converting a print_data into a blob which can then be loaded back again later. Useful for systems who don't want to use my simple storage system (which is only aimed at a single user). File format is now defined and will be documented on the wiki. The header is larger as we can no longer rely on directory paths in all scenarios. Print data compat check now checks devtype and driver ID.
This commit is contained in:
parent
4e5cfdf92a
commit
294f9ad447
10 changed files with 210 additions and 27 deletions
9
TODO
9
TODO
|
@ -1,6 +1,5 @@
|
|||
LIBRARY
|
||||
=======
|
||||
fingerprint data classifcation by device or device type
|
||||
identification
|
||||
external API documentation
|
||||
test suite against NFIQ compliance set
|
||||
|
@ -8,7 +7,9 @@ test suite against NFIQ compliance set
|
|||
DRIVERS
|
||||
=======
|
||||
Sunplus 895 driver
|
||||
AES3501 driver
|
||||
AES1610 driver
|
||||
AES2501 driver
|
||||
AES3400/3500 driver
|
||||
ID Mouse driver
|
||||
Support for 2nd generation MS devices
|
||||
Support for 2nd generation UPEK devices
|
||||
|
@ -17,10 +18,12 @@ IMAGING
|
|||
=======
|
||||
aes4000 doesn't work very well, maybe due to small minutia count?
|
||||
PPMM parameter to get_minutiae seems to have no effect
|
||||
nbis minutiae should be stored in endian-independent format
|
||||
return images with standard enroll/verify calls
|
||||
|
||||
MISC
|
||||
====
|
||||
upekts/thinkfinger relicensing (GPL --> LGPL)
|
||||
make library optionally asynchronous and maybe thread-safe
|
||||
pkg-config file
|
||||
|
||||
nbis cleanups
|
||||
|
|
|
@ -69,6 +69,10 @@ void fpi_log(enum fpi_log_level level, const char *component,
|
|||
|
||||
static void register_driver(struct fp_driver *drv)
|
||||
{
|
||||
if (drv->id == 0) {
|
||||
fp_err("not registering driver %s: driver ID is 0");
|
||||
return;
|
||||
}
|
||||
registered_drivers = g_list_prepend(registered_drivers, (gpointer) drv);
|
||||
fp_dbg("registered driver %s", drv->name);
|
||||
}
|
||||
|
@ -333,7 +337,7 @@ API_EXPORTED int fp_verify_finger(struct fp_dev *dev,
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!fpi_print_data_compatible(dev, enrolled_print)) {
|
||||
if (!fpi_print_data_compatible(enrolled_print, dev)) {
|
||||
fp_err("print is not compatible with device");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
|
169
libfprint/data.c
169
libfprint/data.c
|
@ -67,16 +67,114 @@ static const char *finger_code_to_str(enum fp_finger finger)
|
|||
return names[finger];
|
||||
}
|
||||
|
||||
static enum fp_print_data_type get_data_type_for_dev(struct fp_dev *dev)
|
||||
{
|
||||
switch (dev->drv->type) {
|
||||
case DRIVER_PRIMITIVE:
|
||||
return PRINT_DATA_RAW;
|
||||
case DRIVER_IMAGING:
|
||||
return PRINT_DATA_NBIS_MINUTIAE;
|
||||
default:
|
||||
fp_err("unrecognised drv type %d", dev->drv->type);
|
||||
return PRINT_DATA_RAW;
|
||||
}
|
||||
}
|
||||
|
||||
static struct fp_print_data *print_data_new(uint16_t driver_id,
|
||||
uint32_t devtype, enum fp_print_data_type type, size_t length)
|
||||
{
|
||||
struct fp_print_data *data = g_malloc(sizeof(*data) + length);
|
||||
fp_dbg("length=%zd driver=%02x devtype=%04x", length, driver_id, devtype);
|
||||
memset(data, 0, sizeof(*data));
|
||||
data->driver_id = driver_id;
|
||||
data->devtype = devtype;
|
||||
data->type = type;
|
||||
data->length = length;
|
||||
return data;
|
||||
}
|
||||
|
||||
struct fp_print_data *fpi_print_data_new(struct fp_dev *dev, size_t length)
|
||||
{
|
||||
struct fp_print_data *data = g_malloc(sizeof(*data) + length);
|
||||
memset(data, 0, sizeof(*data));
|
||||
fp_dbg("length=%zd", length);
|
||||
data->driver_name = dev->drv->name;
|
||||
data->length = length;
|
||||
return print_data_new(dev->drv->id, dev->devtype,
|
||||
get_data_type_for_dev(dev), length);
|
||||
}
|
||||
|
||||
API_EXPORTED size_t fp_print_data_get_data(struct fp_print_data *data,
|
||||
unsigned char **ret)
|
||||
{
|
||||
struct fpi_print_data_fp1 *buf;
|
||||
size_t buflen;
|
||||
|
||||
fp_dbg("");
|
||||
|
||||
buflen = sizeof(*buf) + data->length;
|
||||
buf = malloc(buflen);
|
||||
if (!buf)
|
||||
return 0;
|
||||
|
||||
*ret = (unsigned char *) buf;
|
||||
buf->prefix[0] = 'F';
|
||||
buf->prefix[1] = 'P';
|
||||
buf->prefix[2] = '1';
|
||||
buf->driver_id = GUINT16_TO_LE(data->driver_id);
|
||||
buf->devtype = GUINT32_TO_LE(data->devtype);
|
||||
buf->data_type = data->type;
|
||||
memcpy(buf->data, data->data, data->length);
|
||||
return buflen;
|
||||
}
|
||||
|
||||
API_EXPORTED struct fp_print_data *fp_print_data_from_data(unsigned char *buf,
|
||||
size_t buflen)
|
||||
{
|
||||
struct fpi_print_data_fp1 *raw = (struct fpi_print_data_fp1 *) buf;
|
||||
size_t print_data_len;
|
||||
struct fp_print_data *data;
|
||||
|
||||
fp_dbg("buffer size %zd", buflen);
|
||||
if (buflen < sizeof(*raw))
|
||||
return NULL;
|
||||
|
||||
if (strncmp(raw->prefix, "FP1", 3) != 0) {
|
||||
fp_dbg("bad header prefix");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
print_data_len = buflen - sizeof(*raw);
|
||||
data = print_data_new(GUINT16_FROM_LE(raw->driver_id),
|
||||
GUINT32_FROM_LE(raw->devtype), raw->data_type, print_data_len);
|
||||
memcpy(data->data, raw->data, print_data_len);
|
||||
return data;
|
||||
}
|
||||
|
||||
static char *__get_path_to_storedir(uint16_t driver_id, uint32_t devtype)
|
||||
{
|
||||
char idstr[5];
|
||||
char devtypestr[9];
|
||||
|
||||
g_snprintf(idstr, sizeof(idstr), "%04x", driver_id);
|
||||
g_snprintf(devtypestr, sizeof(devtypestr), "%08x", devtype);
|
||||
|
||||
return g_build_filename(base_store, idstr, devtypestr, NULL);
|
||||
}
|
||||
|
||||
static char *get_path_to_storedir(struct fp_dev *dev)
|
||||
{
|
||||
return __get_path_to_storedir(dev->drv->id, dev->devtype);
|
||||
}
|
||||
|
||||
static char *get_path_to_print(struct fp_dev *dev, const char *fingerstr)
|
||||
{
|
||||
char *dirpath;
|
||||
char *path;
|
||||
|
||||
dirpath = get_path_to_storedir(dev);
|
||||
path = g_build_filename(dirpath, fingerstr, NULL);
|
||||
g_free(dirpath);
|
||||
return path;
|
||||
}
|
||||
|
||||
API_EXPORTED int fp_print_data_save(struct fp_print_data *data,
|
||||
enum fp_finger finger)
|
||||
{
|
||||
|
@ -84,6 +182,8 @@ API_EXPORTED int fp_print_data_save(struct fp_print_data *data,
|
|||
char *path;
|
||||
char *dirpath;
|
||||
const char *fingerstr = finger_code_to_str(finger);
|
||||
unsigned char *buf;
|
||||
size_t len;
|
||||
int r;
|
||||
|
||||
if (!fingerstr)
|
||||
|
@ -92,8 +192,12 @@ API_EXPORTED int fp_print_data_save(struct fp_print_data *data,
|
|||
if (!base_store)
|
||||
storage_setup();
|
||||
|
||||
fp_dbg("save %s print from %s", fingerstr, data->driver_name);
|
||||
dirpath = g_build_filename(base_store, data->driver_name, NULL);
|
||||
fp_dbg("save %s print from driver %04x", fingerstr, data->driver_id);
|
||||
len = fp_print_data_get_data(data, &buf);
|
||||
if (!len)
|
||||
return -ENOMEM;
|
||||
|
||||
dirpath = __get_path_to_storedir(data->driver_id, data->devtype);
|
||||
r = g_mkdir_with_parents(dirpath, DIR_PERMS);
|
||||
if (r < 0) {
|
||||
fp_err("couldn't create storage directory");
|
||||
|
@ -103,19 +207,57 @@ API_EXPORTED int fp_print_data_save(struct fp_print_data *data,
|
|||
|
||||
path = g_build_filename(dirpath, fingerstr, NULL);
|
||||
fp_dbg("saving to %s", path);
|
||||
g_file_set_contents(path, data->buffer, data->length, &err);
|
||||
g_file_set_contents(path, buf, len, &err);
|
||||
free(buf);
|
||||
g_free(dirpath);
|
||||
g_free(path);
|
||||
if (err) {
|
||||
r = err->code;
|
||||
fp_err("%s save failed: %s", fingerstr, err->message);
|
||||
g_error_free(err);
|
||||
/* FIXME interpret error codes */
|
||||
return r;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
gboolean fpi_print_data_compatible(struct fp_print_data *data,
|
||||
struct fp_dev *dev)
|
||||
{
|
||||
struct fp_driver *drv = dev->drv;
|
||||
|
||||
if (drv->id != data->driver_id) {
|
||||
fp_dbg("driver name mismatch: %02x vs %02x", drv->id, data->driver_id);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (dev->devtype != data->devtype) {
|
||||
fp_dbg("devtype mismatch: %04x vs %04x", dev->devtype, data->devtype);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
switch (data->type) {
|
||||
case PRINT_DATA_RAW:
|
||||
if (drv->type != DRIVER_PRIMITIVE) {
|
||||
fp_dbg("raw data vs primitive driver mismatch");
|
||||
return FALSE;
|
||||
}
|
||||
break;
|
||||
case PRINT_DATA_NBIS_MINUTIAE:
|
||||
if (drv->type != DRIVER_IMAGING) {
|
||||
fp_dbg("minutiae data vs imaging driver mismatch");
|
||||
return FALSE;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
fp_err("unrecognised data type %d", data->type);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
API_EXPORTED int fp_print_data_load(struct fp_dev *dev,
|
||||
enum fp_finger finger, struct fp_print_data **data)
|
||||
{
|
||||
|
@ -132,7 +274,7 @@ API_EXPORTED int fp_print_data_load(struct fp_dev *dev,
|
|||
if (!base_store)
|
||||
storage_setup();
|
||||
|
||||
path = g_build_filename(base_store, dev->drv->name, fingerstr, NULL);
|
||||
path = get_path_to_print(dev, fingerstr);
|
||||
fp_dbg("from %s", path);
|
||||
g_file_get_contents(path, &contents, &length, &err);
|
||||
g_free(path);
|
||||
|
@ -147,9 +289,14 @@ API_EXPORTED int fp_print_data_load(struct fp_dev *dev,
|
|||
return r;
|
||||
}
|
||||
|
||||
fdata = fpi_print_data_new(dev, length);
|
||||
memcpy(fdata->buffer, contents, length);
|
||||
fdata = fp_print_data_from_data(contents, length);
|
||||
g_free(contents);
|
||||
|
||||
if (!fpi_print_data_compatible(fdata, dev)) {
|
||||
fp_err("print data is not compatible!");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
*data = fdata;
|
||||
return 0;
|
||||
}
|
||||
|
@ -159,7 +306,3 @@ API_EXPORTED void fp_print_data_free(struct fp_print_data *data)
|
|||
g_free(data);
|
||||
}
|
||||
|
||||
int fpi_print_data_compatible(struct fp_dev *dev, struct fp_print_data *data)
|
||||
{
|
||||
return strcmp(dev->drv->name, data->driver_name) == 0;
|
||||
}
|
||||
|
|
|
@ -210,6 +210,7 @@ static const struct usb_id id_table[] = {
|
|||
|
||||
struct fp_img_driver aes4000_driver = {
|
||||
.driver = {
|
||||
.id = 3,
|
||||
.name = FP_COMPONENT,
|
||||
.full_name = "AuthenTec AES4000",
|
||||
.id_table = id_table,
|
||||
|
|
|
@ -653,7 +653,7 @@ static int enroll(struct fp_dev *dev, gboolean initial,
|
|||
}
|
||||
|
||||
fdata = fpi_print_data_new(dev, data_len - sizeof(scan_comp));
|
||||
memcpy(fdata->buffer, data + sizeof(scan_comp), data_len - sizeof(scan_comp));
|
||||
memcpy(fdata->data, data + sizeof(scan_comp), data_len - sizeof(scan_comp));
|
||||
*_data = fdata;
|
||||
comp_out:
|
||||
g_free(data);
|
||||
|
@ -679,7 +679,7 @@ static int verify(struct fp_dev *dev, struct fp_print_data *print)
|
|||
|
||||
data = g_malloc(data_len);
|
||||
memcpy(data, verify_hdr, sizeof(verify_hdr));
|
||||
memcpy(data + sizeof(verify_hdr), print->buffer, print->length);
|
||||
memcpy(data + sizeof(verify_hdr), print->data, print->length);
|
||||
|
||||
r = send_cmd28(dev, 0x03, data, data_len);
|
||||
if (r < 0)
|
||||
|
@ -781,6 +781,7 @@ static const struct usb_id id_table[] = {
|
|||
};
|
||||
|
||||
struct fp_driver upekts_driver = {
|
||||
.id = 1,
|
||||
.name = FP_COMPONENT,
|
||||
.full_name = "UPEK TouchStrip",
|
||||
.id_table = id_table,
|
||||
|
|
|
@ -489,6 +489,7 @@ static const struct usb_id id_table[] = {
|
|||
|
||||
struct fp_img_driver uru4000_driver = {
|
||||
.driver = {
|
||||
.id = 2,
|
||||
.name = FP_COMPONENT,
|
||||
.full_name = "Digital Persona U.are.U 4000/4000B",
|
||||
.id_table = id_table,
|
||||
|
|
|
@ -78,6 +78,7 @@ void fpi_log(enum fpi_log_level, const char *component, const char *function,
|
|||
struct fp_dev {
|
||||
struct fp_driver *drv;
|
||||
usb_dev_handle *udev;
|
||||
uint32_t devtype;
|
||||
void *priv;
|
||||
|
||||
int nr_enroll_stages;
|
||||
|
@ -104,6 +105,7 @@ enum fp_driver_type {
|
|||
};
|
||||
|
||||
struct fp_driver {
|
||||
const uint16_t id;
|
||||
const char *name;
|
||||
const char *full_name;
|
||||
const struct usb_id * const id_table;
|
||||
|
@ -152,14 +154,30 @@ struct fp_dscv_dev {
|
|||
unsigned long driver_data;
|
||||
};
|
||||
|
||||
struct fp_print_data {
|
||||
const char *driver_name;
|
||||
size_t length;
|
||||
unsigned char buffer[0];
|
||||
enum fp_print_data_type {
|
||||
PRINT_DATA_RAW = 0, /* memset-imposed default */
|
||||
PRINT_DATA_NBIS_MINUTIAE,
|
||||
};
|
||||
|
||||
struct fp_print_data {
|
||||
uint16_t driver_id;
|
||||
uint32_t devtype;
|
||||
enum fp_print_data_type type;
|
||||
size_t length;
|
||||
unsigned char data[0];
|
||||
};
|
||||
|
||||
struct fpi_print_data_fp1 {
|
||||
char prefix[3];
|
||||
uint16_t driver_id;
|
||||
uint32_t devtype;
|
||||
unsigned char data_type;
|
||||
unsigned char data[0];
|
||||
} __attribute__((__packed__));
|
||||
|
||||
struct fp_print_data *fpi_print_data_new(struct fp_dev *dev, size_t length);
|
||||
int fpi_print_data_compatible(struct fp_dev *dev, struct fp_print_data *data);
|
||||
gboolean fpi_print_data_compatible(struct fp_print_data *data,
|
||||
struct fp_dev *dev);
|
||||
|
||||
/* bit values for fp_img.flags */
|
||||
#define FP_IMG_V_FLIPPED (1<<0)
|
||||
|
|
|
@ -87,6 +87,9 @@ int fp_print_data_load(struct fp_dev *dev, enum fp_finger finger,
|
|||
struct fp_print_data **data);
|
||||
int fp_print_data_save(struct fp_print_data *data, enum fp_finger finger);
|
||||
void fp_print_data_free(struct fp_print_data *data);
|
||||
size_t fp_print_data_get_data(struct fp_print_data *data, unsigned char **ret);
|
||||
struct fp_print_data *fp_print_data_from_data(unsigned char *buf,
|
||||
size_t buflen);
|
||||
|
||||
/* Imaging devices */
|
||||
int fp_imgdev_capture(struct fp_img_dev *imgdev, int unconditional,
|
||||
|
|
|
@ -243,7 +243,8 @@ int fpi_img_detect_minutiae(struct fp_img_dev *imgdev, struct fp_img *img,
|
|||
/* FIXME: space is wasted if we dont hit the max minutiae count. would
|
||||
* be good to make this dynamic. */
|
||||
print = fpi_print_data_new(imgdev->dev, sizeof(struct xyt_struct));
|
||||
minutiae_to_xyt(minutiae, bw, bh, print->buffer);
|
||||
print->type = PRINT_DATA_NBIS_MINUTIAE;
|
||||
minutiae_to_xyt(minutiae, bw, bh, print->data);
|
||||
/* FIXME: the print buffer at this point is endian-specific, and will
|
||||
* only work when loaded onto machines with identical endianness. not good!
|
||||
* data format should be platform-independant. */
|
||||
|
@ -263,11 +264,17 @@ int fpi_img_detect_minutiae(struct fp_img_dev *imgdev, struct fp_img *img,
|
|||
int fpi_img_compare_print_data(struct fp_print_data *enrolled_print,
|
||||
struct fp_print_data *new_print)
|
||||
{
|
||||
struct xyt_struct *gstruct = (struct xyt_struct *) enrolled_print->buffer;
|
||||
struct xyt_struct *pstruct = (struct xyt_struct *) new_print->buffer;
|
||||
struct xyt_struct *gstruct = (struct xyt_struct *) enrolled_print->data;
|
||||
struct xyt_struct *pstruct = (struct xyt_struct *) new_print->data;
|
||||
GTimer *timer;
|
||||
int r;
|
||||
|
||||
if (enrolled_print->type != PRINT_DATA_NBIS_MINUTIAE ||
|
||||
new_print->type != PRINT_DATA_NBIS_MINUTIAE) {
|
||||
fp_err("invalid print format");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
timer = g_timer_new();
|
||||
r = bozorth_main(pstruct, gstruct);
|
||||
g_timer_stop(timer);
|
||||
|
|
|
@ -195,6 +195,8 @@ static int img_dev_verify(struct fp_dev *dev,
|
|||
|
||||
r = fpi_img_compare_print_data(enrolled_print, print);
|
||||
fp_print_data_free(print);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r >= 40)
|
||||
return FP_VERIFY_MATCH;
|
||||
else
|
||||
|
|
Loading…
Reference in a new issue