IOException with host based card emulation

I am using HCE and encountered an IOException on

 isoDep.connect(); 

on a specific android cr100 simcent reader .

HCE works great when I enable read mode in NFC with the flags below.

 public static int READER_FLAGS = NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK; 

But with that, I can’t read NDEF tags. Although the same code works fine on a Nexus 7 (2012) tablet.

Full code attached

CardReaderFragment

 public class CardReaderFragment extends Fragment implements LoyaltyCardReader.AccountCallback { public static final String TAG = "CardReaderFragment"; // Recommend NfcAdapter flags for reading from other Android devices. Indicates that this // activity is interested in NFC-A devices (including other Android devices), and that the // system should not check for the presence of NDEF-formatted data (eg Android Beam). public static int READER_FLAGS = NfcAdapter.FLAG_READER_NFC_A | NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK; public LoyaltyCardReader mLoyaltyCardReader; private TextView mAccountField; /** Called when sample is created. Displays generic UI with welcome text. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View v = inflater.inflate(R.layout.main_fragment, container, false); if (v != null) { mAccountField = (TextView) v.findViewById(R.id.card_account_field); mAccountField.setText("Waiting..."); mLoyaltyCardReader = new LoyaltyCardReader(this); // Disable Android Beam and register our card reader callback enableReaderMode(); } return v; } @Override public void onPause() { super.onPause(); disableReaderMode(); } @Override public void onResume() { super.onResume(); enableReaderMode(); } private void enableReaderMode() { Log.i(TAG, "Enabling reader mode"); Activity activity = getActivity(); NfcAdapter nfc = NfcAdapter.getDefaultAdapter(activity); if (nfc != null) { nfc.enableReaderMode(activity, mLoyaltyCardReader, READER_FLAGS, null); } } private void disableReaderMode() { Log.i(TAG, "Disabling reader mode"); Activity activity = getActivity(); NfcAdapter nfc = NfcAdapter.getDefaultAdapter(activity); if (nfc != null) { nfc.disableReaderMode(activity); } } @Override public void onAccountReceived(final String account) { // This callback is run on a background thread, but updates to UI elements must be performed // on the UI thread. getActivity().runOnUiThread(new Runnable() { @Override public void run() { mAccountField.setText(account); } }); } } 

LoyaltyCardReader

 public class LoyaltyCardReader implements NfcAdapter.ReaderCallback { private static final String TAG = "LoyaltyCardReader"; // AID for our loyalty card service. private static final String SAMPLE_LOYALTY_CARD_AID = "F222222222"; // ISO-DEP command HEADER for selecting an AID. // Format: [Class | Instruction | Parameter 1 | Parameter 2] private static final String SELECT_APDU_HEADER = "00A40400"; // "OK" status word sent in response to SELECT AID command (0x9000) private static final byte[] SELECT_OK_SW = {(byte) 0x90, (byte) 0x00}; // Weak reference to prevent retain loop. mAccountCallback is responsible for exiting // foreground mode before it becomes invalid (eg during onPause() or onStop()). private WeakReference<AccountCallback> mAccountCallback; public interface AccountCallback { public void onAccountReceived(String account); } public LoyaltyCardReader(AccountCallback accountCallback) { mAccountCallback = new WeakReference<AccountCallback>(accountCallback); } /** * Callback when a new tag is discovered by the system. * <p> * <p>Communication with the card should take place here. * * @param tag Discovered tag */ @Override public void onTagDiscovered(Tag tag) { Log.i(TAG, "New tag discovered"); // Android Host-based Card Emulation (HCE) feature implements the ISO-DEP (ISO 14443-4) // protocol. // // In order to communicate with a device using HCE, the discovered tag should be processed // using the IsoDep class. IsoDep isoDep = IsoDep.get(tag); if (isoDep != null) { try { // Connect to the remote NFC device isoDep.connect(); // Build SELECT AID command for our loyalty card service. // This command tells the remote device which service we wish to communicate with. Log.i(TAG, "Requesting remote AID: " + SAMPLE_LOYALTY_CARD_AID); byte[] command = BuildSelectApdu(SAMPLE_LOYALTY_CARD_AID); // Send command to remote device Log.i(TAG, "Sending: " + ByteArrayToHexString(command)); byte[] result = isoDep.transceive(command); // If AID is successfully selected, 0x9000 is returned as the status word (last 2 // bytes of the result) by convention. Everything before the status word is // optional payload, which is used here to hold the account number. int resultLength = result.length; byte[] statusWord = {result[resultLength - 2], result[resultLength - 1]}; byte[] payload = Arrays.copyOf(result, resultLength - 2); if (Arrays.equals(SELECT_OK_SW, statusWord)) { // The remote NFC device will immediately respond with its stored account number String accountNumber = new String(payload, "UTF-8"); Log.i(TAG, "Received: " + accountNumber); // Inform CardReaderFragment of received account number mAccountCallback.get().onAccountReceived(accountNumber); } } catch (IOException e) { Log.e(TAG, "Error communicating with card: " + e.toString()); } } else { Ndef ndef = Ndef.get(tag); if (ndef == null) { // NDEF is not supported by this Tag. Log.d("NFCCardTagNDEF", "even this is null"); // return; } NdefMessage ndefMessage = ndef.getCachedNdefMessage(); if (ndefMessage == null) { Log.d("NFCCardTagNDEF", "ndef message is null"); // return; } NdefRecord[] records = ndefMessage.getRecords(); String text = ndefRecordToString(records[0]); Log.d("NFCCardTagNFC", "old" + text); mAccountCallback.get().onAccountReceived(text); } } public String ndefRecordToString(NdefRecord record) { byte[] payload = record.getPayload(); return new String(payload); } /** * Build APDU for SELECT AID command. This command indicates which service a reader is * interested in communicating with. See ISO 7816-4. * * @param aid Application ID (AID) to select * @return APDU for SELECT AID command */ public static byte[] BuildSelectApdu(String aid) { // Format: [CLASS | INSTRUCTION | PARAMETER 1 | PARAMETER 2 | LENGTH | DATA] return HexStringToByteArray(SELECT_APDU_HEADER + String.format("%02X", aid.length() / 2) + aid); } /** * Utility class to convert a byte array to a hexadecimal string. * * @param bytes Bytes to convert * @return String, containing hexadecimal representation. */ public static String ByteArrayToHexString(byte[] bytes) { final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; char[] hexChars = new char[bytes.length * 2]; int v; for (int j = 0; j < bytes.length; j++) { v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars); } /** * Utility class to convert a hexadecimal string to a byte string. * <p> * <p>Behavior with input strings containing non-hexadecimal characters is undefined. * * @param s String containing hexadecimal characters to convert * @return Byte array generated from input */ public static byte[] HexStringToByteArray(String s) { int len = s.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i + 1), 16)); } return data; } } 

Any help would be appreciated

+5
source share
1 answer

Generally, you cannot make this mistake. IOException (or more specific TagLostException ) on IsoDep.connect() indicates that reading the phone cannot initiate communication with the HCE device. This often happens due to connection problems caused by poor communication (e.g. poorly matched antenna sizes, poor antenna design, antennas partially covered by the battery of the device without proper antenna design, etc.) or a long power-up time (usually only with passive cards and not with HCE). Thus, usually this error is not caused by logical problems with the communication protocol (which you could fix using software), but by a problem with the physical characteristics of the devices (which usually require modification of the equipment).

Unfortunately, you are unlikely to be able to do this. Possible workarounds include:

  • Try to connect the two phones better (so that the antenna of the reader device and the antenna of the HCE device) coincide with each other, and the metal parts of the device’s case or the battery of the device do not cover the other antenna of the device. In case of significantly different antenna sizes, try to align the other borders of the antennas with each other, so that the smaller is inside the larger.

  • On some (!) Devices, this can help increase the transceiver timeout before calling connect() :

     isoDep.setTimeout(10000); 

    However, this timeout does not seem to have any effect before the call was called on most devices.

  • There are NFC “accelerators” antennas that either tune the HF resonant frequency to combine better, mitigate communication problems, adapt the shape of the antenna around the batteries, or completely replace the original antenna. I do not have much experience with this, and I cannot indicate any source for such accelerators.

EDIT

After reading your question again, it seems that the connection between the cr100 device and the HCE works if you set the NfcAdapter.FLAG_READER_SKIP_NDEF_CHECK flag on the reader. If this is the case, an IOException may indeed be caused by a logical error. In this case, it may be that cr100 has a broken NFC detection implementation that breaks if the ISO-DEP card (for example, an HCE device) does not implement the NDEF tag specification. Although I find this highly unlikely, you can easily test this by using a tag type 4 application in your HCE application. See my answer here and the source code here for an example on how to do this.

0
source

Source: https://habr.com/ru/post/1269595/


All Articles