add tests for CcidTransceiver
This commit is contained in:
@@ -23,6 +23,7 @@ import java.nio.ByteOrder;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
import android.support.annotation.VisibleForTesting;
|
||||||
|
|
||||||
import com.google.auto.value.AutoValue;
|
import com.google.auto.value.AutoValue;
|
||||||
import org.sufficientlysecure.keychain.securitytoken.usb.tpdu.T1ShortApduProtocol;
|
import org.sufficientlysecure.keychain.securitytoken.usb.tpdu.T1ShortApduProtocol;
|
||||||
@@ -54,6 +55,11 @@ abstract class CcidDescription {
|
|||||||
public abstract int getProtocols();
|
public abstract int getProtocols();
|
||||||
public abstract int getFeatures();
|
public abstract int getFeatures();
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static CcidDescription fromValues(byte maxSlotIndex, byte voltageSupport, int protocols, int features) {
|
||||||
|
return new AutoValue_CcidDescription(maxSlotIndex, voltageSupport, protocols, features);
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
static CcidDescription fromRawDescriptors(byte[] desc) throws UsbTransportException {
|
static CcidDescription fromRawDescriptors(byte[] desc) throws UsbTransportException {
|
||||||
int dwProtocols = 0, dwFeatures = 0;
|
int dwProtocols = 0, dwFeatures = 0;
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import com.google.auto.value.AutoValue;
|
|||||||
import org.bouncycastle.util.Arrays;
|
import org.bouncycastle.util.Arrays;
|
||||||
import org.bouncycastle.util.encoders.Hex;
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException.UsbCcidErrorException;
|
||||||
|
|
||||||
|
|
||||||
public class CcidTransceiver {
|
public class CcidTransceiver {
|
||||||
@@ -40,6 +41,7 @@ public class CcidTransceiver {
|
|||||||
|
|
||||||
private static final int MESSAGE_TYPE_RDR_TO_PC_DATA_BLOCK = 0x80;
|
private static final int MESSAGE_TYPE_RDR_TO_PC_DATA_BLOCK = 0x80;
|
||||||
private static final int MESSAGE_TYPE_PC_TO_RDR_ICC_POWER_ON = 0x62;
|
private static final int MESSAGE_TYPE_PC_TO_RDR_ICC_POWER_ON = 0x62;
|
||||||
|
private static final int MESSAGE_TYPE_PC_TO_RDR_ICC_POWER_OFF = 0x63;
|
||||||
private static final int MESSAGE_TYPE_PC_TO_RDR_XFR_BLOCK = 0x6f;
|
private static final int MESSAGE_TYPE_PC_TO_RDR_XFR_BLOCK = 0x6f;
|
||||||
|
|
||||||
private static final int COMMAND_STATUS_SUCCESS = 0;
|
private static final int COMMAND_STATUS_SUCCESS = 0;
|
||||||
@@ -86,15 +88,20 @@ public class CcidTransceiver {
|
|||||||
CcidDataBlock response = null;
|
CcidDataBlock response = null;
|
||||||
for (CcidDescription.Voltage v : usbCcidDescription.getVoltages()) {
|
for (CcidDescription.Voltage v : usbCcidDescription.getVoltages()) {
|
||||||
Log.v(Constants.TAG, "CCID: attempting to power on with voltage " + v.toString());
|
Log.v(Constants.TAG, "CCID: attempting to power on with voltage " + v.toString());
|
||||||
response = iccPowerOnVoltage(v.powerOnValue);
|
try {
|
||||||
|
response = iccPowerOnVoltage(v.powerOnValue);
|
||||||
|
} catch (UsbCcidErrorException e) {
|
||||||
|
if (e.getErrorResponse().getError() == 7) { // Power select error
|
||||||
|
Log.v(Constants.TAG, "CCID: failed to power on with voltage " + v.toString());
|
||||||
|
iccPowerOff();
|
||||||
|
Log.v(Constants.TAG, "CCID: powered off");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (response.getStatus() == 1 && response.getError() == 7) { // Power select error
|
throw e;
|
||||||
Log.v(Constants.TAG, "CCID: failed to power on with voltage " + v.toString());
|
|
||||||
iccPowerOff();
|
|
||||||
Log.v(Constants.TAG, "CCID: powered off");
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
if (response == null) {
|
if (response == null) {
|
||||||
throw new UsbTransportException("Couldn't power up ICC2");
|
throw new UsbTransportException("Couldn't power up ICC2");
|
||||||
@@ -127,7 +134,7 @@ public class CcidTransceiver {
|
|||||||
private void iccPowerOff() throws UsbTransportException {
|
private void iccPowerOff() throws UsbTransportException {
|
||||||
byte sequenceNumber = currentSequenceNumber++;
|
byte sequenceNumber = currentSequenceNumber++;
|
||||||
final byte[] iccPowerCommand = {
|
final byte[] iccPowerCommand = {
|
||||||
0x63,
|
MESSAGE_TYPE_PC_TO_RDR_ICC_POWER_OFF,
|
||||||
0x00, 0x00, 0x00, 0x00,
|
0x00, 0x00, 0x00, 0x00,
|
||||||
0x00,
|
0x00,
|
||||||
sequenceNumber,
|
sequenceNumber,
|
||||||
@@ -193,7 +200,7 @@ public class CcidTransceiver {
|
|||||||
} while (response.isStatusTimeoutExtensionRequest());
|
} while (response.isStatusTimeoutExtensionRequest());
|
||||||
|
|
||||||
if (!response.isStatusSuccess()) {
|
if (!response.isStatusSuccess()) {
|
||||||
throw new UsbTransportException("USB-CCID error: " + response);
|
throw new UsbCcidErrorException("USB-CCID error!", response);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
@@ -213,7 +220,6 @@ public class CcidTransceiver {
|
|||||||
|
|
||||||
throw new UsbTransportException("USB-CCID error - bad CCID header type " + inputBuffer[0]);
|
throw new UsbTransportException("USB-CCID error - bad CCID header type " + inputBuffer[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
CcidDataBlock result = CcidDataBlock.parseHeaderFromBytes(inputBuffer);
|
CcidDataBlock result = CcidDataBlock.parseHeaderFromBytes(inputBuffer);
|
||||||
|
|
||||||
if (expectedSequenceNumber != result.getSeq()) {
|
if (expectedSequenceNumber != result.getSeq()) {
|
||||||
@@ -235,6 +241,7 @@ public class CcidTransceiver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
result = result.withData(dataBuffer);
|
result = result.withData(dataBuffer);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,9 @@
|
|||||||
|
|
||||||
package org.sufficientlysecure.keychain.securitytoken.usb;
|
package org.sufficientlysecure.keychain.securitytoken.usb;
|
||||||
|
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
import android.hardware.usb.UsbConstants;
|
import android.hardware.usb.UsbConstants;
|
||||||
import android.hardware.usb.UsbDevice;
|
import android.hardware.usb.UsbDevice;
|
||||||
import android.hardware.usb.UsbDeviceConnection;
|
import android.hardware.usb.UsbDeviceConnection;
|
||||||
@@ -28,15 +31,13 @@ import android.support.annotation.Nullable;
|
|||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
import org.sufficientlysecure.keychain.Constants;
|
import org.sufficientlysecure.keychain.Constants;
|
||||||
|
import org.sufficientlysecure.keychain.securitytoken.CommandApdu;
|
||||||
|
import org.sufficientlysecure.keychain.securitytoken.ResponseApdu;
|
||||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo.TokenType;
|
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo.TokenType;
|
||||||
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo.TransportType;
|
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo.TransportType;
|
||||||
import org.sufficientlysecure.keychain.securitytoken.Transport;
|
import org.sufficientlysecure.keychain.securitytoken.Transport;
|
||||||
import org.sufficientlysecure.keychain.securitytoken.CommandApdu;
|
|
||||||
import org.sufficientlysecure.keychain.securitytoken.ResponseApdu;
|
|
||||||
import org.sufficientlysecure.keychain.util.Log;
|
import org.sufficientlysecure.keychain.util.Log;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Based on USB CCID Specification rev. 1.1
|
* Based on USB CCID Specification rev. 1.1
|
||||||
* http://www.usb.org/developers/docs/devclass_docs/DWG_Smart-Card_CCID_Rev110.pdf
|
* http://www.usb.org/developers/docs/devclass_docs/DWG_Smart-Card_CCID_Rev110.pdf
|
||||||
@@ -136,6 +137,7 @@ public class UsbTransport implements Transport {
|
|||||||
}
|
}
|
||||||
|
|
||||||
CcidDescription ccidDescription = CcidDescription.fromRawDescriptors(usbConnection.getRawDescriptors());
|
CcidDescription ccidDescription = CcidDescription.fromRawDescriptors(usbConnection.getRawDescriptors());
|
||||||
|
Log.d(Constants.TAG, "CCID Description: " + ccidDescription);
|
||||||
CcidTransceiver transceiver = new CcidTransceiver(usbConnection, usbBulkIn, usbBulkOut, ccidDescription);
|
CcidTransceiver transceiver = new CcidTransceiver(usbConnection, usbBulkIn, usbBulkOut, ccidDescription);
|
||||||
|
|
||||||
ccidTransportProtocol = ccidDescription.getSuitableTransportProtocol();
|
ccidTransportProtocol = ccidDescription.getSuitableTransportProtocol();
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ package org.sufficientlysecure.keychain.securitytoken.usb;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import org.sufficientlysecure.keychain.securitytoken.usb.CcidTransceiver.CcidDataBlock;
|
||||||
|
|
||||||
|
|
||||||
public class UsbTransportException extends IOException {
|
public class UsbTransportException extends IOException {
|
||||||
public UsbTransportException(String detailMessage) {
|
public UsbTransportException(String detailMessage) {
|
||||||
super(detailMessage);
|
super(detailMessage);
|
||||||
@@ -31,4 +34,17 @@ public class UsbTransportException extends IOException {
|
|||||||
public UsbTransportException(Throwable cause) {
|
public UsbTransportException(Throwable cause) {
|
||||||
super(cause);
|
super(cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class UsbCcidErrorException extends UsbTransportException {
|
||||||
|
private CcidDataBlock errorResponse;
|
||||||
|
|
||||||
|
UsbCcidErrorException(String detailMessage, CcidDataBlock errorResponse) {
|
||||||
|
super(detailMessage + " " + errorResponse);
|
||||||
|
this.errorResponse = errorResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
CcidDataBlock getErrorResponse() {
|
||||||
|
return errorResponse;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,313 @@
|
|||||||
|
package org.sufficientlysecure.keychain.securitytoken.usb;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.hardware.usb.UsbDeviceConnection;
|
||||||
|
import android.hardware.usb.UsbEndpoint;
|
||||||
|
import android.os.Build.VERSION_CODES;
|
||||||
|
|
||||||
|
import org.bouncycastle.util.Arrays;
|
||||||
|
import org.bouncycastle.util.encoders.Hex;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.mockito.invocation.InvocationOnMock;
|
||||||
|
import org.mockito.stubbing.Answer;
|
||||||
|
import org.sufficientlysecure.keychain.KeychainTestRunner;
|
||||||
|
import org.sufficientlysecure.keychain.securitytoken.usb.CcidTransceiver.CcidDataBlock;
|
||||||
|
import org.sufficientlysecure.keychain.securitytoken.usb.UsbTransportException.UsbCcidErrorException;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals;
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
import static org.mockito.AdditionalMatchers.aryEq;
|
||||||
|
import static org.mockito.Matchers.any;
|
||||||
|
import static org.mockito.Matchers.eq;
|
||||||
|
import static org.mockito.Matchers.same;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
@RunWith(KeychainTestRunner.class)
|
||||||
|
@TargetApi(VERSION_CODES.JELLY_BEAN_MR2)
|
||||||
|
public class CcidTransceiverTest {
|
||||||
|
static final String ATR = "3bda11ff81b1fe551f0300318473800180009000e4";
|
||||||
|
static final int MAX_PACKET_LENGTH_IN = 61;
|
||||||
|
static final int MAX_PACKET_LENGTH_OUT = 63;
|
||||||
|
|
||||||
|
UsbDeviceConnection usbConnection;
|
||||||
|
UsbEndpoint usbBulkIn;
|
||||||
|
UsbEndpoint usbBulkOut;
|
||||||
|
|
||||||
|
LinkedList<byte[]> expectReplies;
|
||||||
|
LinkedList<byte[]> expectRepliesVerify;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
usbConnection = mock(UsbDeviceConnection.class);
|
||||||
|
usbBulkIn = mock(UsbEndpoint.class);
|
||||||
|
when(usbBulkIn.getMaxPacketSize()).thenReturn(MAX_PACKET_LENGTH_IN);
|
||||||
|
usbBulkOut = mock(UsbEndpoint.class);
|
||||||
|
when(usbBulkOut.getMaxPacketSize()).thenReturn(MAX_PACKET_LENGTH_OUT);
|
||||||
|
|
||||||
|
expectReplies = new LinkedList<>();
|
||||||
|
expectRepliesVerify = new LinkedList<>();
|
||||||
|
when(usbConnection.bulkTransfer(same(usbBulkIn), any(byte[].class), any(Integer.class), any(Integer.class)))
|
||||||
|
.thenAnswer(
|
||||||
|
new Answer<Integer>() {
|
||||||
|
@Override
|
||||||
|
public Integer answer(InvocationOnMock invocation) throws Throwable {
|
||||||
|
byte[] reply = expectReplies.poll();
|
||||||
|
if (reply == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] buf = invocation.getArgumentAt(1, byte[].class);
|
||||||
|
assertEquals(buf.length, MAX_PACKET_LENGTH_IN);
|
||||||
|
|
||||||
|
int len = Math.min(buf.length, reply.length);
|
||||||
|
System.arraycopy(reply, 0, buf, 0, len);
|
||||||
|
|
||||||
|
if (len < reply.length) {
|
||||||
|
byte[] rest = Arrays.copyOfRange(reply, len, reply.length);
|
||||||
|
expectReplies.addFirst(rest);
|
||||||
|
}
|
||||||
|
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAutoVoltageSelection() throws Exception {
|
||||||
|
CcidDescription description = CcidDescription.fromValues((byte) 0, (byte) 1, 2, 132218);
|
||||||
|
CcidTransceiver ccidTransceiver = new CcidTransceiver(usbConnection, usbBulkIn, usbBulkOut, description);
|
||||||
|
|
||||||
|
byte[] iccPowerOnVoltageAutoCommand = Hex.decode("62000000000000000000");
|
||||||
|
byte[] iccPowerOnReply = Hex.decode("80150000000000000000" + ATR);
|
||||||
|
expectReadPreamble();
|
||||||
|
expect(iccPowerOnVoltageAutoCommand, iccPowerOnReply);
|
||||||
|
|
||||||
|
|
||||||
|
CcidDataBlock ccidDataBlock = ccidTransceiver.iccPowerOn();
|
||||||
|
|
||||||
|
|
||||||
|
verifyDialog();
|
||||||
|
assertArrayEquals(Hex.decode(ATR), ccidDataBlock.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testManualVoltageSelection() throws Exception {
|
||||||
|
CcidDescription description = CcidDescription.fromValues((byte) 0, (byte) 1, 2, 132210);
|
||||||
|
CcidTransceiver ccidTransceiver = new CcidTransceiver(usbConnection, usbBulkIn, usbBulkOut, description);
|
||||||
|
|
||||||
|
byte[] iccPowerOnVoltage5VCommand = Hex.decode("62000000000000010000");
|
||||||
|
byte[] iccPowerOnReply = Hex.decode("80150000000000000000" + ATR);
|
||||||
|
expectReadPreamble();
|
||||||
|
expect(iccPowerOnVoltage5VCommand, iccPowerOnReply);
|
||||||
|
|
||||||
|
|
||||||
|
CcidDataBlock ccidDataBlock = ccidTransceiver.iccPowerOn();
|
||||||
|
|
||||||
|
|
||||||
|
verifyDialog();
|
||||||
|
assertArrayEquals(Hex.decode(ATR), ccidDataBlock.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testManualVoltageSelection_failFirst() throws Exception {
|
||||||
|
CcidDescription description = CcidDescription.fromValues((byte) 0, (byte) 3, 2, 132210);
|
||||||
|
CcidTransceiver ccidTransceiver = new CcidTransceiver(usbConnection, usbBulkIn, usbBulkOut, description);
|
||||||
|
|
||||||
|
byte[] iccPowerOnVoltage5VCommand = Hex.decode("62000000000000010000");
|
||||||
|
byte[] iccPowerOnFailureReply = Hex.decode("80000000000000010700");
|
||||||
|
byte[] iccPowerOffCommand = Hex.decode("6300000000000100");
|
||||||
|
byte[] iccPowerOnVoltage3VCommand = Hex.decode("62000000000002020000");
|
||||||
|
byte[] iccPowerOnReply = Hex.decode("80150000000002000000" + ATR);
|
||||||
|
expectReadPreamble();
|
||||||
|
expect(iccPowerOnVoltage5VCommand, iccPowerOnFailureReply);
|
||||||
|
expect(iccPowerOffCommand, null);
|
||||||
|
expect(iccPowerOnVoltage3VCommand, iccPowerOnReply);
|
||||||
|
|
||||||
|
|
||||||
|
CcidDataBlock ccidDataBlock = ccidTransceiver.iccPowerOn();
|
||||||
|
|
||||||
|
|
||||||
|
verifyDialog();
|
||||||
|
assertArrayEquals(Hex.decode(ATR), ccidDataBlock.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testXfer() throws Exception {
|
||||||
|
CcidTransceiver ccidTransceiver = new CcidTransceiver(usbConnection, usbBulkIn, usbBulkOut, null);
|
||||||
|
|
||||||
|
String commandData = "010203";
|
||||||
|
byte[] command = Hex.decode("6F030000000000000000" + commandData);
|
||||||
|
String responseData = "0304";
|
||||||
|
byte[] response = Hex.decode("80020000000000000000" + responseData);
|
||||||
|
expect(command, response);
|
||||||
|
|
||||||
|
CcidDataBlock ccidDataBlock = ccidTransceiver.sendXfrBlock(Hex.decode(commandData));
|
||||||
|
|
||||||
|
verifyDialog();
|
||||||
|
assertArrayEquals(Hex.decode(responseData), ccidDataBlock.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testXfer_IncrementalSeqNums() throws Exception {
|
||||||
|
CcidTransceiver ccidTransceiver = new CcidTransceiver(usbConnection, usbBulkIn, usbBulkOut, null);
|
||||||
|
|
||||||
|
String commandData = "010203";
|
||||||
|
byte[] commandSeq1 = Hex.decode("6F030000000000000000" + commandData);
|
||||||
|
byte[] commandSeq2 = Hex.decode("6F030000000001000000" + commandData);
|
||||||
|
String responseData = "0304";
|
||||||
|
byte[] responseSeq1 = Hex.decode("80020000000000000000" + responseData);
|
||||||
|
byte[] responseSeq2 = Hex.decode("80020000000001000000" + responseData);
|
||||||
|
expect(commandSeq1, responseSeq1);
|
||||||
|
expect(commandSeq2, responseSeq2);
|
||||||
|
|
||||||
|
ccidTransceiver.sendXfrBlock(Hex.decode(commandData));
|
||||||
|
ccidTransceiver.sendXfrBlock(Hex.decode(commandData));
|
||||||
|
|
||||||
|
verifyDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = UsbTransportException.class)
|
||||||
|
public void testXfer_badSeqNumberReply() throws Exception {
|
||||||
|
CcidTransceiver ccidTransceiver = new CcidTransceiver(usbConnection, usbBulkIn, usbBulkOut, null);
|
||||||
|
|
||||||
|
String commandData = "010203";
|
||||||
|
byte[] command = Hex.decode("6F030000000000000000" + commandData);
|
||||||
|
String responseData = "0304";
|
||||||
|
byte[] response = Hex.decode("800200000000AA000000" + responseData);
|
||||||
|
expect(command, response);
|
||||||
|
|
||||||
|
|
||||||
|
ccidTransceiver.sendXfrBlock(Hex.decode(commandData));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testXfer_errorReply() throws Exception {
|
||||||
|
CcidTransceiver ccidTransceiver = new CcidTransceiver(usbConnection, usbBulkIn, usbBulkOut, null);
|
||||||
|
|
||||||
|
String commandData = "010203";
|
||||||
|
byte[] command = Hex.decode("6F030000000000000000" + commandData);
|
||||||
|
byte[] response = Hex.decode("80000000000000012A00");
|
||||||
|
expect(command, response);
|
||||||
|
|
||||||
|
try {
|
||||||
|
ccidTransceiver.sendXfrBlock(Hex.decode(commandData));
|
||||||
|
} catch (UsbCcidErrorException e) {
|
||||||
|
assertEquals(0x01, e.getErrorResponse().getIccStatus());
|
||||||
|
assertEquals(0x2A, e.getErrorResponse().getError());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fail();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testXfer_chainedCommand() throws Exception {
|
||||||
|
CcidTransceiver ccidTransceiver = new CcidTransceiver(usbConnection, usbBulkIn, usbBulkOut, null);
|
||||||
|
|
||||||
|
String commandData =
|
||||||
|
"0000000000000123456789000000000000000000000000000000000000000000" +
|
||||||
|
"0000000000000000000000012345678900000000000000000000000000000000" +
|
||||||
|
"00000000000001234567890000000000";
|
||||||
|
byte[] command = Hex.decode("6F500000000000000000" + commandData);
|
||||||
|
String responseData = "0304";
|
||||||
|
byte[] response = Hex.decode("80020000000000000000" + responseData);
|
||||||
|
expectChained(command, response);
|
||||||
|
|
||||||
|
CcidDataBlock ccidDataBlock = ccidTransceiver.sendXfrBlock(Hex.decode(commandData));
|
||||||
|
|
||||||
|
verifyDialog();
|
||||||
|
assertArrayEquals(Hex.decode(responseData), ccidDataBlock.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testXfer_chainedReply() throws Exception {
|
||||||
|
CcidTransceiver ccidTransceiver = new CcidTransceiver(usbConnection, usbBulkIn, usbBulkOut, null);
|
||||||
|
|
||||||
|
String commandData = "010203";
|
||||||
|
byte[] command = Hex.decode("6F030000000000000000" + commandData);
|
||||||
|
String responseData =
|
||||||
|
"0000000000000000000000000000000000012345678900000000000000000000" +
|
||||||
|
"0000000000000000000000000001234567890000000000000000000000000000" +
|
||||||
|
"00000012345678900000000000000000";
|
||||||
|
byte[] response = Hex.decode("80500000000000000000" + responseData);
|
||||||
|
expect(command, response);
|
||||||
|
|
||||||
|
CcidDataBlock ccidDataBlock = ccidTransceiver.sendXfrBlock(Hex.decode(commandData));
|
||||||
|
|
||||||
|
verifyDialog();
|
||||||
|
assertArrayEquals(Hex.decode(responseData), ccidDataBlock.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testXfer_timeoutExtensionReply() throws Exception {
|
||||||
|
CcidTransceiver ccidTransceiver = new CcidTransceiver(usbConnection, usbBulkIn, usbBulkOut, null);
|
||||||
|
|
||||||
|
String commandData = "010203";
|
||||||
|
byte[] command = Hex.decode("6F030000000000000000" + commandData);
|
||||||
|
byte[] timeExtensionResponse = Hex.decode("80000000000000800000");
|
||||||
|
String responseData = "0304";
|
||||||
|
byte[] response = Hex.decode("80020000000000000000" + responseData);
|
||||||
|
expect(command, timeExtensionResponse);
|
||||||
|
expect(null, response);
|
||||||
|
|
||||||
|
CcidDataBlock ccidDataBlock = ccidTransceiver.sendXfrBlock(Hex.decode(commandData));
|
||||||
|
|
||||||
|
verifyDialog();
|
||||||
|
assertArrayEquals(Hex.decode(responseData), ccidDataBlock.getData());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyDialog() {
|
||||||
|
assertTrue(expectReplies.isEmpty());
|
||||||
|
assertFalse(expectRepliesVerify.isEmpty());
|
||||||
|
|
||||||
|
for (byte[] command : expectRepliesVerify) {
|
||||||
|
if (command == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
verify(usbConnection).bulkTransfer(same(usbBulkIn), aryEq(command), any(Integer.class), any(Integer.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
expectRepliesVerify.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectReadPreamble() {
|
||||||
|
expectReplies.add(null);
|
||||||
|
expectRepliesVerify.add(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expectChained(byte[] command, byte[] reply) {
|
||||||
|
for (int i = 0; i < command.length; i+= MAX_PACKET_LENGTH_OUT) {
|
||||||
|
int len = Math.min(MAX_PACKET_LENGTH_OUT, command.length - i);
|
||||||
|
when(usbConnection.bulkTransfer(same(usbBulkOut), aryEq(command), eq(i), eq(len),
|
||||||
|
any(Integer.class))).thenReturn(len);
|
||||||
|
}
|
||||||
|
if (reply != null) {
|
||||||
|
expectReplies.add(reply);
|
||||||
|
expectRepliesVerify.add(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void expect(byte[] command, byte[] reply) {
|
||||||
|
if (command != null) {
|
||||||
|
when(usbConnection.bulkTransfer(same(usbBulkOut), aryEq(command), eq(0), eq(command.length),
|
||||||
|
any(Integer.class))).thenReturn(command.length);
|
||||||
|
}
|
||||||
|
if (reply != null) {
|
||||||
|
expectReplies.add(reply);
|
||||||
|
expectRepliesVerify.add(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user