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 java.io.PrintStream;
022import java.util.Objects;
023
024import org.jpos.iso.ISOUtil;
025import org.jpos.util.Loggeable;
026
027/**
028 * CVR parser based on A.19 CVR, M/Chip 4 Issuer Guide to Debit and Credit Parameter Management, December 2004
029 */
030public class CVRMastercard implements Loggeable {
031
032    private final byte[] cvr;
033
034    /**
035     * Creates a CVRMastercard from raw bytes.
036     * @param cvr byte array containing the CVR value (must be 6 bytes)
037     */
038    public CVRMastercard(byte[] cvr) {
039        Objects.requireNonNull("cvr", "CVR cannot be null.");
040        if (cvr.length != 6)
041            throw new IllegalArgumentException(
042                    String.format("Invalid length. Expected = 6, actual = %s.", cvr.length));
043        this.cvr = cvr;
044    }
045
046    /**
047     * Creates a CVRMastercard from a hex string.
048     * @param cvr hexadecimal string representation (must be 12 chars)
049     */
050    public CVRMastercard(String cvr) {
051        Objects.requireNonNull("cvr", "CVR cannot be null.");
052        cvr = cvr.trim();
053        if (cvr.length() != 12)
054            throw new IllegalArgumentException(
055                    String.format("Invalid length. Expected = 12, actual = %s.", cvr.length()));
056        this.cvr = ISOUtil.hex2byte(cvr);
057    }
058
059    /**
060     * Returns true if aac returned in second generate a c.
061     * @return true if condition applies
062     */
063    public boolean aacReturnedInSecondGenerateAC() {
064        return !isBitOn(cvr[0], 8) && !isBitOn(cvr[0], 7);
065    }
066
067    /**
068     * Returns true if aac returned in first generate a c.
069     * @return true if condition applies
070     */
071    public boolean aacReturnedInFirstGenerateAC() {
072        return !isBitOn(cvr[0], 6) && !isBitOn(cvr[0], 5);
073    }
074
075    /**
076     * Returns true if tc returned in second generate a c.
077     * @return true if condition applies
078     */
079    public boolean tcReturnedInSecondGenerateAC() {
080        return !isBitOn(cvr[0], 8) && isBitOn(cvr[0], 7);
081    }
082
083    /**
084     * Returns true if arqc returned in first generate a c.
085     * @return true if condition applies
086     */
087    public boolean arqcReturnedInFirstGenerateAC() {
088        return isBitOn(cvr[0], 6) && !isBitOn(cvr[0], 5);
089    }
090
091    /**
092     * Returns true if tc returned in first generate a c.
093     * @return true if condition applies
094     */
095    public boolean tcReturnedInFirstGenerateAC() {
096        return !isBitOn(cvr[0], 6) && isBitOn(cvr[0], 5);
097    }
098        
099    /**
100     * Returns true if offline p i n verification performed.
101     * @return true if condition applies
102     */
103    public boolean offlinePINVerificationPerformed() {
104        return isBitOn(cvr[0], 3);
105    }
106
107    /**
108     * Returns true if offline p i n verification not performed.
109     * @return true if condition applies
110     */
111    public boolean offlinePINVerificationNotPerformed() {
112        return isBitOn(cvr[3], 6);
113    }    
114    
115    /**
116     * Returns true if dda returned.
117     * @return true if condition applies
118     */
119    public boolean ddaReturned() {
120        return isBitOn(cvr[1], 8);
121    }
122
123    /**
124     * Returns true if combined d d a a c generation returned in first generate a c.
125     * @return true if condition applies
126     */
127    public boolean combinedDDAACGenerationReturnedInFirstGenerateAC() {
128        return isBitOn(cvr[1], 7);
129    }
130
131    /**
132     * Returns true if combined d d a a c generation returned in second generate a c.
133     * @return true if condition applies
134     */
135    public boolean combinedDDAACGenerationReturnedInSecondGenerateAC() {
136        return isBitOn(cvr[1], 6);
137    }
138
139    /**
140     * Returns true if issuer authentication failed.
141     * @return true if condition applies
142     */
143    public boolean issuerAuthenticationFailed() {
144        return isBitOn(cvr[4], 3);
145    }
146
147    /**
148     * Returns true if script received.
149     * @return true if condition applies
150     */
151    public boolean scriptReceived() {
152        return isBitOn(cvr[4], 2);
153    }
154
155    /**
156     * Returns true if script failed.
157     * @return true if condition applies
158     */
159    public boolean scriptFailed() {
160        return isBitOn(cvr[4], 1);
161    }    
162
163    /**
164     * Returns true if ciac default skipped on c a t3.
165     * @return true if condition applies
166     */
167    public boolean ciacDefaultSkippedOnCAT3() {
168        return isBitOn(cvr[1], 4);
169    }
170
171    /**
172     * Returns true if match found in additional check table.
173     * @return true if condition applies
174     */
175    public boolean matchFoundInAdditionalCheckTable() {
176        return isBitOn(cvr[5], 2);
177    }
178
179    /**
180     * Returns true if no match found in additional check table.
181     * @return true if condition applies
182     */
183    public boolean noMatchFoundInAdditionalCheckTable() {
184        return isBitOn(cvr[5], 1);
185    }    
186
187    /**
188     * Returns the right nibble of script counter.
189     * @return the value
190     */
191    public int rightNibbleOfScriptCounter() {
192        StringBuilder sb = new StringBuilder();
193        sb.append(isBitOn(cvr[2], 8) ? "1" : "0");
194        sb.append(isBitOn(cvr[2], 7) ? "1" : "0");
195        sb.append(isBitOn(cvr[2], 6) ? "1" : "0");
196        sb.append(isBitOn(cvr[2], 5) ? "1" : "0");
197        return Integer.parseInt(sb.toString(), 2);
198    }
199
200    /**
201     * Returns the right nibble of p i n try counter.
202     * @return the value
203     */
204    public int rightNibbleOfPINTryCounter() {
205        StringBuilder sb = new StringBuilder();
206        sb.append(isBitOn(cvr[2], 4) ? "1" : "0");
207        sb.append(isBitOn(cvr[2], 3) ? "1" : "0");
208        sb.append(isBitOn(cvr[2], 2) ? "1" : "0");
209        sb.append(isBitOn(cvr[2], 1) ? "1" : "0");
210        return Integer.parseInt(sb.toString(), 2);
211    }
212    
213    /**
214     * Returns true if offline p i n verification failed.
215     * @return true if condition applies
216     */
217    public boolean offlinePINVerificationFailed() {
218        return isBitOn(cvr[0], 2);
219    }
220
221    /**
222     * Returns true if ptl exceeded.
223     * @return true if condition applies
224     */
225    public boolean ptlExceeded() {
226        return isBitOn(cvr[0], 2);
227    }
228    
229    /**
230     * Returns true if international transaction.
231     * @return true if condition applies
232     */
233    public boolean internationalTransaction() {
234        return isBitOn(cvr[3], 3);
235    }
236
237    /**
238     * Returns true if domestic transaction.
239     * @return true if condition applies
240     */
241    public boolean domesticTransaction() {
242        return isBitOn(cvr[3], 2);
243    }
244    
245    /**
246     * Returns true if terminal erroneously considers offline p i n o k.
247     * @return true if condition applies
248     */
249    public boolean terminalErroneouslyConsidersOfflinePINOK() {
250        return isBitOn(cvr[3], 1);
251    }
252
253    /**
254     * Returns true if lower consecutive offline limit exceeded.
255     * @return true if condition applies
256     */
257    public boolean lowerConsecutiveOfflineLimitExceeded() {
258        return isBitOn(cvr[4], 8);
259    }
260
261    /**
262     * Returns true if upper consecutive offline limit exceeded.
263     * @return true if condition applies
264     */
265    public boolean upperConsecutiveOfflineLimitExceeded() {
266        return isBitOn(cvr[4], 7);
267    }
268
269    /**
270     * Returns true if lower cumulative offline limit exceeded.
271     * @return true if condition applies
272     */
273    public boolean lowerCumulativeOfflineLimitExceeded() {
274        return isBitOn(cvr[4], 6);
275    }
276
277    /**
278     * Returns true if upper cumulative offline limit exceeded.
279     * @return true if condition applies
280     */
281    public boolean upperCumulativeOfflineLimitExceeded() {
282        return isBitOn(cvr[4], 5);
283    }    
284
285    /**
286     * Returns true if go online on next transaction set.
287     * @return true if condition applies
288     */
289    public boolean goOnlineOnNextTransactionSet() {
290        return isBitOn(cvr[4], 4);
291    }
292         
293    /**
294     * Returns true if unable to go online.
295     * @return true if condition applies
296     */
297    public boolean unableToGoOnline() {
298        return isBitOn(cvr[3], 7);
299    }    
300    
301    /**
302     * Returns true if second generate a c not requested.
303     * @return true if condition applies
304     */
305    public boolean secondGenerateACNotRequested() {
306        return isBitOn(cvr[0], 8) && !isBitOn(cvr[0], 7);
307    }
308
309    /**
310     * Returns true if issuer authentication performed.
311     * @return true if condition applies
312     */
313    public boolean issuerAuthenticationPerformed() {
314        return isBitOn(cvr[1], 5);
315    }
316
317    /**
318     * Returns true if offline encrypted p i n verification performed.
319     * @return true if condition applies
320     */
321    public boolean offlineEncryptedPINVerificationPerformed() {
322        return isBitOn(cvr[0], 2);
323    }
324
325    private boolean isBitOn(byte value, int position) {
326        return ((value >> (position - 1)) & 1) == 1;
327    }
328
329    @Override
330    public void dump(PrintStream p, String indent) {
331        String inner = indent + "  ";
332        String inner2 = inner + "  ";
333        StringBuilder sb = new StringBuilder();
334        p.printf("%s<cvr-mastercard value='%s'>%s%n", indent, ISOUtil.hexString(cvr), sb.toString());
335
336        p.printf("%sBYTE 1:%n", inner);
337        if (aacReturnedInSecondGenerateAC())
338            p.printf("%sACC RETURNED IN SECOND GENERATE AC%n", inner2);
339        if (tcReturnedInSecondGenerateAC())
340            p.printf("%sTC RETURNED IN SECOND GENERATE AC%n", inner2);
341        if (secondGenerateACNotRequested())
342            p.printf("%sSECOND GENERATE AC NOT REQUESTED%n", inner2);
343        if (aacReturnedInFirstGenerateAC())
344            p.printf("%sACC RETURNED IN FIRST GENERATE AC%n", inner2);
345        if (tcReturnedInFirstGenerateAC())
346            p.printf("%sTC RETURNED IN FIRST GENERATE AC%n", inner2);
347        if (arqcReturnedInFirstGenerateAC())
348            p.printf("%sARQC RETURNED IN FIRST GENERATE AC%n", inner2);
349        if (offlinePINVerificationPerformed())
350            p.printf("%sOFFLINE PIN VERIFICATION PERFORMED%n", inner2);
351        if (offlineEncryptedPINVerificationPerformed())
352            p.printf("%sOFFLINE ENCRYPTED PIN VERIFICATION PERFORMED%n", inner2);
353            
354        p.printf("%n%sBYTE 2:%n", inner);
355        if (ddaReturned())
356            p.printf("%sDDA RETURNED%n", inner2);
357        if (combinedDDAACGenerationReturnedInFirstGenerateAC())
358            p.printf("%sCOMBINED DDA/AC GENERATION RETURNED IN FIRST GENERATE AC%n", inner2);
359        if (combinedDDAACGenerationReturnedInSecondGenerateAC())
360            p.printf("%sCOMBINED DDA/AC GENERATION RETURNED IN SECOND GENERATE AC%n", inner2);
361        if (issuerAuthenticationPerformed())
362            p.printf("%sISSUER AUTHENTICATION PEFORMED%n", inner2);
363        if (ciacDefaultSkippedOnCAT3())
364            p.printf("%sCIAC-DEFAULT SKIPPED ON CAT3%n", inner2);            
365
366        p.printf("%n%sBYTE 3:%n", inner);
367        p.printf("%sRIGHT NIBBLE OF SCRIPT COUNTER = %s%n", inner2, rightNibbleOfScriptCounter());
368        p.printf("%sRIGHT NIBBLE OF PIN TRY COUNTER = %s%n", inner2, rightNibbleOfPINTryCounter());
369
370        p.printf("%n%sBYTE 4:%n", inner);               
371        if (unableToGoOnline())
372            p.printf("%sUNABLE TO GO ONLINE INDICATED%n", inner2);
373        if (offlinePINVerificationNotPerformed())
374            p.printf("%sOFFLINE PIN VERIFICATION NOT PERFORMED%n", inner2);
375        if (offlinePINVerificationFailed())
376            p.printf("%sOFFLINE PIN VERIFICATION FAILED%n", inner2);
377        if (ptlExceeded())
378            p.printf("%sPTL EXCEEDED%n", inner2);
379        if (internationalTransaction())
380            p.printf("%sINTERNATIONAL TRANSACTION%n", inner2);            
381        if (domesticTransaction())
382            p.printf("%sDOMESTIC TRANSACTION%n", inner2);            
383        if (terminalErroneouslyConsidersOfflinePINOK())
384            p.printf("%sTERMINAL ERRONEOUSLY CONSIDERS OFFLINE PIN OK%n", inner2);   
385
386        p.printf("%n%sBYTE 5:%n", inner);
387        if (lowerConsecutiveOfflineLimitExceeded())
388            p.printf("%sLOWER CONSECUTIVE OFFLINE LIMIT EXCEEDED%n", inner2);   
389        if (upperConsecutiveOfflineLimitExceeded())
390            p.printf("%sUPPER CONSECUTIVE OFFLINE LIMIT EXCEEDED%n", inner2);   
391        if (lowerCumulativeOfflineLimitExceeded())
392            p.printf("%sLOWER CUMULATIVE OFFLINE LIMIT EXCEEDED%n", inner2);   
393        if (upperCumulativeOfflineLimitExceeded())
394            p.printf("%sUPPER CUMULATIVE OFFLINE LIMIT EXCEEDED%n", inner2);
395        if (goOnlineOnNextTransactionSet())
396            p.printf("%sGO ONLINE ON NEXT TRANSACTION WAS SET%n", inner2);
397        if (issuerAuthenticationFailed())
398            p.printf("%sISSUER AUTHENTICATION FAILED%n", inner2);
399        if (scriptReceived())
400            p.printf("%sSCRIPT RECEIVED%n", inner2);
401        if (scriptFailed())
402            p.printf("%sSCRIPT FAILED%n", inner2);
403            
404        p.printf("%n%sBYTE 6:%n", inner);
405        if (matchFoundInAdditionalCheckTable())
406            p.printf("%sMATCH FOUND IN ADDITIONAL CHECK TABLE%n", inner2);
407        if (noMatchFoundInAdditionalCheckTable())
408            p.printf("%sNO MATCH FOUND IN ADDITIONAL CHECK TABLE%n", inner2);          
409            
410        p.printf("%s</cvr-mastercard>%n", indent);        
411    }
412}