Mateusz Cieslinski
Mateusz Cieslinski

Reputation: 3

Send APDU command over USB Android Java

I am trying to build plugin for react native that allows me to read data from driver card data, but non stop I am receiving error from data reading. Here is a code

package com.plainreactnativeapp;

import androidx.annotation.NonNull;

import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.usb.*;
import android.util.Log;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;

import java.util.Arrays;

public class ApduModule extends ReactContextBaseJavaModule {

    private final ReactApplicationContext reactContext;
    private final int TIMEOUT = 5000;

    // USB settings
    private UsbManager mUsbManager;
    private UsbEndpoint epOut;
    private UsbEndpoint epIn;
    private UsbInterface usbInterface;
    private UsbDeviceConnection connection;
    private UsbDevice mPendingDevice;
    private Promise mPendingPromise;

    // APDU sender / receiver
    private StringBuilder result;

    private static final byte CCID_MESSAGE_TYPE_PC_TO_RDR_ICC_POWER_ON = 0x62;
    private static final byte CCID_MESSAGE_TYPE_PC_TO_RDR_XFR_BLOCK = 0x6F;
    private static final byte CCID_MESSAGE_TYPE_RDR_TO_PC_DATA_BLOCK = (byte) 0x80;
    private static final int CCID_HEADER_LENGTH = 10;

    private static final String ACTION_USB_PERMISSION = "com.plainreactnativeapp.USB_PERMISSION";

    public ApduModule(ReactApplicationContext context) {
        super(context);
        this.reactContext = context;

        mUsbManager = (UsbManager) reactContext.getSystemService(Context.USB_SERVICE);
        PendingIntent permIntent = PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE);
        IntentFilter filter = new IntentFilter(ACTION_USB_PERMISSION);
        reactContext.registerReceiver(usbReceiver, filter);
    }

    @NonNull
    @Override
    public String getName() {
        return "Apdu";
    }

    private final BroadcastReceiver usbReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (ACTION_USB_PERMISSION.equals(action)) {
                synchronized (this) {
                    UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
                    if (device != null && device.equals(mPendingDevice)) {
                        boolean granted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
                        if (granted) {
                            proceedWithDevice(device);
                        } else {
                            mPendingPromise.reject("PermissionDenied", "USB permission was denied");
                        }
                    }
                }
            }
        }
    };

    @ReactMethod
    public void listUsbDevices(Promise promise) {
        try {
            if (mUsbManager == null) {
                promise.reject("USB Manager Error", "Unable to access USB Manager");
                return;
            }

            WritableArray devicesArray = Arguments.createArray();
            for (UsbDevice device : mUsbManager.getDeviceList().values()) {
                WritableMap deviceMap = Arguments.createMap();
                deviceMap.putString("deviceName", device.getDeviceName());
                deviceMap.putInt("deviceId", device.getDeviceId());
                deviceMap.putInt("vendorId", device.getVendorId());
                deviceMap.putInt("productId", device.getProductId());
                deviceMap.putString("version", device.getVersion());
                devicesArray.pushMap(deviceMap);
            }

            Log.i("ListingUsbDevices", "Usb devices were listed");
            promise.resolve(devicesArray);
        } catch (Exception e) {
            Log.e("ListingUsbDevices", "Failed to list USB devices", e);
            promise.reject("ListingUsbDevicesFailed", "Failed to list USB devices", e);
        }
    }

    private UsbDevice getDeviceById(int deviceId) {
        try {
            if (mUsbManager == null) {
                Log.e("GetDeviceById", "Unable to access USB Manager");
                return null;
            }

            for (UsbDevice device : mUsbManager.getDeviceList().values()) {
                if (device.getDeviceId() == deviceId) {
                    return device;
                }
            }

            Log.e("GetDeviceById", "No USB device found with the given ID: " + deviceId);
            return null;
        } catch (Exception e) {
            Log.e("GetDeviceById", "Failed to get USB device by ID", e);
            return null;
        }
    }


    @ReactMethod
    public void openDevice(int deviceId, Promise promise) {
        UsbDevice device = getDeviceById(deviceId);

        if (device == null) {
            Log.e("DeviceNotFoundError", "Device with provided id wasn't found");
            promise.reject("DeviceNotFoundError", "Unable to find device with provided ID");
            return;
        }

        mPendingDevice = device;
        mPendingPromise = promise;

        if (!mUsbManager.hasPermission(device)) {
            PendingIntent permIntent = PendingIntent.getBroadcast(reactContext, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_IMMUTABLE);
            mUsbManager.requestPermission(device, permIntent);
        } else {
            proceedWithDevice(device);
        }
    }

    @ReactMethod
    public void closeDevice(Promise promise) {
        if (connection == null) {
            Log.i("NoDeviceOpened", "Developer tried to disconnect null device");
            promise.reject("NoDeviceOpened", "Any device is currently connected");
        } else {
            connection.close();
            Log.i("ConnectionClosed", "Connection to the device was closed");
            promise.resolve("Connection was closed");
        }
    }

    private void proceedWithDevice(UsbDevice device) {
        try {
            connection = mUsbManager.openDevice(device);
            if (connection == null) {
                mPendingPromise.reject("ConnectionNotEstablished", "Couldn't establish connection to USB device");
                return;
            }

            for (int i = 0; i < device.getInterfaceCount(); i++) {
                usbInterface = device.getInterface(i);
                connection.claimInterface(usbInterface, true);

                for (int j = 0; j < usbInterface.getEndpointCount(); j++) {
                    UsbEndpoint end = usbInterface.getEndpoint(j);
                    if (end.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
                        if (end.getDirection() == UsbConstants.USB_DIR_OUT) {
                            epOut = end;
                        } else if (end.getDirection() == UsbConstants.USB_DIR_IN) {
                            epIn = end;
                        }
                    }
                }
            }

            if (epOut == null) {
                mPendingPromise.reject("EndpointError", "Output endpoint not found");
                return;
            }

            if (epIn == null) {
                mPendingPromise.reject("EndpointError", "Input endpoint not found");
                return;
            }

            byte[] atrCommand = new byte[]{(byte) 0x00, (byte) 0xA4, (byte) 0x00, (byte) 0x00, (byte) 0x02, (byte) 0x3F, (byte) 0x00};
            write(connection, epOut, atrCommand);
            read(connection, epIn);

            Log.i("DeviceOpened", "Device was successfully opened");
            mPendingPromise.resolve("Connection established");
        } catch (Exception e) {
            Log.e("OpenDeviceError", "Failed to open USB device", e);
            mPendingPromise.reject("OpenDeviceError", "Failed to open USB device", e);
        }
    }

    public void write(UsbDeviceConnection connection, UsbEndpoint epOut, byte[] command) {
        result = new StringBuilder();
        int byteCount = connection.bulkTransfer(epOut, command, command.length, TIMEOUT);
        if (byteCount < 0) {
            Log.e("WriteError", "An error occurred while transferring command to smartcard");
        } else {
            for (byte bb : command) {
                result.append(String.format("%02X", bb));
            }
            Log.i("CommandSent", "Command sent: " + Arrays.toString(command));
        }
    }

    public int read(UsbDeviceConnection connection, UsbEndpoint epIn) {
        result = new StringBuilder();
        final byte[] buffer = new byte[epIn.getMaxPacketSize()];
        int retryCount = 10;
        int byteCount = -1;

        while (retryCount > 0 && byteCount < 0) {
            byteCount = connection.bulkTransfer(epIn, buffer, buffer.length, TIMEOUT);
            if (byteCount < 0) {
                Log.w("ReadWarning", "Retry reading, attempts left: " + (retryCount - 1));
                retryCount--;
                try {
                    Thread.sleep(100); // Short delay before retry
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        }

        if (byteCount >= 0) {
            for (int i = 0; i < byteCount; i++) {
                result.append(String.format(" %02X ", buffer[i]));
            }
            Log.i("ReadSuccess", "Buffer received was: " + result.toString());
        } else {
            Log.e("ReadError", "Problem occurred while reading response from smartcard. Byte count: " + byteCount);
        }

        return byteCount;
    }
}

In open device I am just trying to select Master Card file and read response from command if it wa s correct but I am receiving non stop error. Here some logs from logcat

2024-07-26 10:37:12.429  9075-9687  ListingUsbDevices       com.plainreactnativeapp              I  Usb devices were listed
2024-07-26 10:37:17.562  9075-9687  WriteError              com.plainreactnativeapp              E  An error occurred while transferring command to smartcard
2024-07-26 10:37:17.562  9075-9687  ReadWarning             com.plainreactnativeapp              W  Retry reading, attempts left: 9
2024-07-26 10:37:17.664  9075-9687  ReadWarning             com.plainreactnativeapp              W  Retry reading, attempts left: 8
2024-07-26 10:37:17.766  9075-9687  ReadWarning             com.plainreactnativeapp              W  Retry reading, attempts left: 7
2024-07-26 10:37:17.868  9075-9687  ReadWarning             com.plainreactnativeapp              W  Retry reading, attempts left: 6
2024-07-26 10:37:17.969  9075-9687  ReadWarning             com.plainreactnativeapp              W  Retry reading, attempts left: 5
2024-07-26 10:37:18.069  9075-9687  ReadWarning             com.plainreactnativeapp              W  Retry reading, attempts left: 4
2024-07-26 10:37:18.170  9075-9687  ReadWarning             com.plainreactnativeapp              W  Retry reading, attempts left: 3
2024-07-26 10:37:18.272  9075-9687  ReadWarning             com.plainreactnativeapp              W  Retry reading, attempts left: 2
2024-07-26 10:37:18.373  9075-9687  ReadWarning             com.plainreactnativeapp              W  Retry reading, attempts left: 1
2024-07-26 10:37:18.475  9075-9687  ReadWarning             com.plainreactnativeapp              W  Retry reading, attempts left: 0
2024-07-26 10:37:18.575  9075-9687  ReadError               com.plainreactnativeapp              E  Problem occurred while reading response from smartcard. Byte count: -1
2024-07-26 10:37:18.575  9075-9687  DeviceOpened            com.plainreactnativeapp              I  Device was successfully opened
2024-07-26 10:37:18.577  9075-9686  ReactNativeJS           com.plainreactnativeapp              I  Connection established
2024-07-26 10:37:18.579  9075-9687  UsbDeviceConnectionJNI  com.plainreactnativeapp              D  close
2024-07-26 10:37:18.580  9075-9687  ConnectionClosed        com.plainreactnativeapp              I  Connection to the device was closed

Why I am retrying few times? Sometimes I think that I can be possibly connected to maybe not proper response time. For any answer. THANK YOU! :D

Using native android usb package. Used different card readers. Always the same problem.

Upvotes: 0

Views: 39

Answers (0)

Related Questions