有没有办法检查终端是否收到主机卡仿真中的最后一条消息?

Is there a way to check if the last message in Host Card Emulation was received by the terminal?

在我的 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
        }
    }
}

所以根据我的观察,onDeactivated() 回调可以恰好在 processCommandApdu() 的中间出现,即使这样 OS 似乎更早地认识到 NFC 字段丢失了比 onDeactivated() 被调用。

以下是通信过程中丢失字段的示例:

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() 更好的方法来检查是否收到消息(这在时间上似乎非常不确定)?

简而言之:终端作为卡片的角色(在 HCE 中)无法检查消息是否收到T=CL (ISO7816) 协议在 NFC 通信期间对最后一个块的数据进行分块时不提供 ack

此外,在 Android 中使用 NFC 与 com.android.nfc.NfcService through a Messenger. This is exactly what e.g. HostApduService or HostNfcFService 进行通信。

有3种基本信号:

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

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

所以最后,你只能判断终端是否肯定没有收到响应(当停用在响应之前),否则你就任凭摆布了NfcService 的 HAL 实现或驱动程序。

你不能。相反,如果您确实需要可靠地检测这种情况,则需要调整您的通信协议。

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

协议栈专为询问器驱动的通信而设计(询问器是终端)。通信以 command-response 顺序执行。原则上,每个 command-response 序列都包含以下步骤(还有一些额外的极端情况):

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

应用协议 (ISO/IEC 7816-4) 和传输协议 (ISO/IEC 14443-4 aka ISO-DEP) 均未通过任何形式的确认确认智能卡响应。一旦智能卡发送了它的响应,它就被认为已经完成处理。

实际上,这对于经典智能卡(接触式或非接触式)来说不是问题。中断的通信将导致卡为 power-cycled(因为 link-loss 也意味着 power-loss 或因为终端执行显式重置)。因此智能卡此时将无法依赖清理序列。

然而,这并不意味着没有办法克服这个限制。经典智能卡即使在 power-cycles 期间也保持持久状态。关键操作作为原子事务执行。在 power-loss 的情况下,cleanup/rollback 通常在重置时执行 (boot-up)。但是,映射到 Android 并不容易,因为 link-loss 不会导致 HCE 端的执行中断。因此,在将响应发送回 reader 之前,无法检测到 HCE 智能卡已被拔出。尽管如此,也没有原子事务会被 link-loss 中断。因此,重置(即接收选择您的应用程序的 SELECT(按 DF 名称)命令)仍然是执行清理(例如重置应用程序状态)的正确位置。

根据您的具体要求,典型的方法是调整 application-level 协议并添加一个确认命令以确认收到(然后是第二个)最后一个响应。 IE。如果你目前有类似的东西:

T--->  SELECT APPLICATION
<---C  FCI | 9000
T--->  PERFORM CRITICAL OPERATION
<---C  CRITICAL OPERATION RESULT

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

T--->  SELECT APPLICATION
<---C  FCI | 9000
T--->  PERFORM CRITICAL OPERATION
<---C  CRITICAL OPERATION RESULT
T--->  CONFIRM RECEPTION OF RESULT
<---C  9000

现在您不会真正关心最终响应 (9000) 是否在发送到终端的途中丢失了。