summaryrefslogtreecommitdiff
path: root/smcc/src/main/java/at/gv/egiz/smcc/ccid
diff options
context:
space:
mode:
Diffstat (limited to 'smcc/src/main/java/at/gv/egiz/smcc/ccid')
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/ccid/CCID.java45
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/ccid/DefaultReader.java345
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/ccid/OMNIKEYCardMan3621.java39
-rw-r--r--smcc/src/main/java/at/gv/egiz/smcc/ccid/ReaderFactory.java2
4 files changed, 384 insertions, 47 deletions
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/ccid/CCID.java b/smcc/src/main/java/at/gv/egiz/smcc/ccid/CCID.java
index 2c56ce98..56ebaffe 100644
--- a/smcc/src/main/java/at/gv/egiz/smcc/ccid/CCID.java
+++ b/smcc/src/main/java/at/gv/egiz/smcc/ccid/CCID.java
@@ -45,17 +45,48 @@ public interface CCID {
"FEATURE_WRITE_DISPLAY",
"FEATURE_GET_KEY",
"FEATURE_IFD_DISPLAY_PROPERTIES"};
-
- Byte FEATURE_IFD_PIN_PROPERTIES = new Byte((byte) 10);
- Byte FEATURE_MCT_READER_DIRECT = new Byte((byte) 8);
- Byte FEATURE_MODIFY_PIN_DIRECT = new Byte((byte) 7);
- Byte FEATURE_VERIFY_PIN_DIRECT = new Byte((byte) 6);
-
+
+ Byte FEATURE_VERIFY_PIN_START = new Byte((byte) 0x01);
+ Byte FEATURE_VERIFY_PIN_FINISH = new Byte((byte) 0x02);
+ Byte FEATURE_MODIFY_PIN_START = new Byte((byte) 0x03);
+ Byte FEATURE_MODIFY_PIN_FINISH = new Byte((byte) 0x04);
+ Byte FEATURE_GET_KEY_PRESSED = new Byte((byte) 0x05);
+ Byte FEATURE_VERIFY_PIN_DIRECT = new Byte((byte) 0x06);
+ Byte FEATURE_MODIFY_PIN_DIRECT = new Byte((byte) 0x07);
+ Byte FEATURE_MCT_READER_DIRECT = new Byte((byte) 0x08);
+ Byte FEATURE_MCT_UNIVERSAL = new Byte((byte) 0x09);
+ Byte FEATURE_IFD_PIN_PROPERTIES = new Byte((byte) 0x0a);
+ //TODO continue list
+
+ String getName();
+
Card connect() throws CardException;
boolean hasFeature(Byte feature);
/**
+ * not supported by OMNIKEY CardMan 3621 with ACOS card
+ * @param PIN_VERIFY
+ * @return
+ * @throws at.gv.egiz.smcc.PINOperationAbortedException
+ * @throws javax.smartcardio.CardException
+ */
+ byte[] verifyPin(byte[] PIN_VERIFY) throws PINOperationAbortedException, CardException;
+
+ byte[] verifyPinDirect(byte[] PIN_VERIFY) throws CardException;
+
+ /**
+ * not supported by OMNIKEY CardMan 3621 with ACOS card
+ * @param PIN_MODIFY
+ * @return
+ * @throws at.gv.egiz.smcc.PINOperationAbortedException
+ * @throws javax.smartcardio.CardException
+ */
+ byte[] modifyPin(byte[] PIN_MODIFY) throws PINOperationAbortedException, CardException;
+
+ byte[] modifyPinDirect(byte[] PIN_MODIFY) throws CardException;
+
+ /**
*
* @param feature the corresponding control code will be transmitted
* @param ctrlCommand
@@ -63,7 +94,7 @@ public interface CCID {
* @throws at.gv.egiz.smcc.SignatureCardException if feature is not supported
* or card communication fails
*/
- byte[] transmitControlCommand(Byte feature, byte[] ctrlCommand) throws SignatureCardException;
+// byte[] transmitControlCommand(Byte feature, byte[] ctrlCommand) throws SignatureCardException;
/**
* allow subclasses to override default (deal with reader bugs)
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/ccid/DefaultReader.java b/smcc/src/main/java/at/gv/egiz/smcc/ccid/DefaultReader.java
index 2cc77dc9..9b1787a0 100644
--- a/smcc/src/main/java/at/gv/egiz/smcc/ccid/DefaultReader.java
+++ b/smcc/src/main/java/at/gv/egiz/smcc/ccid/DefaultReader.java
@@ -20,6 +20,8 @@ import at.gv.egiz.smcc.*;
import at.gv.egiz.smcc.util.SMCCHelper;
import java.util.HashMap;
import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
import javax.smartcardio.Card;
import javax.smartcardio.CardException;
import javax.smartcardio.CardTerminal;
@@ -35,12 +37,17 @@ public class DefaultReader implements CCID {
protected final static Log log = LogFactory.getLog(DefaultReader.class);
private static int CTL_CODE(int code) {
- return 0x42000000 + code;
+ String os_name = System.getProperty("os.name").toLowerCase();
+ if (os_name.indexOf("windows") > -1) {
+ // cf. WinIOCTL.h
+ return (0x31 << 16 | (code) << 2);
+ }
+ // cf. reader.h
+ return 0x42000000 + (code);
}
int IOCTL_GET_FEATURE_REQUEST = CTL_CODE(3400);
-
protected Card icc;
protected CardTerminal ct;
@@ -58,14 +65,19 @@ public class DefaultReader implements CCID {
features = queryFeatures();
}
- public Card connect() throws CardException { //SignatureCardException {
-// try {
+ /**
+ *
+ * @return the card terminals name
+ */
+ @Override
+ public String getName() {
+ return ct.getName();
+ }
+
+ @Override
+ public Card connect() throws CardException {
icc = ct.connect("*");
return icc;
-// } catch (CardException ex) {
-// log.error(ex.getMessage(), ex);
-// throw new SignatureCardException("Failed to connect to card: " + ex.getMessage());
-// }
}
Map<Byte, Integer> queryFeatures() {
@@ -81,7 +93,7 @@ public class DefaultReader implements CCID {
" on " + ct.getName());
}
byte[] resp = icc.transmitControlCommand(IOCTL_GET_FEATURE_REQUEST,
- new byte[]{});
+ new byte[0]);
if (log.isTraceEnabled()) {
log.trace("Response TLV " + SMCCHelper.toString(resp));
@@ -91,8 +103,10 @@ public class DefaultReader implements CCID {
// control code value for supported feature (in big endian)
for (int i = 0; i < resp.length; i += 6) {
Byte feature = new Byte(resp[i]);
- int ioctlBigEndian = (resp[i + 2] << 24) |
- (resp[i + 3] << 16) | (resp[i + 4] << 8) | resp[i + 5];
+ int ioctlBigEndian = ((0xff & resp[i + 2]) << 24) |
+ ((0xff & resp[i + 3]) << 16) |
+ ((0xff & resp[i + 4]) << 8) |
+ (0xff & resp[i + 5]);
Integer ioctl = new Integer(ioctlBigEndian);
if (log.isInfoEnabled()) {
log.info("CCID supports " + FEATURES[feature.intValue()] +
@@ -117,37 +131,29 @@ public class DefaultReader implements CCID {
return false;
}
-// public Integer getIOCTL(Byte feature) {
-// if (features != null) {
-// return features.get(feature);
+// protected byte[] transmitControlCommand(Byte feature, byte[] ctrlCommand)
+// throws CardException {
+// try {
+// if (!features.containsKey(feature)) {
+// throw new CardException(FEATURES[feature.intValue()] + " not supported");
+// }
+// int ioctl = features.get(feature);
+// if (log.isTraceEnabled()) {
+// log.trace("CtrlCommand (" + Integer.toHexString(ioctl) +
+// ") " + SMCCHelper.toString(ctrlCommand));
+// }
+// byte[] resp = icc.transmitControlCommand(ioctl, ctrlCommand);
+// if (log.isTraceEnabled()) {
+// log.trace("CtrlCommand Response " + SMCCHelper.toString(resp));
+// }
+// return resp;
+// } catch (CardException ex) {
+// log.error(ex.getMessage());
+// throw new SignatureCardException("Failed to transmit CtrlCommand for " +
+// FEATURES[feature.intValue()]);
// }
-// return null;
// }
- @Override
- public byte[] transmitControlCommand(Byte feature, byte[] ctrlCommand)
- throws SignatureCardException {
- try {
- if (!features.containsKey(feature)) {
- throw new SignatureCardException(FEATURES[feature.intValue()] + " not supported");
- }
- int ioctl = features.get(feature);
- if (log.isTraceEnabled()) {
- log.trace("CtrlCommand (" + Integer.toHexString(ioctl) +
- ") " + SMCCHelper.toString(ctrlCommand));
- }
- byte[] resp = icc.transmitControlCommand(ioctl, ctrlCommand);
- if (log.isTraceEnabled()) {
- log.trace("CtrlCommand Response " + SMCCHelper.toString(resp));
- }
- return resp;
- } catch (CardException ex) {
- log.error(ex.getMessage());
- throw new SignatureCardException("Failed to transmit CtrlCommand for " +
- FEATURES[feature.intValue()]);
- }
- }
-
@Override
public byte getbTimeOut() {
@@ -162,7 +168,7 @@ public class DefaultReader implements CCID {
@Override
public byte getwPINMaxExtraDigitL() {
- return (byte) 0x12;
+ return (byte) 0x12; // signed int
}
@Override
@@ -175,6 +181,265 @@ public class DefaultReader implements CCID {
return (byte) 0x02; // validation key pressed
}
+ void verifyPinStart(byte[] PIN_VERIFY) throws CardException {
+ if (!features.containsKey(FEATURE_VERIFY_PIN_START)) {
+ throw new CardException("FEATURE_VERIFY_PIN_START not supported");
+ }
+ int ioctl = features.get(FEATURE_VERIFY_PIN_START);
+ if (log.isTraceEnabled()) {
+ log.trace("VERIFY_PIN_START (" + Integer.toHexString(ioctl) +
+ ") " + SMCCHelper.toString(PIN_VERIFY));
+ }
+ byte[] resp = icc.transmitControlCommand(ioctl, PIN_VERIFY);
+ if (resp != null && resp.length > 0) {
+ if (resp[0] == (byte) 0x57) {
+ log.error("Invalid parameter in PIN_VERIFY structure");
+ throw new CardException("ERROR_INVALID_PARAMETER");
+ } else {
+ log.error("unexpected response to VERIFY_PIN_START: " +
+ SMCCHelper.toString(resp));
+ throw new CardException("unexpected response to VERIFY_PIN_START: " +
+ SMCCHelper.toString(resp));
+ }
+ }
+ }
+
+ byte[] verifyPinFinish() throws CardException {
+ if (!features.containsKey(FEATURE_VERIFY_PIN_FINISH)) {
+ throw new CardException("FEATURE_VERIFY_FINISH_FINISH not supported");
+ }
+ int ioctl = features.get(FEATURE_VERIFY_PIN_FINISH);
+ if (log.isTraceEnabled()) {
+ log.trace("VERIFY_PIN_FINISH (" + Integer.toHexString(ioctl) + ")");
+ }
+ byte[] resp = icc.transmitControlCommand(ioctl, new byte[0]);
+ if (resp != null && resp.length == 2) {
+ if (log.isTraceEnabled()) {
+ log.trace("response " + SMCCHelper.toString(resp));
+ }
+ return resp;
+ }
+ log.error("unexpected response to VERIFY_PIN_FINISH: " +
+ SMCCHelper.toString(resp));
+ throw new CardException("unexpected response to VERIFY_PIN_FINISH: " +
+ SMCCHelper.toString(resp));
+ }
+
+ void modifyPinStart(byte[] PIN_MODIFY) throws CardException {
+ if (!features.containsKey(FEATURE_MODIFY_PIN_START)) {
+ throw new CardException("FEATURE_MODIFY_PIN_START not supported");
+ }
+ int ioctl = features.get(FEATURE_MODIFY_PIN_START);
+ if (log.isTraceEnabled()) {
+ log.trace("MODFIY_PIN_START (" + Integer.toHexString(ioctl) +
+ ") " + SMCCHelper.toString(PIN_MODIFY));
+ }
+ byte[] resp = icc.transmitControlCommand(ioctl, PIN_MODIFY);
+ if (resp != null && resp.length > 0) {
+ if (resp[0] == (byte) 0x57) {
+ log.error("Invalid parameter in PIN_MODIFY structure");
+ throw new CardException("ERROR_INVALID_PARAMETER");
+ } else {
+ log.error("unexpected response to MODIFY_PIN_START: " +
+ SMCCHelper.toString(resp));
+ throw new CardException("unexpected response to MODIFY_PIN_START: " +
+ SMCCHelper.toString(resp));
+ }
+ }
+ }
+
+ byte[] modifyPinFinish() throws CardException {
+ if (!features.containsKey(FEATURE_MODIFY_PIN_FINISH)) {
+ throw new CardException("FEATURE_MODIFY_FINISH_FINISH not supported");
+ }
+ int ioctl = features.get(FEATURE_MODIFY_PIN_FINISH);
+ if (log.isTraceEnabled()) {
+ log.trace("MODIFY_PIN_FINISH (" + Integer.toHexString(ioctl) + ")");
+ }
+ byte[] resp = icc.transmitControlCommand(ioctl, new byte[0]);
+ if (resp != null && resp.length == 2) {
+ if (log.isTraceEnabled()) {
+ log.trace("response " + SMCCHelper.toString(resp));
+ }
+ return resp;
+ }
+ log.error("unexpected response to MODIFY_PIN_FINISH: " +
+ SMCCHelper.toString(resp));
+ throw new CardException("unexpected response to MODIFY_PIN_FINISH: " +
+ SMCCHelper.toString(resp));
+ }
+
+
+ byte getKeyPressed() throws CardException {
+ if (!features.containsKey(FEATURE_GET_KEY_PRESSED)) {
+ throw new CardException("FEATURE_GET_KEY_PRESSED not supported");
+ }
+ int ioctl = features.get(FEATURE_GET_KEY_PRESSED);
+// if (log.isTraceEnabled()) {
+// log.trace("GET_KEY_PRESSED (" + Integer.toHexString(ioctl) + ")");
+// }
+ byte[] resp = icc.transmitControlCommand(ioctl, new byte[0]);
+ if (resp != null && resp.length == 1) {
+// if (log.isTraceEnabled()) {
+// log.trace("response " + SMCCHelper.toString(resp));
+// }
+ return resp[0];
+ }
+ log.error("unexpected response to GET_KEY_PRESSED: " +
+ SMCCHelper.toString(resp));
+ throw new CardException("unexpected response to GET_KEY_PRESSED: " +
+ SMCCHelper.toString(resp));
+ }
+
+
+
+ @Override
+ public byte[] verifyPinDirect(byte[] PIN_VERIFY) throws CardException {
+ if (!features.containsKey(FEATURE_VERIFY_PIN_DIRECT)) {
+ throw new CardException("FEATURE_VERIFY_PIN_DIRECT not supported");
+ }
+ int ioctl = features.get(FEATURE_VERIFY_PIN_DIRECT);
+ if (log.isTraceEnabled()) {
+ log.trace("VERIFY_PIN_DIRECT (" + Integer.toHexString(ioctl) +
+ ") " + SMCCHelper.toString(PIN_VERIFY));
+ }
+ byte[] resp = icc.transmitControlCommand(ioctl, PIN_VERIFY);
+ if (log.isTraceEnabled()) {
+ log.trace("response " + SMCCHelper.toString(resp));
+ }
+ return resp;
+ }
+
+ @Override
+ public byte[] verifyPin(byte[] PIN_VERIFY) throws PINOperationAbortedException, CardException {
+ verifyPinStart(PIN_VERIFY);
+ byte resp;
+ do {
+ resp = getKeyPressed();
+ if (resp == (byte) 0x00) {
+ synchronized(this) {
+ try {
+ wait(200);
+ } catch (InterruptedException ex) {
+ log.error("interrupted in VERIFY_PIN");
+ }
+ }
+ } else if (resp == (byte) 0x0d) {
+ log.trace("user confirmed");
+ break;
+ } else if (resp == (byte) 0x2b) {
+ log.trace("user entered valid key (0-9)");
+ } else if (resp == (byte) 0x1b) {
+ log.info("user cancelled VERIFY_PIN via cancel button");
+ return verifyPinFinish();
+// return new byte[] { (byte) 0x64, (byte) 0x01 };
+ } else if (resp == (byte) 0x08) {
+ log.trace("user pressed correction/backspace button");
+ } else if (resp == (byte) 0x0e) {
+ log.trace("timeout occured");
+ return verifyPinFinish(); // return 0x64 0x00
+ } else if (resp == (byte) 0x40) {
+ log.trace("PIN_Operation_Aborted");
+ throw new PINOperationAbortedException("PIN_Operation_Aborted");
+ } else if (resp == (byte) 0x0a) {
+ log.trace("all keys cleared");
+ } else {
+ log.error("unexpected response to GET_KEY_PRESSED: " +
+ Integer.toHexString(resp));
+ throw new CardException("unexpected response to GET_KEY_PRESSED: " +
+ Integer.toHexString(resp));
+ }
+ } while (true); //resp != (byte) 0x0d);
+
+ return verifyPinFinish();
+ }
+
+ @Override
+ public byte[] modifyPin(byte[] PIN_MODIFY) throws PINOperationAbortedException, CardException {
+ modifyPinStart(PIN_MODIFY);
+ log.debug(PIN_MODIFY[9] + " pin confirmations expected");
+
+ byte resp;
+ short pinConfirmations = 0;
+ do {
+ resp = getKeyPressed();
+ if (resp == (byte) 0x00) {
+ synchronized(this) {
+ try {
+ wait(200);
+ } catch (InterruptedException ex) {
+ log.error("interrupted in MODIFY_PIN");
+ }
+ }
+ } else if (resp == (byte) 0x0d) {
+ log.trace("user confirmed");
+ pinConfirmations++;
+ continue;
+ } else if (resp == (byte) 0x2b) {
+ log.trace("user entered valid key (0-9)");
+ } else if (resp == (byte) 0x1b) {
+ log.info("user cancelled MODIFY_PIN via cancel button");
+// return verifyPinFinish();
+ return new byte[] { (byte) 0x64, (byte) 0x01 };
+ } else if (resp == (byte) 0x08) {
+ log.trace("user pressed correction/backspace button");
+ } else if (resp == (byte) 0x0e) {
+ log.trace("timeout occured");
+ return new byte[] { (byte) 0x64, (byte) 0x00 };
+// return verifyPinFinish(); // return 0x64 0x00
+ } else if (resp == (byte) 0x40) {
+ log.trace("PIN_Operation_Aborted");
+ throw new PINOperationAbortedException("PIN_Operation_Aborted");
+ } else if (resp == (byte) 0x0a) {
+ log.trace("all keys cleared");
+ } else {
+ log.error("unexpected response to GET_KEY_PRESSED: " +
+ Integer.toHexString(resp));
+ throw new CardException("unexpected response to GET_KEY_PRESSED: " +
+ Integer.toHexString(resp));
+ }
+ } while (pinConfirmations < PIN_MODIFY[9]);
+
+ return modifyPinFinish();
+ }
+
+ /**
+ * NOT SUPPORTED FOR ACOS ON OMNIKEY CardMan 3621
+ *
+ * @param PIN_MODIFY
+ * @return
+ * @throws javax.smartcardio.CardException
+ */
+ @Override
+ public byte[] modifyPinDirect(byte[] PIN_MODIFY) throws CardException {
+ if (!features.containsKey(FEATURE_MODIFY_PIN_DIRECT)) {
+ throw new CardException("FEATURE_MODIFY_PIN_DIRECT not supported");
+ }
+ int ioctl = features.get(FEATURE_MODIFY_PIN_DIRECT);
+ if (log.isTraceEnabled()) {
+ log.trace("MODIFY_PIN_DIRECT (" + Integer.toHexString(ioctl) +
+ ") " + SMCCHelper.toString(PIN_MODIFY));
+ }
+ byte[] resp = icc.transmitControlCommand(ioctl, PIN_MODIFY);
+ if (log.isTraceEnabled()) {
+ log.trace("response " + SMCCHelper.toString(resp));
+ }
+ return resp;
+ }
+
+
+//[TRACE] SmartCardIO - terminal 'PC/SC terminal OMNIKEY CardMan 3621 0' card inserted : PC/SC card in OMNIKEY CardMan 3621 0, protocol T=1, state OK
+//[TRACE] DefaultReader - GET_FEATURE_REQUEST 313520 on OMNIKEY CardMan 3621 0
+//[TRACE] DefaultReader - Response TLV [01:04:00:31:30:00:02:04:00:31:2f:d4:03:04:00:31:30:04:04:04:00:31:2f:dc:05:04:00:31:2f:e0:0a:04
+//00:31:30:08]
+//[INFO] DefaultReader - CCID supports FEATURE_VERIFY_PIN_START: 313000
+//[INFO] DefaultReader - CCID supports FEATURE_VERIFY_PIN_FINISH: ffffffd4
+//[INFO] DefaultReader - CCID supports FEATURE_MODIFY_PIN_START: 313004
+//[INFO] DefaultReader - CCID supports FEATURE_MODIFY_PIN_FINISH: ffffffdc
+//[INFO] DefaultReader - CCID supports FEATURE_GET_KEY_PRESSED: ffffffe0
+//[INFO] DefaultReader - CCID supports FEATURE_IFD_PIN_PROPERTIES: 313008
+//[TRACE] AbstractSignatureCard - Setting IFS (information field size) to 254
+
// protected byte ifdGetKeyPressed() throws CardException {
// if (ifdSupportsFeature(FEATURE_VERIFY_PIN_DIRECT)) {
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/ccid/OMNIKEYCardMan3621.java b/smcc/src/main/java/at/gv/egiz/smcc/ccid/OMNIKEYCardMan3621.java
new file mode 100644
index 00000000..35dd4f99
--- /dev/null
+++ b/smcc/src/main/java/at/gv/egiz/smcc/ccid/OMNIKEYCardMan3621.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2008 Federal Chancellery Austria and
+ * Graz University of Technology
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package at.gv.egiz.smcc.ccid;
+
+import javax.smartcardio.Card;
+import javax.smartcardio.CardTerminal;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * Fails with ACOS cards (Problem might be 'short' VERIFY which is not supported by ACOS)
+ * TODO
+ *
+ * @author Clemens Orthacker <clemens.orthacker@iaik.tugraz.at>
+ */
+public class OMNIKEYCardMan3621 extends DefaultReader {
+
+ protected static final Log log = LogFactory.getLog(OMNIKEYCardMan3621.class);
+
+ public OMNIKEYCardMan3621(Card icc, CardTerminal ct) {
+ super(icc, ct);
+ log.warn("This card reader does not support ACOS cards.");
+ log.debug("TODO: fall back to software pin entry");
+ }
+}
diff --git a/smcc/src/main/java/at/gv/egiz/smcc/ccid/ReaderFactory.java b/smcc/src/main/java/at/gv/egiz/smcc/ccid/ReaderFactory.java
index 2cfcef19..07c16c3e 100644
--- a/smcc/src/main/java/at/gv/egiz/smcc/ccid/ReaderFactory.java
+++ b/smcc/src/main/java/at/gv/egiz/smcc/ccid/ReaderFactory.java
@@ -29,6 +29,8 @@ public class ReaderFactory {
public static CCID getReader(Card icc, CardTerminal ct) {
if ("Gemplus GemPC Pinpad 00 00".equals(ct.getName())) {
return new GemplusGemPCPinpad(icc, ct);
+ } else if ("OmniKey CardMan 3621 00 00".equals(ct.getName())) {
+ return new OMNIKEYCardMan3621(icc, ct);
}
return new DefaultReader(icc, ct);
}