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.emv.cryptogram.MChipCryptogram;
022import org.jpos.emv.cryptogram.CryptogramSpec;
023import org.jpos.emv.cryptogram.VISACryptogram;
024import org.jpos.iso.ISOUtil;
025import org.jpos.util.Loggeable;
026
027import java.io.PrintStream;
028import java.util.Arrays;
029import java.util.Objects;
030
031/**
032 * Issuer Application Data parser (IAD, tag 0x9F10) with support for the following formats:
033 * 
034 * - VIS 1.5
035 * - M/CHIP 4
036 * - EMV Format A (as per the EMV 4.3 Book 3 spec)
037 */
038public final class IssuerApplicationData implements Loggeable {
039
040    private final String iad;
041    private String cvn;
042    private String cvr;
043    private String dki;
044    private String idd;
045    private String dac;
046    private String counters;
047    private String lastOnlineATC;
048    private Format format;
049    private String cci;
050
051    private CryptogramSpec cryptogramSpec = null;
052    /**
053     * 
054     * @param hexIAD Hexadecimal (String) representation of the IAD.
055     */
056    public IssuerApplicationData(String hexIAD) {
057
058        Objects.requireNonNull(hexIAD, "IAD data cannot be null.");
059
060        iad = hexIAD.trim();
061
062        if (iad.length() < 14)
063            throw new IllegalArgumentException("Invalid IAD length.");
064
065        format = Format.UNKNOWN;
066
067        int len = iad.length() / 2; // length in bytes
068
069        if (Arrays.asList(18, 20, 26, 28).contains(len)) {
070            //Therefore, the length of the Issuer Application Data is 18 bytes and for 
071            // M/Chip Advance it may be 18, 20, 26, or 28.
072            unpackMCHIP(iad);
073        } else if (len == 32 && iad.startsWith("0F") && iad.startsWith("0F", 32)) {
074            // EMV_v4.3_Book_3
075            // C7.2 Issuer Application Data for Format Code 'A'
076            unpackEMVFormatA(iad);
077        } else if ((len >= 7 && len <= 23) || len == 32) {
078            // IAD Format 0/1/3 or IAD Format 2
079            unpackVIS(iad);
080        } else {
081            unpackOther(iad);
082        }
083    }
084
085    /**
086     * 
087     * @param iad Byte array representation of the IAD.
088     */
089    public IssuerApplicationData(byte[] iad) {
090
091        this(ISOUtil.byte2hex(Objects.requireNonNull(iad, "IAD data cannot be null.")));
092    }
093
094    private void unpackMCHIP(String data) {
095        
096        int length = data.length();
097        
098        format = Format.M_CHIP;
099        dki = data.substring(0, 2);
100        cvn = data.substring(2, 4);
101        cryptogramSpec = new MChipCryptogram(cvn);
102        cvr = data.substring(4, 16);
103        dac = data.substring(16, 20);
104
105        //Counters (Plaintext or Encrypted)/Accumulators
106        if (length <= 40) { //20 bytes
107            counters = data.substring(20, 36);
108        } else {
109            counters = data.substring(20, 52);
110        }
111
112        // Last Online ATC (Only for M/Chip Advance)
113        if (length == 40) {
114            lastOnlineATC = data.substring(36, 40);
115        } else if (length == 56) {
116            lastOnlineATC = data.substring(52, 56);
117        }
118    }
119
120    private void unpackVIS(String iad) {
121
122        format = Format.VIS;
123        boolean format2 = iad.length() == 64;
124
125        if (format2) {
126            cvn = iad.substring(2, 4);
127            dki = iad.substring(4, 6);
128            cvr = iad.substring(6, 16);
129            idd = iad.substring(16);
130        }
131        else {
132            dki = iad.substring(2, 4);
133            cvn = iad.substring(4, 6);
134            cvr = iad.substring(6, 14);
135
136            if (iad.length() > 14)
137                idd = iad.substring(13);
138        }
139        cryptogramSpec = new VISACryptogram(cvn);
140    }
141
142    private void unpackEMVFormatA(String data) {
143
144        format = Format.EMV_FORMAT_A;
145        cci = data.substring(2, 4);
146        dki = data.substring(4, 6);
147        cvr = data.substring(6, 16);
148        idd = data.substring(34);
149    }  
150
151    private void unpackOther(String data) {
152
153        format = Format.OTHER;
154        dki = data.substring(2, 4);
155        cvn = data.substring(4, 6);
156
157        if (data.length() == 64) {            
158            String bridge = dki;
159            dki = cvn;
160            cvn = bridge;            
161        }
162
163        int cvrLength = Integer.parseInt(data.substring(0, 2), 16);
164        cvr = data.substring(8, 8 + cvrLength);
165
166        if ((8 + 2 + cvrLength) < data.length())
167            idd = data.substring(8 + 2 + cvrLength);        
168    }    
169
170    /**
171     * 
172     * @return A hexadecimal representation of the Derivation Key Index (DKI).
173     */
174    public String getDerivationKeyIndex() {
175        return dki;
176    }
177
178    /**
179     * 
180     * @return A hexadecimal representation of the Cryptogram Version Number (CVN).
181     */
182    public String getCryptogramVersionNumber() {
183        return cvn;
184    }
185
186    /**
187     * 
188     * @return A hexadecimal representation of the Common Core Identifier (CCI)
189     */
190    public String getCommonCoreIdentifier() {
191        return cci;
192    }
193    
194    /**
195     * 
196     * @return A hexadecimal representation of the Card Verification Results (CVR).
197     */
198    public String getCardVerificationResults() {
199        return cvr;
200    }
201
202    /**
203     * 
204     * @return A hexadecimal representation of the DAC/ICC dynamic number.
205     */
206    public String getDAC() {
207        return dac;
208    }
209
210    /**
211     * 
212     * @return A hexadecimal representation of multiple counters, depending on the format.
213     */
214    public String getCounters() {
215        return counters;
216    }
217
218    /**
219     * 
220     * @return The format of the IAD.
221     */
222    public Format getFormat() {
223        return format;
224    }
225
226    /**
227     * 
228     * @return The Isser Discretionary Data (IDD).
229     */
230    public String getIssuerDiscretionaryData() {
231        return idd;
232    }
233    
234    public String getLastOnlineATC() {
235        return lastOnlineATC;
236    }
237
238    public CryptogramSpec getCryptogramSpec() {
239        return Objects.requireNonNull(cryptogramSpec, "CryptogramSpec not implemented");
240    }
241    @Override
242    public String toString() {
243        return iad;
244    }
245
246    @Override
247    public void dump(PrintStream p, String indent) {
248        
249        String inner = indent + "  ";
250        
251        p.printf("%s<iad format='%s' value='%s'>", indent, getFormat().toString(), iad);
252
253        if (cci != null)
254            p.printf("%n%sCommon core identifier: '%s'", inner, getCommonCoreIdentifier());
255
256        if (dki != null)
257            p.printf("%n%sKey derivation index: '%s'", inner, getDerivationKeyIndex());
258
259        if (cvn != null)
260            p.printf("%n%sCryptogram version number: '%s'", inner, getCryptogramVersionNumber());
261
262        if (cvr != null)
263            p.printf("%n%sCard verification results: '%s'", inner, getCardVerificationResults());
264
265        if (idd != null)
266            p.printf("%n%sIssuer discretionary data: '%s'", inner, getIssuerDiscretionaryData());            
267
268        if (dac != null)
269            p.printf("%n%sDAC/ICC dynamic number: '%s'", inner, getDAC());            
270
271        if (counters != null)
272            p.printf("%n%sPlaintext/Encrypted counters: '%s'", inner, getCounters());
273        if (lastOnlineATC != null)
274            p.printf("%n%sLast ATC Online: '%s'", inner, getLastOnlineATC());
275        p.printf("%n%s</iad>%n", indent);  
276    }
277
278    public enum Format {
279        UNKNOWN,
280        OTHER,
281        M_CHIP,
282        EMV_FORMAT_A,
283        VIS
284    }
285}