- * Recoverable validation exceptions encountered during parsing will be fed to the provided exception handler.
- * Pass in {@link Exceptions#silence()} to ignore all recoverable validation errors
- * or {@link Exceptions#propagate()} to throw exception even for recoverable errors.
- *
- * @param template
- * serialized template in ANSI INCITS 378-2004 format
- * @param handler
- * handler for recoverable validation exceptions
- * @throws TemplateFormatException
- * if unrecoverable validation error is encountered or the provided exception handler returns {@code false}
- */
- public Ansi378v2004Template(byte[] template, ExceptionHandler handler) {
- if (!accepts(template))
- throw new TemplateFormatException("This is not an ANSI INCITS 378-2004 template.");
- TemplateUtils.decodeTemplate(template, in -> {
- in.skipBytes(MAGIC.length);
- skipLength(in, handler);
- vendorId = in.readUnsignedShort();
- subformat = in.readUnsignedShort();
- sensorId = in.readUnsignedShort();
- sensorCertified = (sensorId & 0x8000) != 0;
- ValidateTemplate.condition((sensorId & 0x7000) == 0, handler, "Unrecognized sensor compliance bits.");
- sensorId &= 0xfff;
- width = in.readUnsignedShort();
- height = in.readUnsignedShort();
- resolutionX = in.readUnsignedShort();
- resolutionY = in.readUnsignedShort();
- int count = in.readUnsignedByte();
- in.skipBytes(1);
- for (int i = 0; i < count; ++i)
- fingerprints.add(new Ansi378v2004Fingerprint(in, handler));
- ValidateTemplate.condition(in.available() == 0, handler, "Extra data at the end of the template.");
- ValidateTemplate.structure(this::validate, handler);
- });
- }
- private void skipLength(TemplateReader in, ExceptionHandler handler) {
- int available = in.available();
- int length = in.readUnsignedShort();
- if (length == 0) {
- /*
- * Zero 2-byte length means this template has a 6-byte length field.
- */
- length = (in.readUnsignedShort() << 16) | in.readUnsignedShort();
- ValidateTemplate.condition(length >= 0x10000, "Not strictly compliant template: 6-byte length field should have value of at least 0x10000.");
- }
- ValidateTemplate.condition(length >= 26, "Total length must be at least 26 bytes.");
- ValidateTemplate.condition(length <= MAGIC.length + available, handler, "Total length indicates trimmed template.");
- }
- /**
- * Validates and serializes the template in ANSI INCITS 378-2004 format.
- *
- * @return serialized template in ANSI INCITS 378-2004 format
- * @throws TemplateFormatException
- * if the template fails validation
- */
- public byte[] toByteArray() {
- validate();
- TemplateWriter out = new TemplateWriter();
- out.write(MAGIC);
- int length = measure();
- if (length < 0x10000)
- out.writeShort(length);
- else {
- out.writeShort(0);
- out.writeInt(length);
- }
- out.writeShort(vendorId);
- out.writeShort(subformat);
- out.writeShort((sensorCertified ? 0x8000 : 0) | sensorId);
- out.writeShort(width);
- out.writeShort(height);
- out.writeShort(resolutionX);
- out.writeShort(resolutionY);
- out.writeByte(fingerprints.size());
- out.writeByte(0);
- for (Ansi378v2004Fingerprint fp : fingerprints)
- fp.write(out);
- return out.toByteArray();
- }
- private int measure() {
- int length = 26;
- length += fingerprints.stream().mapToInt(Ansi378v2004Fingerprint::measure).sum();
- return length < 0x10000 ? length : length + 4;
- }
- private void validate() {
- ValidateTemplate.nonzero16(vendorId, "Vendor ID must be a non-zero unsigned 16-bit number.");
- ValidateTemplate.int16(subformat, "Vendor subformat must be an unsigned 16-bit number.");
- ValidateTemplate.range(sensorId, 0, 0xfff, "Sensor ID must be an unsigned 12-bit number.");
- ValidateTemplate.nonzero16(width, "Image width must be a non-zero unsigned 16-bit number.");
- ValidateTemplate.nonzero16(height, "Image height must be a non-zero unsigned 16-bit number.");
- ValidateTemplate.nonzero16(resolutionX, "Horizontal pixel density must be a non-zero unsigned 16-bit number.");
- ValidateTemplate.nonzero16(resolutionY, "Vertical pixel density must be a non-zero unsigned 16-bit number.");
- ValidateTemplate.int8(fingerprints.size(), "There cannot be more than 255 fingerprints.");
- for (Ansi378v2004Fingerprint fp : fingerprints)
- fp.validate(width, height);
- if (fingerprints.size() != fingerprints.stream().mapToInt(fp -> (fp.position.ordinal() << 16) + fp.view).distinct().count())
- throw new TemplateFormatException("Every fingerprint must have a unique combination of finger position and view offset.");
- fingerprints.stream()
- .collect(groupingBy(fp -> fp.position))
- .values().stream()
- .forEach(l -> {
- for (int i = 0; i < l.size(); ++i) {
- ValidateTemplate.range(l.get(i).view, 0, l.size() - 1, "Fingerprint view numbers must be assigned contiguously, starting from zero.");
- if (!l.equals(l.stream().sorted(Comparator.comparingInt(fp -> fp.view)).collect(toList())))
- throw new TemplateFormatException("Fingerprints with the same finger position must be sorted by view number.");
- }
- });
- }
+ private static final byte[] MAGIC = new byte[] { 'F', 'M', 'R', 0, ' ', '2', '0', 0 };
+ /**
+ * Checks whether provided template is an ANSI INCITS 378-2004 template.
+ * This method does not do any template validation or conformance checking.
+ * It just differentiates ANSI INCITS 378-2004 from other template formats
+ * as quickly as possible, mostly by looking at template header.
+ *
+ * @param template
+ * serialized template that is to be evaluated
+ * @return {@code true} if {@code template} is an ANSI INCITS 378-2004 template, {@code false} otherwise
+ */
+ public static boolean accepts(byte[] template) {
+ if (template.length < MAGIC.length + 4)
+ return false;
+ if (!Arrays.equals(MAGIC, Arrays.copyOf(template, MAGIC.length)))
+ return false;
+ TemplateReader in = new TemplateReader(template);
+ in.skipBytes(MAGIC.length);
+ /*
+ * Differentiate from ISO 19794-2 by examining the length field.
+ */
+ int bytes01 = in.readUnsignedShort();
+ if (bytes01 >= 26) {
+ /*
+ * Too big for ISO 19794-2. It's indeed ANSI 378-2004 with 2-byte length field.
+ */
+ return true;
+ } else if (bytes01 > 0) {
+ /*
+ * Invalid length field for ANSI 378. Must be ISO 19794-2.
+ */
+ return false;
+ } else {
+ int bytes23 = in.readUnsignedShort();
+ if (bytes23 >= 24) {
+ /*
+ * Too big for ANSI 378. Must be ISO 19794-2.
+ */
+ return false;
+ } else {
+ /*
+ * It's ANSI 378-2004 with 6-byte length field.
+ */
+ return true;
+ }
+ }
+ }
+ /**
+ * Vendor ID (VENDOR).
+ * Defaults to {@link IbiaOrganizations#UNKNOWN}.
+ */
+ public int vendorId = IbiaOrganizations.UNKNOWN;
+ /**
+ * Vendor-specified subformat (SUBFORMAT).
+ */
+ public int subformat;
+ /**
+ * Indicates that the fingerprint reader has certificate of compliance with Appendix F of CJIS-RS-0010 V7.
+ * This is the top bit of DEVSTAMP field.
+ */
+ public boolean sensorCertified;
+ /**
+ * Sensor ID (DEVID).
+ */
+ public int sensorId;
+ /**
+ * Image width (WIDTH).
+ */
+ public int width;
+ /**
+ * Image height (HEIGHT).
+ */
+ public int height;
+ /**
+ * Horizontal pixel density (RESOLUTIONX).
+ * Defaults to 197 (500dpi).
+ */
+ public int resolutionX = 197;
+ /**
+ * Vertical pixel density (RESOLUTIONY).
+ * Defaults to 197 (500dpi).
+ */
+ public int resolutionY = 197;
+ /**
+ * List of fingerprints (FINGERPRINT).
+ */
+ public List
+ * Recoverable validation exceptions encountered during parsing will be fed to the provided exception handler.
+ * Pass in {@link Exceptions#silence()} to ignore all recoverable validation errors
+ * or {@link Exceptions#propagate()} to throw exception even for recoverable errors.
+ *
+ * @param template
+ * serialized template in ANSI INCITS 378-2004 format
+ * @param handler
+ * handler for recoverable validation exceptions
+ * @throws TemplateFormatException
+ * if unrecoverable validation error is encountered or the provided exception handler returns {@code false}
+ */
+ public Ansi378v2004Template(byte[] template, ExceptionHandler handler) {
+ if (!accepts(template))
+ throw new TemplateFormatException("This is not an ANSI INCITS 378-2004 template.");
+ TemplateUtils.decodeTemplate(template, in -> {
+ in.skipBytes(MAGIC.length);
+ skipLength(in, handler);
+ vendorId = in.readUnsignedShort();
+ subformat = in.readUnsignedShort();
+ sensorId = in.readUnsignedShort();
+ sensorCertified = (sensorId & 0x8000) != 0;
+ ValidateTemplate.condition((sensorId & 0x7000) == 0, handler, "Unrecognized sensor compliance bits.");
+ sensorId &= 0xfff;
+ width = in.readUnsignedShort();
+ height = in.readUnsignedShort();
+ resolutionX = in.readUnsignedShort();
+ resolutionY = in.readUnsignedShort();
+ int count = in.readUnsignedByte();
+ in.skipBytes(1);
+ for (int i = 0; i < count; ++i)
+ fingerprints.add(new Ansi378v2004Fingerprint(in, handler));
+ ValidateTemplate.condition(in.available() == 0, handler, "Extra data at the end of the template.");
+ ValidateTemplate.structure(this::validate, handler);
+ });
+ }
+ private void skipLength(TemplateReader in, ExceptionHandler handler) {
+ int available = in.available();
+ int length = in.readUnsignedShort();
+ if (length == 0) {
+ /*
+ * Zero 2-byte length means this template has a 6-byte length field.
+ */
+ length = (in.readUnsignedShort() << 16) | in.readUnsignedShort();
+ ValidateTemplate.condition(length >= 0x10000, handler,
+ "Not strictly compliant template: 6-byte length field should have value of at least 0x10000.");
+ }
+ ValidateTemplate.condition(length >= 26, handler, "Total length must be at least 26 bytes.");
+ ValidateTemplate.condition(length <= MAGIC.length + available, handler, "Total length indicates trimmed template.");
+ }
+ /**
+ * Validates and serializes the template in ANSI INCITS 378-2004 format.
+ *
+ * @return serialized template in ANSI INCITS 378-2004 format
+ * @throws TemplateFormatException
+ * if the template fails validation
+ */
+ public byte[] toByteArray() {
+ validate();
+ TemplateWriter out = new TemplateWriter();
+ out.write(MAGIC);
+ int length = measure();
+ if (length < 0x10000)
+ out.writeShort(length);
+ else {
+ out.writeShort(0);
+ out.writeInt(length);
+ }
+ out.writeShort(vendorId);
+ out.writeShort(subformat);
+ out.writeShort((sensorCertified ? 0x8000 : 0) | sensorId);
+ out.writeShort(width);
+ out.writeShort(height);
+ out.writeShort(resolutionX);
+ out.writeShort(resolutionY);
+ out.writeByte(fingerprints.size());
+ out.writeByte(0);
+ for (Ansi378v2004Fingerprint fp : fingerprints)
+ fp.write(out);
+ return out.toByteArray();
+ }
+ private int measure() {
+ int length = 26;
+ length += fingerprints.stream().mapToInt(Ansi378v2004Fingerprint::measure).sum();
+ return length < 0x10000 ? length : length + 4;
+ }
+ private void validate() {
+ ValidateTemplate.nonzero16(vendorId, "Vendor ID must be a non-zero unsigned 16-bit number.");
+ ValidateTemplate.int16(subformat, "Vendor subformat must be an unsigned 16-bit number.");
+ ValidateTemplate.range(sensorId, 0, 0xfff, "Sensor ID must be an unsigned 12-bit number.");
+ ValidateTemplate.nonzero16(width, "Image width must be a non-zero unsigned 16-bit number.");
+ ValidateTemplate.nonzero16(height, "Image height must be a non-zero unsigned 16-bit number.");
+ ValidateTemplate.nonzero16(resolutionX, "Horizontal pixel density must be a non-zero unsigned 16-bit number.");
+ ValidateTemplate.nonzero16(resolutionY, "Vertical pixel density must be a non-zero unsigned 16-bit number.");
+ ValidateTemplate.int8(fingerprints.size(), "There cannot be more than 255 fingerprints.");
+ for (Ansi378v2004Fingerprint fp : fingerprints)
+ fp.validate(width, height);
+ if (fingerprints.size() != fingerprints.stream().mapToInt(fp -> (fp.position.ordinal() << 16) + fp.view).distinct().count())
+ throw new TemplateFormatException("Every fingerprint must have a unique combination of finger position and view offset.");
+ fingerprints.stream()
+ .collect(groupingBy(fp -> fp.position))
+ .values().stream()
+ .forEach(l -> {
+ for (int i = 0; i < l.size(); ++i) {
+ ValidateTemplate.range(l.get(i).view, 0, l.size() - 1, "Fingerprint view numbers must be assigned contiguously, starting from zero.");
+ if (!l.equals(l.stream().sorted(Comparator.comparingInt(fp -> fp.view)).collect(toList())))
+ throw new TemplateFormatException("Fingerprints with the same finger position must be sorted by view number.");
+ }
+ });
+ }
}
diff --git a/src/main/java/com/machinezoo/fingerprintio/ansi378v2009/Ansi378v2009Template.java b/src/main/java/com/machinezoo/fingerprintio/ansi378v2009/Ansi378v2009Template.java
index f080e6e..29c498a 100644
--- a/src/main/java/com/machinezoo/fingerprintio/ansi378v2009/Ansi378v2009Template.java
+++ b/src/main/java/com/machinezoo/fingerprintio/ansi378v2009/Ansi378v2009Template.java
@@ -1,198 +1,198 @@
// Part of FingerprintIO: https://fingerprintio.machinezoo.com
package com.machinezoo.fingerprintio.ansi378v2009;
-import static java.util.stream.Collectors.*;
-import java.io.*;
-import java.util.*;
-import com.machinezoo.fingerprintio.*;
-import com.machinezoo.fingerprintio.ansi378v2009am1.*;
-import com.machinezoo.fingerprintio.common.*;
-import com.machinezoo.fingerprintio.utils.*;
-import com.machinezoo.noexception.*;
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.toList;
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import com.machinezoo.fingerprintio.TemplateFormatException;
+import com.machinezoo.fingerprintio.ansi378v2009am1.Ansi378v2009Am1Template;
+import com.machinezoo.fingerprintio.common.IbiaOrganizations;
+import com.machinezoo.fingerprintio.utils.TemplateUtils;
+import com.machinezoo.fingerprintio.utils.TemplateWriter;
+import com.machinezoo.fingerprintio.utils.ValidateTemplate;
+import com.machinezoo.noexception.ExceptionHandler;
+import com.machinezoo.noexception.Exceptions;
/**
* ANSI INCITS 378-2009 template.
- *
+ *
* @see ANSI INCITS 378-2009 Summary
*/
public class Ansi378v2009Template {
- private static final byte[] MAGIC = new byte[] { 'F', 'M', 'R', 0, '0', '3', '0', 0 };
- /**
- * Checks whether provided template is an ANSI INCITS 378-2009 template.
- * This method does not do any template validation or conformance checking.
- * It just differentiates ANSI INCITS 378-2009 from other template formats
- * as quickly as possible, mostly by looking at template header.
- *
- * @param template
- * serialized template that is to be evaluated
- * @return {@code true} if {@code template} is an ANSI INCITS 378-2009 template, {@code false} otherwise
- */
- public static boolean accepts(byte[] template) {
- if (!Arrays.equals(MAGIC, Arrays.copyOf(template, MAGIC.length)))
- return false;
- /*
- * We differentiate the format from ISO 19794-2:2011 by failing to interpret the data as ISO 19794-2:2011,
- * which is why the code below is copied from Iso19794p2v2001Template with return values inverted.
- */
- try {
- DataInputStream in = new DataInputStream(new ByteArrayInputStream(template));
- in.skip(MAGIC.length);
- long total = 0xffff_ffffL & in.readInt();
- int count = in.readUnsignedShort();
- in.skip(1);
- long sum = 0;
- for (int i = 0; i < count; ++i) {
- long length = 0xffff_ffffL & in.readInt();
- if (length < 34)
- return true;
- sum += length;
- in.skip(length - 4);
- }
- return total != 15 + sum;
- } catch (Throwable ex) {
- return true;
- }
- }
- /**
- * Vendor ID (VENDOR).
- * Defaults to {@link IbiaOrganizations#UNKNOWN}.
- */
- public int vendorId = IbiaOrganizations.UNKNOWN;
- /**
- * Vendor-specified subformat (SUBFORMAT).
- */
- public int subformat;
- /**
- * Indicates that the fingerprint reader has certificate of compliance with Appendix F of CJIS-RS-0010 V7.
- * This is the top bit of DEVSTAMP field.
- */
- public boolean sensorCertified;
- /**
- * Sensor ID (DEVID).
- */
- public int sensorId;
- /**
- * List of fingerprints (FINGERPRINT).
- */
- public List
- * Recoverable validation exceptions encountered during parsing will be fed to the provided exception handler.
- * Pass in {@link Exceptions#silence()} to ignore all recoverable validation errors
- * or {@link Exceptions#propagate()} to throw exception even for recoverable errors.
- *
- * @param template
- * serialized template in ANSI INCITS 378-2009 format
- * @param handler
- * handler for recoverable validation exceptions
- * @throws TemplateFormatException
- * if unrecoverable validation error is encountered or the provided exception handler returns {@code false}
- */
- public Ansi378v2009Template(byte[] template, ExceptionHandler handler) {
- if (!accepts(template)) {
- if (Ansi378v2009Am1Template.accepts(template)) {
- ValidateTemplate.fail(handler, "This is ANSI INCITS 378-2009/AM1 template, not ANSI INCITS 378-2009 template.");
- template = Arrays.copyOf(template, template.length);
- template[6] = '0';
- } else
- throw new TemplateFormatException("This is not an ANSI INCITS 378-2009 template.");
- }
- TemplateUtils.decodeTemplate(template, in -> {
- in.skipBytes(MAGIC.length);
- long length = 0xffff_ffffL & in.readInt();
- ValidateTemplate.condition(length >= 21, "Total length must be at least 21 bytes.");
- ValidateTemplate.condition(length <= MAGIC.length + 4 + in.available(), handler, "Total length indicates trimmed template.");
- vendorId = in.readUnsignedShort();
- subformat = in.readUnsignedShort();
- int certification = in.readUnsignedByte();
- sensorCertified = (certification & 0x80) != 0;
- ValidateTemplate.condition((certification & 0x7f) == 0, handler, "Unrecognized sensor compliance bits.");
- sensorId = in.readUnsignedShort();
- int count = in.readUnsignedByte();
- in.skipBytes(1);
- for (int i = 0; i < count; ++i)
- fingerprints.add(new Ansi378v2009Fingerprint(in, handler));
- ValidateTemplate.condition(in.available() == 0, handler, "Extra data at the end of the template.");
- ValidateTemplate.structure(this::validate, handler);
- });
- }
- /**
- * Validates and serializes the template in ANSI INCITS 378-2009 format.
- *
- * @return serialized template in ANSI INCITS 378-2009 format
- * @throws TemplateFormatException
- * if the template fails validation
- */
- public byte[] toByteArray() {
- validate();
- TemplateWriter out = new TemplateWriter();
- out.write(MAGIC);
- out.writeInt(measure());
- out.writeShort(vendorId);
- out.writeShort(subformat);
- out.writeByte(sensorCertified ? 0x80 : 0);
- out.writeShort(sensorId);
- out.writeByte(fingerprints.size());
- out.writeByte(0);
- for (Ansi378v2009Fingerprint fp : fingerprints)
- fp.write(out);
- return out.toByteArray();
- }
- private int measure() {
- return 21 + fingerprints.stream().mapToInt(Ansi378v2009Fingerprint::measure).sum();
- }
- private void validate() {
- ValidateTemplate.nonzero16(vendorId, "Vendor ID must be a non-zero unsigned 16-bit number.");
- ValidateTemplate.int16(subformat, "Vendor subformat must be an unsigned 16-bit number.");
- ValidateTemplate.int16(sensorId, "Sensor ID must be an unsigned 16-bit number.");
- ValidateTemplate.int8(fingerprints.size(), "There cannot be more than 255 fingerprints.");
- for (Ansi378v2009Fingerprint fp : fingerprints)
- fp.validate();
- if (fingerprints.size() != fingerprints.stream().map(fp -> (fp.position.ordinal() << 16) + fp.view).distinct().count())
- throw new TemplateFormatException("Every fingerprint must have a unique combination of finger position and view offset.");
- fingerprints.stream()
- .collect(groupingBy(fp -> fp.position))
- .values().stream()
- .forEach(l -> {
- for (int i = 0; i < l.size(); ++i) {
- ValidateTemplate.range(l.get(i).view, 0, l.size() - 1, "Fingerprint view numbers must be assigned contiguously, starting from zero.");
- if (!l.equals(l.stream().sorted(Comparator.comparingInt(fp -> fp.view)).collect(toList())))
- throw new TemplateFormatException("Fingerprints with the same finger position must be sorted by view number.");
- if (fingerprints.indexOf(l.get(0)) + l.size() - 1 != fingerprints.indexOf(l.get(l.size() - 1)))
- throw new TemplateFormatException("Fingerprints with the same finger position must be listed in the template together.");
- }
- });
- }
+ private static final byte[] MAGIC = new byte[] { 'F', 'M', 'R', 0, '0', '3', '0', 0 };
+ /**
+ * Checks whether provided template is an ANSI INCITS 378-2009 template.
+ * This method does not do any template validation or conformance checking.
+ * It just differentiates ANSI INCITS 378-2009 from other template formats
+ * as quickly as possible, mostly by looking at template header.
+ *
+ * @param template
+ * serialized template that is to be evaluated
+ * @return {@code true} if {@code template} is an ANSI INCITS 378-2009 template, {@code false} otherwise
+ */
+ public static boolean accepts(byte[] template) {
+ if (!Arrays.equals(MAGIC, Arrays.copyOf(template, MAGIC.length)))
+ return false;
+ /*
+ * We differentiate the format from ISO 19794-2:2011 by failing to interpret the data as ISO 19794-2:2011,
+ * which is why the code below is copied from Iso19794p2v2001Template with return values inverted.
+ */
+ try {
+ DataInputStream in = new DataInputStream(new ByteArrayInputStream(template));
+ in.skip(MAGIC.length);
+ long total = 0xffff_ffffL & in.readInt();
+ int count = in.readUnsignedShort();
+ in.skip(1);
+ long sum = 0;
+ for (int i = 0; i < count; ++i) {
+ long length = 0xffff_ffffL & in.readInt();
+ if (length < 34)
+ return true;
+ sum += length;
+ in.skip(length - 4);
+ }
+ return total != 15 + sum;
+ } catch (Throwable ex) {
+ return true;
+ }
+ }
+ /**
+ * Vendor ID (VENDOR).
+ * Defaults to {@link IbiaOrganizations#UNKNOWN}.
+ */
+ public int vendorId = IbiaOrganizations.UNKNOWN;
+ /**
+ * Vendor-specified subformat (SUBFORMAT).
+ */
+ public int subformat;
+ /**
+ * Indicates that the fingerprint reader has certificate of compliance with Appendix F of CJIS-RS-0010 V7.
+ * This is the top bit of DEVSTAMP field.
+ */
+ public boolean sensorCertified;
+ /**
+ * Sensor ID (DEVID).
+ */
+ public int sensorId;
+ /**
+ * List of fingerprints (FINGERPRINT).
+ */
+ public List
+ * Recoverable validation exceptions encountered during parsing will be fed to the provided exception handler.
+ * Pass in {@link Exceptions#silence()} to ignore all recoverable validation errors
+ * or {@link Exceptions#propagate()} to throw exception even for recoverable errors.
+ *
+ * @param template
+ * serialized template in ANSI INCITS 378-2009 format
+ * @param handler
+ * handler for recoverable validation exceptions
+ * @throws TemplateFormatException
+ * if unrecoverable validation error is encountered or the provided exception handler returns {@code false}
+ */
+ public Ansi378v2009Template(byte[] template, ExceptionHandler handler) {
+ if (!accepts(template)) {
+ if (Ansi378v2009Am1Template.accepts(template)) {
+ ValidateTemplate.fail(handler, "This is ANSI INCITS 378-2009/AM1 template, not ANSI INCITS 378-2009 template.");
+ template = Arrays.copyOf(template, template.length);
+ template[6] = '0';
+ } else
+ throw new TemplateFormatException("This is not an ANSI INCITS 378-2009 template.");
+ }
+ TemplateUtils.decodeTemplate(template, in -> {
+ in.skipBytes(MAGIC.length);
+ long length = 0xffff_ffffL & in.readInt();
+ ValidateTemplate.condition(length >= 21, handler, "Total length must be at least 21 bytes.");
+ ValidateTemplate.condition(length <= MAGIC.length + 4 + in.available(), handler, "Total length indicates trimmed template.");
+ vendorId = in.readUnsignedShort();
+ subformat = in.readUnsignedShort();
+ int certification = in.readUnsignedByte();
+ sensorCertified = (certification & 0x80) != 0;
+ ValidateTemplate.condition((certification & 0x7f) == 0, handler, "Unrecognized sensor compliance bits.");
+ sensorId = in.readUnsignedShort();
+ int count = in.readUnsignedByte();
+ in.skipBytes(1);
+ for (int i = 0; i < count; ++i)
+ fingerprints.add(new Ansi378v2009Fingerprint(in, handler));
+ ValidateTemplate.condition(in.available() == 0, handler, "Extra data at the end of the template.");
+ ValidateTemplate.structure(this::validate, handler);
+ });
+ }
+ /**
+ * Validates and serializes the template in ANSI INCITS 378-2009 format.
+ *
+ * @return serialized template in ANSI INCITS 378-2009 format
+ * @throws TemplateFormatException
+ * if the template fails validation
+ */
+ public byte[] toByteArray() {
+ validate();
+ TemplateWriter out = new TemplateWriter();
+ out.write(MAGIC);
+ out.writeInt(measure());
+ out.writeShort(vendorId);
+ out.writeShort(subformat);
+ out.writeByte(sensorCertified ? 0x80 : 0);
+ out.writeShort(sensorId);
+ out.writeByte(fingerprints.size());
+ out.writeByte(0);
+ for (Ansi378v2009Fingerprint fp : fingerprints)
+ fp.write(out);
+ return out.toByteArray();
+ }
+ private int measure() { return 21 + fingerprints.stream().mapToInt(Ansi378v2009Fingerprint::measure).sum(); }
+ private void validate() {
+ ValidateTemplate.nonzero16(vendorId, "Vendor ID must be a non-zero unsigned 16-bit number.");
+ ValidateTemplate.int16(subformat, "Vendor subformat must be an unsigned 16-bit number.");
+ ValidateTemplate.int16(sensorId, "Sensor ID must be an unsigned 16-bit number.");
+ ValidateTemplate.int8(fingerprints.size(), "There cannot be more than 255 fingerprints.");
+ for (Ansi378v2009Fingerprint fp : fingerprints)
+ fp.validate();
+ if (fingerprints.size() != fingerprints.stream().map(fp -> (fp.position.ordinal() << 16) + fp.view).distinct().count())
+ throw new TemplateFormatException("Every fingerprint must have a unique combination of finger position and view offset.");
+ fingerprints.stream()
+ .collect(groupingBy(fp -> fp.position))
+ .values().stream()
+ .forEach(l -> {
+ for (int i = 0; i < l.size(); ++i) {
+ ValidateTemplate.range(l.get(i).view, 0, l.size() - 1, "Fingerprint view numbers must be assigned contiguously, starting from zero.");
+ if (!l.equals(l.stream().sorted(Comparator.comparingInt(fp -> fp.view)).collect(toList())))
+ throw new TemplateFormatException("Fingerprints with the same finger position must be sorted by view number.");
+ if (fingerprints.indexOf(l.get(0)) + l.size() - 1 != fingerprints.indexOf(l.get(l.size() - 1)))
+ throw new TemplateFormatException("Fingerprints with the same finger position must be listed in the template together.");
+ }
+ });
+ }
}
diff --git a/src/main/java/com/machinezoo/fingerprintio/ansi378v2009am1/Ansi378v2009Am1Template.java b/src/main/java/com/machinezoo/fingerprintio/ansi378v2009am1/Ansi378v2009Am1Template.java
index e6614e5..1da2af7 100644
--- a/src/main/java/com/machinezoo/fingerprintio/ansi378v2009am1/Ansi378v2009Am1Template.java
+++ b/src/main/java/com/machinezoo/fingerprintio/ansi378v2009am1/Ansi378v2009Am1Template.java
@@ -1,175 +1,172 @@
// Part of FingerprintIO: https://fingerprintio.machinezoo.com
package com.machinezoo.fingerprintio.ansi378v2009am1;
-import static java.util.stream.Collectors.*;
-import java.util.*;
-import com.machinezoo.fingerprintio.*;
-import com.machinezoo.fingerprintio.ansi378v2009.*;
-import com.machinezoo.fingerprintio.common.*;
-import com.machinezoo.fingerprintio.utils.*;
-import com.machinezoo.noexception.*;
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.toList;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import com.machinezoo.fingerprintio.TemplateFormatException;
+import com.machinezoo.fingerprintio.ansi378v2009.Ansi378v2009Template;
+import com.machinezoo.fingerprintio.common.IbiaOrganizations;
+import com.machinezoo.fingerprintio.utils.TemplateUtils;
+import com.machinezoo.fingerprintio.utils.TemplateWriter;
+import com.machinezoo.fingerprintio.utils.ValidateTemplate;
+import com.machinezoo.noexception.ExceptionHandler;
+import com.machinezoo.noexception.Exceptions;
/**
* ANSI INCITS 378-2009/AM 1 template.
- *
+ *
* @see ANSI INCITS 378-2009/AM 1 Summary
*/
public class Ansi378v2009Am1Template {
- private static final byte[] MAGIC = new byte[] { 'F', 'M', 'R', 0, '0', '3', '5', 0 };
- /**
- * Checks whether provided template is an ANSI INCITS 378-2009/AM 1 template.
- * This method does not do any template validation or conformance checking.
- * It just differentiates ANSI INCITS 378-2009/AM 1 from other template formats
- * as quickly as possible, mostly by looking at template header.
- *
- * @param template
- * serialized template that is to be evaluated
- * @return {@code true} if {@code template} is an ANSI INCITS 378-2009/AM 1 template, {@code false} otherwise
- */
- public static boolean accepts(byte[] template) {
- return template.length >= MAGIC.length && Arrays.equals(MAGIC, Arrays.copyOf(template, MAGIC.length));
- }
- /**
- * Vendor ID (VENDOR).
- * Defaults to {@link IbiaOrganizations#UNKNOWN}.
- */
- public int vendorId = IbiaOrganizations.UNKNOWN;
- /**
- * Vendor-specified subformat (SUBFORMAT).
- */
- public int subformat;
- /**
- * Indicates that the fingerprint reader has certificate of compliance with Appendix F of CJIS-RS-0010 V7.
- * This is the top bit of DEVSTAMP field.
- */
- public boolean sensorCertified;
- /**
- * Sensor ID (DEVID).
- */
- public int sensorId;
- /**
- * List of fingerprints (FINGERPRINT).
- */
- public List
- * Recoverable validation exceptions encountered during parsing will be fed to the provided exception handler.
- * Pass in {@link Exceptions#silence()} to ignore all recoverable validation errors
- * or {@link Exceptions#propagate()} to throw exception even for recoverable errors.
- *
- * @param template
- * serialized template in ANSI INCITS 378-2009/AM 1 format
- * @param handler
- * handler for recoverable validation exceptions
- * @throws TemplateFormatException
- * if unrecoverable validation error is encountered or the provided exception handler returns {@code false}
- */
- public Ansi378v2009Am1Template(byte[] template, ExceptionHandler handler) {
- if (!accepts(template)) {
- if (Ansi378v2009Template.accepts(template)) {
- ValidateTemplate.fail(handler, "This is ANSI INCITS 378-2009 template, not ANSI INCITS 378-2009/AM1 template.");
- template = Arrays.copyOf(template, template.length);
- template[6] = '5';
- } else
- throw new TemplateFormatException("This is not an ANSI INCITS 378-2009/AM1 template.");
- }
- TemplateUtils.decodeTemplate(template, in -> {
- in.skipBytes(MAGIC.length);
- long length = 0xffff_ffffL & in.readInt();
- ValidateTemplate.condition(length >= 21, "Total length must be at least 21 bytes.");
- ValidateTemplate.condition(length <= MAGIC.length + 4 + in.available(), handler, "Total length indicates trimmed template.");
- vendorId = in.readUnsignedShort();
- subformat = in.readUnsignedShort();
- int certification = in.readUnsignedByte();
- sensorCertified = (certification & 0x80) != 0;
- ValidateTemplate.condition((certification & 0x7f) == 0, handler, "Unrecognized sensor compliance bits.");
- sensorId = in.readUnsignedShort();
- int count = in.readUnsignedByte();
- in.skipBytes(1);
- for (int i = 0; i < count; ++i)
- fingerprints.add(new Ansi378v2009Am1Fingerprint(in, handler));
- ValidateTemplate.condition(in.available() == 0, handler, "Extra data at the end of the template.");
- ValidateTemplate.structure(this::validate, handler);
- });
- }
- /**
- * Validates and serializes the template in ANSI INCITS 378-2009/AM 1 format.
- *
- * @return serialized template in ANSI INCITS 378-2009/AM 1 format
- * @throws TemplateFormatException
- * if the template fails validation
- */
- public byte[] toByteArray() {
- validate();
- TemplateWriter out = new TemplateWriter();
- out.write(MAGIC);
- out.writeInt(measure());
- out.writeShort(vendorId);
- out.writeShort(subformat);
- out.writeByte(sensorCertified ? 0x80 : 0);
- out.writeShort(sensorId);
- out.writeByte(fingerprints.size());
- out.writeByte(0);
- for (Ansi378v2009Am1Fingerprint fp : fingerprints)
- fp.write(out);
- return out.toByteArray();
- }
- private int measure() {
- return 21 + fingerprints.stream().mapToInt(Ansi378v2009Am1Fingerprint::measure).sum();
- }
- private void validate() {
- ValidateTemplate.nonzero16(vendorId, "Vendor ID must be a non-zero unsigned 16-bit number.");
- ValidateTemplate.int16(subformat, "Vendor subformat must be an unsigned 16-bit number.");
- ValidateTemplate.int16(sensorId, "Sensor ID must be an unsigned 16-bit number.");
- ValidateTemplate.int8(fingerprints.size(), "There cannot be more than 255 fingerprints.");
- ValidateTemplate.nonzero(fingerprints.size(), "At least one fingerprint must be present in the template.");
- for (Ansi378v2009Am1Fingerprint fp : fingerprints)
- fp.validate();
- if (fingerprints.size() != fingerprints.stream().map(fp -> (fp.position.ordinal() << 16) + fp.view).distinct().count())
- throw new TemplateFormatException("Every fingerprint must have a unique combination of finger position and view offset.");
- fingerprints.stream()
- .collect(groupingBy(fp -> fp.position))
- .values().stream()
- .forEach(l -> {
- for (int i = 0; i < l.size(); ++i) {
- ValidateTemplate.range(l.get(i).view, 0, l.size() - 1, "Fingerprint view numbers must be assigned contiguously, starting from zero.");
- if (!l.equals(l.stream().sorted(Comparator.comparingInt(fp -> fp.view)).collect(toList())))
- throw new TemplateFormatException("Fingerprints with the same finger position must be sorted by view number.");
- if (fingerprints.indexOf(l.get(0)) + l.size() - 1 != fingerprints.indexOf(l.get(l.size() - 1)))
- throw new TemplateFormatException("Fingerprints with the same finger position must be listed in the template together.");
- }
- });
- }
+ private static final byte[] MAGIC = new byte[] { 'F', 'M', 'R', 0, '0', '3', '5', 0 };
+ /**
+ * Checks whether provided template is an ANSI INCITS 378-2009/AM 1 template.
+ * This method does not do any template validation or conformance checking.
+ * It just differentiates ANSI INCITS 378-2009/AM 1 from other template formats
+ * as quickly as possible, mostly by looking at template header.
+ *
+ * @param template
+ * serialized template that is to be evaluated
+ * @return {@code true} if {@code template} is an ANSI INCITS 378-2009/AM 1 template, {@code false} otherwise
+ */
+ public static boolean accepts(byte[] template) { return template.length >= MAGIC.length && Arrays.equals(MAGIC, Arrays.copyOf(template, MAGIC.length)); }
+ /**
+ * Vendor ID (VENDOR).
+ * Defaults to {@link IbiaOrganizations#UNKNOWN}.
+ */
+ public int vendorId = IbiaOrganizations.UNKNOWN;
+ /**
+ * Vendor-specified subformat (SUBFORMAT).
+ */
+ public int subformat;
+ /**
+ * Indicates that the fingerprint reader has certificate of compliance with Appendix F of CJIS-RS-0010 V7.
+ * This is the top bit of DEVSTAMP field.
+ */
+ public boolean sensorCertified;
+ /**
+ * Sensor ID (DEVID).
+ */
+ public int sensorId;
+ /**
+ * List of fingerprints (FINGERPRINT).
+ */
+ public List
+ * Recoverable validation exceptions encountered during parsing will be fed to the provided exception handler.
+ * Pass in {@link Exceptions#silence()} to ignore all recoverable validation errors
+ * or {@link Exceptions#propagate()} to throw exception even for recoverable errors.
+ *
+ * @param template
+ * serialized template in ANSI INCITS 378-2009/AM 1 format
+ * @param handler
+ * handler for recoverable validation exceptions
+ * @throws TemplateFormatException
+ * if unrecoverable validation error is encountered or the provided exception handler returns {@code false}
+ */
+ public Ansi378v2009Am1Template(byte[] template, ExceptionHandler handler) {
+ if (!accepts(template)) {
+ if (Ansi378v2009Template.accepts(template)) {
+ ValidateTemplate.fail(handler, "This is ANSI INCITS 378-2009 template, not ANSI INCITS 378-2009/AM1 template.");
+ template = Arrays.copyOf(template, template.length);
+ template[6] = '5';
+ } else
+ throw new TemplateFormatException("This is not an ANSI INCITS 378-2009/AM1 template.");
+ }
+ TemplateUtils.decodeTemplate(template, in -> {
+ in.skipBytes(MAGIC.length);
+ long length = 0xffff_ffffL & in.readInt();
+ ValidateTemplate.condition(length >= 21, handler, "Total length must be at least 21 bytes.");
+ ValidateTemplate.condition(length <= MAGIC.length + 4 + in.available(), handler, "Total length indicates trimmed template.");
+ vendorId = in.readUnsignedShort();
+ subformat = in.readUnsignedShort();
+ int certification = in.readUnsignedByte();
+ sensorCertified = (certification & 0x80) != 0;
+ ValidateTemplate.condition((certification & 0x7f) == 0, handler, "Unrecognized sensor compliance bits.");
+ sensorId = in.readUnsignedShort();
+ int count = in.readUnsignedByte();
+ in.skipBytes(1);
+ for (int i = 0; i < count; ++i)
+ fingerprints.add(new Ansi378v2009Am1Fingerprint(in, handler));
+ ValidateTemplate.condition(in.available() == 0, handler, "Extra data at the end of the template.");
+ ValidateTemplate.structure(this::validate, handler);
+ });
+ }
+ /**
+ * Validates and serializes the template in ANSI INCITS 378-2009/AM 1 format.
+ *
+ * @return serialized template in ANSI INCITS 378-2009/AM 1 format
+ * @throws TemplateFormatException
+ * if the template fails validation
+ */
+ public byte[] toByteArray() {
+ validate();
+ TemplateWriter out = new TemplateWriter();
+ out.write(MAGIC);
+ out.writeInt(measure());
+ out.writeShort(vendorId);
+ out.writeShort(subformat);
+ out.writeByte(sensorCertified ? 0x80 : 0);
+ out.writeShort(sensorId);
+ out.writeByte(fingerprints.size());
+ out.writeByte(0);
+ for (Ansi378v2009Am1Fingerprint fp : fingerprints)
+ fp.write(out);
+ return out.toByteArray();
+ }
+ private int measure() { return 21 + fingerprints.stream().mapToInt(Ansi378v2009Am1Fingerprint::measure).sum(); }
+ private void validate() {
+ ValidateTemplate.nonzero16(vendorId, "Vendor ID must be a non-zero unsigned 16-bit number.");
+ ValidateTemplate.int16(subformat, "Vendor subformat must be an unsigned 16-bit number.");
+ ValidateTemplate.int16(sensorId, "Sensor ID must be an unsigned 16-bit number.");
+ ValidateTemplate.int8(fingerprints.size(), "There cannot be more than 255 fingerprints.");
+ ValidateTemplate.nonzero(fingerprints.size(), "At least one fingerprint must be present in the template.");
+ for (Ansi378v2009Am1Fingerprint fp : fingerprints)
+ fp.validate();
+ if (fingerprints.size() != fingerprints.stream().map(fp -> (fp.position.ordinal() << 16) + fp.view).distinct().count())
+ throw new TemplateFormatException("Every fingerprint must have a unique combination of finger position and view offset.");
+ fingerprints.stream()
+ .collect(groupingBy(fp -> fp.position))
+ .values().stream()
+ .forEach(l -> {
+ for (int i = 0; i < l.size(); ++i) {
+ ValidateTemplate.range(l.get(i).view, 0, l.size() - 1, "Fingerprint view numbers must be assigned contiguously, starting from zero.");
+ if (!l.equals(l.stream().sorted(Comparator.comparingInt(fp -> fp.view)).collect(toList())))
+ throw new TemplateFormatException("Fingerprints with the same finger position must be sorted by view number.");
+ if (fingerprints.indexOf(l.get(0)) + l.size() - 1 != fingerprints.indexOf(l.get(l.size() - 1)))
+ throw new TemplateFormatException("Fingerprints with the same finger position must be listed in the template together.");
+ }
+ });
+ }
}
diff --git a/src/main/java/com/machinezoo/fingerprintio/iso19794p2v2005/Iso19794p2v2005Template.java b/src/main/java/com/machinezoo/fingerprintio/iso19794p2v2005/Iso19794p2v2005Template.java
index 0afcbeb..0c4ce31 100644
--- a/src/main/java/com/machinezoo/fingerprintio/iso19794p2v2005/Iso19794p2v2005Template.java
+++ b/src/main/java/com/machinezoo/fingerprintio/iso19794p2v2005/Iso19794p2v2005Template.java
@@ -1,214 +1,214 @@
// Part of FingerprintIO: https://fingerprintio.machinezoo.com
package com.machinezoo.fingerprintio.iso19794p2v2005;
-import static java.util.stream.Collectors.*;
-import java.util.*;
-import com.machinezoo.fingerprintio.*;
-import com.machinezoo.fingerprintio.utils.*;
-import com.machinezoo.noexception.*;
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.toList;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import com.machinezoo.fingerprintio.TemplateFormatException;
+import com.machinezoo.fingerprintio.utils.TemplateReader;
+import com.machinezoo.fingerprintio.utils.TemplateUtils;
+import com.machinezoo.fingerprintio.utils.TemplateWriter;
+import com.machinezoo.fingerprintio.utils.ValidateTemplate;
+import com.machinezoo.noexception.ExceptionHandler;
+import com.machinezoo.noexception.Exceptions;
/**
* ISO/IEC 19794-2:2005 off-card template.
- *
+ *
* @see ISO/IEC 19794-2:2005 Summary
*/
public class Iso19794p2v2005Template {
- private static final byte[] MAGIC = new byte[] { 'F', 'M', 'R', 0, ' ', '2', '0', 0 };
- /**
- * Checks whether provided template is an ISO/IEC 19794-2:2005 off-card template.
- * This method does not do any template validation or conformance checking.
- * It just differentiates off-card ISO/IEC 19794-2:2005 from other template formats
- * as quickly as possible, mostly by looking at template header.
- *
- * @param template
- * serialized template that is to be evaluated
- * @return {@code true} if {@code template} is an ISO/IEC 19794-2:2005 off-card template, {@code false} otherwise
- */
- public static boolean accepts(byte[] template) {
- if (template.length < MAGIC.length + 4)
- return false;
- if (!Arrays.equals(MAGIC, Arrays.copyOf(template, MAGIC.length)))
- return false;
- TemplateReader in = new TemplateReader(template);
- in.skipBytes(MAGIC.length);
- /*
- * Differentiate from ANSI 378 by examining the length field.
- */
- int bytes01 = in.readUnsignedShort();
- if (bytes01 >= 26) {
- /*
- * Too big for ISO 19794-2. Must be ANSI 378-2004 with 2-byte length field.
- */
- return false;
- } else if (bytes01 > 0) {
- /*
- * Invalid length field for ANSI 378. Must be ISO 19794-2.
- */
- return true;
- } else {
- int bytes23 = in.readUnsignedShort();
- if (bytes23 >= 24) {
- /*
- * Too big for ANSI 378. Must be ISO 19794-2.
- */
- return true;
- } else {
- /*
- * It's ANSI 378-2004 with 6-byte length field.
- */
- return false;
- }
- }
- }
- /**
- * Indicates that the fingerprint reader has certificate of compliance with Annex B of ISO 19794-2 spec,
- * which is a copy of Appendix F of CJIS-RS-0010 V7.
- * This is the top bit of DEVSTAMP field.
- */
- public boolean sensorCertified;
- /**
- * Sensor ID (DEVID).
- */
- public int sensorId;
- /**
- * Image width (WIDTH).
- */
- public int width;
- /**
- * Image height (HEIGHT).
- */
- public int height;
- /**
- * Horizontal pixel density (RESOLUTIONX).
- * Defaults to 197 (500dpi).
- */
- public int resolutionX = 197;
- /**
- * Vertical pixel density (RESOLUTIONY).
- * Defaults to 197 (500dpi).
- */
- public int resolutionY = 197;
- /**
- * List of fingerprints (FINGERPRINT).
- */
- public List
- * Recoverable validation exceptions encountered during parsing will be fed to the provided exception handler.
- * Pass in {@link Exceptions#silence()} to ignore all recoverable validation errors
- * or {@link Exceptions#propagate()} to throw exception even for recoverable errors.
- *
- * @param template
- * serialized template in ISO/IEC 19794-2:2005 off-card format
- * @param handler
- * handler for recoverable validation exceptions
- * @throws TemplateFormatException
- * if unrecoverable validation error is encountered or the provided exception handler returns {@code false}
- */
- public Iso19794p2v2005Template(byte[] template, ExceptionHandler handler) {
- if (!accepts(template))
- throw new TemplateFormatException("This is not an ISO/IEC 19794-2:2005 off-card template.");
- TemplateUtils.decodeTemplate(template, in -> {
- in.skipBytes(MAGIC.length);
- long length = 0xffff_ffffL & in.readInt();
- ValidateTemplate.condition(length >= 24, "Total length must be at least 24 bytes.");
- ValidateTemplate.condition(length <= MAGIC.length + 4 + in.available(), handler, "Total length indicates trimmed template.");
- sensorId = in.readUnsignedShort();
- sensorCertified = (sensorId & 0x8000) != 0;
- ValidateTemplate.condition((sensorId & 0x7000) == 0, handler, "Unrecognized sensor compliance bits.");
- sensorId &= 0xfff;
- width = in.readUnsignedShort();
- height = in.readUnsignedShort();
- resolutionX = in.readUnsignedShort();
- resolutionY = in.readUnsignedShort();
- int count = in.readUnsignedByte();
- in.skipBytes(1);
- for (int i = 0; i < count; ++i)
- fingerprints.add(new Iso19794p2v2005Fingerprint(in, width, height, handler));
- ValidateTemplate.condition(in.available() == 0, handler, "Extra data at the end of the template.");
- ValidateTemplate.structure(this::validate, handler);
- });
- }
- /**
- * Validates and serializes the template in ISO/IEC 19794-2:2005 off-card format.
- *
- * @return serialized template in ISO/IEC 19794-2:2005 off-card format
- * @throws TemplateFormatException
- * if the template fails validation
- */
- public byte[] toByteArray() {
- validate();
- TemplateWriter out = new TemplateWriter();
- out.write(MAGIC);
- out.writeInt(measure());
- out.writeShort((sensorCertified ? 0x8000 : 0) | sensorId);
- out.writeShort(width);
- out.writeShort(height);
- out.writeShort(resolutionX);
- out.writeShort(resolutionY);
- out.writeByte(fingerprints.size());
- out.writeByte(0);
- for (Iso19794p2v2005Fingerprint fp : fingerprints)
- fp.write(out);
- return out.toByteArray();
- }
- private int measure() {
- return 24 + fingerprints.stream().mapToInt(Iso19794p2v2005Fingerprint::measure).sum();
- }
- private void validate() {
- ValidateTemplate.range(sensorId, 0, 0xfff, "Sensor ID must be an unsigned 12-bit number.");
- ValidateTemplate.nonzero16(width, "Image width must be a non-zero unsigned 16-bit number.");
- ValidateTemplate.nonzero16(height, "Image height must be a non-zero unsigned 16-bit number.");
- ValidateTemplate.nonzero16(resolutionX, "Horizontal pixel density must be a non-zero unsigned 16-bit number.");
- ValidateTemplate.condition(resolutionX >= 99, "Horizontal pixel density must be at least 99 (DPI 250+).");
- ValidateTemplate.nonzero16(resolutionY, "Vertical pixel density must be a non-zero unsigned 16-bit number.");
- ValidateTemplate.condition(resolutionY >= 99, "Vertical pixel density must be at least 99 (DPI 250+).");
- ValidateTemplate.range(fingerprints.size(), 0, 176, "There cannot be more than 176 fingerprints.");
- for (Iso19794p2v2005Fingerprint fp : fingerprints)
- fp.validate(width, height);
- if (fingerprints.size() != fingerprints.stream().mapToInt(fp -> (fp.position.ordinal() << 16) + fp.view).distinct().count())
- throw new TemplateFormatException("Every fingerprint must have a unique combination of finger position and view offset.");
- fingerprints.stream()
- .collect(groupingBy(fp -> fp.position))
- .values().stream()
- .forEach(l -> {
- for (int i = 0; i < l.size(); ++i) {
- ValidateTemplate.range(l.get(i).view, 0, l.size() - 1, "Fingerprint view numbers must be assigned contiguously, starting from zero.");
- if (!l.equals(l.stream().sorted(Comparator.comparingInt(fp -> fp.view)).collect(toList())))
- throw new TemplateFormatException("Fingerprints with the same finger position must be sorted by view number.");
- }
- });
- }
+ private static final byte[] MAGIC = new byte[] { 'F', 'M', 'R', 0, ' ', '2', '0', 0 };
+ /**
+ * Checks whether provided template is an ISO/IEC 19794-2:2005 off-card template.
+ * This method does not do any template validation or conformance checking.
+ * It just differentiates off-card ISO/IEC 19794-2:2005 from other template formats
+ * as quickly as possible, mostly by looking at template header.
+ *
+ * @param template
+ * serialized template that is to be evaluated
+ * @return {@code true} if {@code template} is an ISO/IEC 19794-2:2005 off-card template, {@code false} otherwise
+ */
+ public static boolean accepts(byte[] template) {
+ if (template.length < MAGIC.length + 4)
+ return false;
+ if (!Arrays.equals(MAGIC, Arrays.copyOf(template, MAGIC.length)))
+ return false;
+ TemplateReader in = new TemplateReader(template);
+ in.skipBytes(MAGIC.length);
+ /*
+ * Differentiate from ANSI 378 by examining the length field.
+ */
+ int bytes01 = in.readUnsignedShort();
+ if (bytes01 >= 26) {
+ /*
+ * Too big for ISO 19794-2. Must be ANSI 378-2004 with 2-byte length field.
+ */
+ return false;
+ } else if (bytes01 > 0) {
+ /*
+ * Invalid length field for ANSI 378. Must be ISO 19794-2.
+ */
+ return true;
+ } else {
+ int bytes23 = in.readUnsignedShort();
+ if (bytes23 >= 24) {
+ /*
+ * Too big for ANSI 378. Must be ISO 19794-2.
+ */
+ return true;
+ } else {
+ /*
+ * It's ANSI 378-2004 with 6-byte length field.
+ */
+ return false;
+ }
+ }
+ }
+ /**
+ * Indicates that the fingerprint reader has certificate of compliance with Annex B of ISO 19794-2 spec,
+ * which is a copy of Appendix F of CJIS-RS-0010 V7.
+ * This is the top bit of DEVSTAMP field.
+ */
+ public boolean sensorCertified;
+ /**
+ * Sensor ID (DEVID).
+ */
+ public int sensorId;
+ /**
+ * Image width (WIDTH).
+ */
+ public int width;
+ /**
+ * Image height (HEIGHT).
+ */
+ public int height;
+ /**
+ * Horizontal pixel density (RESOLUTIONX).
+ * Defaults to 197 (500dpi).
+ */
+ public int resolutionX = 197;
+ /**
+ * Vertical pixel density (RESOLUTIONY).
+ * Defaults to 197 (500dpi).
+ */
+ public int resolutionY = 197;
+ /**
+ * List of fingerprints (FINGERPRINT).
+ */
+ public List
+ * Recoverable validation exceptions encountered during parsing will be fed to the provided exception handler.
+ * Pass in {@link Exceptions#silence()} to ignore all recoverable validation errors
+ * or {@link Exceptions#propagate()} to throw exception even for recoverable errors.
+ *
+ * @param template
+ * serialized template in ISO/IEC 19794-2:2005 off-card format
+ * @param handler
+ * handler for recoverable validation exceptions
+ * @throws TemplateFormatException
+ * if unrecoverable validation error is encountered or the provided exception handler returns {@code false}
+ */
+ public Iso19794p2v2005Template(byte[] template, ExceptionHandler handler) {
+ if (!accepts(template))
+ throw new TemplateFormatException("This is not an ISO/IEC 19794-2:2005 off-card template.");
+ TemplateUtils.decodeTemplate(template, in -> {
+ in.skipBytes(MAGIC.length);
+ long length = 0xffff_ffffL & in.readInt();
+ ValidateTemplate.condition(length >= 24, handler, "Total length must be at least 24 bytes.");
+ ValidateTemplate.condition(length <= MAGIC.length + 4 + in.available(), handler, "Total length indicates trimmed template.");
+ sensorId = in.readUnsignedShort();
+ sensorCertified = (sensorId & 0x8000) != 0;
+ ValidateTemplate.condition((sensorId & 0x7000) == 0, handler, "Unrecognized sensor compliance bits.");
+ sensorId &= 0xfff;
+ width = in.readUnsignedShort();
+ height = in.readUnsignedShort();
+ resolutionX = in.readUnsignedShort();
+ resolutionY = in.readUnsignedShort();
+ int count = in.readUnsignedByte();
+ in.skipBytes(1);
+ for (int i = 0; i < count; ++i)
+ fingerprints.add(new Iso19794p2v2005Fingerprint(in, width, height, handler));
+ ValidateTemplate.condition(in.available() == 0, handler, "Extra data at the end of the template.");
+ ValidateTemplate.structure(this::validate, handler);
+ });
+ }
+ /**
+ * Validates and serializes the template in ISO/IEC 19794-2:2005 off-card format.
+ *
+ * @return serialized template in ISO/IEC 19794-2:2005 off-card format
+ * @throws TemplateFormatException
+ * if the template fails validation
+ */
+ public byte[] toByteArray() {
+ validate();
+ TemplateWriter out = new TemplateWriter();
+ out.write(MAGIC);
+ out.writeInt(measure());
+ out.writeShort((sensorCertified ? 0x8000 : 0) | sensorId);
+ out.writeShort(width);
+ out.writeShort(height);
+ out.writeShort(resolutionX);
+ out.writeShort(resolutionY);
+ out.writeByte(fingerprints.size());
+ out.writeByte(0);
+ for (Iso19794p2v2005Fingerprint fp : fingerprints)
+ fp.write(out);
+ return out.toByteArray();
+ }
+ private int measure() { return 24 + fingerprints.stream().mapToInt(Iso19794p2v2005Fingerprint::measure).sum(); }
+ private void validate() {
+ ValidateTemplate.range(sensorId, 0, 0xfff, "Sensor ID must be an unsigned 12-bit number.");
+ ValidateTemplate.nonzero16(width, "Image width must be a non-zero unsigned 16-bit number.");
+ ValidateTemplate.nonzero16(height, "Image height must be a non-zero unsigned 16-bit number.");
+ ValidateTemplate.nonzero16(resolutionX, "Horizontal pixel density must be a non-zero unsigned 16-bit number.");
+ ValidateTemplate.condition(resolutionX >= 99, "Horizontal pixel density must be at least 99 (DPI 250+).");
+ ValidateTemplate.nonzero16(resolutionY, "Vertical pixel density must be a non-zero unsigned 16-bit number.");
+ ValidateTemplate.condition(resolutionY >= 99, "Vertical pixel density must be at least 99 (DPI 250+).");
+ ValidateTemplate.range(fingerprints.size(), 0, 176, "There cannot be more than 176 fingerprints.");
+ for (Iso19794p2v2005Fingerprint fp : fingerprints)
+ fp.validate(width, height);
+ if (fingerprints.size() != fingerprints.stream().mapToInt(fp -> (fp.position.ordinal() << 16) + fp.view).distinct().count())
+ throw new TemplateFormatException("Every fingerprint must have a unique combination of finger position and view offset.");
+ fingerprints.stream()
+ .collect(groupingBy(fp -> fp.position))
+ .values().stream()
+ .forEach(l -> {
+ for (int i = 0; i < l.size(); ++i) {
+ ValidateTemplate.range(l.get(i).view, 0, l.size() - 1, "Fingerprint view numbers must be assigned contiguously, starting from zero.");
+ if (!l.equals(l.stream().sorted(Comparator.comparingInt(fp -> fp.view)).collect(toList())))
+ throw new TemplateFormatException("Fingerprints with the same finger position must be sorted by view number.");
+ }
+ });
+ }
}