package center.system.msg;
import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;
import java.util.Iterator;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.io.OutputStream;
import ru.vassaev.core.types.StringList;
import ru.vassaev.core.util.Bytes;
import ru.vassaev.core.exception.SysException;
import ru.vassaev.core.io.CloseableOutputStream;
import ru.vassaev.core.io.ByteMsg;
import ru.vassaev.core.PrmInterface;
/**
* Сообщение формата ISO8583, включая N байта длины сообщения
*
* @author Vassaev A.V.
* @version 1.2 15/08/2012
*/
public final class ISO8583Msg extends ByteMsg {
private static final int TPDU = -2;
private static final int TYPE = -1;
private static final int LRC = 129;
/**
* Получить индекс параметра по имени
* @param key - имя параметра
* @return индекс
*/
public static int getIndexByKey(String key) {
if ("lrc".equals(key))
return LRC;
if ("type".equals(key))
return TYPE;
else if ("tpdu".equals(key))
return TPDU;
else
return Integer.parseInt(key);
}
/**
* Получить имя параметра по индексу
* @param index - индекс
* @return имя параметра
*/
public static String getKeyByIndex(int index) {
if (index == TPDU)
return "tpdu";
if (index == TYPE)
return "type";
if (index == LRC)
return "lrc";
return Integer.toString(index);
}
private static enum TypeHeader {
ALPHA, BYTE, DECX, HEX
}
// кодировка ibm866
private static String dec_enc = "ibm866";
// Значения по умолчанию
private TypeHeader th;
private byte[] hpos;
private int ll; // Длина в байтах поля длины сообщения
// Данные по полям
private Map<Integer, byte[]> btf;
// биты 64-128
private byte[] bitsh;
// биты 0-63
private byte[] bitsl;
private byte hcf; // Количество полей (индекс > 64)
private int len; // Длина данных полей
// Форматы полей
private Map<Integer, ISO8583Field> btff;
public ISO8583Msg() {
th = TypeHeader.ALPHA;
hpos = new byte[] { 0, 1, 2, 3 };
ll = 4; // Длина в байтах поля длины сообщения
btf = new TreeMap<Integer, byte[]>();
btff = new TreeMap<Integer, ISO8583Field>();
ISO8583Field typef = new ISO8583Field("type", "4");
btff.put(TYPE, typef);
ISO8583Field bitshf = new ISO8583Field("0", "16H");
btff.put(0, bitshf);
ISO8583Field bitslf = new ISO8583Field("1", "16H");
btff.put(1, bitslf);
bitsh = new byte[8];
bitsl = new byte[8];
hcf = 0;
len = 0;
}
/*
* Конструктор
*/
public ISO8583Msg(String headerFormat) {
this();
int l = ll;
if (headerFormat != null && (l = headerFormat.length() - 1) <= 4) {
if (l > 0) {
char ch = headerFormat.substring(0, 1).charAt(0);
switch (ch) {
case 'A':// ALPHA
th = TypeHeader.ALPHA;
break;
case 'H':// HEX
th = TypeHeader.HEX;
break;
case 'B':// BINARY
th = TypeHeader.BYTE;
break;
case 'D':// Десятичная запись в HEX (2 цифры на 1 байт от 00 до
// 99)
th = TypeHeader.DECX;
break;
default:
throw new NumberFormatException("Unsupported header format");
}
hpos = new byte[l];
for (int i = 1; i <= l; i++) {
byte p = Byte.parseByte(headerFormat.substring(i, i + 1));
hpos[i - 1] = p;
}
} else
l = 0;
}
ll = l;
}
/**
* Получить формат заголовка (длины)
* @return формат заголовка
*/
public String getHeaderFormat() {
StringBuffer sb = new StringBuffer();
if (ll > 0) {
sb.append(th.name().substring(0, 1));// A | B | H
for (int i = 0; i < ll; i++)
sb.append(Byte.toString(hpos[i]));// Порядок
}
return sb.toString();
}
public void reset() {
ISO8583Field tpdu = btff.get(TPDU);
if (tpdu != null)
tpdu.reset();
ISO8583Field type = btff.get(TYPE);
if (type != null)
type.reset();
btf.clear();
bitsh = new byte[8];
bitsl = new byte[8];
hcf = 0;
len = 0;
}
/**
* Получить длину в байтах всех полей
* @return длина в байтах только полей
*/
public int getLengthFields() {
return len;
}
/**
* Получить длину только сообщения
* @return длина сообщения без заголовка
*/
public int getLengthMsg() {
byte[] tpdu = btf.get(TPDU);
byte[] type = btf.get(TYPE);
byte[] lrc = btf.get(LRC);
return ((tpdu != null) ? tpdu.length : 0)
+ ((type != null) ? type.length : 0)
+ bitsl.length
+ (hcf > 0 ? bitsh.length : 0) + len
+ ((lrc != null) ? lrc.length : 0)
;
}
/**
* Длина сообщения для передачи по сети
* @return длина в заданном формате
*/
public byte[] getDataLenMsg() {
if (ll <= 0)
return new byte[0];
int l = getLengthMsg();
if (th.equals(TypeHeader.BYTE))
return Bytes.longToByte(l, hpos);
byte[] res = new byte[ll];
byte[] frmt;
if (th.equals(TypeHeader.HEX))
frmt = String.format("%1$" + ll + "X", l).replace(' ', '0')
.getBytes();
else
frmt = String.format("%1$" + ll + "d", l).replace(' ', '0')
.getBytes();
for (int i = 0; i < ll; i++) {
int ppos = hpos[i];
res[ppos] = frmt[i];
}
return res;
}
/**
* Получить полную длину сообщения, включая поля длины сообщения
* @return Полная длина сообщения
*/
public int getLengthFull() {
return getLengthMsg() + ll;
}
/**
* Установить поле
* @param index - индекс параметра
* @param val - значение параметра
* @throws SysException - несответствие формату
*/
private void setField(int index, String value) throws SysException {
System.out.println("Setting "+index +" = " + value);
if ((value == null) || (value.length() == 0)) {
clearField(index);
return;
}
ISO8583Field ff = btff.get(index);
if (ff == null)
throw new SysException("Unknown format for field \"" + index + "\"");
if (index == TYPE || index == TPDU || index == LRC) {
ff.setValue(value);
btf.put(index, ff.getData());
return;
}
if (index <= 1 || index > 128)
return;
ff.setValue(value);
byte[] val = ff.getData();
byte[] o = btf.put(index, val);
if (o == null) { // Требуется установка бита
int ndx = index - 1;
int i = ndx / 8;
if (ndx <= 64) {
byte x = bitsl[i];
x = (byte) ((1 << (7 - (ndx % 8))) | x);
bitsl[i] = x;
} else {
i = i - 8;
byte x = bitsh[i];
x = (byte) ((1 << (7 - (ndx % 8))) | x);
bitsh[i] = x;
if (hcf == 0)
bitsl[0] = (byte) (bitsl[0] | 128);
hcf++;
}
} else // длину предыдущих данных надо вычесть
len = len - o.length;
len += val.length;
}
/**
* Получить значение поля
*
* @param index - индекс поля
* @return значение
*/
private byte[] getField(int index) {
switch (index) {
case 0:
return bitsl;
case 1:
return (hcf > 0) ? bitsh : null;
default:
return btf.get(index);
}
}
/**
* Задано ли поле
* @param index - индекс поля
* @return true - поле задано
*/
private boolean isPresentField(int index) {
switch (index) {
case 0:
return true;
case 1:
return (hcf > 0);
default:
return btf.get(index) != null;
}
}
/**
* Очистить поле
* @param index - индекс поля
*/
private void clearField(int index) {
if (index == 0 || index == 1 || index > 128 || index < TPDU)
return;
byte[] o = btf.remove(index);
if (index < 2 || o == null)
return;
int ndx = index - 1;
int i = ndx / 8;
if (ndx <= 64) {
byte x = bitsl[i];
x = (byte) ((~(1 << (7 - (ndx % 8)))) & x);
bitsl[i] = x;
} else {
i = i - 8;
byte x = bitsh[i];
x = (byte) ((~(1 << (7 - (ndx % 8)))) & x);
bitsh[i] = x;
if (o != null) {
hcf--;
if (hcf == 0)
bitsl[0] = (byte) (bitsl[0] & (~128));
}
}
len = len - o.length;
}
/**
* Сообщение в виде последовательности массивов байт
* @return ArrayList
*/
public ArrayList<byte[]> getMsg() {
ArrayList<byte[]> ls = new ArrayList<byte[]>();
Iterator<Integer> itr = btf.keySet().iterator();
ls.add(getDataLenMsg());
boolean sf = false;
while (itr.hasNext()) {
int i = itr.next();
if (!sf) {
if (i >= 2) {
ls.add(bitsl);
if (hcf > 0)
ls.add(bitsh);
sf = true;
}
}
byte[] val = btf.get(i);
if ((val != null) && (val.length > 0))
ls.add(val);
}
return ls;
}
public String getKey() throws SysException {
return prms.getField("11");
}
public void setKey(String key) throws SysException {
prms.setField("11", key);
}
/**
* Установить формат поля
* @param index - индекс
* @param val - формат
*/
protected void setFieldFormat(int index, String val) {
switch (index) {
case TPDU :
btff.put(index, new ISO8583Field("tpdu", val));
return;
case TYPE :
btff.put(index, new ISO8583Field("type", val));
return;
case LRC:
if ("true".equalsIgnoreCase(val))
btff.put(index, new ISO8583Field("lrc", "2H"));
else
btff.remove(index);
return;
default:
if (index > 1 && index < 128)
btff.put(index, new ISO8583Field(String.valueOf(index), val));
}
}
/**
* Установить формат поля
* @param key - название поля
* @param val - формат
*/
public void setFieldFormat(String key, String val) {
setFieldFormat(getIndexByKey(key), val);
}
/**
* Получить формат поля
* @param key - наименование поля
* @return формат
*/
public String getFieldFormat(String key) {
ISO8583Field f = btff.get(getIndexByKey(key));
if (f != null)
return f.toString();
return null;
}
private class MessageOutputStream extends CloseableOutputStream {
private int i = 0;
private int pos = 0;
private byte[] lengthb = new byte[ll];
private ISO8583Field currentF = null;
private int length = 0;
private Iterator<Integer> itr = null;
private Integer currentK = null;
private void createFieldBuffers() throws IOException {
for (int j = 0; j < bitsl.length; j++) {
int k = 0; // номер бита
int b = bitsl[j] & 0xff; // байт на разбор
int m = 1 << 7; // маска 1000 0000
while (b != 0) {
if ((m & b) > 0) {
int ndx = k + j * 8 + 1;
System.out.println("Bit " + ndx + " is set");
if ((j != 0) || (k != 0)) {
if (btff.get(ndx) == null)
throw new IOException("Type of field is unknown : " + ndx);
btf.put(ndx, null);
}
b = b & (~m);
}
m = m >> 1;
k++;
}
}
hcf = 0; // Количество полей (индекс > 64)
for (int j = 0; j < bitsh.length; j++) {
int k = 0;
int b = bitsh[j] & 0xff;
int m = 128;
while (b != 0) {
if ((m & b) > 0) {
int ndx = k + (bitsh.length + j) * 8 + 1;
if (btff.get(ndx) == null)
throw new IOException("Type of field is unknown : " + ndx);
btf.put(ndx, null);
b = b & (~m);
hcf++;
}
m = m >> 1;
k++;
}
}
if (ll > 0) {
int l = th.equals(TypeHeader.ALPHA) ? Integer.parseInt(new String(
lengthb)) : (int) (0xffff & Bytes.byteReversToLong(lengthb));
byte[] type = btf.get(TYPE);
byte[] tpdu = btf.get(TPDU);
len = l - (((tpdu != null) ? tpdu.length : 0)
+ ((type != null) ? type.length : 0)
+ bitsl.length
+ (hcf > 0 ? bitsh.length : 0));
} else
len = 0;
}
public void write(int b) throws IOException {
if (i < 0) // Если поток закрыт
throw new IOException("The stream is already closed");
//System.out.println("WRITE:"+(char)b);
if (i == 0) { // Общая длина - LL байт
if (ll == 0) {
length = -1;
len = 0;
i++;
pos = 0;
} else {
byte ppos = hpos[pos];
lengthb[ppos] = (byte) b;
pos++;
if (pos == lengthb.length) {
if (th.equals(TypeHeader.ALPHA))
length = Integer.parseInt(new String(lengthb));
else
length = (int) (0xffff & Bytes.byteReversToLong(lengthb));
System.out.println("length[" + lengthb.length + "]..." + length);
len = 0;
i++;
pos = 0;
}
return;
}
}
switch (i) {
case 1: // tpdu - по умолчанию 5 байт
ISO8583Field tpduf = btff.get(TPDU);
if ((tpduf != null) && (!tpduf.isClosed())) {
tpduf.add((byte) b);
pos++;
if (tpduf.isClosed()) {
i++;
pos = 0;
byte[] data = tpduf.getData();
btf.put(TPDU, data);
System.out.println("tpdu[" + data.length + "]..." + tpduf.getDataStr16());
}
break;
} else
i++;
case 2: // Тип сообщения - по умолчанию 4 байта
ISO8583Field typef = btff.get(TYPE);
if ((typef != null) && (!typef.isClosed())) {
typef.add((byte) b);
pos++;
if (typef.isClosed()) {
i++;
pos = 0;
byte[] data = typef.getData();
btf.put(TYPE, data);
System.out.println("type[" + data.length + "]..." + typef.getDataStr16());
}
break;
} else
i++;
case 3: // Битовая маска1 - 8 байт
bitsl[pos] = (byte) b;
pos++;
if (pos == bitsl.length) {
i++;
if ((bitsl[0] & 0x80) == 0) {
createFieldBuffers();
itr = btf.keySet().iterator();
i++;
}
pos = 0;
}
break;
case 4: // Битовая маска2 - 8 байт
bitsh[pos] = (byte) b;
pos++;
if (pos == bitsh.length) {
// printStatusHeader();
createFieldBuffers();
itr = btf.keySet().iterator();
i++;
pos = 0;
}
break;
default: // Поля
if (currentF == null) {
while(currentK == null && itr.hasNext()) {
Integer i = itr.next();
if (i > 0)
currentK = i;
}
if (currentK == null) {
if (length > 0)
throw new IOException(
"There are no enough data or the message's header is incorrect");
close();
return;
}
// System.out.println("Reading field " + currentK);
currentF = btff.get(currentK);
currentF.reset();
}
currentF.add((byte) b);
if (currentF.isClosed()) {
byte[] data = currentF.getData();
btf.put(currentK, data);
if (ll == 0)
len += data.length;
currentF = null;
if (!itr.hasNext()) {
if (length > 1)
throw new IOException(
"There are no enough data or the message's header is incorrect");
close();
return;
}
currentK = itr.next();
i++;
}
}
if (i > 0 && length != -1) {
length--;
//System.out.println("length:"+length);
if (length <= 0) {
close();
}
}
}
public void close() throws IOException {
i = -1;
pos = -1;
}
public boolean isClosed() {
return (i == -1);
}
}
/**
* Получить входящий поток сообщения
*
* @return OutputStream
*/
public CloseableOutputStream getOutputStream() throws IOException {
return new MessageOutputStream();
}
public void sendTo(OutputStream os) throws IOException {
byte[] buf = new byte[getLengthMsg()];
ArrayList<byte[]> res = getMsg();
int l = res.size();
int r = 0;
try {
for (int i = 1; i < l; i++) {
byte[] f = res.get(i);
System.arraycopy(f, 0, buf, r, f.length);
r += f.length;
}
} catch (Throwable e) {
throw new IOException(e);
}
for (byte aBuf : buf) {
String s = Integer.toHexString(0xff & aBuf);
if (s.length() == 1)
s = "0" + s;
System.out.print(s + " ");
}
System.out.println();
os.write(res.get(0));
os.write(buf);
os.flush();
}
public void sendTo1(OutputStream os) throws IOException {
byte[] buf = new byte[getLengthFull()];
ArrayList<byte[]> res = getMsg();
int l = res.size();
int r = 0;
try {
for (int i = 0; i < l; i++) {
byte[] f = res.get(i);
System.arraycopy(f, 0, buf, r, f.length);
r += f.length;
}
} catch (Throwable e) {
throw new IOException(e);
}
for (byte aBuf : buf) {
String s = Integer.toHexString(0xff & aBuf);
if (s.length() == 1)
s = "0" + s;
System.out.print(s + " ");
}
System.out.println();
os.write(buf);
os.flush();
}
private final PrmInterface prms = new Prm();
public PrmInterface getPrmInterface() throws SysException {
return prms;
}
public class Prm implements PrmInterface {
public byte[] getByteField(String key) throws SysException {
try {
int j;
if ((j = key.indexOf('[')) > 0) {
String name = key.substring(0, j);
int i = getIndexByKey(name);
ISO8583Field ff = btff.get(i);
if (ff == null)
return null;
int k = key.indexOf(']', j);
if (k < 0)
k = key.length();
String tag = key.substring(j + 1, k);
byte[] val = ISO8583Msg.this.getField(i);
if (val == null)
return null;
ff.reset();
try {
ff.write(val);
} catch (IOException e) {
throw new SysException(e);
}
return ff.getTag(tag).getBytes(dec_enc);
}
int i = getIndexByKey(key);
return ISO8583Msg.this.getField(i);
} catch (UnsupportedEncodingException e) {
throw new SysException(e);
}
}
public void setField(String key, String val) throws SysException {
int i;
String v = val;
{
int j;
if ((j = key.indexOf('[')) > 0) {
String name = key.substring(0, j);
i = getIndexByKey(name);
ISO8583Field ff = btff.get(i);
int k = key.indexOf(']', j);
if (k < 0)
k = key.length();
String tag = key.substring(j + 1, k);
String fld = getField(name);
ff.setValue(fld);
ff.setTag(tag, val);
v = ff.getValue();
} else
i = getIndexByKey(key);
}
ISO8583Msg.this.setField(i, v);
}
public void clearField(String key) throws SysException {
int index = getIndexByKey(key);
ISO8583Msg.this.clearField(index);
}
public void clearFields() throws SysException {
ArrayList<String> flds = getFieldNames();
for (int i = flds.size() - 1; i >= 0; i--)
clearField(flds.get(i));
}
public boolean isPresentField(String key) throws SysException {
int index = getIndexByKey(key);
return ISO8583Msg.this.isPresentField(index);
}
public String getField(String key) throws SysException {
try {
int j;
if ((j = key.indexOf('[')) > 0) {
String name = key.substring(0, j);
int i = getIndexByKey(name);
int k = key.indexOf(']', j);
if (k < 0)
k = key.length();
String tag = key.substring(j + 1, k);
byte[] val = ISO8583Msg.this.getField(i);
if (val == null)
return null;
ISO8583Field ff = btff.get(i);
ff.reset();
ff.write(val);
return ff.getTag(tag);
}
int i = getIndexByKey(key);
byte[] val = ISO8583Msg.this.getField(i);
if (val == null)
return null;
//if ((i == 0) || (i == 1))
// return Util.getBase64String(val);
ISO8583Field ff = btff.get(i);
ff.reset();
ff.write(val);
return ff.getValue();
} catch (IOException e) {
throw new SysException(e);
}
}
public StringList getFieldNames() {
StringList ls = new StringList();
Iterator<Integer> itr = btf.keySet().iterator();
while (itr.hasNext()) {
Integer index = itr.next();
if (index != null)
ls.add(getKeyByIndex(index));
}
for(int i = 0; i < ls.size(); i++) {
String key = ls.get(i);
int index = getIndexByKey(key);
if (index > 1) {
ls.add(i, "0");
if (hcf > 0)
ls.add(i + 1, "1");
break;
}
}
return ls;
}
}
public static void main(String[] args) throws SysException, IOException {
ISO8583Msg msg = new ISO8583Msg("B10");
msg.setFieldFormat("tpdu", "10H");
msg.setFieldFormat("type", "4H");
msg.setFieldFormat(2, "HH19H");
msg.setFieldFormat(3, "6H");
msg.setFieldFormat(4, "12H");
msg.setFieldFormat(7, "10H");
msg.setFieldFormat(11, "6H");
msg.setFieldFormat(12, "6H");
msg.setFieldFormat(13, "4H");
msg.setFieldFormat(22, "3H");
msg.setFieldFormat(24, "3H");
msg.setFieldFormat(25, "2H");
msg.setFieldFormat(35, "HH37H");
msg.setFieldFormat(37, "12");
msg.setFieldFormat(38, "6");
msg.setFieldFormat(39, "2");
msg.setFieldFormat(41, "8");
msg.setFieldFormat(42, "15");
msg.setFieldFormat(62, "HHH999");
//*
byte[] res = new byte[] {0x00, 0x3E, 0x60, 0x00, 0x00, 0x00, 0x00, 0x02, 0x10, 0x30,
0x38, 0x01, 0x00, 0x0E, (byte)0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0x01, 0x28, 0x19, 0x31, 0x09, 0x08, 0x25, 0x00, 0x00,
0x32, 0x32, 0x33, 0x38, 0x31, 0x35, 0x30, 0x31, 0x35, 0x36, 0x33, 0x38, 0x30,
0x38, 0x30, 0x39, 0x37, 0x35, 0x30, 0x30, 0x33, 0x38, 0x31, 0x30, 0x30, 0x31,
0x32, 0x39};
msg.getOutputStream().write(res);
//*/
msg.printStatusHeader();
PrmInterface prm = msg.getPrmInterface();
StringList flds = prm.getFieldNames();
for (String fld : flds) {
System.out.println(fld + " = " + prm.getField(fld));
}
msg.sendTo(System.out);
System.out.println();
//ISO8583Msg msg1 = new ISO8583Msg("B10");
//msg1.setFieldFormat("tpdu", "10H");
//msg1.setFieldFormat("type", "4H");
//msg1.setFieldFormat(2, "HH19H");
//msg.sendTo(msg1.getOutputStream());
//msg1.printStatusHeader();
//msg1.sendTo(System.out);
//System.out.println();
// prm.setField("3", "123456");
// prm.setField("10", "61000000");
// prm.setField("11", "123456");
// prm.setField("12", "110306000000");
// prm.setField("14", "1303");
// prm.setField("22", "211201213031");
// prm.setField("24", "108");
// prm.setField("26", "6010");
// prm.setField("32", "06000001");
// prm.setField("33", "06000001");
// prm.setField("35", "370000000000000000000000000000000000000");
// prm.setField("37", "000351234567");
// prm.setField("40", "000");
// prm.setField("41", "abc12345");
// prm.setField("42", "000000000000001");
// prm.setField("43",
// "65Tsentr Razvitiya \"Avangard\"\\Ul.Molokova, 12\\Perm\\ RUS");
// prm.setField("45", "03000");
// prm.setField("49", "643");
// prm.setField("51", "643");
// prm.setField("52", "00000000");
// prm.setField("53", "161234567890123456");
// prm.setField("63", val);
// prm.setField("93", "049999");
// prm.setField("99", "049999");
// prm.setField("100", "049999");
// prm.setField("128", "03643");
}
public void printStatusHeader() {
for (int j = 0; j < bitsl.length; j++) {
int k = 0; // номер бита
int b = bitsl[j] & 0xff; // байт на разбор
int m = 128; // маска 1000 0000
while (b != 0) {
if ((m & b) > 0) {
int ndx = k + j * 8 + 1;
System.out.println("Bit " + ndx + " is set");
b = b & (~m);
}
m = m >> 1;
k++;
}
}
for (int j = 0; j < bitsh.length; j++) {
int k = 0;
int b = bitsh[j] & 0xff;
int m = 128; // 1
while (b != 0) {
if ((m & b) > 0) {
int ndx = k + (bitsh.length + j) * 8 + 1;
System.out.println("Bit " + ndx + " is set");
b = b & (~m);
}
m = m >> 1;
k++;
}
}
}
}