001/*
002 * jPOS Project [http://jpos.org]
003 * Copyright (C) 2000-2026 jPOS Software SRL
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jpos.emv;
020
021import org.jpos.iso.ISOUtil;
022import org.jpos.util.Loggeable;
023
024import java.io.PrintStream;
025import java.util.Objects;
026
027/**
028 * Terminal verification results (TVR) parser.
029 */
030public final class TerminalVerificationResults implements Loggeable {
031    
032    private final byte[] tvr;
033
034    public TerminalVerificationResults(byte[] tvr) {
035        Objects.requireNonNull(tvr);
036        if (tvr.length != 5)
037            throw new IllegalArgumentException("TVR length must be 5.");
038        this.tvr = tvr;
039    }
040
041    public TerminalVerificationResults(String hexTVR) {
042        this(ISOUtil.hex2byte(hexTVR));
043    }
044
045    public boolean offlineDataProcNotPerformed() {
046        return isBitOn(tvr[0], 8);
047    }
048
049    public boolean sdaFailed() {
050        return isBitOn(tvr[0], 7);
051    }
052
053    public boolean iccDataMissing() {
054        return isBitOn(tvr[0], 6);
055    }
056
057    public boolean panInHotlist() {
058        return isBitOn(tvr[0], 5);
059    }
060
061    public boolean ddaFailed() {
062        return isBitOn(tvr[0], 4);
063    }
064
065    public boolean cdaFailed() {
066        return isBitOn(tvr[0], 3);
067    }
068
069    public boolean sdaSelected() {
070        return isBitOn(tvr[0], 2);
071    }
072
073    public boolean rfu() {
074        return !isBitOn(tvr[0], 1) || !isBitOn(tvr[1], 1) ||
075                !isBitOn(tvr[1], 2) || !isBitOn(tvr[1], 3) ||
076                !isBitOn(tvr[2], 2) || !isBitOn(tvr[2], 1) ||
077                !isBitOn(tvr[3], 3) || !isBitOn(tvr[3], 2) ||
078                !isBitOn(tvr[3], 1) ||
079                (isBitOn(tvr[4], 2) && isBitOn(tvr[4], 1));
080    }
081
082    public boolean cardAndTerminalDiffApps() {
083        return isBitOn(tvr[1], 8);
084    }
085
086    public boolean expiredApplication() {
087        return isBitOn(tvr[1], 7);
088    }
089
090    public boolean applicationNotEffective() {
091        return isBitOn(tvr[1], 6);
092    }
093
094    public boolean serviceNotAllowedForCardProduct() {
095        return isBitOn(tvr[1], 5);
096    }
097
098    public boolean newCard() {
099        return isBitOn(tvr[1], 4);
100    }
101
102    public boolean cardholderVerificationNotSuccessful() {
103        return isBitOn(tvr[2], 8);
104    }
105
106    public boolean unrecognisedCVM() {
107        return isBitOn(tvr[2], 7);
108    }
109
110    public boolean pinTryLimitExceeded() {
111        return isBitOn(tvr[2], 6);
112    }
113
114    public boolean pinRequiredButNoPinPadPresent() {
115        return isBitOn(tvr[2], 5);
116    }
117
118    public boolean pinRequiredButNotEntered() {
119        return isBitOn(tvr[2], 4);
120    }
121
122    public boolean onlinePINEntered() {
123        return isBitOn(tvr[2], 3);
124    }
125
126    public boolean transactionExceedsFloorLimit() {
127        return isBitOn(tvr[3], 8);
128    }
129
130    public boolean lowerConsecutiveOfflineLimitExceeded() {
131        return isBitOn(tvr[3], 7);
132    }
133
134    public boolean upperConsecutiveOfflineLimitExceeded() {
135        return isBitOn(tvr[3], 6);
136    }
137
138    public boolean transactionSelectedRandomlyOnlineProcessing() {
139        return isBitOn(tvr[3], 5);
140    }
141
142    public boolean merchantForcedTransactionOnline() {
143        return isBitOn(tvr[3], 4);
144    }
145
146    public boolean defaultTDOLUsed() {
147        return isBitOn(tvr[4], 8);
148    }
149
150    public boolean issuerAuthenticationFailed() {
151        return isBitOn(tvr[4], 7);
152    }
153
154    public boolean scriptFailedBeforeFinalGenerateAC() {
155        return isBitOn(tvr[4], 6);
156    }
157
158    public boolean scriptFailedAfterFinalGenerateAC() {
159        return isBitOn(tvr[4], 5);
160    }
161
162    public boolean relayResistanceThresholdExceeded() {
163        return isBitOn(tvr[4], 4);
164    }
165
166    public boolean relayResistanceTimeLimitsExceeded() {
167        return isBitOn(tvr[4], 3);
168    }
169
170    public boolean relayResistanceProtocolNotSupported() {
171        return !isBitOn(tvr[4], 2) && !isBitOn(tvr[4], 1);
172    }
173
174    public boolean relayResistanceProtocolNotPerformed() {
175        return !isBitOn(tvr[4], 2) && isBitOn(tvr[4], 1);
176    }
177
178    public boolean relayResistanceProtocolPerformed() {
179        return isBitOn(tvr[4], 2) && !isBitOn(tvr[4], 1);
180    }
181
182    private boolean isBitOn(byte value, int position) {
183        return ((value >> (position - 1)) & 1) == 1;
184    }
185
186    @Override
187    public void dump(PrintStream p, String indent) {
188        String inner = indent + "  ";
189        p.printf("%s<terminal-verification-results value='%s'>%n", indent, ISOUtil.hexString(tvr));
190        p.printf("%sByte 1: %s%n", inner, String.format("%8s", Integer.toBinaryString(tvr[0] & 0xFF)).replace(' ', '0'));
191        p.printf("%sByte 2: %s%n", inner, String.format("%8s", Integer.toBinaryString(tvr[1] & 0xFF)).replace(' ', '0'));
192        p.printf("%sByte 3: %s%n", inner, String.format("%8s", Integer.toBinaryString(tvr[2] & 0xFF)).replace(' ', '0'));
193        p.printf("%sByte 4: %s%n", inner, String.format("%8s", Integer.toBinaryString(tvr[3] & 0xFF)).replace(' ', '0'));
194        p.printf("%sByte 5: %s%n", inner, String.format("%8s", Integer.toBinaryString(tvr[4] & 0xFF)).replace(' ', '0'));
195        p.printf("%s</terminal-verification-results>%n", indent);
196    }
197}