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     * 
036     * @param cvr Byte array containing the CVR value.
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     * 
048     * @param cvr Hexadecimal string representation of the CVR value.
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    public boolean aacReturnedInSecondGenerateAC() {
060        return !isBitOn(cvr[0], 8) && !isBitOn(cvr[0], 7);
061    }
062
063    public boolean aacReturnedInFirstGenerateAC() {
064        return !isBitOn(cvr[0], 6) && !isBitOn(cvr[0], 5);
065    }
066
067    public boolean tcReturnedInSecondGenerateAC() {
068        return !isBitOn(cvr[0], 8) && isBitOn(cvr[0], 7);
069    }
070
071    public boolean arqcReturnedInFirstGenerateAC() {
072        return isBitOn(cvr[0], 6) && !isBitOn(cvr[0], 5);
073    }
074
075    public boolean tcReturnedInFirstGenerateAC() {
076        return !isBitOn(cvr[0], 6) && isBitOn(cvr[0], 5);
077    }
078        
079    public boolean offlinePINVerificationPerformed() {
080        return isBitOn(cvr[0], 3);
081    }
082
083    public boolean offlinePINVerificationNotPerformed() {
084        return isBitOn(cvr[3], 6);
085    }    
086    
087    public boolean ddaReturned() {
088        return isBitOn(cvr[1], 8);
089    }
090
091    public boolean combinedDDAACGenerationReturnedInFirstGenerateAC() {
092        return isBitOn(cvr[1], 7);
093    }
094
095    public boolean combinedDDAACGenerationReturnedInSecondGenerateAC() {
096        return isBitOn(cvr[1], 6);
097    }
098
099    public boolean issuerAuthenticationFailed() {
100        return isBitOn(cvr[4], 3);
101    }
102
103    public boolean scriptReceived() {
104        return isBitOn(cvr[4], 2);
105    }
106
107    public boolean scriptFailed() {
108        return isBitOn(cvr[4], 1);
109    }    
110
111    public boolean ciacDefaultSkippedOnCAT3() {
112        return isBitOn(cvr[1], 4);
113    }
114
115    public boolean matchFoundInAdditionalCheckTable() {
116        return isBitOn(cvr[5], 2);
117    }
118
119    public boolean noMatchFoundInAdditionalCheckTable() {
120        return isBitOn(cvr[5], 1);
121    }    
122
123    public int rightNibbleOfScriptCounter() {
124        StringBuilder sb = new StringBuilder();
125        sb.append(isBitOn(cvr[2], 8) ? "1" : "0");
126        sb.append(isBitOn(cvr[2], 7) ? "1" : "0");
127        sb.append(isBitOn(cvr[2], 6) ? "1" : "0");
128        sb.append(isBitOn(cvr[2], 5) ? "1" : "0");
129        return Integer.parseInt(sb.toString(), 2);
130    }
131
132    public int rightNibbleOfPINTryCounter() {
133        StringBuilder sb = new StringBuilder();
134        sb.append(isBitOn(cvr[2], 4) ? "1" : "0");
135        sb.append(isBitOn(cvr[2], 3) ? "1" : "0");
136        sb.append(isBitOn(cvr[2], 2) ? "1" : "0");
137        sb.append(isBitOn(cvr[2], 1) ? "1" : "0");
138        return Integer.parseInt(sb.toString(), 2);
139    }
140    
141    public boolean offlinePINVerificationFailed() {
142        return isBitOn(cvr[0], 2);
143    }
144
145    public boolean ptlExceeded() {
146        return isBitOn(cvr[0], 2);
147    }
148    
149    public boolean internationalTransaction() {
150        return isBitOn(cvr[3], 3);
151    }
152
153    public boolean domesticTransaction() {
154        return isBitOn(cvr[3], 2);
155    }
156    
157    public boolean terminalErroneouslyConsidersOfflinePINOK() {
158        return isBitOn(cvr[3], 1);
159    }
160
161    public boolean lowerConsecutiveOfflineLimitExceeded() {
162        return isBitOn(cvr[4], 8);
163    }
164
165    public boolean upperConsecutiveOfflineLimitExceeded() {
166        return isBitOn(cvr[4], 7);
167    }
168
169    public boolean lowerCumulativeOfflineLimitExceeded() {
170        return isBitOn(cvr[4], 6);
171    }
172
173    public boolean upperCumulativeOfflineLimitExceeded() {
174        return isBitOn(cvr[4], 5);
175    }    
176
177    public boolean goOnlineOnNextTransactionSet() {
178        return isBitOn(cvr[4], 4);
179    }
180         
181    public boolean unableToGoOnline() {
182        return isBitOn(cvr[3], 7);
183    }    
184    
185    public boolean secondGenerateACNotRequested() {
186        return isBitOn(cvr[0], 8) && !isBitOn(cvr[0], 7);
187    }
188
189    public boolean issuerAuthenticationPerformed() {
190        return isBitOn(cvr[1], 5);
191    }
192
193    public boolean offlineEncryptedPINVerificationPerformed() {
194        return isBitOn(cvr[0], 2);
195    }
196
197    private boolean isBitOn(byte value, int position) {
198        return ((value >> (position - 1)) & 1) == 1;
199    }
200
201    @Override
202    public void dump(PrintStream p, String indent) {
203        String inner = indent + "  ";
204        String inner2 = inner + "  ";
205        StringBuilder sb = new StringBuilder();
206        p.printf("%s<cvr-mastercard value='%s'>%s%n", indent, ISOUtil.hexString(cvr), sb.toString());
207
208        p.printf("%sBYTE 1:%n", inner);
209        if (aacReturnedInSecondGenerateAC())
210            p.printf("%sACC RETURNED IN SECOND GENERATE AC%n", inner2);
211        if (tcReturnedInSecondGenerateAC())
212            p.printf("%sTC RETURNED IN SECOND GENERATE AC%n", inner2);
213        if (secondGenerateACNotRequested())
214            p.printf("%sSECOND GENERATE AC NOT REQUESTED%n", inner2);
215        if (aacReturnedInFirstGenerateAC())
216            p.printf("%sACC RETURNED IN FIRST GENERATE AC%n", inner2);
217        if (tcReturnedInFirstGenerateAC())
218            p.printf("%sTC RETURNED IN FIRST GENERATE AC%n", inner2);
219        if (arqcReturnedInFirstGenerateAC())
220            p.printf("%sARQC RETURNED IN FIRST GENERATE AC%n", inner2);
221        if (offlinePINVerificationPerformed())
222            p.printf("%sOFFLINE PIN VERIFICATION PERFORMED%n", inner2);
223        if (offlineEncryptedPINVerificationPerformed())
224            p.printf("%sOFFLINE ENCRYPTED PIN VERIFICATION PERFORMED%n", inner2);
225            
226        p.printf("%n%sBYTE 2:%n", inner);
227        if (ddaReturned())
228            p.printf("%sDDA RETURNED%n", inner2);
229        if (combinedDDAACGenerationReturnedInFirstGenerateAC())
230            p.printf("%sCOMBINED DDA/AC GENERATION RETURNED IN FIRST GENERATE AC%n", inner2);
231        if (combinedDDAACGenerationReturnedInSecondGenerateAC())
232            p.printf("%sCOMBINED DDA/AC GENERATION RETURNED IN SECOND GENERATE AC%n", inner2);
233        if (issuerAuthenticationPerformed())
234            p.printf("%sISSUER AUTHENTICATION PEFORMED%n", inner2);
235        if (ciacDefaultSkippedOnCAT3())
236            p.printf("%sCIAC-DEFAULT SKIPPED ON CAT3%n", inner2);            
237
238        p.printf("%n%sBYTE 3:%n", inner);
239        p.printf("%sRIGHT NIBBLE OF SCRIPT COUNTER = %s%n", inner2, rightNibbleOfScriptCounter());
240        p.printf("%sRIGHT NIBBLE OF PIN TRY COUNTER = %s%n", inner2, rightNibbleOfPINTryCounter());
241
242        p.printf("%n%sBYTE 4:%n", inner);               
243        if (unableToGoOnline())
244            p.printf("%sUNABLE TO GO ONLINE INDICATED%n", inner2);
245        if (offlinePINVerificationNotPerformed())
246            p.printf("%sOFFLINE PIN VERIFICATION NOT PERFORMED%n", inner2);
247        if (offlinePINVerificationFailed())
248            p.printf("%sOFFLINE PIN VERIFICATION FAILED%n", inner2);
249        if (ptlExceeded())
250            p.printf("%sPTL EXCEEDED%n", inner2);
251        if (internationalTransaction())
252            p.printf("%sINTERNATIONAL TRANSACTION%n", inner2);            
253        if (domesticTransaction())
254            p.printf("%sDOMESTIC TRANSACTION%n", inner2);            
255        if (terminalErroneouslyConsidersOfflinePINOK())
256            p.printf("%sTERMINAL ERRONEOUSLY CONSIDERS OFFLINE PIN OK%n", inner2);   
257
258        p.printf("%n%sBYTE 5:%n", inner);
259        if (lowerConsecutiveOfflineLimitExceeded())
260            p.printf("%sLOWER CONSECUTIVE OFFLINE LIMIT EXCEEDED%n", inner2);   
261        if (upperConsecutiveOfflineLimitExceeded())
262            p.printf("%sUPPER CONSECUTIVE OFFLINE LIMIT EXCEEDED%n", inner2);   
263        if (lowerCumulativeOfflineLimitExceeded())
264            p.printf("%sLOWER CUMULATIVE OFFLINE LIMIT EXCEEDED%n", inner2);   
265        if (upperCumulativeOfflineLimitExceeded())
266            p.printf("%sUPPER CUMULATIVE OFFLINE LIMIT EXCEEDED%n", inner2);
267        if (goOnlineOnNextTransactionSet())
268            p.printf("%sGO ONLINE ON NEXT TRANSACTION WAS SET%n", inner2);
269        if (issuerAuthenticationFailed())
270            p.printf("%sISSUER AUTHENTICATION FAILED%n", inner2);
271        if (scriptReceived())
272            p.printf("%sSCRIPT RECEIVED%n", inner2);
273        if (scriptFailed())
274            p.printf("%sSCRIPT FAILED%n", inner2);
275            
276        p.printf("%n%sBYTE 6:%n", inner);
277        if (matchFoundInAdditionalCheckTable())
278            p.printf("%sMATCH FOUND IN ADDITIONAL CHECK TABLE%n", inner2);
279        if (noMatchFoundInAdditionalCheckTable())
280            p.printf("%sNO MATCH FOUND IN ADDITIONAL CHECK TABLE%n", inner2);          
281            
282        p.printf("%s</cvr-mastercard>%n", indent);        
283    }
284}