import AcsCommon.*;
import Pcsc.PcscReader;

public class MifareUltralight {
	final static public int FOUR_BYTES = 4;
	final static public int TRANSPARENT_SESSION_EXPECTED_LENGTH = 7;
	final static public int RANDOM_NUMBER_LENGTH = 8;
	final static public int ACTIVATE_TYPE_A_SWITCH_EXPECTED_LENGTH = 10;
	final static public int RECEIVED_RANDOM_NUMBER_INDEX = 15;
	final static public int SUM_OF_RANDOM_NUMBERS = 16;
	final static public int SIXTEEN_BYTES = 16;
	final static public int GET_RANDOM_NUMBER_EXPECTED_LENGTH = 25;
	final static public int VERIFY_RANDOM_NUMBER_EXPECTED_LENGTH = 26;

	private PcscReader pcscReader_;
	private byte[] initialVector_;
	private byte[] authenticationKey_;

	public PcscReader getPcscConnection() {
		return this.pcscReader_;
	}

	public void setPcscConnection(PcscReader pcscConnection) {
		this.pcscReader_ = pcscConnection;
	}

	public MifareUltralight() {
		pcscReader_ = null;
	}

	public MifareUltralight(PcscReader pcscReader) {
		setPcscConnection(pcscReader);
	}

	public byte[] getInitialVector() {
		return this.initialVector_;
	}

	public void setInitialVector(byte[] initialVector) {
		this.initialVector_ = initialVector;
	}

	public byte[] getAuthenticationKey() {
		return this.authenticationKey_;
	}

	public void setAuthenticationKey(byte[] authenticationKey) {
		this.authenticationKey_ = authenticationKey;
	}

	public boolean isPageNotUserData(byte pageNumber) {

		if ((pageNumber > 0x03) || (pageNumber < 0x28 && pageNumber > 0x2F))
			return false;
		else
			return true;
	}

	public void startSession() throws Exception {
		Apdu apdu = new Apdu();
		byte[] data = { (byte) 0x81, (byte) 0x00 };

		apdu.setCommand(new byte[] { (byte) 0xFF, (byte) 0xC2, (byte) 0x00, (byte) 0x00, (byte) 0x02 });

		apdu.setSendData(data);
		apdu.setLengthExpected(TRANSPARENT_SESSION_EXPECTED_LENGTH);

		getPcscConnection().sendApduCommand(apdu);

		if (apdu.getStatusWord()[0] != (byte) 0x90)
			throw new Exception(
					"[" + Helper.byteAsString(apdu.getStatusWord(), false) + "]" + " Start Transparent Session failed");
	}

	public void endSession() throws Exception {
		Apdu apdu = new Apdu();
		byte[] data = { (byte) 0x82, (byte) 0x00 };

		apdu.setCommand(new byte[] { (byte) 0xFF, (byte) 0xC2, (byte) 0x00, (byte) 0x00, (byte) 0x02 });
		apdu.setSendData(data);
		apdu.setLengthExpected(TRANSPARENT_SESSION_EXPECTED_LENGTH);

		getPcscConnection().sendApduCommand(apdu);

		if (apdu.getStatusWord()[0] != (byte) 0x90)
			throw new Exception(
					"[" + Helper.byteAsString(apdu.getStatusWord(), false) + "]" + " End Transparent Session failed");
	}

	public void switchAntennaField() throws Exception {
		Apdu apdu = new Apdu();
		byte[] data = { (byte) 0x8F, (byte) 0x02, (byte) 0x00, (byte) 0x03 };

		apdu.setCommand(new byte[] { (byte) 0xFF, (byte) 0xC2, (byte) 0x00, (byte) 0x02, (byte) 0x04 });
		apdu.setSendData(data);
		apdu.setLengthExpected(ACTIVATE_TYPE_A_SWITCH_EXPECTED_LENGTH);

		getPcscConnection().sendApduCommand(apdu);

		if (apdu.getStatusWord()[0] != (byte) 0x90)
			throw new Exception(
					"[" + Helper.byteAsString(apdu.getStatusWord(), false) + "]" + " ISO 14443-3 Type A Switch failed");
	}

	public byte[] getCardRandomNumber() throws Exception {
		Apdu apdu = new Apdu();
		byte[] tmpArray = new byte[RANDOM_NUMBER_LENGTH];
		byte[] data = { (byte) 0x5F, (byte) 0x46, (byte) 0x04, (byte) 0x00, (byte) 0x80, (byte) 0x00, (byte) 0x00,
				(byte) 0x95, (byte) 0x02, (byte) 0x1A, (byte) 0x00 };

		apdu.setCommand(new byte[] { (byte) 0xFF, (byte) 0xC2, (byte) 0x00, (byte) 0x01, (byte) 0x0B });

		apdu.setSendData(data);
		apdu.setLengthExpected(GET_RANDOM_NUMBER_EXPECTED_LENGTH);

		getPcscConnection().sendApduCommand(apdu);

		if (apdu.getStatusWord()[0] != (byte) 0x90)
			throw new Exception(
					"[" + Helper.byteAsString(apdu.getStatusWord(), false) + "]" + " Authentication Part 1 failed");

		System.arraycopy(apdu.getReceiveData(), RECEIVED_RANDOM_NUMBER_INDEX, tmpArray, 0, RANDOM_NUMBER_LENGTH);

		return tmpArray;
	}

	public byte[] getDataFromCard(byte[] encryptedRandomNumbers) throws Exception {
		Apdu apdu = new Apdu();
		byte[] tmpArray = new byte[RANDOM_NUMBER_LENGTH];
		byte[] data = new byte[VERIFY_RANDOM_NUMBER_EXPECTED_LENGTH];
		byte[] tempData = { (byte) 0x5F, (byte) 0x46, (byte) 0x04, (byte) 0x00, (byte) 0x80, (byte) 0x00, (byte) 0x00,
				(byte) 0x95, (byte) 0x11, (byte) 0xAF };

		System.arraycopy(tempData, 0, data, 0, tempData.length);
		System.arraycopy(encryptedRandomNumbers, 0, data, tempData.length,
				VERIFY_RANDOM_NUMBER_EXPECTED_LENGTH - tempData.length);

		apdu.setCommand(new byte[] { (byte) 0xFF, (byte) 0xC2, (byte) 0x00, (byte) 0x01, (byte) 0x1A });

		apdu.setSendData(data);
		apdu.setLengthExpected(GET_RANDOM_NUMBER_EXPECTED_LENGTH);

		getPcscConnection().sendApduCommand(apdu);

		if (apdu.getStatusWord()[0] != (byte) 0x90)
			throw new Exception(
					"[" + Helper.byteAsString(apdu.getStatusWord(), false) + "]" + " Authentication Part 2 failed");

		System.arraycopy(apdu.getReceiveData(), RECEIVED_RANDOM_NUMBER_INDEX, tmpArray, 0, RANDOM_NUMBER_LENGTH);

		return tmpArray;
	}

	public byte[] readBinaryPage(byte pageNum, byte length) throws Exception {
		Apdu apdu;
		byte[] tmpArray = new byte[16];
		apdu = new Apdu();
		apdu.setCommand(new byte[] { (byte) 0xFF, (byte) 0xB0, 0, pageNum, length });
		apdu.setLengthExpected(length);

		getPcscConnection().sendApduCommand(apdu);

		if (apdu.getStatusWord()[0] != (byte) 0x90)
			throw new Exception("[" + Helper.byteAsString(apdu.getStatusWord(), false) + "]" + " Read failed");

		System.arraycopy(apdu.getReceiveData(), 0, tmpArray, 0, length);

		return tmpArray;
	}

	public void writeBinaryPage(byte pageNum, byte[] data, byte length) throws Exception {
		Apdu apdu;
		apdu = new Apdu();
		apdu.setCommand(new byte[] { (byte) 0xFF, (byte) 0xD6, (byte) 0x00, pageNum, length });
		apdu.setSendData(data);

		getPcscConnection().sendApduCommand(apdu);

		if (apdu.getStatusWord()[0] != (byte) 0x90)
			throw new Exception("[" + Helper.byteAsString(apdu.getStatusWord(), false) + "]" + " Write failed");
	}

	public void sendCommand(byte[] cmd, byte[] sendData) throws Exception {
		Apdu apdu = new Apdu();
		apdu.setCommand(cmd);
		apdu.setSendData(sendData);

		getPcscConnection().sendApduCommand(apdu);

		if (apdu.getStatusWord()[0] != (byte) 0x90)
			throw new Exception("[" + Helper.byteAsString(apdu.getStatusWord(), false) + "]" + " Command failed");
	}
}
