任务描述:
新建applet之后会自动生成一个.java文件,里面已经写好了一些代码框架。这个小练习自己添加的代码就几行。怎么做?
(1)首先,得理解一点最基础的,你这个代码是运行在哪里的,这个关乎从哪接受数据数据又将发送到何处去的问题!显然,都叫java card applet开发了,当然就是运行在card卡片一端的啦!偏偏自己刚开始时搞混了也就没法写代码了。对,这些代码都是以card为主人的,那终端(读写器)呢?终端不就是你在eclipse JCOP Debug页面的命令行输入命令句的那里么!你在命令行那写命令发送就是发送给card,然后card通过你写的applet代码获取到终端发送过来的命令,根据命令进行一系列的处理(也就是代码的运转啦)。
(2)既然代码要把终端发送过来的命令获取并处理,那要怎么获取呢?JCOP已经帮我们封装好了一个东西,叫apdu,这个东西(是一个class)里面存的就是从终端发送过来的命令,对的,终端发送过来的命令就是apdu命令,啥叫apdu?:APDU(应用协议数据单元 application protocol data units)命令。
(3)既然终端来的apdu命令就在apdu这个类当中,拿我们就可以通过使用这个apdu类来获取到终端发送过来的命令中的所有东西,那这个所谓的命令包括什么东西呢?上图:
对,这就是终端发送过来的apdu命令的几大模块,每块都如其名称(缩写),第一个字节(注意真的是字节不是两个比特!)表示的是class指令的类,第二个字节是ins指令编码,第三和第四个字节是参数1和参数2,第五个字节LC表示的是后面Data的长度(多少个字节),然后Data部分就是真正的数据了,最长可以是255个字节,最后的LE字节表示的是期望卡片发回来的字节长度,比如08就表示我要卡片你给我发回来8字节的数据(不包括9000这两个字节),如果是00就表示多多益善,卡片你能发回来多长数据给我就都发过来。
嗯,这是终端发送给card的apdu命令,那卡片发送给终端的apdu命令结构是怎样的呢?如下图:
第一块表示的是要返回的数据,长度不定,这里联系到上面刚刚说到的终端期望card要发回多长的数据,说的就是这个数据的长度。后面两个字节是两个状态字节,两个加起来是9000时表示正常完成了指令的处理,也就是搞掂没问题!
(4)嗯,上面的都是写代码之前必须了解的基础,然后就说回代码中吧:首先得把终端发送过来的apdu命令读取到卡片的缓冲区当中,有两个方法,一个是用函数:Short setIncomingAndReceive(),第二个是用函数public short receiveBytes(short bOff),只接受一次数据时用前面的函数就够了,后面的是用于多次接受apdu的。详细的解释在教材《JAVA智能卡原理与应用开发》的101页有。对,有教材啊!只要你愿意花时间啃教材,哪还怕不会敲代码?所以别从头到尾都是对着eclipse想怎样敲代码,把原理搞懂了代码就能秒杀!所以说你是不是越来越码农化了?危险啊!!戒之慎之!!!
(5)嗯,上面已经把apdu存储都卡片的缓冲区当中了,然后通过
byte[] buf = apdu.getBuffer();
这句命令,把缓冲区的内容存储到buf字节数组当中。注意,这里相当于是用一个指针指向了缓冲区了,所以缓冲区的变化会实时更新到buf数组当中。
(6)然后通过上面的buf数组,就可以获取到apdu命令的每一个模块了,这里需要了解个鬼东西叫ISO7816 接口,详细的解释在刚才说的那本教材的105页,回去翻翻看吧。总之,用
byte ins = buf[ISO7816.OFFSET_INS];
这句代码就能读取出apdu中的ins模块,啥模块?看看上面刚说的apdu命令的几大模块!
(7)然后怎样获取到data数据模块部分呢?这里需要用到一个特殊的函数:
Util.arrayCopyNonAtomic(buf, ISO7816.OFFSET_CDATA, src, (short)0, lc);
这句命令是将buf也就是缓冲区数组里面的内容copy一份给src字节数组中(当然要实现定义好src数组并初始化),第二个参数表示offset,也就是从buf数组的哪一位开始复制,最后一个参数表示复制的长度,这里lc表示的就是apdu命令中data的长度,所以就是从data部分的第一个字节开始,把全部data字节复制出来。
这句命令非常重要,因为如果要自定义一个字节数组src,想把它里面的内容返回给终端,就要也是通过这条命令把src的内容复制给buf缓冲区数组,只需要把这句命令的参数更改或调换下位置即可。
(8)然后就是将缓冲区的内容发送回给终端,注意,终端发送过来的apdu命令是先存在缓冲区当中,然后card要发送回去的apdu命令也是先放到缓冲区再发给终端的,所以就会产生覆盖,所以可以先把缓冲区的内容取出来放到另一个数组再覆盖。当然,上面的这个题只需要直接把原来缓冲区的部分内容直接发送回给终端就够了,如下代码:
apdu.setOutgoingAndSend((short)5, lc);
这句代码表示的就是把缓冲区的内容发回给终端,第一个参数表示的是offset偏移量,表示从缓冲区的第几个字节开始发送(注意(short)0表示从第一个字节开始发送,那(short)5就表示的是从第6个字节开始发送。为什么是第6个开始?看看Capdu命令的几大结构,前面的是命令头部,第6个字节开始才是真正的数据[其实计算机网络里面的IP啥的也是有这样的头部,可见IT里面很多基础知识或者说经验是共用的]),后面的是长度,要把缓冲区的多长字节发送给终端(这和一开始终端期望发回数据的长度有联系)。
嗯,针对这题就这几点了,最后运行applet,在命令行发送命令,最后显示出card返回的内容,这题是卡片直接将终端发送的数据原封不动地返回给终端:
先在命令行发送select applet的id
然后就可以发送内容命令了:
看到那两个箭头没,没错,右向箭头表示终端发送给卡片,左向箭头表示从卡片回收。最终卡片发回的命令是:原data+9000。
补充一个改进版本的代码,这份代码可以把存在字节数组的"hello"复制到缓冲区返回给终端输出:
再来一份代码,这份代码实现了返回Hello world!的功能,和上面类似,但是用了new的方法新建一个缓存数组,new的方法不需要像byte[] src……这样的方法必须要初始化才能用(这里说的用主要是指在Util函数那用)。并且教材也是这样用new的方法定义一个新的缓存数组的。
运行结果:
最后,说几个小技巧:
(1)运行直接点运行右边的三角形,在弹出窗口中选择项目名字单机就可以直接运行了,并不需要每次都run as这么麻烦
(2)打开了运行界面用上面的方法重新运行会实时更新代码带来的改变,不需要先close掉运行窗口