diff --git a/jpos/src/main/java/org/jpos/iso/PosCapability.java b/jpos/src/main/java/org/jpos/iso/PosCapability.java new file mode 100644 index 0000000000..1308394005 --- /dev/null +++ b/jpos/src/main/java/org/jpos/iso/PosCapability.java @@ -0,0 +1,205 @@ +/* + * jPOS Project [http://jpos.org] + * Copyright (C) 2000-2022 jPOS Software SRL + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.jpos.iso; + +import java.io.PrintStream; + +import org.jpos.util.Loggeable; + +@SuppressWarnings("unused") +public class PosCapability extends PosFlags implements Loggeable { + public enum ReadingCapability implements Flag { + UNKNOWN (1, "Unknown"), + CONTACTLESS (1 << 1, "Information not taken from card"), // i.e.: RFID + PHYSICAL (1 << 2, "Physical entry"), // i.e.: Manual Entry or OCR + BARCODE (1 << 3, "Bar code"), + MAGNETIC_STRIPE (1 << 4, "Magnetic Stripe"), + ICC (1 << 5, "ICC"), + DATA_ON_FILE (1 << 6, "Data on file"), + ICC_FAILED (1 << 11, "ICC read but failed"), + MAGNETIC_STRIPE_FAILED (1 << 12, "Magnetic Stripe read but failed"), + FALLBACK (1 << 13, "Fallback"), + TRACK1_PRESENT (1 << 27, "Track1 data present"), // jCard private field + TRACK2_PRESENT (1 << 28, "Track2 data present"); // jCard private field + + private int val; + private String description; + ReadingCapability (int val, String description) { + this.val = val; + this.description = description; + } + public int intValue() { + return val; + } + public String toString () { + return description; + } + + public static int OFFSET = 0; + @Override + public int getOffset() { + return OFFSET; + } + } + + public enum VerificationCapability implements Flag { + UNKNOWN (1, "Unknown"), + NONE (1 << 1, "None"), + MANUAL_SIGNATURE (1 << 2, "Manual signature"), + ONLINE_PIN (1 << 3, "Online PIN"), + OFFLINE_PIN_IN_CLEAR (1 << 4, "Offline PIN in clear"), + OFFLINE_PIN_ENCRYPTED (1 << 5, "Offline PIN encrypted"), + OFFLINE_DIGITIZED_SIGNATURE_ANALYSIS (1 << 6, "Offline digitized signature analysis"), + OFFLINE_BIOMETRICS (1 << 7, "Offline biometrics"), + OFFLINE_MANUAL_VERIFICATION (1 << 8, "Offline manual verification"), + OFFLINE_BIOGRAPHICS (1 << 9, "Offline biographics"), + ACCOUNT_BASED_DIGITAL_SIGNATURE (1 << 10, "Account based digital signature"), + PUBLIC_KEY_BASED_DIGITAL_SIGNATURE (1 << 11, "Public key based digital signature"); + + private int val; + private String description; + VerificationCapability (int val, String description) { + this.val = val; + this.description = description; + } + public int intValue() { + return val; + } + public String toString () { + return description; + } + + public static int OFFSET = 0; + @Override + public int getOffset() { + return OFFSET; + } + } + + private byte[] b = new byte[8]; + + public PosCapability() {} + + public PosCapability ( + int readingCapability, + int verificationCapability) + { + super(); + + b[0] = (byte) readingCapability; + b[1] = (byte) (readingCapability >>> 8); + b[2] = (byte) (readingCapability >>> 16); + b[3] = (byte) (readingCapability >>> 24); + + b[4] = (byte) verificationCapability; + b[5] = (byte) (verificationCapability >>> 8); + b[6] = (byte) (verificationCapability >>> 16); + b[7] = (byte) (verificationCapability >>> 24); + } + + private PosCapability (byte[] b) { + if (b != null) { + // will always use our own internal copy of array + int copyLen= Math.min(b.length, 16); + System.arraycopy(b, 0, this.b, 0, copyLen); + } + } + + public boolean hasReadingCapability (int readingMethods) { + int i = b[3] << 24 | b[2] << 16 & 0xFF0000 | b[1] << 8 & 0xFF00 | b[0] & 0xFF ; + return (i & readingMethods) == readingMethods; + } + public boolean hasReadingCapability (ReadingCapability method) { + return hasReadingCapability (method.intValue()); + } + public boolean hasVerificationCapability (int verificationMethods) { + int i = b[7] << 24 | b[6] << 16 & 0xFF0000 | b[5] << 8 & 0xFF00 | b[4] & 0xFF; + return (i & verificationMethods) == verificationMethods; + } + public boolean hasVerificationCapability (VerificationCapability method) { + return hasVerificationCapability(method.intValue()); + } + public byte[] getBytes() { + return b; + } + public boolean canEMV() { + return hasReadingCapability(ReadingCapability.ICC) || hasReadingCapability(ReadingCapability.CONTACTLESS); + } + public boolean canManualEntry() { + return hasReadingCapability(ReadingCapability.PHYSICAL); + } + public boolean isSwiped() { + return hasReadingCapability(ReadingCapability.MAGNETIC_STRIPE); + } + public String toString() { + return super.toString() + "[" + ISOUtil.hexString (getBytes())+ "]"; + } + + public static PosCapability valueOf (byte[] b) { + return new PosCapability(b); // we create new objects for now, but may return cached instances in the future + } + + public void dump(PrintStream p, String indent) { + String inner = indent + " "; + StringBuilder sb = new StringBuilder(); + p.printf("%s%n", indent, ISOUtil.hexString(getBytes())); + for (ReadingCapability m : ReadingCapability.values()) { + if (hasReadingCapability(m)) { + if (sb.length() > 0) + sb.append(','); + sb.append(m.name()); + } + } + p.printf ("%src: %s%n", inner, sb); + sb = new StringBuilder(); + for (VerificationCapability m : VerificationCapability.values()) { + if (hasVerificationCapability(m)) { + if (sb.length() > 0) + sb.append(','); + sb.append(m.name()); + } + } + p.printf ("%svc: %s%n", inner, sb); + p.println(""); + } + + public void setReadingCapabilities(boolean value, ReadingCapability ... capabilities ){ + setFlags(value, capabilities); + } + + public void unsetReadingCapabilities(ReadingCapability ... capabilities) { + setReadingCapabilities(false, capabilities); + } + + public void setReadingCapabilities(ReadingCapability ... capabilities) { + setReadingCapabilities(true, capabilities); + } + + public void setVerificationCapabilities(boolean value, VerificationCapability ... capabilities){ + setFlags(value, capabilities); + } + + public void unsetVerificationCapabilities(VerificationCapability ... capabilities) { + setVerificationCapabilities(false, capabilities); + } + + public void setVerificationCapabilities(VerificationCapability ... capabilities) { + setVerificationCapabilities(true, capabilities); + } +} diff --git a/jpos/src/main/java/org/jpos/iso/PosDataCode.java b/jpos/src/main/java/org/jpos/iso/PosDataCode.java index 9dc934b9cf..c48637cc35 100644 --- a/jpos/src/main/java/org/jpos/iso/PosDataCode.java +++ b/jpos/src/main/java/org/jpos/iso/PosDataCode.java @@ -23,12 +23,7 @@ import org.jpos.util.Loggeable; @SuppressWarnings("unused") -public class PosDataCode implements Loggeable { - - public interface Flag { - int getOffset(); - int intValue(); - } +public class PosDataCode extends PosFlags implements Loggeable { public enum ReadingMethod implements Flag { UNKNOWN (1, "Unknown"), CONTACTLESS (1 << 1, "Information not taken from card"), // i.e.: RFID @@ -310,30 +305,7 @@ public void dump(PrintStream p, String indent) { p.printf ("%ssc: %s%n", inner, sb.toString()); p.println(""); } - - - /** - * Sets or unsets a set of flags according to value - * @param value if true flags are set, else unset - * @param flags flag set to set or unset - */ - public void setFlags(boolean value, Flag... flags) { - if (value) { - for (Flag flag : flags) { - for (int v = flag.intValue(), offset = flag.getOffset(); v != 0; v >>>= 8, offset++) { - b[offset] |= (byte) v; - } - } - } else { - for (Flag flag : flags) { - for (int v = flag.intValue(), offset = flag.getOffset(); v != 0; v >>>= 8, offset++) { - b[offset] &= (byte) ~v; - } - } - } - - } - + public void setReadingMethods(boolean value, ReadingMethod ... methods ){ setFlags(value, methods); } diff --git a/jpos/src/main/java/org/jpos/iso/PosFlags.java b/jpos/src/main/java/org/jpos/iso/PosFlags.java new file mode 100644 index 0000000000..9f0ddfa005 --- /dev/null +++ b/jpos/src/main/java/org/jpos/iso/PosFlags.java @@ -0,0 +1,57 @@ +/* + * jPOS Project [http://jpos.org] + * Copyright (C) 2000-2022 jPOS Software SRL + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.jpos.iso; + +@SuppressWarnings("unused") +public abstract class PosFlags { + + public interface Flag { + int getOffset(); + int intValue(); + } + + /** + * Sets or unsets a set of flags according to value + * @param value if true flags are set, else unset + * @param flags flag set to set or unset + */ + protected void setFlags(boolean value, Flag... flags) { + byte[] b = getBytes(); + if (value) { + for (Flag flag : flags) { + for (int v = flag.intValue(), offset = flag.getOffset(); v != 0; v >>>= 8, offset++) { + if (offset < b.length) + b[offset] |= (byte) v; + } + } + } else { + for (Flag flag : flags) { + for (int v = flag.intValue(), offset = flag.getOffset(); v != 0; v >>>= 8, offset++) { + if (offset < b.length) + b[offset] &= (byte) ~v; + } + } + } + } + public abstract byte[] getBytes(); + + public String toString() { + return super.toString() + "[" + ISOUtil.hexString (getBytes())+ "]"; + } +} diff --git a/jpos/src/main/resources/packager/cmf.xml b/jpos/src/main/resources/packager/cmf.xml index 5f723ee6ac..589af2aac5 100644 --- a/jpos/src/main/resources/packager/cmf.xml +++ b/jpos/src/main/resources/packager/cmf.xml @@ -190,12 +190,67 @@ name="Merchant category code" pad="false" class="org.jpos.iso.IFB_NUMERIC"/> - + + + + + + + + + + + . + */ + +package org.jpos.iso; + +import static org.junit.jupiter.api.Assertions.*; + +import org.jpos.iso.packager.GenericPackager; +import org.junit.jupiter.api.Test; + +public class PosCapabilityTest { + private byte[] POSCAP = + ISOUtil.hex2byte( + "FFFFFFFF" // Reading Capabilities + +"FFFFFFFF" // Verification Capabilities + +ISOUtil.hexString( + ( + "6" // Approval Code Length + +"040" // Cardholder receipt data length + +"080" // Card acceptor receipt data length + +"016" // Cardholder display data length + +"032" // Card acceptor display data length + +"003" // ICC scripts data length + +"N" // Magnetic stripe track 3 rewrite capability + +"Y" // Card capture capability + +"6" // PIN Input length + ).getBytes() + ) + ); + + @Test + public void testPosCapabiiltyCreation() throws ISOException { + ISOPackager p = new GenericPackager("jar:packager/cmf.xml"); + ISOMsg m = new ISOMsg("2800"); + m.set("27.0", ISOUtil.hex2byte("FFFFFFFFFFFFFFFF")); + m.set("27.1", "6"); + m.set("27.2", "040"); + m.set("27.3", "080"); + m.set("27.4", "016"); + m.set("27.5", "032"); + m.set("27.6", "003"); + m.set("27.7", "N"); + m.set("27.8", "Y"); + m.set("27.9", "6"); + m.setPackager(p); + assertEquals(ISOUtil.hexString(POSCAP), ISOUtil.hexString(m.pack()).substring(20)); + PosCapability pc = PosCapability.valueOf(m.getBytes("27.0")); + for (PosCapability.ReadingCapability rc : PosCapability.ReadingCapability.values()) { + assertTrue (pc.hasReadingCapability(rc)); + } + for (PosCapability.VerificationCapability vc : PosCapability.VerificationCapability.values()) { + assertTrue (pc.hasVerificationCapability(vc)); + } + pc = PosCapability.valueOf(ISOUtil.hex2byte("0000000000000000")); + for (PosCapability.ReadingCapability rc : PosCapability.ReadingCapability.values()) { + assertFalse (pc.hasReadingCapability(rc)); + } + for (PosCapability.VerificationCapability vc : PosCapability.VerificationCapability.values()) { + assertFalse (pc.hasVerificationCapability(vc)); + } + } +}