package applet;

import javacard.framework.*;

public class SIMFiles extends DFile {
    private Object[] selectFiles = null;
    private boolean[] isUserAuthed = null;
    private final byte[] opc;
    private final byte[] key;
    public Milenage milenage = null;
    private short[] recordPointer; // set default pointer as -10

    public SIMFiles() {
        super((short) 0x3F00, new byte[]{(byte) 0x62, (byte) 0x54, (byte) 0x82, (byte) 0x02, (byte) 0x78, (byte) 0x21,
                (byte) 0x83, (byte) 0x02, (byte) 0x3f, (byte) 0x00, (byte) 0xa5, (byte) 0x19, (byte) 0x80, (byte) 0x01,
                (byte) 0x71, (byte) 0x83, (byte) 0x02, (byte) 0x7f, (byte) 0xff, (byte) 0xcb, (byte) 0x0d, (byte) 0x00,
                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00,
                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0xca, (byte) 0x01, (byte) 0x82, (byte) 0x8a,
                (byte) 0x01, (byte) 0x05, (byte) 0xab, (byte) 0x1b, (byte) 0x84, (byte) 0x01, (byte) 0x2e, (byte) 0x90,
                (byte) 0x00, (byte) 0x84, (byte) 0x01, (byte) 0x88, (byte) 0xa4, (byte) 0x06, (byte) 0x83, (byte) 0x01,
                (byte) 0x01, (byte) 0x95, (byte) 0x01, (byte) 0x08, (byte) 0x84, (byte) 0x01, (byte) 0xfc, (byte) 0xa4,
                (byte) 0x06, (byte) 0x83, (byte) 0x01, (byte) 0x0a, (byte) 0x95, (byte) 0x01, (byte) 0x08, (byte) 0xc6,
                (byte) 0x0f, (byte) 0x90, (byte) 0x01, (byte) 0x70, (byte) 0x83, (byte) 0x01, (byte) 0x01, (byte) 0x83,
                (byte) 0x01, (byte) 0x0a, (byte) 0x83, (byte) 0x01, (byte) 0x0b, (byte) 0x83, (byte) 0x01, (byte) 0x81
        });
        this.isUserAuthed = JCSystem.makeTransientBooleanArray((short) 1, JCSystem.CLEAR_ON_DESELECT);
        this.selectFiles = JCSystem.makeTransientObjectArray((short) 2, JCSystem.CLEAR_ON_DESELECT); // Store the DF and EF
        this.recordPointer = JCSystem.makeTransientShortArray((short) 1, JCSystem.CLEAR_ON_DESELECT); // Store the recordPointer
        this.selectFiles[0] = this; // Set current DF to MF
        this.opc = new byte[]{
                (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03,
                (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07,
                (byte) 0x08, (byte) 0x09, (byte) 0x0a, (byte) 0x0b,
                (byte) 0x0c, (byte) 0x0d, (byte) 0x0e, (byte) 0x0f,
        };
        this.key = new byte[]{
                (byte) 0x00, (byte) 0x01, (byte) 0x02, (byte) 0x03,
                (byte) 0x04, (byte) 0x05, (byte) 0x06, (byte) 0x07,
                (byte) 0x08, (byte) 0x09, (byte) 0x0a, (byte) 0x0b,
                (byte) 0x0c, (byte) 0x0d, (byte) 0x0e, (byte) 0x0f,
        };
        this.milenage = new Milenage(this.opc, this.key);
    }

    public DFile getSelectedDF() {
        return ((DFile) selectFiles[0]);
    }

    public EFile getSelectedEF() {
        return ((EFile) selectFiles[1]);
    }

    public void setUserAuthed(boolean isAuthed) {
        this.isUserAuthed[0] = isAuthed;
    }

    public DFile findDFByName(byte[] dfName, short nameOffset, short nameLength) {
        if (checkNameTag(dfName, nameOffset, nameLength)) {
            return this;
        }
        return super.findDFByNameRec(dfName, nameOffset, nameLength);
    }

    public File findFile(short fID, byte filetype) {
        if (fID == this.getFileID() && filetype != EF_File) {
            return this;
        }
        if (fID == ((DFile) selectFiles[0]).getFileID() && filetype != EF_File) {
            return (DFile) selectFiles[0];
        }
        return ((DFile) selectFiles[0]).findChildrenRec(fID, filetype);
    }

    public void selectFile(File file) {
        if (file == null) {
            selectFiles[0] = this;
            selectFiles[1] = null;
            recordPointer[0] = -10;
        } else if (file instanceof DFile) {
            selectFiles[0] = file;
            selectFiles[1] = null;
            recordPointer[0] = -10;
        } else if (file instanceof EFile) {
            selectFiles[1] = file;
            selectFiles[0] = ((EFile) selectFiles[1]).getParentDF();
            recordPointer[0] = -10;
        }
    }

    public void addFile(File file) {
        file.setParentDF(getSelectedDF());
        getSelectedDF().addChildren(file);
    }


    private File createFile(byte[] FCP, short offset, short length) throws IllegalArgumentsException {
        short pos, len;
        if ((short) (FCP.length) <= (short) (offset + length)) {
            throw IllegalArgumentsException.getException();
        }

        // Check FCP Tag 0x62
        if (FCP[offset] != (byte) 0x62) {
            ISOException.throwIt((short) 37892); // 9404: Comparison pattern not found.
        }

        short contentLength = TLVHelper.getLengthFieldValue(FCP, (short) (offset + 1));
        if (contentLength != (short) (length - 1 - TLVHelper.getLengthFieldLength(contentLength))) {
            throw IllegalArgumentsException.getException();
        }

        short contentOffset = (short) (offset + 1 + TLVHelper.getLengthFieldLength(contentLength));
        if (TLVHelper.TLVNotValid(FCP, contentOffset, contentLength)) {
            throw IllegalArgumentsException.getException();
        }

        // Get the FID Tag 0x83
        pos = TLVHelper.findTag(FCP, contentOffset, contentLength, (byte) 0x83);
        len = TLVHelper.getLengthFieldValue(FCP, (short) (pos + 1));
        if (len != (short) 2) {
            throw IllegalArgumentsException.getException();
        }
        short fID = Util.getShort(FCP, (short) (pos + 1 + TLVHelper.getLengthFieldLength(len)));
        try {
            this.findFile(fID, ALL_File);
            throw IllegalArgumentsException.getException();
        } catch (Exception ignored) {
        }

        // Get File Descriptor Tag 0x82
        pos = TLVHelper.findTag(FCP, contentOffset, contentLength, (byte) 0x82);
        len = TLVHelper.getLengthFieldValue(FCP, (short) (pos + 1));
        if (len < (short) 1 || len > (short) 6) {
            throw IllegalArgumentsException.getException();
        }
        byte fileDescByte = FCP[(short) (pos + 2)];
        byte[] fcpTmp;
        if ((fileDescByte & 0x3F) == 0x38) { // Add new DFile
            fcpTmp = new byte[length];
            Util.arrayCopy(FCP, offset, fcpTmp, (short) 0, length);
            return new DFile(fID, fcpTmp);
        } else if ((fileDescByte & 0x30) == 0x00) { // Add new EFile
            if ((fileDescByte & 0x07) == (byte) 0x01) { // Transparent EFile
                short dataSize = 128;
                try {
                    pos = TLVHelper.findTag(FCP, (short) (offset + 2), FCP[(short) (offset + 1)], (byte) 0x80);
                    if (FCP[(short) (pos + 1)] == (byte) 0x02) {
                        dataSize = Util.getShort(FCP, (short) (pos + 2));
                    } else if (FCP[(short) (pos + 1)] == (byte) 0x01) {
                        dataSize = FCP[(short) (pos + 2)];
                    } else {
                        ISOException.throwIt(ISO7816.SW_DATA_INVALID);
                    }
                } catch (Exception ignored) {
                }
                fcpTmp = new byte[length];
                Util.arrayCopy(FCP, offset, fcpTmp, (short) 0, length);
                return new TransparentEFile(fID, fcpTmp, dataSize);
            } else {
                short recordLength = 128;
                byte numOfRecords = 8;
                switch (FCP[(short) (pos + 1)]) {
                    case 0x03:
                        recordLength = FCP[(short) (pos + 4)];
                        break;
                    case 0x04:
                        recordLength = Util.getShort(FCP, (short) (pos + 4));
                        break;
                    case 0x05:
                        recordLength = Util.getShort(FCP, (short) (pos + 4));
                        numOfRecords = FCP[(short) (pos + 6)];
                        break;
                }
                if ((fileDescByte & 0x07) == (byte) 0x02) { // Linear structure
                    fcpTmp = new byte[length];
                    Util.arrayCopy(FCP, offset, fcpTmp, (short) 0, length);
                    return new LinearEFile(fID, fcpTmp, numOfRecords, recordLength);
                } else if ((fileDescByte & 0x07) == (byte) 0x06) { // Cyclic structure
                    fcpTmp = new byte[length];
                    Util.arrayCopy(FCP, offset, fcpTmp, (short) 0, length);
                    return new CyclicEFile(fID, fcpTmp, numOfRecords, recordLength);
                } else {
                    throw IllegalArgumentsException.getException();
                }
            }
        } else {
            throw IllegalArgumentsException.getException();
        }
    }


    public void sfSelectFile(APDU apdu) throws ISOException {
        byte[] buf = apdu.getBuffer();
        byte p1 = buf[ISO7816.OFFSET_P1];
        byte p2 = buf[ISO7816.OFFSET_P2];
        short lc, offset_cdata, fid;
        File candidate = null;

        if ((p2 & 0xF3) != 0x00) {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }

        lc = apdu.setIncomingAndReceive();
        if (lc != apdu.getIncomingLength()) {
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        }
        offset_cdata = apdu.getOffsetCdata();

        // Determine the type of selection according to ts_102221 Table 11.1
        switch (p1) {
            case 0x00: // Select DF, EF or MF by file id
                if (lc == 0) {
                    candidate = this;
                } else if (lc == 2) {
                    fid = Util.makeShort(buf[offset_cdata], buf[(short) (offset_cdata + 1)]);
                    try {
                        candidate = findFile(fid, ALL_File);
                    } catch (Exception e) {
                        ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
                    }
                }
                break;
            case 0x01: // Select child DF of the current DF
                fid = Util.makeShort(buf[offset_cdata], buf[(short) (offset_cdata + 1)]);
                try {
                    candidate = getSelectedDF().findChildrenRec(fid, DF_File);
                } catch (Exception e) {
                    ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
                }
                break;
            case 0x03: // Select parent DF of the current DF
                if (lc != 0) {
                    ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
                }
                if (getSelectedDF() != this) {
                    candidate = getSelectedDF().getParentDF();
                } else {
                    ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
                }
                break;
            case 0x04: // Selection by DF AID
                try {
                    candidate = findDFByName(buf, offset_cdata, lc);
                } catch (Exception e) {
                    ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
                }
                break;
            case 0x08: // Select by path from MF
                try {
                    candidate = this.findChildrenByPath(buf, offset_cdata, lc);
                } catch (Exception e) {
                    ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
                }
                break;
            case 0x09: // Select by path from current DF
                try {
                    candidate = getSelectedDF().findChildrenByPath(buf, offset_cdata, lc);
                } catch (Exception e) {
                    ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
                }
                break;
            default:
                ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }

        selectFile(candidate);
        switch (p2 & 0x9C) {
            case 0x04: // Return FCP template
                if (candidate != null && candidate.getFCP() != null) {
                    Util.arrayCopy(candidate.getFCP(), (short) 0, buf, (short) 0, (short) candidate.getFCP().length);
                    lc = (short) candidate.getFCP().length;
                    apdu.setOutgoingAndSend((short) 0, lc);
                }
                break;
            case 0x0C: // No data returned
                break;
        }
    }

    public void sfReadBinary(APDU apdu) {
        byte[] buf = apdu.getBuffer();
        byte p1 = buf[ISO7816.OFFSET_P1];
        byte p2 = buf[ISO7816.OFFSET_P2];

        // Check P1 and P2 according to Table 11.10
        short offset = -1;
        EFile ef = null;
        if ((p1 & 0x80) == 0x00) {
            // b7 to b1 is the offset to the first byte to read, P2 is the low part of the offset
            offset = (short) ((short) (p1 & 0x7F) << (short) 8);
            offset |= (short) (p2 & 0x00FF);
            ef = getSelectedEF();
        } else if ((p1 & 0xE0) == 0x80) {
            // SFI referencing used, b1 to b5 are the SFI and P2 is the offset to the first byte to read
            byte sfi = (byte) (p1 & 0x1F);
            offset = p2;
            try {
                ef = getSelectedDF().findChildEFileBySFI(sfi);
            } catch (Exception e) {
                ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
            }
        } else {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }

        TransparentEFile trEF = null;
        if (ef instanceof TransparentEFile) {
            trEF = (TransparentEFile) ef;
        } else {
            // 6981: Command incompatible with file structure
            ISOException.throwIt((short) 27009);
        }

        short le = apdu.setOutgoing();
        byte[] data = trEF.getData();

        if ((offset >= data.length) || (offset < 0)) {
            // 9D17: Invalid offset
            ISOException.throwIt((short) 40215);
        }

        short maxle = (short) (data.length - offset);
        le = le>maxle? maxle:le;
        apdu.setOutgoingLength(le);
        apdu.sendBytesLong(data, offset, le);
    }

    public void sfReadRecord(APDU apdu) {
        byte[] buf = apdu.getBuffer();
        byte p1 = buf[ISO7816.OFFSET_P1];
        byte p2 = buf[ISO7816.OFFSET_P2];
        byte p2type = (byte) (p2 & 0x07);

        // Check P2 according to 102221 Table 11.11: 0x04-current, 0x02-next, 0x03-previous
        EFile ef = null;
        if (p2type == 0x04 || p2type == 0x02 || p2type == 0x03) {
            ef = getSelectedEF();
        } else {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }

        LinearEFile linearEF = null;
        CyclicEFile cyclicEF = null;
        if (ef instanceof CyclicEFile) {
            cyclicEF = (CyclicEFile) ef;
        } else if (ef instanceof LinearEFile) {
            linearEF = (LinearEFile) ef;
        } else {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }

        short le = apdu.setOutgoing();
        byte[] fileData = null;

        if (p2type == 0x04){ // process Current
            if(linearEF != null){
                if ((short) p1 <= 0 || (short) p1 > linearEF.getCurrentRecordNum()) {
                    ISOException.throwIt(ISO7816.SW_RECORD_NOT_FOUND);
                } else {
                    fileData = linearEF.getRecord((byte) ((short) p1 - 1));
                }
            }else{
                if ((short) p1 <= 0 || (short) p1 > cyclicEF.getCurrentRecordNum()) {
                    ISOException.throwIt(ISO7816.SW_RECORD_NOT_FOUND);
                } else {
                    fileData = cyclicEF.getRecord((byte) ((short) p1 - 1));
                }
            }
        }else if (p2type == 0x02){ // process Next
            if(linearEF != null){
                if (p1 != 0x00) {
                    ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
                } else {
                    recordPointer[0]++;
                    if(recordPointer[0] == -9) recordPointer[0] = 0;
                    if(recordPointer[0] >=0 && recordPointer[0] < linearEF.getCurrentRecordNum()){
                        fileData = linearEF.getRecord((byte) recordPointer[0]);
                    }else if (recordPointer[0] == linearEF.getCurrentRecordNum()){
                        recordPointer[0]--;
                        return;
                    }
                }
            }else{
                if (p1 != 0x00) {
                    ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
                } else {
                    recordPointer[0]++;
                    if(recordPointer[0] == -9) recordPointer[0] = 0;
                    recordPointer[0] %= cyclicEF.getCurrentRecordNum();
                    fileData = cyclicEF.getRecord((byte) recordPointer[0]);
                }
            }
        }else{ // process Previous
            if(linearEF != null){
                if (p1 != 0x00) {
                    ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
                } else {
                    recordPointer[0]--;
                    if(recordPointer[0] == -11) recordPointer[0] = (short) (linearEF.getCurrentRecordNum() - 1);
                    if(recordPointer[0] >=0 && recordPointer[0] < linearEF.getCurrentRecordNum()){
                        fileData = linearEF.getRecord((byte) recordPointer[0]);
                    }else if (recordPointer[0] == -1){
                        recordPointer[0]++;
                        return;
                    }
                }
            }else{
                if (p1 != 0x00) {
                    ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
                } else {
                    recordPointer[0]--;
                    if(recordPointer[0] == -11) recordPointer[0] = (short) (cyclicEF.getCurrentRecordNum() - 1);
                    recordPointer[0] %= cyclicEF.getCurrentRecordNum();
                    fileData = cyclicEF.getRecord((byte) recordPointer[0]);
                }
            }
        }

        short maxle = (short) (fileData.length);
        if (le > maxle) {
            le = maxle;
        }
        apdu.setOutgoingLength(le);
        apdu.sendBytesLong(fileData, (short) 0, le);
    }


    public void sfUpdateRecord(APDU apdu) {
        byte[] buf = apdu.getBuffer();
        byte p1 = buf[ISO7816.OFFSET_P1];
        byte p2 = buf[ISO7816.OFFSET_P2];
        short lc, recvlen, offsetFile = 0;
        byte p2type = (byte) (p2 & 0x07);


        recvlen = apdu.setIncomingAndReceive();
        lc = apdu.getIncomingLength();

        // Check P2 according to 102221 Table 11.11: 0x04-current, 0x02-next, 0x03-previous
        EFile ef = null;
        if (p2type == 0x04 || p2type == 0x02 || p2type == 0x03) {
            ef = getSelectedEF();
        } else {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }

        LinearEFile linearEF = null;
        CyclicEFile cyclicEF = null;
        if (ef instanceof CyclicEFile) {
            cyclicEF = (CyclicEFile) ef;
        } else if (ef instanceof LinearEFile) {
            linearEF = (LinearEFile) ef;
        } else {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }

        if (p2type == 0x04){ // process Current
            if(linearEF != null){
                if ((short) p1 <= 0 || (short) p1 > linearEF.getCurrentRecordNum()) {
                    ISOException.throwIt(ISO7816.SW_RECORD_NOT_FOUND);
                } else {
                    // Update file.
                    if (((short) linearEF.getRecord((byte) (p1 - 1)).length >= (short) (offsetFile + lc))) {
                        short offsetCdata = apdu.getOffsetCdata();
                        while (recvlen > 0) {
                            Util.arrayCopy(buf, offsetCdata, linearEF.getRecord((byte) (p1 - 1)), offsetFile, recvlen);
                            offsetFile += recvlen;
                            recvlen = apdu.receiveBytes(offsetCdata);
                        }
                    } else {
                        // 9D17: Invalid offset
                        ISOException.throwIt((short) 40215);
                    }
                }
            }else{
                if ((short) p1 <= 0 || (short) p1 > cyclicEF.getCurrentRecordNum()) {
                    ISOException.throwIt(ISO7816.SW_RECORD_NOT_FOUND);
                } else {
                    if (((short) cyclicEF.getRecord((byte) (p1 - 1)).length >= (short) (offsetFile + lc))) {
                        short offsetCdata = apdu.getOffsetCdata();
                        while (recvlen > 0) {
                            Util.arrayCopy(buf, offsetCdata, cyclicEF.getRecord((byte) (p1 - 1)), offsetFile, recvlen);
                            offsetFile += recvlen;
                            recvlen = apdu.receiveBytes(offsetCdata);
                        }
                    } else {
                        // 9D17: Invalid offset
                        ISOException.throwIt((short) 40215);
                    }
                }
            }
        }else if (p2type == 0x02){ // process Next
            if(linearEF != null){
                if (p1 != 0x00) {
                    ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
                } else {
                    recordPointer[0]++;
                    if(recordPointer[0] == -9) recordPointer[0] = 0;
                    if(recordPointer[0] >=0 && recordPointer[0] < linearEF.getCurrentRecordNum()){
                        if (((short) linearEF.getRecord((byte) (p1 - 1)).length >= (short) (offsetFile + lc))) {
                            short offsetCdata = apdu.getOffsetCdata();
                            while (recvlen > 0) {
                                Util.arrayCopy(buf, offsetCdata, linearEF.getRecord((byte) recordPointer[0]), offsetFile, recvlen);
                                offsetFile += recvlen;
                                recvlen = apdu.receiveBytes(offsetCdata);
                            }
                        } else {
                            // 9D17: Invalid offset
                            ISOException.throwIt((short) 40215);
                        }
                    }else if (recordPointer[0] == linearEF.getCurrentRecordNum()){
                        recordPointer[0]--;
                        return;
                    }
                }
            }else{
                if (p1 != 0x00) {
                    ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
                } else {
                    recordPointer[0]++;
                    if(recordPointer[0] == -9) recordPointer[0] = 0;
                    recordPointer[0] %= cyclicEF.getCurrentRecordNum();
                    if (((short) cyclicEF.getRecord((byte) (p1 - 1)).length >= (short) (offsetFile + lc))) {
                        short offsetCdata = apdu.getOffsetCdata();
                        while (recvlen > 0) {
                            Util.arrayCopy(buf, offsetCdata, cyclicEF.getRecord((byte) recordPointer[0]), offsetFile, recvlen);
                            offsetFile += recvlen;
                            recvlen = apdu.receiveBytes(offsetCdata);
                        }
                    } else {
                        // 9D17: Invalid offset
                        ISOException.throwIt((short) 40215);
                    }
                }
            }
        }else{ // process Previous
            if(linearEF != null){
                if (p1 != 0x00) {
                    ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
                } else {
                    recordPointer[0]--;
                    if(recordPointer[0] == -11) recordPointer[0] = (short) (linearEF.getCurrentRecordNum() - 1);
                    if(recordPointer[0] >=0 && recordPointer[0] < linearEF.getCurrentRecordNum()){
                        if (((short) linearEF.getRecord((byte) (p1 - 1)).length >= (short) (offsetFile + lc))) {
                            short offsetCdata = apdu.getOffsetCdata();
                            while (recvlen > 0) {
                                Util.arrayCopy(buf, offsetCdata, linearEF.getRecord((byte) recordPointer[0]), offsetFile, recvlen);
                                offsetFile += recvlen;
                                recvlen = apdu.receiveBytes(offsetCdata);
                            }
                        } else {
                            // 9D17: Invalid offset
                            ISOException.throwIt((short) 40215);
                        }
                    }else if (recordPointer[0] == -1){
                        recordPointer[0]++;
                        return;
                    }
                }
            }else{
                if (p1 != 0x00) {
                    ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
                } else {
                    recordPointer[0]--;
                    if(recordPointer[0] == -11) recordPointer[0] = (short) (cyclicEF.getCurrentRecordNum() - 1);
                    recordPointer[0] %= cyclicEF.getCurrentRecordNum();
                    if (((short) cyclicEF.getRecord((byte) (p1 - 1)).length >= (short) (offsetFile + lc))) {
                        short offsetCdata = apdu.getOffsetCdata();
                        while (recvlen > 0) {
                            Util.arrayCopy(buf, offsetCdata, cyclicEF.getRecord((byte) recordPointer[0]), offsetFile, recvlen);
                            offsetFile += recvlen;
                            recvlen = apdu.receiveBytes(offsetCdata);
                        }
                    } else {
                        // 9D17: Invalid offset
                        ISOException.throwIt((short) 40215);
                    }
                }
            }
        }
    }


    public void sfUpdateBinary(APDU apdu) {
        byte[] buf = apdu.getBuffer();
        byte p1 = buf[ISO7816.OFFSET_P1];
        byte p2 = buf[ISO7816.OFFSET_P2];
        short lc, recvlen, offsetFile = -1;
        EFile ef = null;

        recvlen = apdu.setIncomingAndReceive();
        lc = apdu.getIncomingLength();

        if ((p1 & 0xE0) == 0x80) {
            offsetFile = p2;
            try {
                ef = getSelectedDF().findChildEFileBySFI((byte) (p1 & 0x1F));
            } catch (Exception e) {
                ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
            }
        } else if ((p1 & 0x80) == 0x00) {
            offsetFile = (short) ((short) (p1 & 0x7F) << (short) 8);
            offsetFile |= (short) (p2 & 0x00FF);
            ef = getSelectedEF();
        } else {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }

        TransparentEFile trEF = null;
        if (ef instanceof TransparentEFile) {
            trEF = (TransparentEFile) ef;
        } else {
            // 6981: Command incompatible with file structure
            ISOException.throwIt((short) 27009);
        }

        if (((short) trEF.getData().length >= (short) (offsetFile + lc)) && ((short) ((short) 32767 - offsetFile) >= lc)) {
            short offsetCdata = apdu.getOffsetCdata();
            while (recvlen > 0) {
                Util.arrayCopy(buf, offsetCdata, trEF.getData(), offsetFile, recvlen);
                offsetFile += recvlen;
                recvlen = apdu.receiveBytes(offsetCdata);
            }
        } else {
            // 9D17: Invalid offset
            ISOException.throwIt((short) 40215);
        }
    }


    // Delete File according to ts_102222 Sec 6.4
    public void sfDeleteFile(APDU apdu) throws ISOException {
        byte[] buf = apdu.getBuffer();
        byte p1 = buf[ISO7816.OFFSET_P1];
        byte p2 = buf[ISO7816.OFFSET_P2];
        short lc, offset_cdata, fID;
        File fileToDelete = null;

        if (p1 != 0x00 || p2 != 0x00) {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }

        lc = apdu.setIncomingAndReceive();
        if (lc != apdu.getIncomingLength() || lc != 2) {
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        }
        offset_cdata = apdu.getOffsetCdata();
        fID = Util.getShort(buf, offset_cdata);
        try {
            fileToDelete = findFile(fID, ALL_File);
        } catch (Exception e) {
            ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
        }

        if (fileToDelete == this) {
            ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
        }
        selectFile(fileToDelete.getParentDF());
        try {
            getSelectedDF().deleteChildren(fID);
        } catch (Exception e) {
            ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
        }
    }

    // Create File according to ts_102222 Sec 6.3
    public void sfCreateFile(APDU apdu) throws ISOException {
        byte[] buf = apdu.getBuffer();
        byte p1 = buf[ISO7816.OFFSET_P1];
        byte p2 = buf[ISO7816.OFFSET_P2];
        short lc;
        short offset_cdata;

        if (p1 != 0x00 || p2 != 0x00) {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }

        lc = apdu.setIncomingAndReceive();
        if (lc != apdu.getIncomingLength()) {
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        }
        offset_cdata = apdu.getOffsetCdata();

        try {
            // Add the file to the filesystem and select it.
            File newFile = createFile(buf, offset_cdata, lc); // getSafeFile performs permission checks.
            addFile(newFile);
            selectFile(newFile);
        } catch (IllegalArgumentsException e) {
            ISOException.throwIt(ISO7816.SW_DATA_INVALID);
        } catch (Exception e) {
            ISOException.throwIt(ISO7816.SW_FILE_FULL);
        }
    }

    // INSTALL PROFILE apdu. This method change the imsi, opc and key for the current profile
    public void sfInstallProfile(byte[] imsi, byte[] opc, byte[] key) {
        short fid;
        TransparentEFile trEF;
        this.milenage.resetKeys(opc, key);

        // Select DF_GSM 7F20
        selectFile(null);
        fid = (short) 32544;
        try {
            selectFile(findFile(fid, ALL_File));
        } catch (Exception e) {
            ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
        }
        // Select EF_IMSI 6F07
        fid = (short) 28423;
        try {
            selectFile(findFile(fid, ALL_File));
        } catch (Exception e) {
            ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
        }

        trEF = (TransparentEFile) getSelectedEF();
        Util.arrayCopyNonAtomic(imsi, (short) 0, trEF.getData(), (short) 0, (short) 9);

        // Select DF_ADF 7FFF
        selectFile(null);
        fid = (short) 32767;
        try {
            selectFile(findFile(fid, ALL_File));
        } catch (Exception e) {
            ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
        }
        // Select EF_IMSI 6F07
        fid = (short) 28423;
        try {
            selectFile(findFile(fid, ALL_File));
        } catch (Exception e) {
            ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
        }
        trEF = (TransparentEFile) getSelectedEF();
        Util.arrayCopyNonAtomic(imsi, (short) 0, trEF.getData(), (short) 0, (short) 9);
    }
}