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    /** Raw 5-byte TVR value. */
033    private final byte[] tvr;
034
035    /**
036     * Constructs a TerminalVerificationResults from a 5-byte TVR array.
037     *
038     * @param tvr the 5-byte Terminal Verification Results value
039     * @throws IllegalArgumentException if the array length is not 5
040     */
041    public TerminalVerificationResults(byte[] tvr) {
042        Objects.requireNonNull(tvr);
043        if (tvr.length != 5)
044            throw new IllegalArgumentException("TVR length must be 5.");
045        this.tvr = tvr;
046    }
047
048    /**
049     * Constructs a TerminalVerificationResults from a hex-encoded TVR string.
050     *
051     * @param hexTVR 10-character hexadecimal string representing the 5-byte TVR
052     */
053    public TerminalVerificationResults(String hexTVR) {
054        this(ISOUtil.hex2byte(hexTVR));
055    }
056
057    /**
058     * Returns {@code true} if offline data authentication was not performed.
059     *
060     * @return {@code true} if byte 1 bit 8 is set
061     */
062    public boolean offlineDataProcNotPerformed() {
063        return isBitOn(tvr[0], 8);
064    }
065
066    /**
067     * Returns {@code true} if Static Data Authentication (SDA) failed.
068     *
069     * @return {@code true} if byte 1 bit 7 is set
070     */
071    public boolean sdaFailed() {
072        return isBitOn(tvr[0], 7);
073    }
074
075    /**
076     * Returns {@code true} if ICC data is missing.
077     *
078     * @return {@code true} if byte 1 bit 6 is set
079     */
080    public boolean iccDataMissing() {
081        return isBitOn(tvr[0], 6);
082    }
083
084    /**
085     * Returns {@code true} if the PAN appears in the hotlist.
086     *
087     * @return {@code true} if byte 1 bit 5 is set
088     */
089    public boolean panInHotlist() {
090        return isBitOn(tvr[0], 5);
091    }
092
093    /**
094     * Returns {@code true} if Dynamic Data Authentication (DDA) failed.
095     *
096     * @return {@code true} if byte 1 bit 4 is set
097     */
098    public boolean ddaFailed() {
099        return isBitOn(tvr[0], 4);
100    }
101
102    /**
103     * Returns {@code true} if Combined DDA/Application Cryptogram Generation (CDA) failed.
104     *
105     * @return {@code true} if byte 1 bit 3 is set
106     */
107    public boolean cdaFailed() {
108        return isBitOn(tvr[0], 3);
109    }
110
111    /**
112     * Returns {@code true} if SDA was selected.
113     *
114     * @return {@code true} if byte 1 bit 2 is set
115     */
116    public boolean sdaSelected() {
117        return isBitOn(tvr[0], 2);
118    }
119
120    /**
121     * Returns {@code true} if any reserved-for-future-use (RFU) bit is unset as expected.
122     *
123     * @return {@code true} if none of the RFU bits are unexpectedly set
124     */
125    public boolean rfu() {
126        return !isBitOn(tvr[0], 1) || !isBitOn(tvr[1], 1) ||
127                !isBitOn(tvr[1], 2) || !isBitOn(tvr[1], 3) ||
128                !isBitOn(tvr[2], 2) || !isBitOn(tvr[2], 1) ||
129                !isBitOn(tvr[3], 3) || !isBitOn(tvr[3], 2) ||
130                !isBitOn(tvr[3], 1) ||
131                (isBitOn(tvr[4], 2) && isBitOn(tvr[4], 1));
132    }
133
134    /**
135     * Returns {@code true} if the card and terminal have different application versions.
136     *
137     * @return {@code true} if byte 2 bit 8 is set
138     */
139    public boolean cardAndTerminalDiffApps() {
140        return isBitOn(tvr[1], 8);
141    }
142
143    /**
144     * Returns {@code true} if the application has expired.
145     *
146     * @return {@code true} if byte 2 bit 7 is set
147     */
148    public boolean expiredApplication() {
149        return isBitOn(tvr[1], 7);
150    }
151
152    /**
153     * Returns {@code true} if the application is not yet effective.
154     *
155     * @return {@code true} if byte 2 bit 6 is set
156     */
157    public boolean applicationNotEffective() {
158        return isBitOn(tvr[1], 6);
159    }
160
161    /**
162     * Returns {@code true} if the service is not allowed for the card product.
163     *
164     * @return {@code true} if byte 2 bit 5 is set
165     */
166    public boolean serviceNotAllowedForCardProduct() {
167        return isBitOn(tvr[1], 5);
168    }
169
170    /**
171     * Returns {@code true} if this is a new card (first transaction).
172     *
173     * @return {@code true} if byte 2 bit 4 is set
174     */
175    public boolean newCard() {
176        return isBitOn(tvr[1], 4);
177    }
178
179    /**
180     * Returns {@code true} if cardholder verification was not successful.
181     *
182     * @return {@code true} if byte 3 bit 8 is set
183     */
184    public boolean cardholderVerificationNotSuccessful() {
185        return isBitOn(tvr[2], 8);
186    }
187
188    /**
189     * Returns {@code true} if an unrecognised CVM (Cardholder Verification Method) was encountered.
190     *
191     * @return {@code true} if byte 3 bit 7 is set
192     */
193    public boolean unrecognisedCVM() {
194        return isBitOn(tvr[2], 7);
195    }
196
197    /**
198     * Returns {@code true} if the PIN try limit has been exceeded.
199     *
200     * @return {@code true} if byte 3 bit 6 is set
201     */
202    public boolean pinTryLimitExceeded() {
203        return isBitOn(tvr[2], 6);
204    }
205
206    /**
207     * Returns {@code true} if a PIN is required but no PIN pad is present.
208     *
209     * @return {@code true} if byte 3 bit 5 is set
210     */
211    public boolean pinRequiredButNoPinPadPresent() {
212        return isBitOn(tvr[2], 5);
213    }
214
215    /**
216     * Returns {@code true} if a PIN is required but was not entered.
217     *
218     * @return {@code true} if byte 3 bit 4 is set
219     */
220    public boolean pinRequiredButNotEntered() {
221        return isBitOn(tvr[2], 4);
222    }
223
224    /**
225     * Returns {@code true} if an online PIN was entered.
226     *
227     * @return {@code true} if byte 3 bit 3 is set
228     */
229    public boolean onlinePINEntered() {
230        return isBitOn(tvr[2], 3);
231    }
232
233    /**
234     * Returns {@code true} if the transaction amount exceeds the floor limit.
235     *
236     * @return {@code true} if byte 4 bit 8 is set
237     */
238    public boolean transactionExceedsFloorLimit() {
239        return isBitOn(tvr[3], 8);
240    }
241
242    /**
243     * Returns {@code true} if the lower consecutive offline limit was exceeded.
244     *
245     * @return {@code true} if byte 4 bit 7 is set
246     */
247    public boolean lowerConsecutiveOfflineLimitExceeded() {
248        return isBitOn(tvr[3], 7);
249    }
250
251    /**
252     * Returns {@code true} if the upper consecutive offline limit was exceeded.
253     *
254     * @return {@code true} if byte 4 bit 6 is set
255     */
256    public boolean upperConsecutiveOfflineLimitExceeded() {
257        return isBitOn(tvr[3], 6);
258    }
259
260    /**
261     * Returns {@code true} if the transaction was selected randomly for online processing.
262     *
263     * @return {@code true} if byte 4 bit 5 is set
264     */
265    public boolean transactionSelectedRandomlyOnlineProcessing() {
266        return isBitOn(tvr[3], 5);
267    }
268
269    /**
270     * Returns {@code true} if the merchant forced the transaction online.
271     *
272     * @return {@code true} if byte 4 bit 4 is set
273     */
274    public boolean merchantForcedTransactionOnline() {
275        return isBitOn(tvr[3], 4);
276    }
277
278    /**
279     * Returns {@code true} if the default TDOL (Transaction Data Object List) was used.
280     *
281     * @return {@code true} if byte 5 bit 8 is set
282     */
283    public boolean defaultTDOLUsed() {
284        return isBitOn(tvr[4], 8);
285    }
286
287    /**
288     * Returns {@code true} if issuer authentication failed.
289     *
290     * @return {@code true} if byte 5 bit 7 is set
291     */
292    public boolean issuerAuthenticationFailed() {
293        return isBitOn(tvr[4], 7);
294    }
295
296    /**
297     * Returns {@code true} if an issuer script failed before the final Generate AC command.
298     *
299     * @return {@code true} if byte 5 bit 6 is set
300     */
301    public boolean scriptFailedBeforeFinalGenerateAC() {
302        return isBitOn(tvr[4], 6);
303    }
304
305    /**
306     * Returns {@code true} if an issuer script failed after the final Generate AC command.
307     *
308     * @return {@code true} if byte 5 bit 5 is set
309     */
310    public boolean scriptFailedAfterFinalGenerateAC() {
311        return isBitOn(tvr[4], 5);
312    }
313
314    /**
315     * Returns {@code true} if the relay resistance threshold was exceeded.
316     *
317     * @return {@code true} if byte 5 bit 4 is set
318     */
319    public boolean relayResistanceThresholdExceeded() {
320        return isBitOn(tvr[4], 4);
321    }
322
323    /**
324     * Returns {@code true} if the relay resistance time limits were exceeded.
325     *
326     * @return {@code true} if byte 5 bit 3 is set
327     */
328    public boolean relayResistanceTimeLimitsExceeded() {
329        return isBitOn(tvr[4], 3);
330    }
331
332    /**
333     * Returns {@code true} if the relay resistance protocol is not supported.
334     *
335     * @return {@code true} if byte 5 bits 2 and 1 are both clear
336     */
337    public boolean relayResistanceProtocolNotSupported() {
338        return !isBitOn(tvr[4], 2) && !isBitOn(tvr[4], 1);
339    }
340
341    /**
342     * Returns {@code true} if the relay resistance protocol was not performed.
343     *
344     * @return {@code true} if byte 5 bit 2 is clear and bit 1 is set
345     */
346    public boolean relayResistanceProtocolNotPerformed() {
347        return !isBitOn(tvr[4], 2) && isBitOn(tvr[4], 1);
348    }
349
350    /**
351     * Returns {@code true} if the relay resistance protocol was performed.
352     *
353     * @return {@code true} if byte 5 bit 2 is set and bit 1 is clear
354     */
355    public boolean relayResistanceProtocolPerformed() {
356        return isBitOn(tvr[4], 2) && !isBitOn(tvr[4], 1);
357    }
358
359    /**
360     * Tests whether a specific bit position is set in the given byte.
361     *
362     * @param value    the byte to test
363     * @param position bit position (1 = least significant)
364     * @return {@code true} if the bit at the given position is 1
365     */
366    private boolean isBitOn(byte value, int position) {
367        return ((value >> (position - 1)) & 1) == 1;
368    }
369
370    /**
371     * Dumps a human-readable binary representation of the TVR to the given stream.
372     *
373     * @param p      the output stream
374     * @param indent indentation prefix string
375     */
376    @Override
377    public void dump(PrintStream p, String indent) {
378        String inner = indent + "  ";
379        p.printf("%s<terminal-verification-results value='%s'>%n", indent, ISOUtil.hexString(tvr));
380        p.printf("%sByte 1: %s%n", inner, String.format("%8s", Integer.toBinaryString(tvr[0] & 0xFF)).replace(' ', '0'));
381        p.printf("%sByte 2: %s%n", inner, String.format("%8s", Integer.toBinaryString(tvr[1] & 0xFF)).replace(' ', '0'));
382        p.printf("%sByte 3: %s%n", inner, String.format("%8s", Integer.toBinaryString(tvr[2] & 0xFF)).replace(' ', '0'));
383        p.printf("%sByte 4: %s%n", inner, String.format("%8s", Integer.toBinaryString(tvr[3] & 0xFF)).replace(' ', '0'));
384        p.printf("%sByte 5: %s%n", inner, String.format("%8s", Integer.toBinaryString(tvr[4] & 0xFF)).replace(' ', '0'));
385        p.printf("%s</terminal-verification-results>%n", indent);
386    }
387}