| 首页 | 电脑常识 | 程序设计 | 操作系统 | 语法 | 病毒安全 | 软件教程 | 硬件 | 数据库 | 多媒体 | 认证 | 下载 | 
首页>>程序设计 >>J2ME无线编程
JavaCard小应用程序结构

JavaCard小应用程序结构

电脑学习网,xuef.com,最全最新最权威的电脑知识网站.
免费计算机学习教程,电脑入门指南.


  Sun提供了两个模型用来设计JavaCard应用程序(javacard.framework.Applet):传统的JavaCard API和JavaCard Remote Method Invocation(Java Card远程方法调用,JCRMI)编程接口。我们可以使用其中任何一个来编写Java Card小应用程序,开发Java Card小应用程序是一个两步的过程:



  1.定义负责主应用程序和小应用程序之间接口的命令和响应APDU。

  2.编写Java Card小应用程序本身

  JavaCard小应用程序结构

  首先,让我们看一下Java Card小应用程序的结构。

  列表1说明了一个典型的JavaCard小应用程序是如何构造的:

import javacard.framework.*
...
public class MyApplet extends Applet {
// Definitions of APDU-related instruction codes
...
MyApplet() {...} // Constructor
// Life-cycle methods
install() {...}
select() {...}
deselect() {...}
process() {...}
// Private methods
...
}
列表⒈一个JavaCard小应用程序的结构

  一个JavaCard小应用程序通常定义它的APDU相关指令、它的构造器,然后是Java Card小应用程序的生命周期方法:install ()、select ()、deselect ()和process ()。最后,它定义任何合适的私有方法。





  定义APDU指令

  不同的Java Card应用程序有不同的接口(APDU)需求。一个信用卡小应用程序可能支持验证PIN号码的方法,产生信用和借记事务,并且核对帐目余额。一个健康保险小应用程序可能提供访问健康保险信息、保险总额限制、医生、病人信息等等信息的权限。你定义的精确的APDU全依赖你的应用程序需求。

  举例来说,让我们亲身感受一下如何开发经典的Wallet信用卡示例。你可以在Sun Java Card Development工具箱的samples目录下得到这个及其他示例的完整的代码。

  我们将开始定义一个APDU命令来查询保存在Java Card设备上的当前余额数。注意,在一个实际信用卡应用程序中,我们还将定义信用并且借记命令。我们将分配我们的Get Balance APDU一个0x80指令类和一个0x30指令。Get Balance APDU不需要任何指令参数或者数据区,并且预期的响应由包含余额的两个字节组成。下一个表格描述Get Balance APDU命令:

  表1 - Get Balance APDU命令

Name CLA INS P1 P2 Lc Data Field Le (size of response) Get Balance 0x80 0x30 0 0 N/A N/A 2
  虽然Get Balance命令未定义输入数据,但是有一些命令APDU将定义输入数据。举例来说,让我们定义验证从卡片读取器中传递来的PIN号码的Verify PIN APDU命令。下一个表格定义Verify APDU:

  表格2- Verify APDU命令

Name CLA INS P1 P2 Lc Data Field Le (size of response) Verify PIN 0x80 0x20 0 0 PIN Len PIN Value N/A
  注意Le字段,响应的大小是N/A。这是因为没有到Verify PIN的应用程序特定响应;成功或者失败通过响应APDU中的状态字标明。

  为了简化APDU过程,javacard.framework.ISO7816接口定义了许多常数,我们可以用来从process ()方法传送到小应用程序中的输入缓冲器中检索各个的APDU字段:

...
byte cla = buf[ISO7816.OFFSET_CLA];
byte ins = buf[ISO7816.OFFSET_INS];
byte p1 = buf[ISO7816.OFFSET_P1];
byte p2 = buf[ISO7816.OFFSET_P2];
byte lc = buf[ISO7816.OFFSET_LC];
...
// Get APDU data, by copying lc bytes from OFFSET_CDATA, into
// reusable buffer databuf.
Util.arrayCopy(buf, ISO7816.OFFSET_CDATA, databuf, 0, lc);
...
列表2、使用ISO-7816-4常数

  现在我们将定义用于Get Balance和Verify命令的类(CLA)和指令(INS),Get Balance响应的大小,以及在如果PIN验证失败后的出错返回代码。

...
// MyApplet APDU definitions
final static byte MyAPPLET_CLA = (byte)0x80;
final static byte VERIFY_INS = (byte)0x20;
final static byte GET_BALANCE_INS = (byte) 0x30;
final static short GET_BALANCE_RESPONSE_SZ = 2;
// Exception (return code) if PIN verify fails.
final static short SW_PINVERIFY_FAILED = (short)0x6900;
...
列表3、小应用程序的APDU定义

  接下来,让我们定义小应用程序构造器和生命循环方法。


  构造器

  定义一个初始化这个对象的状态的私有构造器。这个构造器被从install()方法调用;换句话说,构造器只在小应用程序的生命周期期间被调用:

/**
* Private Constructor.
*/
private MyApplet() {
super();

// ... Allocate all objects needed during the applet's
// lifetime.
ownerPin = new OwnerPIN(PIN_TRY_LIMIT, MAX_PIN_SIZE);
...
// Register this applet instance with the JCRE.
register();
}
列表4、小应用程序构造器

  在这个示例中,我们使用一个javacard.framework.OwnerPIN,一个描述个人识别号码的对象;这个对象将存在于Java Card小应用程序的一生。回忆一下本文第一部分中的"管理内存和对象",在一个Java Card环境中,数组和基本类型将在对象声明中被声明,而且你应该最小化对象实例,以利于对象重用。在小应用程序生命周期期间,以创建对象一次。做到这点的一个简易的方法是在构造器中创建对象,并且从install()方法中调用这个构造器-- install()本身在小应用程序生命周期中只被调用一次。为了利于再使用,对象应该保持在范围中或者适当的引用中,用于小应用程序的生命周期,并且它们的成员的值在再使用之前适当的重置。因为一个垃圾收集程序并不总是可用,一个应用程序可能从不回收被分配给对象的存储空间。

  install ()方法

  JCRE在安装过程期间调用install()。你必须覆盖这个从javacard.framework.Applet类继承来的方法,并且你的install ()方法必须实例化这个小应用程序,如下:

/**
* Installs the Applet. Creates an instance of MyApplet. The
* JCRE calls this static method during applet installation.
* @param bArray install parameter array.
* @param bOffset where install data begins.
* @param bLength install parameter data length.
* @throw ISOException if the install method fails.
*/
public static void install(byte[] bArray, short bOffset, byte bLength)
throws ISOException {
// Instantiate MyApplet
new MyApplet();
...
}
列表5、install ()小应用程序生命周期方法

  install ()方法必须直接或者间接地调用register ()方法来完成安装;如果这步失败将导致安装失败。在我们的范例中,构造器调用register()。

  select()方法

  JCRE调用select()来通知已经被选作APDU过程的小应用程序。你不必实现这个方法,除非你想提供会话初始化或者个性化。select()方法必须返回true来指明它即将处理进入的APDU,或者返回false来拒绝选择。javacard.framework.Applet类的默认实现返回true。

/**
* Called by the JCRE to inform this applet that it has been
* selected. Perform any initialization that may be required to
* process APDU commands. This method returns a boolean to
* indicate whether it is ready to accept incoming APDU commands
* via its process() method.
* @return If this method returns false, it indicates to the JCRE
* that this Applet declines to be selected.
*/
public boolean select() {
// Perform any applet-specific session initialization.
return true;
}
列表6、select()小应用程序生命周期方法


  deselect()方法

  JCRE调用deselect()来通知小应用程序,它已经被取消选定了。你不必实现这个方法,除非你想提供会话清除。javacard.framework.Applet类的默认实现什么都不做。

/**
* Called by the JCRE to inform this currently selected applet
* it is being deselected on this logical channel. Performs
* the session cleanup.
*/
public void deselect() {
// Perform appropriate cleanup.
ownerPin.reset();
}
列表7、deselect()小应用程序生命周期方法

  在我们的示例中,我们重置了PIN(个人识别号码)。

  process()方法--感受APDU的全过程

  一旦一个小应用程序已经被选择,它将准备接收命令APDUs,如在本文第一部分中"Java Card小应用程序的生命周期"描写的。

  回想一下被从主机端(客户端)应用程序发送到卡片的APDU命令,如下面的说明:


Figure 3. APDU 指令和响应流程

  每次JCRE接收一个APDU命令(通过卡片读取器从主应用程序,或者如果使用Sun Java Card Development工具箱就通过apdutool),它调用小应用程序的process()方法,把输入命令当作一个参数传送给它(APDU命令输入缓冲中的参数)。process()方法然后:

  1.摘录APDU CLA和INS字段

  2.检索应用程序特定的P1、P2和数据字段

  3.处理APDU数据

  4.生成并发送一个响应

  5.优雅地返回,或者抛出相应的ISO异常

  在此时,JCRE发送合适的状态字回到主应用程序,通过读卡器。

  列表8显示一个样本process()方法。

/**
* Called by the JCRE to process an incoming APDU command. An
* applet is expected to perform the action requested and return
* response data if any to the terminal.
*
* Upon normal return from this method the JCRE sends the ISO-
* 7816-4-defined success status (90 00) in the APDU response. If
* this method throws an ISOException the JCRE sends the
* associated reason code as the response status instead.
* @param apdu is the incoming APDU.
* @throw ISOException if the process method fails.
*/
public void process(APDU apdu) throws ISOException {

// Get the incoming APDU buffer.
byte[] buffer = apdu.getBuffer();

// Get the CLA; mask out the logical-channel info.
buffer[ISO7816.OFFSET_CLA] =
(byte)(buffer[ISO7816.OFFSET_CLA] & (byte)0xFC);

// If INS is Select, return - no need to process select
// here.
if ((buffer[ISO7816.OFFSET_CLA] == 0) &&
(buffer[ISO7816.OFFSET_INS] == (byte)(0xA4)) )
return;

// If unrecognized class, return "unsupported class."
if (buffer[ISO7816.OFFSET_CLA] != MyAPPLET_CLA)
ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);

// Process (application-specific) APDU commands aimed at
// MyApplet.
switch (buffer[ISO7816.OFFSET_INS]) {

case VERIFY_INS:
verify(apdu);
break;

case GET_BALANCE_INS:
getBalance(apdu);
break;

default:
ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
break;
}
}
列表8、process()小应用程序生命周期方法


  我们的process()方法调用getBalance()和verify()方法。列表9显示getBalance ()方法,处理get balance APDU并且返回保存在卡片中的余额。

/**
* Retrieves and returns the balance stored in this card.
* @param apdu is the incoming APDU.
*/
private void getBalance(APDU apdu) {

// Get the incoming APDU buffer.
byte[] buffer = apdu.getBuffer();

// Set the data transfer direction to outbound and obtain
// the expected length of response (Le).
short le = apdu.setOutgoing();

// If the expected size is incorrect, send a wrong-length
// status word.
if (le != GET_BALANCE_RESPONSE_SZ)
ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);

// Set the actual number of bytes in the response data field.
apdu.setOutgoingLength((byte)GET_BALANCE_RESPONSE_SZ);

// Set the response data field; split the balance into 2
// separate bytes.
buffer[0] = (byte)(balance >> 8);
buffer[1] = (byte)(balance & 0xFF);

// Send the 2-byte balance starting at the offset in the APDU
// buffer.
apdu.sendBytes((short)0, (short)GET_BALANCE_RESPONSE_SZ);
}
列表9、处理Get Balance APDU

  getBalance ()方法通过调用APDU.getBuffer ()方法取得一个引用到APDU缓冲。在返回响应(当前余额)之前,小应用程序必须设置JCRE模式通过调用APDU.setOutgoing()方法来发送,方便地返回期望的响应大小。我们还必须设置响应数据字段中的字节的实际数字,通过调用APDU.setOutgoingLenth()。APDU缓冲中的响应事实上通过调用APDU.sendBytes ()发送。

  小应用程序不直接发送返回码(状态字);一旦小应用程序调用APDU.setOutgoing ()并且提供任何请求的信息,JCRE注意这个状态字。状态字的值依靠process()方法如何使返回到JCRE来变化。如果所有的已经正常运行,JCRE将返回9000,指明无错。你的小应用程序可以通过抛出一个定义在ISO7816接口中的异常返回一个错误代码。 在列表9中,如果期望响应的大小不正确,方法getBalance()抛出一个ISO7816.SW_WRONG_LENGTH代码。对于有效的状态码值,请参阅ISO7816接口的定义,或者回到本文第一部分的"响应APDU"。

  现在让我们看看在列表10中的verify()方法。因为我们定义的验证PIN APDU命令包含数据,verify()方法必须调用APDU.setIncomingAndReceive ()方法,设置JCRE为接收模式,然后接收输入数据。

/**
* Validates (verifies) the Owner's PIN number.
* @param apdu is the incoming APDU.
*/
private void verify(APDU apdu) {

// Get the incoming APDU buffer.
byte[] buffer = apdu.getBuffer();

// Get the PIN data.
byte bytesRead = (byte)apdu.setIncomingAndReceive();

// Check/verify the PIN number. Read bytesRead number of PIN
// bytes into the APDU buffer at the offset
// ISO7816.OFFSET_CDATA.
if (ownerPin.check(buffer, ISO7816.OFFSET_CDATA, byteRead)
== false )
ISOException.throwIt(SW_PINVERIFY_FAILED);
}
列表10、处理验证APDU

  这个方法通过调用APDU.getBuffer()取得一个到APDU缓冲的引用,调用APDU.setIncomingAndReceive()来接收命令数据,从输入的APDU缓冲中取得PIN数据,并且验证PIN。一个验证失败导致状态码6900被发送回主应用程序。

  有时输入的数据比填充到APDU缓冲中的数据要多,并且小应用程序必须大块的读取数据知道没有数据可以读取。在此情况下,我们必须首先调用APDU.setIncomingAndReceive(),然后重复地调用APDU.receiveBytes(),直到不再有数据可用。列表11显示如何读取大量输入数据。

...
byte[] buffer = apdu.getBuffer();
short bytes_left = (short) buffer[ISO.OFFSET_LC];
short readCount = apdu.setIncomingAndReceive();
while (bytes_left > 0) {

// Process received data in buffer; copy chunk to temp buf.
Util.arrayCopy(buffer, ISO.OFFSET_CDATA, tbuf, 0, readCount);
bytes_left -= readCount;
// Get more data
readCount = apdu.receiveBytes(ISO.OFFSET_CDDATA);
}
...
列表11、读取大量输入数据

  由于每个大块被读取,小应用程序可以把它添加到另一个缓冲中,否则仅仅处理它。
感谢阅读更多内容请点下面连接查看
相 关 文 章
  • 移动数据库和J2ME工具探讨

  • 使用JavaCard RMI接口

  • JavaCard开发教程之小应用程序

  • JavaCard开发教程之虚拟机

  • JavaCard开发教程之接口

  • JavaCard开发教程之程序元素

  • J2ME游戏开发实例讲解

  • Java回归嵌入式无线通信

  • 使用MIDP建立HTTP连接

  • J2ME Timer 使用指南

  • 学府网电脑学习的乐园
    中国电脑教学网,电脑爱好者的乐园,做最好最全的计算机学习网站.