深入理解8583协议
最初,金融系统只有IBM这些大公司来提供设备,象各种主机与终端等,后来有很多大大小小的公司进入,怎样设计一个报文协议,解决各公司金融系统之间的报文交换,暂且称该协议叫做ISO8583协议。例如‘回头客会员管理系统’POS机上应用的就是8583报文。
金融行业涉及到的数据内容是比较少的,如交易类型、帐号、帐户类型、密码、交易金额、交易手续费、日期时间、商户代码、2磁3磁数据、交易序列号等,都总结起来不过100个左右的数据。我们可以简单的设计ISO8583,定义128个字段,将所有金融数据字段按照顺序排列,分别对应128个字段。每个数据类型占固定的长度,要发送一个报文时,就将128个字段按照顺序连起来,然后将整串数据包发送出去。
任何金融软件收到ISO8583包后,直接按照我们定义的规范解包即可,因为整个报文的128个字段都预先定义好了。比如第1个字段是“交易类型”,长度为4位,第2个字段位是“帐号”,为19位等等。接收方就可以先取4位,再取接着的19位,依次类推,直到整个数据包128个字段都解完为止。
不过我们有几个问题要思考下:
1、 我怎么知道每个字段的数据类型呢,是数字还是字符?
2、 每个传送的报文都把128个字段都传过去,有时候我可能只需要其中5个字段。
3、 如果我某些字段的长度不固定,属于变长怎么办。
第一个问题。我在定义ISO8583时除了定义每个字段表示什么,还规定其内容是数字或是字符等类型。可能出现的类型有以下几种:字母、数字、特殊字符、年月日等时间、二进制数据。比如“商户类型”字段定义其长度是15,类型为字母。如果“商户类型”同时包括数字和字母呢?那我们就定义一个字段可以同时属于多个类型。
第二个问题。其本质就是如果我只传128个字段的5个字段,接收方怎么知道我传了哪几个字段。我在报文前面加上个包头,用16个字节,即128个bit来表示128个字段中的某个字段是否存在。每个bit如果是1就表示对应的字段在本次报文中存在,如果是0就是不存在。比如,我要发送5个字段,分别属于128个字段中的第2、3、6、8、9字段,我就可以将128bit的报文头填成011001011000000000………0。
我们把这16个字节称为bit map,即位图,用来表示某个位是否存在。考虑到很多时候报文不需要128个字段,可以将报文头由128bit减到64bit,把ISO8583的128个字段中最常见的都放到前64个字段中,只有在需要的时候才把剩下的64bit放到报文里面?
我把64bit报文头的第一位bit用来代表特殊含义,如果该bit为1,则表示64bit后面跟了剩下的64bit报文头;如果第一位bit为0,则表示64bit后面直接是数据字段内容。因为报文头第二个64bit属于有时候有,所以我们叫它Extended bit map扩展位图,报文头最开始的64bit我们叫它Primary bit map主位图。我们直接把扩展位图固定放到128个字段的第一个字段,而主位图每个数据包都有,就强制性放在所有128个字段的前面。
第三个问题。比如第2个字段是“帐号”,是不定长的,可能有的银行帐号是19位,有的是17位等,在字段的开头加上“帐号”的长度。比如帐号是0123456789,一共10位,我们变成100123456789,接收方收到该字段后,它知道ISO8583规定第2个字段“帐号”是变长的,会先取前面2位出来,然后根据长度获取帐号。在规范里面定义某个字段的属性是“LLVAR”,其中LL表示长度,VAR表示数据,两个LL表示两位长,最大是99,三位就是“LLLVAR”,最大是999。
另外考虑到有些人有特殊的要求,我们将128个字段中的部分定义为自定义字段,算是一种扩展。
字段域的定义
typedef struct ISO8583
{
int bit_flag; /*域数据类型0 -- string, 1 -- int, 2 -- binary*/
char *data_name; /*域名*/
int length; /*数据域长度*/
int length_in_byte;/*实际长度(如果是变长)*/
int variable_flag; /*是否变长标志0:否 2:2位变长, 3:3位变长*/
int datatyp; /*0 -- string, 1 -- int, 2 -- binary*/
char *data; /*存放具体值*/
int attribute; /*保留*/
} ISO8583;
ISO8583 Tbl8583[128] =
{
/* FLD 1 */ {0,"BIT MAP,EXTENDED ", 8, 0, 0, 2, NULL,0},
/* FLD 2 */ {0,"PRIMARY ACCOUNT NUMBER ", 22, 0, 2, 0, NULL,0},
/* FLD 3 */ {0,"PROCESSING CODE ", 6, 0, 0, 0, NULL,0},
/* FLD 4 */ {0,"AMOUNT, TRANSACTION ", 12, 0, 0, 1, NULL,0},
/* FLD 5 */ {0,"NO USE ", 12, 0, 0, 0, NULL,0},
/* FLD 6 */ {0,"NO USE ", 12, 0, 0, 0, NULL,0},
/* FLD 7 */ {0,"TRANSACTION DATE AND TIME ", 10, 0, 0, 0, NULL,0},
/* FLD 8 */ {0,"NO USE ", 8, 0, 0, 0, NULL,0},
/* FLD 9 */ {0,"NO USE ", 8, 0, 0, 0, NULL,0},
/* FLD 10 */ {0,"NO USE ", 8, 0, 0, 0, NULL,0},
/* FLD 11 */ {0,"SYSTEM TRACE AUDIT NUMBER ", 6, 0, 0, 1, NULL,0},
/* FLD 12 */ {0,"TIME, LOCAL TRANSACTION ", 6, 0, 0, 0, NULL,0},
/* FLD 13 */ {0,"DATE, LOCAL TRANSACTION ", 4, 0, 0, 0, NULL,0},
/* FLD 14 */ {0,"DATE, EXPIRATION ", 4, 0, 0, 0, NULL,0},
/* FLD 15 */ {0,"DATE, SETTLEMENT ", 4, 0, 0, 0, NULL,0},
/* FLD 16 */ {0,"NO USE ", 4, 0, 0, 0, NULL,0},
/* FLD 17 */ {0,"DATE, CAPTURE ", 4, 0, 0, 0, NULL,0},
/* FLD 18 */ {0,"MERCHANT'S TYPE ", 4, 0, 0, 0, NULL,0},
//省略部分参考规范文档。
/* FLD 123 */ {0,"NEW PIN DATA ", 8, 0, 3, 2, NULL,0},
/* FLD 124 */ {0,"NO USE ",999, 0, 3, 0, NULL,0},
/* FLD 125 */ {0,"NO USE ",999, 0, 3, 0, NULL,0},
/* FLD 126 */ {0,"NO USE ",999, 0, 3, 0, NULL,0},
/* FLD 127 */ {0,"NO USE ",999, 0, 3, 0, NULL,0},
/* FLD 128 */ {0,"MESSAGE AUTHENTICATION CODE FIELD ", 8, 0, 0, 2, NULL,0}
};
如第二域:域名为主帐号,
数据类型为string
长度为22(最长长度不得超过此数)
是个2位变长域
在打包时需在数据域前加上数据的实际长度,如‘19数据值’(即19为长度)
如第三域:域名为处理码,
数据类型为string
长度为6
是个定长域 必须填满6位。
1,信息类型(message type)定义
位图位置:-