/*
 *   
 *
 * 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.acl;

import com.sun.satsa.util.*;

import java.util.Vector;
import java.io.IOException;

/**
 * This class represents Access Control Entry.
 */
public class ACEntry {
    /** ASN context specific constructed explicit flag used in types (0xA0). */
    public static final int CONTEXT_CONSTRUCTED_0 = TLV.CONTEXT +
                                                               TLV.CONSTRUCTED;
    /** ASN context specific constructed explicit flag used in types (0xA1). */
    public static final int CONTEXT_CONSTRUCTED_1 = CONTEXT_CONSTRUCTED_0 + 1;
    /** ASN context specific constructed explicit flag used in types (0xA2). */
    public static final int CONTEXT_CONSTRUCTED_2 = CONTEXT_CONSTRUCTED_0 + 2;
    /** ASN context specific constructed explicit flag used in types (0xA3). */
    public static final int CONTEXT_CONSTRUCTED_3 = CONTEXT_CONSTRUCTED_0 + 3;
    /** ASN context specific constructed explicit flag used in types (0xA3). */
    public static final int CONTEXT_CONSTRUCTED_4 = CONTEXT_CONSTRUCTED_0 + 4;

    /** ASN context specific primitive explicit flag used in types (0x80). */
    public static final int CONTEXT_PRIMITIVE_0 = TLV.CONTEXT;
    /** ASN context specific primitive explicit flag used in types (0x81). */
    public static final int CONTEXT_PRIMITIVE_1 = CONTEXT_PRIMITIVE_0 + 1;
    /** ASN context specific primitive explicit flag used in types (0x82). */
    public static final int CONTEXT_PRIMITIVE_2 = CONTEXT_PRIMITIVE_0 + 2;
    /** ASN context specific primitive explicit flag used in types (0x83). */
    public static final int CONTEXT_PRIMITIVE_3 = CONTEXT_PRIMITIVE_0 + 3;
    /** ASN context specific primitive explicit flag used in types (0x84). */
    public static final int CONTEXT_PRIMITIVE_4 = CONTEXT_PRIMITIVE_0 + 4;

    /** Vector containing parsed the Access Control File */
    private Vector ACF;
    /** The list of CA names that correspond to rootId element of ACE. */
    private byte[][] roots;

    /** APDU permissions (command - mask pairs). */
    private int[] APDUPermissions;

    /** JCRMI permissions. */
    private JCRMIPermission[] JCRMIPermissions;

    /**
     * Creates new ACEntry object
     * @param v Vector containing parsed the Access Control File
     * @param pin_info Vector for pin information
     * @throws TLVException when some error in the TLV structures treatment
     */
    ACEntry(Vector v, Vector pin_info)
            throws TLVException {
        ACF = v;
        readACE(pin_info);

    }
    /**
     * <pre>
     * Reads one ACE.
     * It is assumed that the ACE is in the following format:
     *   SEQUENCE {                   ACE contents
     *     CONTEXT_CONSTRUCTED_0 {      the Principals    (opt)
     *       < read contents >
     *     }
     *     CONTEXT_CONSTRUCTED_1 {      the Permissions   (opt)
     *       < read permissions contents >
     *     }
     *     CONTEXT_CONSTRUCTED_2 {      the userAuthentications    (opt)
     *       < read user authetications contents >
     *     }
     *   }
     * </pre>
     * @param pin_info Vector for pin information.
     * @throws TLVException - exception in case any problems with TLV structure.
     */
    private void readACE(Vector pin_info)
            throws TLVException {

        Vector t_roots = new Vector();
        Vector t_apdu = new Vector();
        Vector t_jcrmi = new Vector();
        TLV root, t;
        root = (TLV)ACF.firstElement();
        while (root != null) {
            if (root.type != TLV.SEQUENCE_TYPE) { // ACE shall be a sequence
                throw new TLVException("The SEQUENCE_TYPE expected");
            }
            t = root.child; // level of the ACE root
            while (t != null) {
                switch (t.type) {
                case CONTEXT_CONSTRUCTED_0: { // principals
                    readPrincipals(t.child, t_roots);
                    break;
                }
                case CONTEXT_CONSTRUCTED_1: { // permissions
                    readPermissions(t.child, t_apdu, t_jcrmi);
                    break;
                }
                case CONTEXT_CONSTRUCTED_2: { // userAuthentications
                    readUserAuthentications(t.child, pin_info);
                    break;
                }
                }
                t = t.next;
            }
            root = root.next;
        }
        if (! t_roots.isEmpty()) {
            roots = new byte[t_roots.size()][];
            for (int i = 0; i < t_roots.size(); i++) {
                roots[i] = (byte[]) t_roots.elementAt(i);
            }
        }

        if (! t_apdu.isEmpty()) {
            APDUPermissions = new int[t_apdu.size()];
            for (int i = 0; i < t_apdu.size(); i++) {
                byte[] data = (byte[]) t_apdu.elementAt(i);
                APDUPermissions[i] = Utils.getInt(data, 0);
            }
        }

        if (! t_jcrmi.isEmpty()) {
            JCRMIPermissions = new JCRMIPermission[t_jcrmi.size()];
            t_jcrmi.copyInto(JCRMIPermissions);
        }
    }

    /**
     * Reads UserAuthentications information from ACF vector.
     * <pre>
     * It is assumed that the ACE is in the following format:
     *     CONTEXT_CONSTRUCTED_2 {      the UserAuthentications
     *           OCTET_STRING                 AuthID
     *           CONTEXT_CONSTRUCTED_0 {        APDUPinEntry choice
     *             < readApduPINs >
     *           }
     *           CONTEXT_CONSTRUCTED_1 {        JCRMIPinEntry choice
     *             < readJcrmiPINs >
     *           }
     *     }
     * </pre>
     * @param t current position in the ACF vector.
     * @param pin_info Vector for the PINs information.
     * @throws TLVException if I/O error occurs.
     */
    private void readUserAuthentications(TLV t, Vector pin_info)
                                                   throws TLVException {
        TLV root = t;

        while (root != null) {
            TLV u = root.child; // authID
            if (u.type != TLV.OCTETSTR_TYPE) { // shall be Octet string type
                throw new TLVException("The OCTETSTR_TYPE expected");
            }
            int id = u.getId();     /* Strictly speaking, the spec. defines */
                                    /* this field as OCTET STRING, but from */
                                    /* other side in methods where this field */
                                    /* is used, it is defined as int. Now */
                                    /* it is supposed that it is integer */

            u = u.next;   // UserAuthenticationMethod CHOICE
            if (u.type == CONTEXT_CONSTRUCTED_0) {  // apduPINEntry
                pin_info.addElement(new PINData(id, readApduPINs(u.child)));
            }
            if (u.type == CONTEXT_CONSTRUCTED_1) {  // jcrmiPINEntry
                pin_info.addElement(new PINData(id, readJcrmiPINs(u.child)));
            }
            root = root.next;
        }
    }
    /**
     * Reads PINs information from ACF vector.
     * <pre>
     *             CONTEXT_CONSTRUCTED_0 {
     *               OCTET STRING (SIZE(4))       verifyPinAPDU (opt)
     *             }
     *             CONTEXT_CONSTRUCTED_1 {
     *               OCTET STRING (SIZE(4))       changePinAPDU (opt)
     *             }
     *             CONTEXT_CONSTRUCTED_2 {
     *               OCTET STRING (SIZE(4))       disablePinAPDU (opt)
     *             }
     *             CONTEXT_CONSTRUCTED_3 {
     *               OCTET STRING (SIZE(4))       enablePinAPDU (opt)
     *             }
     *             CONTEXT_CONSTRUCTED_4 {
     *               OCTET STRING (SIZE(4))       unblockPinAPDU (opt)
     *             }
     * </pre>
     * @param t current position in the ACF vector.
     * @return Integer[] commands.
     * @throws TLVException if I/O error occurs.
     */
    private Integer[] readApduPINs(TLV t)
            throws TLVException {
        TLV root = t;
        byte[] b;
        int command;
        Integer[] commands = new Integer[ACLPermissions.CMD_COUNT];
        int comIndex = 0;
        while (root != null) {
            switch (root.type) {
                case CONTEXT_PRIMITIVE_0: {    /* verifyPinAPDU */
                    comIndex = ACLPermissions.CMD_VERIFY;
                    break;
                }
                case CONTEXT_PRIMITIVE_1: {    /* changePinAPDU */
                    comIndex = ACLPermissions.CMD_CHANGE;
                    break;
                }
                case CONTEXT_PRIMITIVE_2: {    /* disablePinAPDU */
                    comIndex = ACLPermissions.CMD_DISABLE;
                    break;
                }
                case CONTEXT_PRIMITIVE_3: {    /* enablePinAPDU */
                    comIndex = ACLPermissions.CMD_ENABLE;
                    break;
                }
                case CONTEXT_PRIMITIVE_4: {    /* unblockPinAPDU */
                    comIndex = ACLPermissions.CMD_UNBLOCK;
                    break;
                }
            }
            b = root.getValue();
            command = 0;
            for (int i = 0; i < 4; i++) {
                command = (command << 8) | b[i];
            }
            commands[comIndex] = new Integer(command);
            root = root.next;
        }
        return commands;
    }
    /**
     * Reads PINs information from ACF vector.
     * <pre>
     *             CONTEXT_CONSTRUCTED_0 {
     *               UTF8_STRING           verifyPinMethodID (opt)
     *             }
     *             CONTEXT_CONSTRUCTED_0 {
     *               UTF8_STRING           changePinMethodID (opt)
     *             }
     *             CONTEXT_CONSTRUCTED_0 {
     *               UTF8_STRING           disablePinMethodID (opt)
     *             }
     *             CONTEXT_CONSTRUCTED_0 {
     *               UTF8_STRING           enablePinMethodID (opt)
     *             }
     *             CONTEXT_CONSTRUCTED_0 {
     *               UTF8_STRING           unblockPinMethodID (opt)
     *             }
     * </pre>
     * @param t current position in the ACF vector.
     * @return String[] commands.
     * @throws TLVException if I/O error occurs.
     */
    private String[] readJcrmiPINs(TLV t)
            throws TLVException {
        TLV root = t;
        String[] commands = new String[ACLPermissions.CMD_COUNT];
        int comIndex = 0;
        while (root != null) {
            switch (root.type) {
                case CONTEXT_PRIMITIVE_0: {    /* verifyPinMethodID */
                    comIndex = ACLPermissions.CMD_VERIFY;
                    break;
                }
                case CONTEXT_PRIMITIVE_1: {    /* changePinMethodID */
                    comIndex = ACLPermissions.CMD_CHANGE;
                    break;
                }
                case CONTEXT_PRIMITIVE_2: {    /* disablePinMethodID */
                    comIndex = ACLPermissions.CMD_DISABLE;
                    break;
                }
                case CONTEXT_PRIMITIVE_3: {    /* enablePinMethodID */
                    comIndex = ACLPermissions.CMD_ENABLE;
                    break;
                }
                case CONTEXT_PRIMITIVE_4: {    /* unblockPinMethodID */
                    comIndex = ACLPermissions.CMD_UNBLOCK;
                    break;
                }
            }
            commands[comIndex] = root.getUTF8();
            root = root.next;
        }
        return commands;
    }

    /**
     * Reads the Permissions information from the ACF vector.
     * <pre>
     *           CONTEXT_CONSTRUCTED_0 {        APDUMaskPermission choice
     *             < readAPDUPermissions >
     *           }
     *           CONTEXT_CONSTRUCTED_1 {        JCRMIPermission choice
     *             < readJCRMIPermissions >
     *           }
     * </pre>
     * @param t current position in the ACF vector.
     * @param apdu Vector for the apdu permission information.
     * @param jcrmi Vector for the jcrmi permission information.
     * @throws TLVException if I/O error occurs.
     */
    private void readPermissions(TLV t, Vector apdu, Vector jcrmi)
                                                          throws TLVException {
        TLV root = t;
        while (root != null) {
            if (root.type == CONTEXT_CONSTRUCTED_0) {
                readAPDUPermissions(root.child, apdu);
            }
            if (root.type == CONTEXT_CONSTRUCTED_1) {
                readJCRMIPermissions(root.child, jcrmi);
            }
            root = root.next;
        }
    }

    /**
     * Reads the JCRMI Permissions information from the ACF vector.
     * <pre>
     *               SEQUENCE OF {         ClassList
     *                 UTF8_STRING           Class
     *               }
     *               UTF8_STRING           hashModifier (opt)
     *               SEQUENCE OF {         MethodIDList (opt)
     *                 OCTET_STRING (SIZE(4))  MethodID
     *               }
     * </pre>
     * @param t current position in the ACF vector.
     * @param jcrmi Vector for the jcrmi permission information.
     * @throws TLVException if I/O error occurs.
     */
    private void readJCRMIPermissions(TLV t, Vector jcrmi)
                                                   throws TLVException {
        Vector classes = new Vector();
        Vector methods = new Vector();
        String hashModifier = null;

        TLV root = t;
        /* reading of classes */
        if (root.type != TLV.SEQUENCE_TYPE) {
                // classes shall be a sequence of UTF8String
            throw new TLVException("The SEQUENCE_TYPE expected");
        }
        TLV c = root.child;
        while (c != null) {
            classes.addElement(c.getUTF8());
            c = c.next;
        }
        root = root.next;
        if (root != null) {
            /* reading of hashModifier */
            if (root.type == TLV.UTF8STR_TYPE) {
                    // hashModifier is optional and has UTF8String type
                hashModifier = root.getUTF8();
                root = root.next;
            }
            if (root != null) {
                /* reading of methods */
                if (root.type != TLV.SEQUENCE_TYPE) {
                        // methods shall be sequence of OCTET_STRING
                    throw new TLVException("The SEQUENCE_TYPE expected");
                }
                TLV m = root.child;
                while (m != null) {
                    methods.addElement(m.getValue());
                    m = m.next;
                }
            }
        }
        jcrmi.addElement(new JCRMIPermission(hashModifier, classes, methods));
    }

    /**
     * Reads the APDU Permissions information from the ACF vector.
     * <pre>
     *               OCTET_STRING (SIZE(4))  APDUHeader
     *               OCTET_STRING (SIZE(4))  APDUMask
     *             }
     * </pre>
     * @param t current position in the ACF vector.
     * @param apdu Vector for the apdu permission information.
     * @throws TLVException if I/O error occurs.
     */
    private void readAPDUPermissions(TLV t, Vector apdu)
                                                 throws TLVException {
        TLV root = t;
        byte[] data;

        while (root != null) {
            data = root.getValue();
            apdu.addElement(data);
            root = root.next;
        }
    }

    /**
     * Reads the Principals information from the ACF vector.
     * <pre>
     * It is assumed that the ACE is in the following format:
     *     CONTEXT_CONSTRUCTED_0 {      the Principals
     *       SEQUENCE OF {                Principal contents
     *         CONTEXT_CONSTRUCTED_0        rootID
     *           OCTET_STRING
     *         CONTEXT_CONSTRUCTED_1        endEntityID
     *           OCTET_STRING
     *         CONTEXT_CONSTRUCTED_2        domain
     *           OBJECT_IDENTIFIER
     *       }
     *     }
     * </pre>
     * @param t current position in the ACF vector.
     * @param v Vector for the principals information.
     * @throws TLVException if I/O error occurs.
     *
     */
    private void readPrincipals(TLV t, Vector v)
                                               throws TLVException {
        TLV root = t;
        while (root != null) {
            v.addElement(root.child.getValue());    // UNIVERSAL type level
            root = root.next;
        }
    }

    /**
     * Verifies if this ACE describes permissions for this CA.
     * @param root name of CA that authorized the suite.
     * @return true if this ACE describes permissions for this CA.
     */
    boolean verifyPrincipal(String root) {
        if (roots == null) {
            return true;
        }

        for (int i = 0; i < roots.length; i++) {
            if (root.equals(new String(roots[i]))) {
                return true;
            }
        }

        return false;
    }

    /**
     * Verifies if the ACE contains permissions.
     * @return true if the ACE contains permissions.
     */
    boolean hasPermissions() {
        return (APDUPermissions != null || JCRMIPermissions != null);
    }

    /**
     * Places permissions from this ACE to the vector.
     * @param isAPDU if true, place APDU permissions, otherwise - JCRMI
     * permissions
     * @param permissions the vector for results
     */
    void getPermissions(boolean isAPDU, Vector permissions) {

        if (isAPDU) {
            if (APDUPermissions != null) {
                permissions.addElement(APDUPermissions);
            }
        } else {
            if (JCRMIPermissions != null) {
                for (int k = 0; k < JCRMIPermissions.length; k++) {
                    permissions.addElement(JCRMIPermissions[k]);
                }
            }
        }
    }
}
