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