获取缓冲区内容与将缓冲区内容返回—Java card开发第一篇

Posted Victor _Lv

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了获取缓冲区内容与将缓冲区内容返回—Java card开发第一篇相关的知识,希望对你有一定的参考价值。

任务描述:

技术分享


先上代码,然后再解析:

package test;

//import HelloWorld;
import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.Util;

public class HelloWorld extends Applet {

	public static void install(byte[] bArray, short bOffset, byte bLength) {
		// GP-compliant JavaCard applet registration
		new HelloWorld().register(bArray, (short) (bOffset + 1),
				bArray[bOffset]);
	}

	public void process(APDU apdu) {
		// Good practice: Return 9000 on SELECT
		if (selectingApplet()) {
			return;
		}
		
		byte[] buf = apdu.getBuffer();//定义缓冲区的引用数组,也就是通过buf数组可以实时获取缓冲区内容
		
		//把终端发过来的apdu命令存到缓冲区当中,返回数据(即apdu中的data)的长度
		short lc = apdu.setIncomingAndReceive();
		//byte[] src = {0};//取得buf数组中data部分
		byte ins = buf[ISO7816.OFFSET_INS];
		switch (ins) {
		case (byte) 0x00:
			//从缓冲区数组buf中取得数据然后复制到src中
			//Util.arrayCopyNonAtomic(buf, ISO7816.OFFSET_CDATA, src, (short)0, (short)buf.length);
			
			apdu.setOutgoingAndSend((short)5, lc);//用这句命令直接把缓冲区的数据返回给终端,
			//其中第一个参数表示偏移量,也就是从缓冲区的第几个元素开始返回,后面一个参数表示返回数据的长度,
			//也就是说最终返回给终端的数据是从缓冲区的第offset个元素开始到offset+length个元素的内容
			break;
		default:
			// good practice: If you don't know the INStruction, say so:
			ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
		}
	}

}

新建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"复制到缓冲区返回给终端输出:

package test;

//import HelloWorld;
import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.Util;

public class HelloWorld extends Applet {

	public static void install(byte[] bArray, short bOffset, byte bLength) {
		// GP-compliant JavaCard applet registration
		new HelloWorld().register(bArray, (short) (bOffset + 1),
				bArray[bOffset]);
	}

	public void process(APDU apdu) {
		// Good practice: Return 9000 on SELECT
		if (selectingApplet()) {
			return;
		}
		//定义缓冲区的引用数组,也就是通过buf数组可以实时获取缓冲区内容
		byte[] buf = apdu.getBuffer();
		
		//把终端发过来的apdu命令存到缓冲区当中,返回数据(即apdu中的data)的长度
		short lc = apdu.setIncomingAndReceive();
		byte[] src = {'h','e','l','l','o'};//取得buf数组中data部分
		byte ins = buf[ISO7816.OFFSET_INS];
		switch (ins) {
		case (byte) 0x00:
			//从缓冲区数组buf中取得数据然后复制到src中
			//Util.arrayCopyNonAtomic(buf, ISO7816.OFFSET_CDATA, src, (short)0, (short)buf.length);
			
			//把字节数组src中的数组复制到缓冲区数组中
			Util.arrayCopyNonAtomic(src, (short)0, buf, (short)0, (short)src.length);
			
			apdu.setOutgoingAndSend((short)0, (short)src.length);//用这句命令直接把缓冲区的数据返回给终端,
			//其中第一个参数表示偏移量,也就是从缓冲区的第几个元素开始返回,后面一个参数表示返回数据的长度,
			//也就是说最终返回给终端的数据是从缓冲区的第offset个元素开始到offset+length个元素的内容
			
			//apdu.setOutgoingAndSend((short)0, lc);//这样改一改就会返回8字节的data
			
			break;
		default:
			// good practice: If you don't know the INStruction, say so:
			ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
		}
	}

}

再来一份代码,这份代码实现了返回Hello world!的功能,和上面类似,但是用了new的方法新建一个缓存数组,new的方法不需要像byte[] src……这样的方法必须要初始化才能用(这里说的用主要是指在Util函数那用)。并且教材也是这样用new的方法定义一个新的缓存数组的。

/*author:lvlang
date:2016-4-1*/

package test;

//import HelloWorld;
import javacard.framework.APDU;
import javacard.framework.Applet;
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.framework.Util;

public class HelloWorld extends Applet {

	//定义一个数据缓冲区以及数据最大长度的常量
	private static final short DATA_MAX= 256;
	private byte[] src;
	
	public static void install(byte[] bArray, short bOffset, byte bLength) {
		// GP-compliant JavaCard applet registration
		new HelloWorld().register(bArray, (short) (bOffset + 1),
				bArray[bOffset]);
	}

	public void process(APDU apdu) {
		// Good practice: Return 9000 on SELECT
		if (selectingApplet()) {
			return;
		}
		
		src = new byte[DATA_MAX];//创建相应数据缓冲区
		byte[] str = {'H','e','l','l','o',' ','w','o','r','l','d','!'};
		short str_len = (short)str.length;
		Util.arrayCopyNonAtomic(str, (short)0, src, (short)0, str_len);
		
		//定义缓冲区的引用数组,也就是通过buf数组可以实时获取缓冲区内容
		byte[] buffer = apdu.getBuffer();
		
		//把终端发过来的apdu命令存到缓冲区当中,返回数据(即apdu中的data)的长度
		short lc = apdu.setIncomingAndReceive();
		
		//获取ins段
		byte ins = buffer[ISO7816.OFFSET_INS];
		switch (ins) {
		case (byte) 0x00:
			//从缓冲区数组buf中取得数据然后复制到src中
			//Util.arrayCopyNonAtomic(buffer, ISO7816.OFFSET_CDATA, src, (short)0, lc);
			
			//把字节数组src中的数组复制到缓冲区数组中
			Util.arrayCopyNonAtomic(src, (short)0, buffer, ISO7816.OFFSET_CDATA, str_len);
			
			apdu.setOutgoingAndSend((short)5, str_len);//用这句命令直接把缓冲区的数据返回给终端,
			//其中第一个参数表示偏移量,也就是从缓冲区的第几个元素开始返回,后面一个参数表示返回数据的长度,
			//也就是说最终返回给终端的数据是从缓冲区的第offset个元素开始到offset+length个元素的内容
			
			break;
		default:
			// good practice: If you don't know the INStruction, say so:
			ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
		}
	}

}


运行结果:

技术分享


最后,说几个小技巧:

(1)运行直接点运行右边的三角形,在弹出窗口中选择项目名字单机就可以直接运行了,并不需要每次都run as这么麻烦

技术分享

(2)打开了运行界面用上面的方法重新运行会实时更新代码带来的改变,不需要先close掉运行窗口

技术分享






















以上是关于获取缓冲区内容与将缓冲区内容返回—Java card开发第一篇的主要内容,如果未能解决你的问题,请参考以下文章

如何获取 Binder 事务缓冲区的内容以进行故障排除

angular读取文件

在 C 中获取 Windows 串行端口的输入缓冲区长度

Java NIO 缓冲区

Java-NIO:直接缓冲区与非直接缓冲区

缓冲区的一些说明