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}