/*
 *   
 *
 * Copyright  1990-2006 Sun Microsystems, Inc. All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version
 * 2 only, as published by the Free Software Foundation. 
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License version 2 for more details (a copy is
 * included at /legal/license.txt). 
 * 
 * You should have received a copy of the GNU General Public License
 * version 2 along with this work; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA 
 * 
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
 * Clara, CA 95054 or visit www.sun.com if you need additional
 * information or have any questions. 
 */

package com.sun.satsa.aclapplet;

import javacard.framework.*;
import javacard.security.*;
import javacardx.crypto.Cipher;

/**
 * Card side application for ACL implementation. 
 */
public class ACLApplet extends Applet {

    /** Constant that is used to avoid '(short) x' notation. */
    static final byte x0 = 0;
    /** Constant that is used to avoid '(short) x' notation. */
    static final byte x1 = 1;
    /** Constant that is used to avoid '(short) x' notation. */
    static final byte x2 = 2;
    /** Constant that is used to avoid '(short) x' notation. */
    static final byte x3 = 3;
    /** Constant that is used to avoid '(short) x' notation. */
    static final byte x4 = 4;
    /** Constant that is used to avoid '(short) x' notation. */
    static final byte x5 = 5;
    /** Constant that is used to avoid '(short) x' notation. */
    static final byte x6 = 6;
    /** Constant that is used to avoid '(short) x' notation. */
    static final byte x8 = 8;

    /** INS byte for command APDU. */
    static final byte INS_SELECT    = (byte) 0xa4;
    /** INS byte for command APDU. */
    static final byte INS_READ      = (byte) 0xb0;
    /** INS byte for command APDU. */


    /** Root DF for entire file structure. */
    DFile top;
    /**
     * Root DF for WIM application. All relative paths start from
     * here. */
    DFile base;
    /** Currently selected file. */
    File current;
    /** Constructor. */
    ACLApplet() {
        register();
    }

    /**
     * To create an instance of the Applet subclass, the JCRE will call
     * this static method first.
     * @param bArray the array containing installation parameters
     * @param bOffset the starting offset in bArray
     * @param bLength the length in bytes of the parameter data in bArray
     */
    public static void install(byte[] bArray, short bOffset,
                               byte bLength) {
        new ACLApplet();
    }

    /**
     * Called by the JCRE to inform this applet that it has been
     * selected. When invoked first time initialises the file system.
     * @return true
     */
    public boolean select() {

        if (top == null) {
            init();
        }
        current = base;
        return true;
    }

    /**
     * Initialises the WIM data structures.
     */
    void init() {

        Parser.init(Data.Files);
        top = (DFile) readFile(null);

        if (base == null) {
            ISOException.throwIt((short) 0x9001);
        }

    }

    /**
     * Creates new file object.
     * @param parent parent DF for this file
     * @return the new file object
     */
    File readFile(DFile parent) {

        short id = Parser.getShort();
        short type = Parser.getByte();
        short length = Parser.getShort();

        if ((type & File.DIR) == 0) {

            EFile f;
            if ((type & File.EMPTY) == 0) {
                f = new EFile(parent, id, type, Parser.offset, length,
                              Data.Files);
                Parser.skip(length);
            } else {
                type &= ~File.EMPTY;
                byte[] data = new byte[length];
                short dlen = Parser.getShort();
                Util.arrayCopyNonAtomic(Data.Files, Parser.offset, data,
                        (short) 0, dlen);
                f = new EFile(parent, id, type, (short) 0, length, data);
                Parser.skip(dlen);
            }
            return f;
        }

        DFile f = new DFile(parent, id, type);

        File[] files = new File[length];
        for (short i = 0; i < length; i++) {
            files[i] = readFile(f);
        }

        f.files = files;

        if (type == File.WIM) {
            base = f;
        }

        return f;
    }

    /**
     * Main entry point.
     * @param apdu command APDU
     */
    public void process(APDU apdu) {

        byte[] data = apdu.getBuffer();
        byte CLA = (byte) (data[ISO7816.OFFSET_CLA] & 0xF0);
        byte INS = data[ISO7816.OFFSET_INS];

        if (CLA == 0 &&
            INS == (byte)(0xA4) &&
            data[ISO7816.OFFSET_P1] == 4) {
            return;
        }

        if (CLA != (byte) 0x00) {
            ISOException.throwIt(ISO7816.SW_CLA_NOT_SUPPORTED);
        }

        switch (INS) {

            case INS_SELECT:
                selectFile(apdu);
                return;

            case INS_READ:
                read(apdu);
                return;

        }
        ISOException.throwIt(ISO7816.SW_INS_NOT_SUPPORTED);
    }

    /**
     * Handles SELECT FILE APDU.
     * @param apdu command APDU
     */
    void selectFile(APDU apdu) {

        byte[] data = apdu.getBuffer();

/*        if (Util.getShort(data, x2) != 0) {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }
*/
        checkDataSize(x2, apdu);

        File f = select(Util.getShort(data, x5));

        if (f == null) {
            ISOException.throwIt(ISO7816.SW_FILE_NOT_FOUND);
        }

        current = f;

        if (current.isDF()) {

            Util.setShort(data, x0, (short) 0x6f00);
            apdu.setOutgoingAndSend(x0, x2);
        } else {

            Util.setShort(data, x0, (short) 0x6f04);
            Util.setShort(data, x2, (short) 0x8002);
            Util.setShort(data, x4, ((EFile) current).length);
            apdu.setOutgoingAndSend(x0, x6);
        }
    }

    /**
     * Selects the file specified by file identifier.
     * @param id file identifier
     * @return selected file
     */
    File select(short id) {

        DFile f;
        if (current.isDF()) {
            f = (DFile) current;
        } else  {
            f = current.parent;
        }

        File x = f.getFile(id);
        if (x != null) {
            return x;
        }

        f = f.parent;

        if (f == null) {
            return null;
        }

        return f.getFile(id);
    }

    /**
     * Handles READ BINARY APDU.
     * @param apdu command APDU
     */
    void read(APDU apdu) {

        if (current.isDF()) {
            ISOException.throwIt(ISO7816.SW_COMMAND_NOT_ALLOWED);
        }

        EFile f = (EFile) current;

        byte[] data = apdu.getBuffer();

        short offset = Util.getShort(data, x2);
        if (offset < 0 || offset > f.length) {
            ISOException.throwIt(ISO7816.SW_INCORRECT_P1P2);
        }

        short len = (short) (data[x4] & 0xff);
        if ((short)(offset + len) > f.length) {
            //ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
            len = (short)(f.length - offset);
            if (len < 0) {
                ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
            }
        }

        apdu.setOutgoing();
        apdu.setOutgoingLength(len);
        apdu.sendBytesLong(f.data, (short) (f.offset + offset), len);
    }

    /**
     * Returns the size of DER object for given value size.
     * @param i the value size
     * @return the size of DER object
     */
    static short getDERSize(short i) {

        if (i < 128) {
            return (short) (i + 2);
        }
        return (short) (i + 3);
    }

    /**
     * Places encoded length of DER object into the buffer.
     * @param data the buffer
     * @param offset offset in the buffer where the length must be
     * placed
     * @param length the length to be placed
     * @return the new offset
     */
    static short putLength(byte[] data, short offset, short length) {
        if (length >= 128) {
            data[offset++] = (byte) 0x81;
        }
        data[offset++] = (byte) length;
        return offset;
    }

    /**
     * Returns file object specified by path in the buffer.
     * @param data the buffer
     * @param index path offset
     * @param l path length
     * @return file object or null if not found
     */
    File getFile(byte[] data, short index, short l) {

        // path must contain even number of bytes
        if (l < 2 || l % 2 != 0) {
            return null;
        }
        l = (short) (l / 2);

        short id = Util.getShort(data, index);

        File x;

        if (l == 1) {
            x = base;
        } else {

            if (id == top.id) {
                x = top;
            } else {
                if (id == (short) 0x3fff) {
                    x = base;
                } else {
                    return null;
                }
            }
            index += 2;
            l--;
        }

        while (l != 0) {

            if (! x.isDF()) {
                return null;
            }
            File f = ((DFile) x).getFile(Util.getShort(data, index));
            if (f == null || f.parent != x) {
                return null;
            }
            x = f;
            index += 2;
            l--;
        }

        return x;
    }


    /**
     * Verifies that APDU contains correct number of data bytes.
     * @param expectedSize expected data size
     * @param apdu APDU object
     */
    private void checkDataSize(short expectedSize, APDU apdu) {
        if (expectedSize != apdu.setIncomingAndReceive()) {
            ISOException.throwIt(ISO7816.SW_WRONG_LENGTH);
        }
    }

}

