最近在使用主机卡模拟技术时,有没有一种方法可以检查终端是否接收到了最后一条消息?

4

在我的Android应用程序中,我实现了HostApduService。这里是它的一部分实现代码:

public final class MyHostApduService extends HostApduService {

    private boolean disconnected = false;

    @Override
    public byte[] processCommandApdu(byte[] commandApdu, @Nullable Bundle extras) {
        //process apdu in a background thread and call sendResponseApdu() when ready
        Single.fromCallable(() -> processInternal(commandApdu))
                .subscribeOn(nfcScheduler)
                .subscribe(this::sendResponseApdu, t -> Log.e("could not create response", t));
        return null;
    }

    ...
    @Override
    public void onDeactivated(int reason) {
       disconnected = true;
    }


    private void processInternal(byte[] apdu) {
        //business logic
        if(!disconnected) {
           //last message was probably received by the terminal
        }
    }
}

根据我的观察,即使在 processCommandApdu() 的过程中,onDeactivated() 回调也可能会立即到来,甚至在此之前,操作系统似乎已经意识到 NFC 场强的降低。

以下是通讯期间场强降低的示例:

16:21:16.808 I/MyHostApduService : processApdu[request|13bytes] 0A4040007A000000004306000
16:21:16.811 D/MyHostApduService : do business logic
16:21:16.890 D/HostEmulationManager: notifyHostEmulationDeactivated
16:21:16.890 D/HostEmulationManager: Unbinding from service ComponentInfo{app.debug/internal.MyHostApduService}
16:21:16.890 I/MyHostApduService : onDeactivated LINK_LOSS
16:21:16.898 I/MyHostApduService : processApdu[response|2bytes|90ms] 6A82

问题在于我需要自信地检查最后一条消息是否已被接收或丢弃,因为某些重要的完成代码必须执行(但仅当终端接收到该消息时才执行)。有没有更好的方法来检查消息是否已接收,而不是使用 onDeactivated() (它在时间上似乎非常不确定)?
2个回答

1
简而言之,作为卡片(在HCE中),无法检查终端是否收到了消息。在 NFC 通信的最后一个块期间,T=CL (ISO7816) 协议没有提供 ack
此外,在 Android 中使用 NFC 是通过 Messengercom.android.nfc.NfcService 进行通信。这正是例如 HostApduServiceHostNfcFService 所做的事情。

有三种基本信号:

  • MSG_COMMAND_APDU = 0
  • MSG_RESPONSE_APDU = 1
  • MSG_DEACTIVATED = 2

应用程序服务首先接收到信号0 (MSG_COMMAND_APDU),并响应一个1 (MSG_RESPONSE_APDU)类型的信号。在任何时候都可能发生2 (MSG_DEACTIVATED)。现在,应用程序可以检查是否在请求命令之后,在发送响应之前收到了停用信号。这在大多数情况下运行良好,但不是一致的。停用信号通常会在实际停用之后100毫秒以上才被接收到,因此似乎已经发送了响应。即使进行了此检查,您也只能知道何时将消息传递给NfcService,实际传输还需要一些时间(每个字节大约需要几微秒)。

最终,只有在响应之前进行了停用操作时(当停用在响应之前时),您才能确定终端明确未收到响应,否则您将完全依赖于NfcService及其HAL实现或驱动程序。

1

不行。如果您确实需要可靠地检测该情况,那么您需要调整通信协议。

问题并不是 Android,而是底层通信协议(ISO/IEC 7816-4 覆盖 ISO/IEC 14443-4)。这些协议是为与常规智能卡通信而构建的。智能卡是完全被动设备,当从读卡器中拔出或远离 NFC 射频场时,无法继续处理(由于缺乏能量)。

协议栈设计用于询问者驱动的通信(其中询问者是终端)。通信是通过命令-响应序列执行的。原则上,每个命令-响应序列包括以下步骤(还有一些额外的特殊情况):

  1. 终端发送命令。
  2. 智能卡接收并处理命令。
  3. 智能卡发送响应数据,并以状态字结尾。

应用程序协议(ISO/IEC 7816-4)和传输协议(ISO/IEC 14443-4,也称为 ISO-DEP)都不会通过任何形式的确认来确认智能卡响应。一旦智能卡发送其响应,就被认为已完成处理。

实际上,对于经典智能卡(接触式或非接触式),这不会成为问题。中断的通信将导致卡片重新上电(因为链路丢失也意味着电源丢失,或者终端执行显式复位)。因此,在那一点上,智能卡将无法依赖清理序列。
然而,这并不意味着没有克服这种限制的方法。经典智能卡即使在重新上电后也保持持久状态。关键操作作为原子事务执行。在发生电源故障时,通常会在重置(启动)时执行清理/回滚。但是,这不太容易映射到Android,因为链路丢失不会导致HCE端的执行中断。因此,无法检测到在向读卡器发送响应之前已拔出HCE智能卡。尽管如此,也没有被链路丢失中断的原子事务。因此,重置(即选择(按DF名称)命令的接收,选择您的应用程序)仍然是执行清理(例如重置应用程序状态)的正确位置。
关于您的具体要求,一个典型的方法是调整应用层协议,并添加确认命令以确认接收(然后是倒数第二个)响应。也就是说,如果您当前有类似以下内容的内容:
T--->  选择应用程序
<---C  FCI | 9000
T--->  执行关键操作
<---C  关键操作结果

您可以调整协议以包括最终确认:

T--->  选择应用程序
<---C  FCI | 9000
T--->  执行关键操作
<---C  关键操作结果
T--->  确认已接收到结果
<---C  9000

现在,如果最终响应(9000)在传输到终端的过程中丢失,您也不必真正担心。


谢谢Michael,这正是我想的。我的使用场景是EMVCo交易,所以我无法更改协议。我只能接受一些误报(HCE设备认为已收到响应,但实际上并未收到)。 - Patrick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接