/* * Copyright (C) 2008 The Android Open Source Project * * 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 com.android.internal.telephony; import android.os.*; import android.util.Log; import java.util.ArrayList; /** * {@hide} */ public abstract class IccFileHandler extends Handler implements IccConstants { //from TS 11.11 9.1 or elsewhere static protected final int COMMAND_READ_BINARY = 0xb0; static protected final int COMMAND_UPDATE_BINARY = 0xd6; static protected final int COMMAND_READ_RECORD = 0xb2; static protected final int COMMAND_UPDATE_RECORD = 0xdc; static protected final int COMMAND_SEEK = 0xa2; static protected final int COMMAND_GET_RESPONSE = 0xc0; // from TS 11.11 9.2.5 static protected final int READ_RECORD_MODE_ABSOLUTE = 4; //***** types of files TS 11.11 9.3 static protected final int EF_TYPE_TRANSPARENT = 0; static protected final int EF_TYPE_LINEAR_FIXED = 1; static protected final int EF_TYPE_CYCLIC = 3; //***** types of files TS 11.11 9.3 static protected final int TYPE_RFU = 0; static protected final int TYPE_MF = 1; static protected final int TYPE_DF = 2; static protected final int TYPE_EF = 4; // size of GET_RESPONSE for EF's static protected final int GET_RESPONSE_EF_SIZE_BYTES = 15; static protected final int GET_RESPONSE_EF_IMG_SIZE_BYTES = 10; // Byte order received in response to COMMAND_GET_RESPONSE // Refer TS 51.011 Section 9.2.1 static protected final int RESPONSE_DATA_RFU_1 = 0; static protected final int RESPONSE_DATA_RFU_2 = 1; static protected final int RESPONSE_DATA_FILE_SIZE_1 = 2; static protected final int RESPONSE_DATA_FILE_SIZE_2 = 3; static protected final int RESPONSE_DATA_FILE_ID_1 = 4; static protected final int RESPONSE_DATA_FILE_ID_2 = 5; static protected final int RESPONSE_DATA_FILE_TYPE = 6; static protected final int RESPONSE_DATA_RFU_3 = 7; static protected final int RESPONSE_DATA_ACCESS_CONDITION_1 = 8; static protected final int RESPONSE_DATA_ACCESS_CONDITION_2 = 9; static protected final int RESPONSE_DATA_ACCESS_CONDITION_3 = 10; static protected final int RESPONSE_DATA_FILE_STATUS = 11; static protected final int RESPONSE_DATA_LENGTH = 12; static protected final int RESPONSE_DATA_STRUCTURE = 13; static protected final int RESPONSE_DATA_RECORD_LENGTH = 14; //***** Events /** Finished retrieving size of transparent EF; start loading. */ static protected final int EVENT_GET_BINARY_SIZE_DONE = 4; /** Finished loading contents of transparent EF; post result. */ static protected final int EVENT_READ_BINARY_DONE = 5; /** Finished retrieving size of records for linear-fixed EF; now load. */ static protected final int EVENT_GET_RECORD_SIZE_DONE = 6; /** Finished loading single record from a linear-fixed EF; post result. */ static protected final int EVENT_READ_RECORD_DONE = 7; /** Finished retrieving record size; post result. */ static protected final int EVENT_GET_EF_LINEAR_RECORD_SIZE_DONE = 8; /** Finished retrieving image instance record; post result. */ static protected final int EVENT_READ_IMG_DONE = 9; /** Finished retrieving icon data; post result. */ static protected final int EVENT_READ_ICON_DONE = 10; // member variables protected PhoneBase phone; static class LoadLinearFixedContext { int efid; int recordNum, recordSize, countRecords; boolean loadAll; Message onLoaded; ArrayList<byte[]> results; LoadLinearFixedContext(int efid, int recordNum, Message onLoaded) { this.efid = efid; this.recordNum = java.lang.Math.max(recordNum, 1); // Clamp to 1 since the index is 1 based, just in case this.onLoaded = onLoaded; this.loadAll = false; } LoadLinearFixedContext(int efid, Message onLoaded) { this.efid = efid; this.recordNum = 1; this.loadAll = true; this.onLoaded = onLoaded; } } /** * Default constructor */ protected IccFileHandler(PhoneBase phone) { super(); this.phone = phone; } public void dispose() { } //***** Public Methods /** * Load a record from a SIM Linear Fixed EF * * @param fileid EF id * @param recordNum 1-based (not 0-based) record number * @param onLoaded * * ((AsyncResult)(onLoaded.obj)).result is the byte[] * */ public void loadEFLinearFixed(int fileid, int recordNum, Message onLoaded) { Message response = obtainMessage(EVENT_GET_RECORD_SIZE_DONE, new LoadLinearFixedContext(fileid, recordNum, onLoaded)); phone.mCM.iccIO(COMMAND_GET_RESPONSE, fileid, getEFPath(fileid), 0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, response); } /** * Load a image instance record from a SIM Linear Fixed EF-IMG * * @param recordNum 1-based (not 0-based) record number * @param onLoaded * * ((AsyncResult)(onLoaded.obj)).result is the byte[] * */ public void loadEFImgLinearFixed(int recordNum, Message onLoaded) { Message response = obtainMessage(EVENT_READ_IMG_DONE, new LoadLinearFixedContext(IccConstants.EF_IMG, recordNum, onLoaded)); // TODO(): Verify when path changes are done. phone.mCM.iccIO(COMMAND_GET_RESPONSE, IccConstants.EF_IMG, "img", recordNum, READ_RECORD_MODE_ABSOLUTE, GET_RESPONSE_EF_IMG_SIZE_BYTES, null, null, response); } /** * get record size for a linear fixed EF * * @param fileid EF id * @param onLoaded ((AsnyncResult)(onLoaded.obj)).result is the recordSize[] * int[0] is the record length int[1] is the total length of the EF * file int[3] is the number of records in the EF file So int[0] * * int[3] = int[1] */ public void getEFLinearRecordSize(int fileid, Message onLoaded) { Message response = obtainMessage(EVENT_GET_EF_LINEAR_RECORD_SIZE_DONE, new LoadLinearFixedContext(fileid, onLoaded)); phone.mCM.iccIO(COMMAND_GET_RESPONSE, fileid, getEFPath(fileid), 0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, response); } /** * Load all records from a SIM Linear Fixed EF * * @param fileid EF id * @param onLoaded * * ((AsyncResult)(onLoaded.obj)).result is an ArrayList<byte[]> * */ public void loadEFLinearFixedAll(int fileid, Message onLoaded) { Message response = obtainMessage(EVENT_GET_RECORD_SIZE_DONE, new LoadLinearFixedContext(fileid,onLoaded)); phone.mCM.iccIO(COMMAND_GET_RESPONSE, fileid, getEFPath(fileid), 0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, response); } /** * Load a SIM Transparent EF * * @param fileid EF id * @param onLoaded * * ((AsyncResult)(onLoaded.obj)).result is the byte[] * */ public void loadEFTransparent(int fileid, Message onLoaded) { Message response = obtainMessage(EVENT_GET_BINARY_SIZE_DONE, fileid, 0, onLoaded); phone.mCM.iccIO(COMMAND_GET_RESPONSE, fileid, getEFPath(fileid), 0, 0, GET_RESPONSE_EF_SIZE_BYTES, null, null, response); } /** * Load a SIM Transparent EF-IMG. Used right after loadEFImgLinearFixed to * retrive STK's icon data. * * @param fileid EF id * @param onLoaded * * ((AsyncResult)(onLoaded.obj)).result is the byte[] * */ public void loadEFImgTransparent(int fileid, int highOffset, int lowOffset, int length, Message onLoaded) { Message response = obtainMessage(EVENT_READ_ICON_DONE, fileid, 0, onLoaded); phone.mCM.iccIO(COMMAND_READ_BINARY, fileid, "img", highOffset, lowOffset, length, null, null, response); } /** * Update a record in a linear fixed EF * @param fileid EF id * @param recordNum 1-based (not 0-based) record number * @param data must be exactly as long as the record in the EF * @param pin2 for CHV2 operations, otherwist must be null * @param onComplete onComplete.obj will be an AsyncResult * onComplete.obj.userObj will be a IccIoResult on success */ public void updateEFLinearFixed(int fileid, int recordNum, byte[] data, String pin2, Message onComplete) { phone.mCM.iccIO(COMMAND_UPDATE_RECORD, fileid, getEFPath(fileid), recordNum, READ_RECORD_MODE_ABSOLUTE, data.length, IccUtils.bytesToHexString(data), pin2, onComplete); } /** * Update a transparent EF * @param fileid EF id * @param data must be exactly as long as the EF */ public void updateEFTransparent(int fileid, byte[] data, Message onComplete) { phone.mCM.iccIO(COMMAND_UPDATE_BINARY, fileid, getEFPath(fileid), 0, 0, data.length, IccUtils.bytesToHexString(data), null, onComplete); } //***** Abstract Methods //***** Private Methods private void sendResult(Message response, Object result, Throwable ex) { if (response == null) { return; } AsyncResult.forMessage(response, result, ex); response.sendToTarget(); } /** * Fills a 3 integer array. This is necessary for many canadian carriers for which * UICC contains commands in TLV format (Refer 11.1.1.3 of ETSI TS 102 221) * * @param data * @param arrayToReturn : [0] = Record Size; [1] = File Size; [2] = Number of Records */ private void ParseUICCTLVData(byte[] data, int[] arrayToReturn) throws IccFileTypeMismatch { int curByte = 0; int curTag; int curSectionLen; if (((short)data[curByte++] & 0xFF) == 0x62) { logd("TLV format detected"); curSectionLen = data[curByte++]; int dataLenLeft = data.length-2; if (curSectionLen != dataLenLeft) { logd("Unexpected TLV length of " + curSectionLen + "; we have " + dataLenLeft + "bytes of data left"); // ... It sometimes happens, even if mandatory parts are missing and length > than the actual size. Data is truncated? // throw new IccFileTypeMismatch(); } // File Descriptor '0x82' (mandatory) curTag = ((int)data[curByte++]) & 0xFF; if (curTag != 0x82) { logd("Unexpected TLV data, expecting file descriptor tag, but got " + curTag); throw new IccFileTypeMismatch(); } curSectionLen = data[curByte++]; if (curSectionLen != 5) { // TODO : Currently, a length of 2 is not handled logd("TLV File Description length of " + curSectionLen + " is not handled yet"); throw new IccFileTypeMismatch(); } arrayToReturn[0] = ((data[curByte+2] & 0xff) << 8) + (data[curByte+3] & 0xff); // Length of 1 record arrayToReturn[2] = data[curByte+4]; // Number of records // File size is normally set later, but for some reason, sometimes the data // is missing mandatory section. For this reason, set the information here // it should match anyway... (honestly, I'm not sure) arrayToReturn[1] = arrayToReturn[0] * arrayToReturn[2]; curByte += curSectionLen; // File Identifier '0x83' (mandatory) curTag = ((int)data[curByte++]) & 0xFF; if (curTag != 0x83) { logd("Unexpected TLV data, expecting file identifier tag, but got " + curTag); throw new IccFileTypeMismatch(); } curSectionLen = data[curByte++]; curByte += curSectionLen; // Proprietary info '0xA5' (optional) curTag = ((int)data[curByte++]) & 0xFF; if (curTag == 0xA5) { // Not needed, just skip it... curSectionLen = data[curByte++]; curByte += curSectionLen; } // Data is sometimes truncated!? Mandatory parts are sometimes missing and TLV length > than the actual data size. // Do not throw exception, try to do our best if (data.length > curByte) { // Life Cycle Status Integer '0x8A' (mandatory) curTag = ((int)data[curByte++]) & 0xFF; if (curTag != 0x8A) { logd("Unexpected TLV data, expecting Life Cycle Status Integer tag, but got " + curTag); throw new IccFileTypeMismatch(); } // Not needed, just skip it... curSectionLen = data[curByte++]; curByte += curSectionLen; } // Data is sometimes truncated!? Mandatory parts are sometimes missing and TLV length > than the actual data size. // Do not throw exception, try to do our best if (data.length > curByte) { // Security Attributes '0x8B' / '0x8C' / '0xAB' (exactly one of them is mandatory) curTag = ((int)data[curByte++]) & 0xFF; if (curTag != 0x8B && curTag != 0x8C && curTag != 0xAB) { logd("Unexpected TLV data, expecting Security Attributes tag, but got " + curTag); throw new IccFileTypeMismatch(); } // Not needed, just skip it... curSectionLen = data[curByte++]; curByte += curSectionLen; } // Data is sometimes truncated!? Mandatory parts are sometimes missing and TLV length > than the actual data size. // Do not throw exception, try to do our best if (data.length > curByte) { // File Size '0x80' (mandatory) curTag = ((int)data[curByte++]) & 0xFF; if (curTag != 0x80) { logd("Unexpected TLV data, expecting File Size tag, but got " + curTag); throw new IccFileTypeMismatch(); } curSectionLen = data[curByte++]; arrayToReturn[1] = 0; for (int i = 0; i < curSectionLen; i++) { arrayToReturn[1] += ((data[i] & 0xff) << (8*i)); // File size } curByte += curSectionLen; } logd("ParseUICCTLVData result: Record Size = " + arrayToReturn[0] + "; File Size = " + arrayToReturn[1] + "; Number of Records = " + arrayToReturn[2]); // Total File Size '0x81' (optional) // Short File Identifier '0x88' (optional) // --> not used... } else { logd("Throwing exception : Expecting a TLV tag!"); throw new IccFileTypeMismatch(); } } //***** Overridden from Handler public void handleMessage(Message msg) { AsyncResult ar; IccIoResult result; Message response = null; String str; LoadLinearFixedContext lc; IccException iccException; byte data[]; int size; int fileid; int recordNum; int recordSize[] = new int[3]; try { switch (msg.what) { case EVENT_READ_IMG_DONE: ar = (AsyncResult) msg.obj; lc = (LoadLinearFixedContext) ar.userObj; result = (IccIoResult) ar.result; response = lc.onLoaded; iccException = result.getException(); if (iccException != null) { sendResult(response, result.payload, ar.exception); } break; case EVENT_READ_ICON_DONE: ar = (AsyncResult) msg.obj; response = (Message) ar.userObj; result = (IccIoResult) ar.result; iccException = result.getException(); if (iccException != null) { sendResult(response, result.payload, ar.exception); } break; case EVENT_GET_EF_LINEAR_RECORD_SIZE_DONE: ar = (AsyncResult)msg.obj; lc = (LoadLinearFixedContext) ar.userObj; result = (IccIoResult) ar.result; response = lc.onLoaded; if (ar.exception != null) { sendResult(response, null, ar.exception); break; } iccException = result.getException(); if (iccException != null) { sendResult(response, null, iccException); break; } data = result.payload; if (data[0] == 0x62) { ParseUICCTLVData(data, recordSize); } else { if (TYPE_EF != data[RESPONSE_DATA_FILE_TYPE] || EF_TYPE_LINEAR_FIXED != data[RESPONSE_DATA_STRUCTURE]) { logd("Exception in EVENT_GET_EF_LINEAR_RECORD_SIZE_DONE"); throw new IccFileTypeMismatch(); } recordSize[0] = data[RESPONSE_DATA_RECORD_LENGTH] & 0xFF; recordSize[1] = ((data[RESPONSE_DATA_FILE_SIZE_1] & 0xff) << 8) + (data[RESPONSE_DATA_FILE_SIZE_2] & 0xff); recordSize[2] = recordSize[1] / recordSize[0]; } sendResult(response, recordSize, null); break; case EVENT_GET_RECORD_SIZE_DONE: ar = (AsyncResult)msg.obj; lc = (LoadLinearFixedContext) ar.userObj; result = (IccIoResult) ar.result; response = lc.onLoaded; if (ar.exception != null) { sendResult(response, null, ar.exception); break; } iccException = result.getException(); if (iccException != null) { sendResult(response, null, iccException); break; } data = result.payload; fileid = lc.efid; recordNum = lc.recordNum; if (data[0] == 0x62) { ParseUICCTLVData(data, recordSize); lc.recordSize = recordSize[0]; size = recordSize[1]; lc.countRecords = recordSize[2]; } else { if (TYPE_EF != data[RESPONSE_DATA_FILE_TYPE]) { logd("Exception in EVENT_GET_RECORD_SIZE_DONE"); throw new IccFileTypeMismatch(); } if (EF_TYPE_LINEAR_FIXED != data[RESPONSE_DATA_STRUCTURE]) { logd("Exception in EVENT_GET_RECORD_SIZE_DONE"); throw new IccFileTypeMismatch(); } lc.recordSize = data[RESPONSE_DATA_RECORD_LENGTH] & 0xFF; size = ((data[RESPONSE_DATA_FILE_SIZE_1] & 0xff) << 8) + (data[RESPONSE_DATA_FILE_SIZE_2] & 0xff); lc.countRecords = size / lc.recordSize; } if (lc.loadAll) { lc.results = new ArrayList<byte[]>(lc.countRecords); } phone.mCM.iccIO(COMMAND_READ_RECORD, lc.efid, getEFPath(lc.efid), lc.recordNum, READ_RECORD_MODE_ABSOLUTE, lc.recordSize, null, null, obtainMessage(EVENT_READ_RECORD_DONE, lc)); break; case EVENT_GET_BINARY_SIZE_DONE: ar = (AsyncResult)msg.obj; response = (Message) ar.userObj; result = (IccIoResult) ar.result; if (ar.exception != null) { sendResult(response, null, ar.exception); break; } iccException = result.getException(); if (iccException != null) { sendResult(response, null, iccException); break; } data = result.payload; fileid = msg.arg1; if (data[0] == 0x62) { ParseUICCTLVData(data, recordSize); size = recordSize[1]; } else { if (TYPE_EF != data[RESPONSE_DATA_FILE_TYPE]) { logd("Exception in EVENT_GET_BINARY_SIZE_DONE"); throw new IccFileTypeMismatch(); } if (EF_TYPE_TRANSPARENT != data[RESPONSE_DATA_STRUCTURE]) { logd("Exception in EVENT_GET_BINARY_SIZE_DONE"); throw new IccFileTypeMismatch(); } size = ((data[RESPONSE_DATA_FILE_SIZE_1] & 0xff) << 8) + (data[RESPONSE_DATA_FILE_SIZE_2] & 0xff); } phone.mCM.iccIO(COMMAND_READ_BINARY, fileid, getEFPath(fileid), 0, 0, size, null, null, obtainMessage(EVENT_READ_BINARY_DONE, fileid, 0, response)); break; case EVENT_READ_RECORD_DONE: ar = (AsyncResult)msg.obj; lc = (LoadLinearFixedContext) ar.userObj; result = (IccIoResult) ar.result; response = lc.onLoaded; if (ar.exception != null) { sendResult(response, null, ar.exception); break; } iccException = result.getException(); if (iccException != null) { sendResult(response, null, iccException); break; } if (!lc.loadAll) { sendResult(response, result.payload, null); } else { lc.results.add(result.payload); lc.recordNum++; if (lc.recordNum > lc.countRecords) { sendResult(response, lc.results, null); } else { phone.mCM.iccIO(COMMAND_READ_RECORD, lc.efid, getEFPath(lc.efid), lc.recordNum, READ_RECORD_MODE_ABSOLUTE, lc.recordSize, null, null, obtainMessage(EVENT_READ_RECORD_DONE, lc)); } } break; case EVENT_READ_BINARY_DONE: ar = (AsyncResult)msg.obj; response = (Message) ar.userObj; result = (IccIoResult) ar.result; if (ar.exception != null) { sendResult(response, null, ar.exception); break; } iccException = result.getException(); if (iccException != null) { sendResult(response, null, iccException); break; } sendResult(response, result.payload, null); break; }} catch (Exception exc) { if (response != null) { sendResult(response, null, exc); } else { loge("uncaught exception" + exc); } } } /** * Returns the root path of the EF file. * i.e returns MasterFile + DFfile as a string. * Ex: For EF_ADN on a SIM, it will return "3F007F10" * This function handles only EFids that are common to * RUIM, SIM, USIM and other types of Icc cards. * * @param efId * @return root path of the file. */ protected String getCommonIccEFPath(int efid) { switch(efid) { case EF_ADN: case EF_FDN: case EF_MSISDN: case EF_SDN: case EF_EXT1: case EF_EXT2: case EF_EXT3: return MF_SIM + DF_TELECOM; case EF_ICCID: return MF_SIM; case EF_IMG: return MF_SIM + DF_TELECOM + DF_GRAPHICS; } return null; } protected abstract String getEFPath(int efid); protected abstract void logd(String s); protected abstract void loge(String s); }