diff --git a/src/main/java/com/machinezoo/fingerprintio/ansi378v2004/Ansi378v2004Template.java b/src/main/java/com/machinezoo/fingerprintio/ansi378v2004/Ansi378v2004Template.java index 0d25342..153357a 100644 --- a/src/main/java/com/machinezoo/fingerprintio/ansi378v2004/Ansi378v2004Template.java +++ b/src/main/java/com/machinezoo/fingerprintio/ansi378v2004/Ansi378v2004Template.java @@ -1,246 +1,249 @@ // Part of FingerprintIO: https://fingerprintio.machinezoo.com package com.machinezoo.fingerprintio.ansi378v2004; -import static java.util.stream.Collectors.*; -import java.util.*; -import com.machinezoo.fingerprintio.*; -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.common.IbiaOrganizations; +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; /** * ANSI INCITS 378-2004 template. - * + * * @see ANSI INCITS 378-2004 Summary */ public class Ansi378v2004Template { - 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 fingerprints = new ArrayList<>(); - /** - * Creates new ANSI INCITS 378-2004 template. - */ - public Ansi378v2004Template() { - } - /** - * Parses and validates ANSI INCITS 378-2004 template. - * - * @param template - * serialized template in ANSI INCITS 378-2004 format - * @throws TemplateFormatException - * if the template cannot be parsed or it fails validation - */ - public Ansi378v2004Template(byte[] template) { - this(template, Exceptions.propagate()); - } - /** - * Parses and optionally validates ANSI INCITS 378-2004 template. - * - * @param template - * serialized template in ANSI INCITS 378-2004 format - * @param strict - * {@code true} to validate the template, {@code false} to tolerate parsing errors as much as possible - * @throws TemplateFormatException - * if the template cannot be parsed or if {@code strict} is {@code true} and the template fails validation - * @deprecated Use {@link #Ansi378v2004Template(byte[], ExceptionHandler)} instead. - */ - @Deprecated - public Ansi378v2004Template(byte[] template, boolean strict) { - this(template, strict ? Exceptions.propagate() : Exceptions.silence()); - } - /** - * Parses and optionally validates ANSI INCITS 378-2004 template. - *

- * 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 fingerprints = new ArrayList<>(); + /** + * Creates new ANSI INCITS 378-2004 template. + */ + public Ansi378v2004Template() {} + /** + * Parses and validates ANSI INCITS 378-2004 template. + * + * @param template + * serialized template in ANSI INCITS 378-2004 format + * @throws TemplateFormatException + * if the template cannot be parsed or it fails validation + */ + public Ansi378v2004Template(byte[] template) { this(template, Exceptions.propagate()); } + /** + * Parses and optionally validates ANSI INCITS 378-2004 template. + * + * @param template + * serialized template in ANSI INCITS 378-2004 format + * @param strict + * {@code true} to validate the template, {@code false} to tolerate parsing errors as much as possible + * @throws TemplateFormatException + * if the template cannot be parsed or if {@code strict} is {@code true} and the template fails validation + * @deprecated Use {@link #Ansi378v2004Template(byte[], ExceptionHandler)} instead. + */ + @Deprecated public Ansi378v2004Template(byte[] template, boolean strict) { this(template, strict ? Exceptions.propagate() : Exceptions.silence()); } + /** + * Parses and optionally validates ANSI INCITS 378-2004 template. + *

+ * 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 fingerprints = new ArrayList<>(); - /** - * Creates new ANSI INCITS 378-2009 template. - */ - public Ansi378v2009Template() { - } - /** - * Parses and validates ANSI INCITS 378-2009 template. - * - * @param template - * serialized template in ANSI INCITS 378-2009 format - * @throws TemplateFormatException - * if the template cannot be parsed or it fails validation - */ - public Ansi378v2009Template(byte[] template) { - this(template, Exceptions.propagate()); - } - /** - * Parses and optionally validates ANSI INCITS 378-2009 template. - * - * @param template - * serialized template in ANSI INCITS 378-2009 format - * @param strict - * {@code true} to validate the template, {@code false} to tolerate parsing errors as much as possible - * @throws TemplateFormatException - * if the template cannot be parsed or if {@code strict} is {@code true} and the template fails validation - * @deprecated Use {@link #Ansi378v2009Template(byte[], ExceptionHandler)} instead. - */ - @Deprecated - public Ansi378v2009Template(byte[] template, boolean strict) { - this(template, strict ? Exceptions.propagate() : Exceptions.silence()); - } - /** - * Parses and optionally validates ANSI INCITS 378-2009 template. - *

- * 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 fingerprints = new ArrayList<>(); + /** + * Creates new ANSI INCITS 378-2009 template. + */ + public Ansi378v2009Template() {} + /** + * Parses and validates ANSI INCITS 378-2009 template. + * + * @param template + * serialized template in ANSI INCITS 378-2009 format + * @throws TemplateFormatException + * if the template cannot be parsed or it fails validation + */ + public Ansi378v2009Template(byte[] template) { this(template, Exceptions.propagate()); } + /** + * Parses and optionally validates ANSI INCITS 378-2009 template. + * + * @param template + * serialized template in ANSI INCITS 378-2009 format + * @param strict + * {@code true} to validate the template, {@code false} to tolerate parsing errors as much as possible + * @throws TemplateFormatException + * if the template cannot be parsed or if {@code strict} is {@code true} and the template fails validation + * @deprecated Use {@link #Ansi378v2009Template(byte[], ExceptionHandler)} instead. + */ + @Deprecated public Ansi378v2009Template(byte[] template, boolean strict) { this(template, strict ? Exceptions.propagate() : Exceptions.silence()); } + /** + * Parses and optionally validates ANSI INCITS 378-2009 template. + *

+ * 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 fingerprints = new ArrayList<>(); - /** - * Creates new ANSI INCITS 378-2009/AM 1 template. - */ - public Ansi378v2009Am1Template() { - } - /** - * Parses and validates ANSI INCITS 378-2009/AM 1 template. - * - * @param template - * serialized template in ANSI INCITS 378-2009/AM 1 format - * @throws TemplateFormatException - * if the template cannot be parsed or it fails validation - */ - public Ansi378v2009Am1Template(byte[] template) { - this(template, Exceptions.propagate()); - } - /** - * Parses and optionally validates ANSI INCITS 378-2009/AM 1 template. - * - * @param template - * serialized template in ANSI INCITS 378-2009/AM 1 format - * @param strict - * {@code true} to validate the template, {@code false} to tolerate parsing errors as much as possible - * @throws TemplateFormatException - * if the template cannot be parsed or if {@code strict} is {@code true} and the template fails validation - * @deprecated Use {@link #Ansi378v2009Am1Template(byte[], ExceptionHandler)} instead. - */ - @Deprecated - public Ansi378v2009Am1Template(byte[] template, boolean strict) { - this(template, strict ? Exceptions.propagate() : Exceptions.silence()); - } - /** - * Parses and optionally validates ANSI INCITS 378-2009/AM 1 template. - *

- * 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 fingerprints = new ArrayList<>(); + /** + * Creates new ANSI INCITS 378-2009/AM 1 template. + */ + public Ansi378v2009Am1Template() {} + /** + * Parses and validates ANSI INCITS 378-2009/AM 1 template. + * + * @param template + * serialized template in ANSI INCITS 378-2009/AM 1 format + * @throws TemplateFormatException + * if the template cannot be parsed or it fails validation + */ + public Ansi378v2009Am1Template(byte[] template) { this(template, Exceptions.propagate()); } + /** + * Parses and optionally validates ANSI INCITS 378-2009/AM 1 template. + * + * @param template + * serialized template in ANSI INCITS 378-2009/AM 1 format + * @param strict + * {@code true} to validate the template, {@code false} to tolerate parsing errors as much as possible + * @throws TemplateFormatException + * if the template cannot be parsed or if {@code strict} is {@code true} and the template fails validation + * @deprecated Use {@link #Ansi378v2009Am1Template(byte[], ExceptionHandler)} instead. + */ + @Deprecated public Ansi378v2009Am1Template(byte[] template, boolean strict) { this(template, strict ? Exceptions.propagate() : Exceptions.silence()); } + /** + * Parses and optionally validates ANSI INCITS 378-2009/AM 1 template. + *

+ * 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 fingerprints = new ArrayList<>(); - /** - * Creates new ISO/IEC 19794-2:2005 off-card template. - */ - public Iso19794p2v2005Template() { - } - /** - * Parses and validates ISO/IEC 19794-2:2005 off-card template. - * - * @param template - * serialized template in ISO/IEC 19794-2:2005 off-card format - * @throws TemplateFormatException - * if the template cannot be parsed or it fails validation - */ - public Iso19794p2v2005Template(byte[] template) { - this(template, Exceptions.propagate()); - } - /** - * Parses and optionally validates ISO/IEC 19794-2:2005 off-card template. - * - * @param template - * serialized template in ISO/IEC 19794-2:2005 off-card format - * @param strict - * {@code true} to validate the template, {@code false} to tolerate parsing errors as much as possible - * @throws TemplateFormatException - * if the template cannot be parsed or if {@code strict} is {@code true} and the template fails validation - * @deprecated Use {@link #Iso19794p2v2005Template(byte[], ExceptionHandler)} instead. - */ - @Deprecated - public Iso19794p2v2005Template(byte[] template, boolean strict) { - this(template, strict ? Exceptions.propagate() : Exceptions.silence()); - } - /** - * Parses and optionally validates ISO/IEC 19794-2:2005 off-card template. - *

- * 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 fingerprints = new ArrayList<>(); + /** + * Creates new ISO/IEC 19794-2:2005 off-card template. + */ + public Iso19794p2v2005Template() {} + /** + * Parses and validates ISO/IEC 19794-2:2005 off-card template. + * + * @param template + * serialized template in ISO/IEC 19794-2:2005 off-card format + * @throws TemplateFormatException + * if the template cannot be parsed or it fails validation + */ + public Iso19794p2v2005Template(byte[] template) { this(template, Exceptions.propagate()); } + /** + * Parses and optionally validates ISO/IEC 19794-2:2005 off-card template. + * + * @param template + * serialized template in ISO/IEC 19794-2:2005 off-card format + * @param strict + * {@code true} to validate the template, {@code false} to tolerate parsing errors as much as possible + * @throws TemplateFormatException + * if the template cannot be parsed or if {@code strict} is {@code true} and the template fails validation + * @deprecated Use {@link #Iso19794p2v2005Template(byte[], ExceptionHandler)} instead. + */ + @Deprecated public Iso19794p2v2005Template(byte[] template, boolean strict) { this(template, strict ? Exceptions.propagate() : Exceptions.silence()); } + /** + * Parses and optionally validates ISO/IEC 19794-2:2005 off-card template. + *

+ * 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."); + } + }); + } }