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.iso;
020
021import org.jpos.util.LogEvent;
022import org.jpos.util.LogSource;
023import org.jpos.util.Logger;
024
025import java.io.EOFException;
026import java.io.IOException;
027import java.io.InputStream;
028import java.util.ArrayList;
029import java.util.BitSet;
030import java.util.Map;
031
032/**
033 * provides base functionality for the actual packagers
034 *
035 * @author apr
036 */
037@SuppressWarnings ("unused")
038public abstract class ISOBasePackager implements ISOPackager, LogSource {
039    protected ISOFieldPackager[] fld;
040    protected int thirdBitmapField= -999;       // for implementations where the tertiary bitmap is inside a Data Element
041
042    protected Logger logger = null;
043    protected boolean logFieldName= true;
044    protected String realm = null;
045    protected int headerLength = 0;
046
047    public void setFieldPackager (ISOFieldPackager[] fld) {
048        this.fld = fld;
049    }
050
051    public void setThirdBitmapField(int f) throws ISOException
052    {
053        if (f < 0 || f > 128)
054            throw new ISOException("thirdBitmapField should be >= 0 and <= 128");
055        thirdBitmapField= f;
056    }
057    public int getThirdBitmapField() { return thirdBitmapField; }
058
059    /**
060     * @return true if BitMap have to be emited
061     */
062    protected boolean emitBitMap () {
063        return fld[1] instanceof ISOBitMapPackager;
064    }
065
066    /**
067     * usually 2 for normal fields, 1 for bitmap-less or ANSI X9.2
068     * @return first valid field
069     */
070    protected int getFirstField() {
071        if (!(fld[0] instanceof ISOMsgFieldPackager) && fld.length > 1)
072            return fld[1] instanceof ISOBitMapPackager ? 2 : 1;
073        return 0;
074    }
075
076    /**
077     * pack method that works in conjunction with {@link #unpack(ISOComponent, byte[])}.
078     * <p>
079     * Handles a tertiary bitmap possibly appearing in Data Element {@code thirdBitmapField}.<br>
080     *
081     * @param   m   the Component to pack
082     * @return      Message image
083     * @exception ISOException
084     */
085    @Override
086    public byte[] pack (ISOComponent m) throws ISOException
087    {
088        LogEvent evt = null;
089        if (logger != null)
090            evt = new LogEvent (this, "pack");
091
092        try {
093            if (m.getComposite() != m)
094                throw new ISOException ("Can't call packager on non Composite");
095
096            ArrayList<byte[]> v = new ArrayList<byte[]>(128);
097            byte[] b;
098            byte[] hdr= null;
099            int len = 0;
100
101            Map fields = m.getChildren();
102            ISOComponent c = (ISOComponent) fields.get (0);
103            int first = getFirstField();
104
105
106            // pre-read header, if it exists, and advance total len
107            if (m instanceof ISOMsg && headerLength>0)
108            {
109                hdr= ((ISOMsg) m).getHeader();
110                if (hdr != null)
111                        len += hdr.length;
112            }
113
114            if (first > 0 && c != null) {
115                b = fld[0].pack(c);
116                len += b.length;
117                v.add (b);
118            }
119
120            BitSet bmap12= null;                            // will store primary and secondary part of bitmap
121            BitSet bmap3= null;                             // will store tertiary part of bitmap
122            if (emitBitMap())
123            {   // The ISOComponent stores a single bitmap in field -1, which could be up to
124                // 192 bits long. If we have a thirdBitmapField, we may need to split the full
125                // bitmap into 1 & 2 at the beginning (16 bytes), and 3rd inside the Data Element
126                c = (ISOComponent) fields.get (-1);
127                bmap12= (BitSet)c.getValue();               // the full bitmap (up to 192 bits long)
128
129                if (thirdBitmapField >= 0 &&                // we may need to split it!
130                    fld[thirdBitmapField] instanceof ISOBitMapPackager)
131                {
132                    if (bmap12.length() - 1 > 128)          // some bits are set in the high part (3rd bitmap)
133                    {
134                        bmap3= bmap12.get(128, 193);        // new bitmap, with the high 3rd bitmap (use 128 as dummy bit0)
135                        bmap3.clear(0);                     // don't really need to clear dummy bit0 I guess...
136                        bmap12.set(thirdBitmapField);       // indicate presence of field that will hold the 3rd bitmap
137                        bmap12.clear(129, 193);             // clear high part, so that the field's pack() method will not use it
138
139                        // Now create add-hoc ISOBitMap in position thirdBitmapField to hold 3rd bitmap
140                        ISOBitMap bmField= new ISOBitMap(thirdBitmapField);
141                        bmField.setValue(bmap3);
142                        m.set(bmField);
143                        fields.put(thirdBitmapField, bmField);    // fields is a clone of m's inner map, so we store it here as well
144
145                        // bit65 should only be set if there's a data-containing DE-65 (which should't happen!)
146                        bmap12.set(65, fields.get(65) == null ? false : true);
147                    }
148                    else
149                    {   // else: No bits/fields above 128 in this message.
150                        // In case there's an old (residual/garbage) field `thirdBitmapField` in the message
151                        // we need to clear the bit and the data
152                        m.unset(thirdBitmapField);                // remove from ISOMsg
153                        bmap12.clear(thirdBitmapField);           // remove from inner bitmap
154                        fields.remove(thirdBitmapField);          // remove from fields clone
155                    }
156                }
157                // now will emit the 1st and 2nd bitmaps, and the loop below will take care of 3rd
158                // when emitting field `thirdBitmapField`
159                b = getBitMapfieldPackager().pack(c);
160                len += b.length;
161                v.add (b);
162            }
163
164            // if Field 1 is a BitMap then we are packing an
165            // ISO-8583 message so next field is fld#2.
166            // else we are packing an ANSI X9.2 message, first field is 1
167            int tmpMaxField=Math.min (m.getMaxField(), (bmap3 != null || fld.length > 129) ? 192 : 128);
168
169            for (int i=first; i<=tmpMaxField; i++) {
170                if ((c=(ISOComponent) fields.get (i)) != null)
171                {
172                    try {
173                        ISOFieldPackager fp = fld[i];
174                        if (fp == null)
175                            throw new ISOException ("null field "+i+" packager");
176                        b = fp.pack(c);
177                        len += b.length;
178                        v.add (b);
179                    } catch (ISOException e) {
180                        if (evt != null) {
181                            evt.addMessage ("error packing field "+i);
182                            evt.addMessage (c);
183                            evt.addMessage (e);
184                        }
185                        throw new ISOException("error packing field "+i, e);
186                    }
187                }
188            }
189
190            int k = 0;
191            byte[] d = new byte[len];
192
193            // if ISOMsg insert header (we pre-read it at the beginning)
194            if (hdr != null) {
195                System.arraycopy(hdr, 0, d, k, hdr.length);
196                k += hdr.length;
197            }
198
199            for (byte[] bb : v) {
200                System.arraycopy(bb, 0, d, k, bb.length);
201                k += bb.length;
202            }
203            if (evt != null)  // save a few CPU cycle if no logger available
204                evt.addMessage (ISOUtil.hexString (d));
205
206            return d;
207        } catch (ISOException e) {
208            if (evt != null)
209                evt.addMessage (e);
210            throw e;
211        } finally {
212            if (evt != null)
213                Logger.log(evt);
214        }
215    }
216
217    /**
218     * @param   m   the Container of this message
219     * @param   b   ISO message image
220     * @return      consumed bytes
221     * @exception ISOException
222     */
223    @Override
224    public int unpack (ISOComponent m, byte[] b) throws ISOException {
225        LogEvent evt = logger != null ? new LogEvent (this, "unpack") : null;
226        int consumed = 0;
227
228        try {
229            if (m.getComposite() != m)
230                throw new ISOException ("Can't call packager on non Composite");
231            if (evt != null)  // save a few CPU cycle if no logger available
232                evt.addMessage (ISOUtil.hexString (b));
233
234
235            // if ISOMsg and headerLength defined
236            if (m instanceof ISOMsg /*&& ((ISOMsg) m).getHeader()==null*/ && headerLength>0)
237            {
238                byte[] h = new byte[headerLength];
239                System.arraycopy(b, 0, h, 0, headerLength);
240                ((ISOMsg) m).setHeader(h);
241                consumed += headerLength;
242            }
243
244            if (!(fld[0] == null) && !(fld[0] instanceof ISOBitMapPackager))
245            {
246                ISOComponent mti = fld[0].createComponent(0);
247                consumed  += fld[0].unpack(mti, b, consumed);
248                m.set (mti);
249            }
250
251            BitSet bmap = null;
252            int bmapBytes= 0;                                   // bitmap length in bytes (usually 8, 16, 24)
253            int maxField= fld.length - 1;                       // array length counts position 0!
254
255            if (emitBitMap()) {
256                ISOBitMap bitmap = new ISOBitMap (-1);
257                consumed += getBitMapfieldPackager().unpack(bitmap,b,consumed);
258                bmap = (BitSet) bitmap.getValue();
259                bmapBytes= (bmap.length()-1 + 63) >> 6 << 3;
260                if (evt != null)
261                    evt.addMessage ("<bitmap>"+bmap.toString()+"</bitmap>");
262                m.set (bitmap);
263
264                maxField = Math.min(maxField, bmap.length()-1); // bmap.length behaves similarly to fld.length
265            }
266
267            for (int i= getFirstField(); i <= maxField; i++) {
268                try {
269                    if (bmap == null && fld[i] == null)
270                        continue;
271
272                    // maxField is computed above as min(fld.length-1, bmap.length()-1), therefore
273                    // "maxField > 128" means fld[] has packagers defined above 128, *and*
274                    // the bitmap's length is greater than 128 (i.e., a contiguous tertiary bitmap exists).
275                    // In this case, bit 65 simply indicates a 3rd bitmap contiguous to the 2nd one.
276                    // Therefore, there MUST NOT be a DE-65 with data payload to read.
277                    if (maxField > 128 && i==65)
278                        continue;   // ignore extended bitmap
279
280                    if (bmap == null || bmap.get(i)) {
281                        if (fld[i] == null)
282                            throw new ISOException ("field packager '" + i + "' is null");
283
284                        ISOComponent c = fld[i].createComponent(i);
285                        consumed += fld[i].unpack (c, b, consumed);
286                        if (evt != null)
287                            fieldUnpackLogger(evt, i, c, fld[i], logFieldName);
288                        m.set(c);
289
290                        if (i == thirdBitmapField && fld.length > 129 &&          // fld[128] is at pos 129
291                            bmapBytes == 16 &&
292                            fld[thirdBitmapField] instanceof ISOBitMapPackager)
293                        {   // We have a weird case of tertiary bitmap implemented inside a Data Element
294                            // instead of being contiguous to the primary and secondary bitmaps.
295                            // If enter this "if" it's because we have a proper 16-byte bitmap (1st & 2nd),
296                            // but are expecting more than 128 Data Elements according to fld[].
297                            // Normally, these kind of ISO8583 implementations have the tertiary bitmap in DE-65,
298                            // but sometimes they specify some other DE (given by thirdBitmapField).
299                            // We also double check that the DE has been specified as an ISOBitMapPackager in fld[].
300                            // By now, the tertiary bitmap has already been unpacked into field `thirdBitmapField`.
301                            BitSet bs3rd= (BitSet)((ISOComponent)m.getChildren().get(thirdBitmapField)).getValue();
302                            maxField= 128 + (bs3rd.length() - 1);                 // update loop end condition
303                            for (int bit= 1; bit <= 64; bit++)
304                                bmap.set(bit+128, bs3rd.get(bit));                // extend bmap with new bits above 128
305                        }
306                    }
307                } catch (ISOException e) {
308                    if (evt != null) {
309                        evt.addMessage("error unpacking field " + i + " consumed=" + consumed);
310                        evt.addMessage(e);
311                    }
312                    // jPOS-3
313                    if (e.getNested() == null) {
314                        e = new ISOException(
315                            String.format("%s unpacking field=%d, consumed=%d",
316                            e.getMessage(), i, consumed)
317                        );
318                    } else {
319                        e = new ISOException(
320                            String.format("%s (%s) unpacking field=%d, consumed=%d",
321                            e.getMessage(), e.getNested().toString(), i, consumed)
322                        );
323                    }
324                    throw e;
325                }
326            } // for each field
327
328            if (evt != null && b.length != consumed) {
329                evt.addMessage ("WARNING: unpack len=" +b.length +" consumed=" +consumed);
330            }
331
332            return consumed;
333        } catch (ISOException e) {
334            if (evt != null)
335                evt.addMessage (e);
336            throw e;
337        } catch (Exception e) {
338            if (evt != null)
339                evt.addMessage (e);
340            throw new ISOException (e.getMessage() + " consumed=" + consumed);
341        } finally {
342            if (evt != null)
343                Logger.log (evt);
344        }
345    }
346
347    public void unpack (ISOComponent m, InputStream in)
348        throws IOException, ISOException
349    {
350        LogEvent evt = logger != null ? new LogEvent (this, "unpack") : null;
351        try {
352            if (m.getComposite() != m)
353                throw new ISOException ("Can't call packager on non Composite");
354
355            // if ISOMsg and headerLength defined
356            if (m instanceof ISOMsg && ((ISOMsg) m).getHeader()==null && headerLength>0)
357            {
358                byte[] h = new byte[headerLength];
359                in.read(h, 0, headerLength);
360                ((ISOMsg) m).setHeader(h);
361            }
362
363
364            if (!(fld[0] instanceof ISOMsgFieldPackager) &&
365                !(fld[0] instanceof ISOBitMapPackager))
366            {
367                ISOComponent mti = fld[0].createComponent(0);
368                fld[0].unpack(mti, in);
369                m.set (mti);
370            }
371
372            BitSet bmap = null;
373            int maxField = fld.length;
374            if (emitBitMap()) {
375                ISOBitMap bitmap = new ISOBitMap (-1);
376                getBitMapfieldPackager().unpack(bitmap, in);
377                bmap = (BitSet) bitmap.getValue();
378                if (evt != null)
379                    evt.addMessage ("<bitmap>"+bmap.toString()+"</bitmap>");
380                m.set (bitmap);
381                maxField = Math.min(maxField, bmap.size());
382            }
383
384            for (int i=getFirstField(); i<maxField; i++) {
385                if (bmap == null && fld[i] == null)
386                    continue;
387
388                if (bmap == null || bmap.get(i)) {
389                    if (fld[i] == null)
390                        throw new ISOException ("field packager '" + i + "' is null");
391
392                    ISOComponent c = fld[i].createComponent(i);
393                    fld[i].unpack (c, in);
394                    if (evt != null)
395                        fieldUnpackLogger(evt, i, c, fld[i], logFieldName);
396                    m.set(c);
397                }
398            }
399            if (bmap != null && bmap.get(65) && fld.length > 128 &&
400                fld[65] instanceof ISOBitMapPackager)
401            {
402                bmap= (BitSet) ((ISOComponent) m.getChildren().get(65)).getValue();
403                for (int i=1; i<64; i++) {
404                    if (bmap == null || bmap.get(i)) {
405                        ISOComponent c = fld[i+128].createComponent(i);
406                        fld[i+128].unpack (c, in);
407                        if (evt != null)
408                            fieldUnpackLogger(evt, i+128, c, fld[i+128], logFieldName);
409                        m.set(c);
410                    }
411                }
412            }
413        } catch (ISOException e) {
414            if (evt != null)
415                evt.addMessage (e);
416            throw e;
417        } catch (EOFException e) {
418            throw e;
419        } catch (Exception e) {
420            if (evt != null)
421                evt.addMessage (e);
422            throw new ISOException (e);
423        } finally {
424            if (evt != null)
425                Logger.log (evt);
426        }
427    }
428
429
430    /**
431     * Internal helper logging function.
432     * Assumes evt is not null.
433     */
434    protected static void fieldUnpackLogger(LogEvent evt, int fldno, ISOComponent c, ISOFieldPackager fld, boolean logFieldName) throws ISOException
435    {
436        evt.addMessage ("<unpack fld=\""+fldno
437            +"\" packager=\""+fld.getClass().getName()+ "\">");
438
439        if (logFieldName)
440            evt.addMessage("  <!-- "+fld.getDescription()+" -->");
441
442        if (c.getValue() instanceof ISOMsg)
443            evt.addMessage (c.getValue());
444        else if (c.getValue() instanceof byte[]) {
445            evt.addMessage ("  <value type='binary'>"
446                +ISOUtil.hexString((byte[]) c.getValue())
447                + "</value>");
448        }
449        else {
450            evt.addMessage ("  <value>"+c.getValue()+"</value>");
451        }
452        evt.addMessage ("</unpack>");
453    }
454
455
456    /**
457     * @param   m   the Container (i.e. an ISOMsg)
458     * @param   fldNumber the Field Number
459     * @return  Field Description
460     */
461    public String getFieldDescription(ISOComponent m, int fldNumber) {
462        return fld[fldNumber].getDescription();
463    }
464    /**
465     * @param   fldNumber the Field Number
466     * @return  Field Packager for this field
467     */
468    public ISOFieldPackager getFieldPackager (int fldNumber) {
469        return fld != null && fldNumber < fld.length ? fld[fldNumber] : null;
470    }
471    /**
472     * @param   fldNumber the Field Number
473     * @param   fieldPackager the Field Packager
474     */
475    public void setFieldPackager
476        (int fldNumber, ISOFieldPackager fieldPackager)
477    {
478        fld[fldNumber] = fieldPackager;
479    }
480    public ISOMsg createISOMsg () {
481        return new ISOMsg();
482    }
483    /**
484     * @return 128 for ISO-8583, should return 64 for ANSI X9.2
485     */
486    protected int getMaxValidField() {
487        return 128;
488    }
489    /**
490     * @return suitable ISOFieldPackager for Bitmap
491     */
492    protected ISOFieldPackager getBitMapfieldPackager() {
493        return fld[1];
494    }
495    public void setLogger (Logger logger, String realm) {
496        this.logger = logger;
497        this.realm  = realm;
498    }
499    public String getRealm () {
500        return realm;
501    }
502    public Logger getLogger() {
503        return logger;
504    }
505    public int getHeaderLength ()
506    {
507        return headerLength;
508    }
509    public void setHeaderLength(int len)
510    {
511        headerLength = len;
512    }
513    public String getDescription () {
514        return getClass().getName();
515    }
516}