简单聊聊ISO8583报文

网上应该有不少关于8583的文章,这个算是属于老生常谈了,但是要找一篇细致的,容易理解的可能还真不太好找,那我们今天就来简单的聊聊。

8583协议是基于ISO8583报文国际标准的包格式的通讯协议,8583包最多由128个字段域组成,每个域都有统一的规定,并有定长与变长之分。8583包前面一段为位图,它是打包解包确定字段域的关键代替。8583协议多在POS机的开发上使用。 —— 百度百科

数据格式及符号定义

这个其实银联给的文档上已经很全了,我们再复习下

—— M 强制域(Mandatory),此域在该消息中必须出现否则将被认为消息格式出错。
—— C 条件域(Conditional),此域在一定条件下出现在该消息中,具体的条件请参考备注中的说明。
—— O 选用域(Optional),此域在该消息中由发送方自选。
—— Space 此域在该种消息中不出现。
—— A 字母a-z
—— n 数字0-9
—— s 特殊字符
—— an 字母和数字字符
—— ans 字母、数字和特殊字符
—— MM 月
—— DD 日
—— YY 年
—— hh 小时
—— mm 分
—— ss 秒
—— LL 允许的最大长度为99
—— LLL 允许的最大长度为999
—— VAR 可变长度域
—— b 数据的二进制表示,后跟数字表示位(bit)的个数
—— B 用于表示变长的二进制数,后跟数字表示二进制数据所占字节(Byte)的个数
—— z 按GB/T 15120和GB/T 17552的2、3磁道编码
—— cn BCD压缩编码数值

其它的都不说了,我们就聊聊 LLVAR、LLLVAR BCD这三个数据格式吧

  • LLVAR 代表两位变长,允许该域的最大长度就是99,比如说35域,二磁道数据,那它在8583报文当中的表现形式就是是37 L(len)+"6225xxxxxxx"V(value),表示长度37,数据就是实际的卡磁数据(当然有些地方,长度可能不会是37,因为算法的原因,每家支付公司都会有自己变种的算法,但是实际上规则不变,就是长度+数据,长度在99以内)
    -LLLVAR 代表三位变长,允许该域的最大长度是999,比如说60域,消费的时候存放的是
    -BCD 不是传统的8421BCD编码,类似这样的一个长度为8的HEX字符串“22001686”如果转换为BCD 就会变成一个长度为4的数组0x22,0x00,0x16,0x86,长度显示为0x00,0x08

OK,这些基本的概念统一后,我们就可以上干货了(网上大部分都是C的,咱们今天来个C#的,方便在PC上调试),看下面代码,首先是刚刚咱们说的数据格式,还有8583每条记录的数据结构

        public enum Iso8583DataType
        {
            /// <summary>
            /// 字母字符,A至Z或a至z,左靠,右部多余部分填空格
            /// </summary>
            A,
            /// <summary>
            /// 二进制位,左靠,右部多余部分填零。
            /// </summary>
            B,
            /// <summary>
            /// 数字字符,0至9,右对齐,左补零。若表示金额,无小数点符号,最后两位表示角分
            /// </summary>
            N,
            /// <summary>
            /// 特殊字符
            /// </summary>
            S,
            /// <summary>
            /// 借贷符号,贷记为“C”,借记为“D”,后接一个数字型金额数据元。
            /// </summary>
            X,
            /// <summary>
            /// 由ISO 7811和ISO 7813制定的磁卡第二、三磁道数据
            /// </summary>
            Z,
            /// <summary>
            /// 字母或数字,左靠,右部多余部分填空格
            /// </summary>
            AN,
            /// <summary>
            /// 字母、数字或特殊符号,左靠,右部多余部分填空格
            /// </summary>
            ANS
        }

        public struct ISO8583
        {
            /// <summary>
            /// 位标志
            /// </summary>
            public short bit_flag;
            /// <summary>
            /// 域字段描述
            /// </summary>
            public byte[] data_name;// = new byte[37];  
            /// <summary>
            /// 域字段长度
            /// </summary>
            public short length;
            /// <summary>
            /// 域字段数据类型
            /// </summary>
            public Iso8583DataType attribute;
            /// <summary>
            /// 域字段是否可变长度字段 0:不变长
            /// </summary>
            public short variable_flag;
            /// <summary>
            /// 域字段长度
            /// </summary>
            public byte[] data;// = new byte[128];
        };

这样的注释应该不需要再啰嗦什么了吧。
然后就是,初始化,打包,解包了,请看代码
初始化

        private void master_Init(int iPos, string data_name, short length, Iso8583DataType attribute, short variable_flag, byte[] data)
        {
            if ((iPos < 0) || (iPos > 128))
                return;
            master[iPos].bit_flag = 0;
            master[iPos].data_name = Encoding.ASCII.GetBytes(data_name);
            master[iPos].length = length;
            master[iPos].attribute = attribute;
            master[iPos].variable_flag = variable_flag;
            master[iPos].data = data;
        }

打包的主要部分

                    switch (master[i].attribute)
                    {
                        case Iso8583DataType.N:
                        case Iso8583DataType.Z:
                            if (master[i].variable_flag > 0)        // 该域(字段)是变长
                            {
                                if ((sLen.Length % 2) == 1)
                                    sLen = "0" + sLen;              // 保证长度是偶数
                                tmpstr = Encoding.ASCII.GetString(master[i].data);
                                //tmpstr = tmpstr.PadLeft(master[i].length, '0');

                                if ((tmpstr.Length % 2) == 1)
                                    tmpstr = tmpstr + "0";          // 保证长度是偶数,用左靠BCD码表示

                                tmpstr = sLen + tmpstr;
                                master[i].length = Convert.ToInt16(master[i].data.Length);   // 因为变长,所以更新域长度
                            }
                            else
                            {
                                tmpstr = Encoding.ASCII.GetString(master[i].data);
                                tmpstr = tmpstr.PadLeft(master[i].length, '0');
                                
                                if ((tmpstr.Length % 2) == 1)
                                    tmpstr = "0" + tmpstr;          // 保证长度是偶数,用右靠BCD码表示
                            }

                            tmpbuf = AscToBcd(tmpstr, tmpstr.Length / 2);   // BCD 码
                            tmpbuf.CopyTo(buf, offset);
                            offset += (tmpstr.Length / 2);

                            break;
                        case Iso8583DataType.AN:
                        case Iso8583DataType.ANS:
                            if (master[i].variable_flag > 0)        // 该域(字段)是变长
                            {
                                if ((sLen.Length % 2) == 1)
                                    sLen = "0" + sLen;              // 保证长度是偶数

                                bLen = AscToBcd(sLen, sLen.Length / 2);// Encoding.ASCII.GetBytes(sLen);
                                Array.Copy(bLen, 0, buf, offset, sLen.Length / 2);
                                offset += (sLen.Length / 2);// master[i].variable_flag;

                                tmpstr = Encoding.ASCII.GetString(master[i].data);
                                tmpbuf = Encoding.ASCII.GetBytes(tmpstr);

                                Array.Copy(tmpbuf, 0, buf, offset, tmpbuf.Length);
                                //tmpbuf.CopyTo(buf, offset);
                                offset += master[i].data.Length;// master[i].length;

                                master[i].length = Convert.ToInt16(master[i].data.Length);
                            }
                            else
                            {
                                tmpstr = Encoding.ASCII.GetString(master[i].data);
                                tmpstr = tmpstr.PadRight(master[i].length);
                                tmpbuf = Encoding.ASCII.GetBytes(tmpstr);

                                tmpbuf.CopyTo(buf, offset);
                                offset += master[i].length;
                            }
                            break;
                        default:
                            if (master[i].variable_flag > 0)        // 该域(字段)是变长
                            {
                                bLen = Encoding.ASCII.GetBytes(sLen);
                                bLen.CopyTo(buf, offset);                                   // +长度
                                offset += master[i].variable_flag;
                            }

                            tmpbuf = master[i].data;

                            tmpbuf.CopyTo(buf, offset);
                            offset += master[i].length;
                            break;
                    }

解包

                    switch (master[i].attribute)
                    {
                        case Iso8583DataType.N:
                            if (master[i].variable_flag > 0)        // 该域变长
                            {
                                if ((master[i].variable_flag % 2) == 1)
                                {
                                    Array.Copy(buf, offset, st_len, 0, (master[i].variable_flag + 1) / 2);        // 该域数据长度内容
                                    offset += (master[i].variable_flag + 1) / 2;
                                }
                                else
                                {
                                        Array.Copy(buf, offset, st_len, 0, (master[i].variable_flag / 2));
                                        offset += (master[i].variable_flag / 2);                              
                                }
                                // offset += master[i].variable_flag;

                                string strLen = (BitConverter.ToString(st_len)).Replace("-", "");
                                switch (master[i].variable_flag)
                                {
                                    case 1:
                                    case 2:
                                        iLen = Convert.ToInt16(strLen.Substring(0, 1)) * 10 + Convert.ToInt16(strLen.Substring(1, 1));
                                        break;
                                    case 3:
                                        iLen = Convert.ToInt16(strLen.Substring(1, 1)) * 100 + Convert.ToInt16(strLen.Substring(2, 1)) * 10 + Convert.ToInt16(strLen.Substring(3, 1));
                                        break;
                                }
                                if ((iLen % 2) == 1)
                                    iTempLen = iLen + 1;
                                else
                                    iTempLen = iLen;

                                    bTempData = new byte[iTempLen / 2];
                                    Array.Copy(buf, offset, bTempData, 0, iTempLen / 2);

                                    sTempData = (BitConverter.ToString(bTempData)).Replace("-", "");
                                    sTempData = sTempData.Substring(0, iLen);

                                    //writelog.WriteLog1("len", iLen.ToString());
                                    master[i].data = new byte[iLen];

                                    master[i].data = Encoding.ASCII.GetBytes(sTempData);

                                    offset += (iTempLen / 2);
                            }
                            else
                            {
                                if ((master[i].length % 2) == 1)
                                    iTempLen = master[i].length + 1;
                                else
                                    iTempLen = master[i].length;

                                bTempData = new byte[iTempLen / 2];
                                Array.Copy(buf, offset, bTempData, 0, iTempLen / 2);

                                sTempData = (BitConverter.ToString(bTempData)).Replace("-", "");

                                    sTempData = sTempData.Substring(sTempData.Length - master[i].length, master[i].length);
                                master[i].data = new byte[master[i].length];

                                master[i].data = Encoding.ASCII.GetBytes(sTempData);
                                offset += (iTempLen / 2);
                            }
                            break;
                        case Iso8583DataType.AN:
                        case Iso8583DataType.ANS:
                            if (master[i].variable_flag > 0)        // 该域变长
                            {
                                if ((master[i].variable_flag % 2) == 1)
                                {
                                    Array.Copy(buf, offset, st_len, 0, (master[i].variable_flag + 1) / 2);        // 该域数据长度内容
                                    offset += (master[i].variable_flag + 1) / 2;
                                }
                                else
                                {
                                    Array.Copy(buf, offset, st_len, 0, (master[i].variable_flag / 2));
                                    offset += (master[i].variable_flag / 2);
                                }
                                // offset += master[i].variable_flag;

                                string strLen = (BitConverter.ToString(st_len)).Replace("-", "");
                                switch (master[i].variable_flag)
                                {
                                    case 1:
                                    case 2:
                                        iLen = Convert.ToInt16(strLen.Substring(0, 1)) * 10 + Convert.ToInt16(strLen.Substring(1, 1));
                                        break;
                                    case 3:
                                        iLen = Convert.ToInt16(strLen.Substring(1, 1)) * 100 + Convert.ToInt16(strLen.Substring(2, 1)) * 10 + Convert.ToInt16(strLen.Substring(3, 1));
                                        break;
                                }
                                master[i].data = new byte[iLen];
                                Array.Copy(buf, offset, master[i].data, 0, iLen);
                                offset += iLen;// master[i].length;
                            }
                            else
                            {
                                master[i].data = new byte[master[i].length];
                                Array.Copy(buf, offset, master[i].data, 0, master[i].length);
                                offset += master[i].length;
                            }
                            break;
                        default:
                            if (master[i].variable_flag > 0)        // 该域变长
                            {
                                Array.Copy(buf, offset, st_len, 0, master[i].variable_flag);        // 该域数据长度内容
                                offset += master[i].variable_flag;

                                switch (master[i].variable_flag)
                                {
                                    case 1:
                                        iLen = Convert.ToInt16(st_len[0]) - 48;
                                        break;
                                    case 2:
                                        iLen = (Convert.ToInt16(st_len[0]) - 48) * 10 + (Convert.ToInt16(st_len[1]) - 48);
                                        break;
                                    case 3:
                                        iLen = (Convert.ToInt16(st_len[0]) - 48) * 100 + (Convert.ToInt16(st_len[1]) - 48) * 10 + (Convert.ToInt16(st_len[2]) - 48);
                                        break;
                                }
                                master[i].data = new byte[iLen];
                                Array.Copy(buf, offset, master[i].data, 0, iLen);
                                offset += iLen;
                            }
                            else
                            {
                                master[i].data = new byte[master[i].length];
                                Array.Copy(buf, offset, master[i].data, 0, master[i].length);
                                offset += master[i].length;
                            }
                            break;
                    }

OK,估计大家也不乐意看这些内容,简单说一下就是我们根据bit map 来确定那一个域是否有值,若是有值,再看他是什么格式,不同的格式(A,N,LLVAR等),有不同的对齐方法(左对齐,右对齐),然后来根据这些规则来进行打包,解包。

其实8583不复杂,只要记得初始化,打包,解包,获取某一个域的值,然后进行业务判定,就可以完成相当一部分的工作了。其它比如有些变种,我们可以根据不同的数据类型,在打印或者是解包的时候,改变一下相应的需求。128域的8583报文同样是这个规则,只要掌握了规则,其它的都是在这个基础上进行演变。目前只想到了这么多,其它的想到了,再补上吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容

  • 简介 用简单的话来定义tcpdump,就是:dump the traffic on a network,根据使用者...
    保川阅读 5,933评论 1 13
  • 前言 通常刷卡交易中是用到的8583报文是在前置后台打包,或是在POS端打包,手机端只是作为通信链路的媒介存在,不...
    黑羽肃霜阅读 554评论 0 0
  • 国家电网公司企业标准(Q/GDW)- 面向对象的用电信息数据交换协议 - 报批稿:20170802 前言: 排版 ...
    庭说阅读 10,800评论 6 13
  • 好长时间没来简书总结笔记了,源于新入职适应环境要学很多东西,有点小忙,当然这也是为自己的懒惰找的借口啦,哈哈~ 近...
    夜远曦白阅读 1,973评论 6 4
  • 醉里挑灯看剑,梦回吹角连营。八百里分麾下炙,五十弦翻塞外声,沙场秋点兵。 马作的卢飞快,弓如霹雳弦惊。了却君王天...
    冯少阅读 282评论 0 1