Есть ли способ проверить, было ли последнее сообщение в эмуляции карты Хоста получено терминалом?


В моем 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(), и даже тогда ОС, кажется, распознает немного раньше, что поле 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() (который кажется совершенно недетерминированным по времени)?

2 4

2 ответа:

Вместо этого вам нужно будет адаптировать свой коммуникационный протокол, если вам действительно нужно надежно обнаружить этот случай.

Проблема на самом деле не в Android, а в базовых протоколах связи (ISO/IEC 7816-4 над ISO/IEC 14443-4). Эти протоколы были построены для связи с обычными смарт-картами. Смарт-карты - это полностью пассивные устройства, которые не могут продолжать обработку (из-за нехватки энергии), когда их вытаскивают из считывателя или из поля NFC RF.

В стек протоколов предназначен для связи, управляемой запросчиком (где запросчик является терминалом). Связь осуществляется в командно-ответных последовательностях. В принципе, каждая последовательность команд-ответов состоит из следующих шагов (с несколькими дополнительными угловыми случаями):

  1. терминал посылает команду.
  2. Смарт-Карта принимает и обрабатывает команду.
  3. Смарт-Карта отправляет ответные данные и завершается словом состояния.

Ни приложение протокол (ISO / IEC 7816-4) или протокол передачи (ISO/IEC 14443-4, он же ISO-DEP) подтверждают ответ смарт-карты любой формой подтверждения. После того, как смарт-карта отправила свой ответ, Считается, что обработка завершена.

Фактически это не было бы проблемой для классической смарт-карты (контактной или бесконтактной). Прерывание связи приведет к циклическому включению питания карты (либо потому, что потеря связи также подразумевает потерю питания, либо потому, что терминал выполняет явное сброс). Таким образом, смарт-карта не сможет полагаться на последовательности очистки в этот момент.

Однако это не означает, что нет никаких способов преодолеть это ограничение. Классические смарт-карты поддерживают постоянное состояние даже во время циклов питания. Критические операции выполняются как атомарные транзакции. В случае потери питания очистка / откат обычно выполняется после сброса (загрузки). Однако это не совсем легко сопоставить с Android, так как потеря связи не вызывает выполнения на стороне HCE быть прерванным. Следовательно, нет никакого способа определить, что смарт-карта HCE была извлечена до того, как ответ был отправлен обратно читателю. Тем не менее, нет также атомарных транзакций, которые были бы прерваны потерей связи. Следовательно, reset (то есть получение команды SELECT (by DF name), которая выбирает ваше приложение) по-прежнему является правильным местом для выполнения очистки, такой как сброс состояния приложения.

Что касается вашего конкретного требования, то типичным подходом было бы: адаптируйте протокол уровня приложения и добавьте команду подтверждения, которая подтверждает получение (затем второго)последнего ответа. То есть, если у вас в данный момент есть что-то вроде:

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) будет потерян по пути к терминалу.

In, short: невозможно проверить, было ли сообщение получено терминалом в качестве роли карты (в HCE). Протокол T=CL (ISO7816) не обеспечивает ack при фрагментации данных во время передачи NFC для последнего фрагмента.

Кроме того, использование NFC в Android связывается с com.android.nfc.NfcService через а Messenger. Это именно то, что напр.HostApduService или 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 + мс после фактического деактивация, поэтому кажется, что ответ был отправлен. И даже с этой проверкой вы можете узнать только тогда, когда вы передали сообщение в NfcService, фактическая передача также занимает некоторое время (около пары МКС на байт). Таким образом, в конце концов, вы можете только сказать, был ли ответ определенно не получен терминалом (когда деактивация происходит до ответа) , но в противном случае вы находитесь во власти NfcService и это Хэл имплементация или тот самый водитель.