智能卡 reader 访问中出现未知错误 0x16

Unkown error 0x16 on smartcard reader access

我正在尝试更改 ACR1252U 上的蜂鸣器持续时间。

Link 到 API: http://www.acs.com.hk/download-manual/6402/API-ACR1252U-1.09.pdf

根据 API 文档,我需要 'E0000028010A' 命令来更改蜂鸣器状态,其中“0A”将持续时间标记为 0A*10ms(第 44 页)。

使用以下 Java 代码:

public static void main(String[] args) {
    try {
        byte[] send = new byte[6];

        send[0] = (byte) 0xE0; // Commandclass
        send[1] = (byte) 0x00; // Protocoll
        send[2] = (byte) 0x00; // Param 1
        send[3] = (byte) 0x28; // Param 2: Buzzerstatus
        send[4] = (byte) 0x01; // Change Flag
        send[5] = (byte) 0x0A; // Duration: 0A*10ms => 100ms

        Card card = getCard("DIRECT"); // Works!
        CardChannel channel = card.getBasicChannel(); // Works!
        CommandAPDU command = new CommandAPDU(send); // Works!
        channel.transmit(command); // EXCEPTION!
    } catch (Exception ex) {
        ex.printStackTrace();
    }
}

public static Card getCard(String target) throws Exception {
    TerminalFactory factory = TerminalFactory.getDefault();
    List<CardTerminal> terminals = factory.terminals().list();
    for (CardTerminal t : terminals) {
        if (t.getName().equals("ACS ACR1252 Dual Reader PICC 0")) {
            Card card = t.connect(target);
            return card;
        }
    }
    throw new Exception();
}

但这会导致以下堆栈跟踪表明 "unkown error 0x16":

javax.smartcardio.CardException: sun.security.smartcardio.PCSCException: Unknown error 0x16
    at sun.security.smartcardio.ChannelImpl.doTransmit(ChannelImpl.java:219)
    at sun.security.smartcardio.ChannelImpl.transmit(ChannelImpl.java:90)
    at readerconfig.TagConfig.main(TagConfig.java:24)
Caused by: sun.security.smartcardio.PCSCException: Unknown error 0x16
    at sun.security.smartcardio.PCSC.SCardTransmit(Native Method)
    at sun.security.smartcardio.ChannelImpl.doTransmit(ChannelImpl.java:188)
    ... 2 more

我已经花了几个小时在这个方向上搜索任何东西,但是我找不到任何东西。我什至尝试了另一个设备,它仍然产生这个错误。

要么我完全失明了,要么我的电脑设置不正确。我只能说,我已经使用这个 reader 成功写入和读取 NFC 标签。但我无法更改 reader 本身的配置。

编辑:

我还找到了另一种发送命令的方法:

byte[] send = new byte[5];
send[0] = (byte) 0xE0;
send[1] = (byte) 0x0;
send[2] = (byte) 0x0;
send[3] = (byte) 0x18; // Tries to read firmware version
send[4] = (byte) 0x0;

Card card = CardUtils.getCard("DIRECT"); // Works!
card.transmitControlCommand(3500, send);

但这会导致 "unknown error 0x1":

javax.smartcardio.CardException: transmitControlCommand() failed
    at sun.security.smartcardio.CardImpl.transmitControlCommand(CardImpl.java:236)
    at readerconfig.ReaderConfig.main(ReaderConfig.java:28)
Caused by: sun.security.smartcardio.PCSCException: Unknown error 0x1
    at sun.security.smartcardio.PCSC.SCardControl(Native Method)
    at sun.security.smartcardio.CardImpl.transmitControlCommand(CardImpl.java:232)
    ... 1 more

尝试使用

card.transmitControlCommand(int controlCode, byte[] command)

而不是传输。根据第 5.8 节(您链接到的 pdf 的第 41 页) controlcode 是 3500,虽然我不清楚它是十六进制还是整数,所以如果可以的话,请与 SCARD_CTL_CODE 进行比较。至少,我是这样解释文档的。

通常您使用 transmitControlCommand 与 reader 通信并传输与卡通信。

修复了 ControlCode 中的拼写错误。感谢 Torhan Bartel 告诉我。

有两种方法可以通过 Java 智能卡 IO API 与此 reader 交互:

  1. 首先是打开一个常规的APDU传输通道(从PC/SC的角度来看这映射到T=0或T=1协议)。您可以使用

    Card card = getCard("*");
    

    但是,这将需要 reader 报告卡的存在。否则你无法以这种方式打开连接。

    然后您可以将 APDU 命令传输到卡(在基本通道或逻辑通道上),您可以在基本通道上向 reader 发送特殊命令。这些特殊命令的 class 字节设置为 0xFF,以指示该命令旨在由 reader 解释(而不是转发到卡)。所以这不适用于以 0xE0.

  2. 开头的 "peripherals control" 命令
  3. 那些 "peripherals control" 命令必须使用带有控制代码 SCARD_CTL_CODE(3500) 的控制命令发送到 reader。与打开与卡的连接一样,如果 reader 上存在卡,则可以使用 getCard("*")。但是,如果即使没有卡也能将这些命令发送到 reader,则必须在 "direct" 模式下打开连接:

    Card card = getCard("DIRECT");
    

    然后您可以使用方法card.transmitControlCommand()发送控制命令。此方法将控制代码作为第一个参数,将命令(作为字节数组)作为第二个参数。使用 channel.transmit() 在基本通道或任何逻辑通道上交换命令通常不会在 "direct" 模式下工作(因此错误代码为 0x16)。

    控制码计算为

    public static final int SCARD_CTL_CODE(int command) {
        boolean isWindows = System.getProperty("os.name").startsWith("Windows");
        if (isWindows) {
            return 0x00310000 | (command << 2);
        } else {
            return 0x42000000 | command;
        }
    }
    

    注意Windows和其他平台的区别。

    例如,要发送蜂鸣器控制命令,您可以使用

    byte[] command = new byte[] { (byte)0xE0, (byte)0x00, (byte)0x00, (byte)0x28, (byte)0x01, (byte)0x0A };
    byte[] response = card.transmitControlCommand(SCARD_CTL_CODE(3500), command);
    

    最后,请注意通过 PC/SC 发送 IOCTL 控制代码需要特殊的驱动程序支持 。具体来说,微软提供的标准CCID驱动默认是不支持发送escape命令的(见USB CCID Class Driver Details)。此驱动程序仅在通过注册表值 "EscapeCommandEnable" 启用后才支持转义命令。您在问题中显示的错误 0x1 是缺少对转义命令的支持的典型结果。

    要可靠地支持 reader 的所有功能(包括转义命令),您需要使用 ACS on their website 提供的 "PC/SC Drivers" 包。