/*
 *   
 *
 * 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 java.util.Vector;
import com.sun.satsa.util.*;

/**
 * This class represents PKCS#15 PIN attributes.
 */
public class PINAttributes {

    /** BCD PIN type constant. */
    private static final int TYPE_BCD        = 0;
    /** ASCII PIN type constant. */
    private static final int TYPE_ASCII      = 1;
    /** UTF PIN type constant. */
    private static final int TYPE_UTF        = 2;
    /** HN PIN type constant. */
    private static final int TYPE_HN         = 3;
    /** ISO PIN type constant. */
    private static final int TYPE_ISO        = 4;

    /** CASE_SENSITIVE PIN flag constant. */
    public static final int FLAG_CASE_SENSITIVE            = 0x8000;
    /** LOCAL PIN flag constant. */
    public static final int FLAG_LOCAL                     = 0x4000;
    /** CHANGE_DISABLED PIN flag constant. */
    public static final int FLAG_CHANGE_DISABLED           = 0x2000;
    /** UNBLOCK_DISABLED PIN flag constant. */
    public static final int FLAG_UNBLOCK_DISABLED          = 0x1000;
    /** INITIALIZED PIN flag constant. */
    public static final int FLAG_INITIALIZED               = 0x800;
    /** NEEDS_PADDING PIN flag constant. */
    public static final int FLAG_NEEDS_PADDING             = 0x400;
    /** UNBLOCKING_PIN PIN flag constant. */
    public static final int FLAG_UNBLOCKING_PIN            = 0x200;
    /** SOPIN PIN flag constant. */
    public static final int FLAG_SOPIN                     = 0x100;
    /** DISABLE_ALLOWED PIN flag constant. */
    public static final int FLAG_DISABLE_ALLOWED           = 0x80;
    /** INTEGRITY_PROTECTED PIN flag constant. */
    public static final int FLAG_INTEGRITY_PROTECTED       = 0x40;
    /** CONFIDENTIALITY_PROTECTED PIN flag constant. */
    public static final int FLAG_CONFIDENTIALITY_PROTECTED = 0x20;
    /** EXCHANGEREFDATA PIN flag constant. */
    public static final int FLAG_EXCHANGEREFDATA           = 0x10;


    /** PIN label. */
    public String label;
    /** PIN identifier. */
    public int id;
    /** pinType PIN attribute. */
    public int pinType;
    /** minLength PIN attribute. */
    public int minLength;
    /** storedLength PIN attribute. */
    public int storedLength;
    /** maxLength PIN attribute. */
    public int maxLength;
    /** pinReference PIN attribute. */
    public int pinReference;
    /** padChar PIN attribute. */
    public int padChar;
    /** pinFlags PIN attribute. */
    public int pinFlags;
    /** Path PIN attribute. */
    public short[] path;

    /** This vector contains parsed objects from PIN file. */
    private Vector PIN;
    /** This TLV contains root TLV */
    private TLV root;

    /**
     * Constructs PINAttributes.
     */
    public PINAttributes() {}
    /**
     * Constructs PINAttributes object from the pointed TLV.
     * @param root TLV
     * @throws TLVException if TLV error occurs
     */
    public PINAttributes(TLV root)
                                      throws TLVException {
        if (root.type != TLV.SEQUENCE_TYPE) {      // not a PIN object
            throw new TLVException("Bad PIN record");
        }
        this.root = root;
        readPINs();
    }

    /**
     * Read PIN information.
     * @throws TLVException if parsing error occurs
     */
    private void readPINs()
            throws TLVException {

        TLV t = root.child;        // commonObjectAttributes
        label = t.child.getUTF8().trim();
        t = t.next;         // CommonAuthenticationObjectAttributes
        id = t.child.getId();

        t = t.next;
        if (t.type != ACEntry.CONTEXT_CONSTRUCTED_1) {
            throw new TLVException("Incomplete PIN record");
        }
        t = t.child.child;   // PinAttributes.pinFlags
        byte[] buf = t.getValue();
        int mask = 0;
        for (int i = 0; i < buf[0]; i++) {
            mask = mask | (1 << i);
        }
        mask = ~mask;
        pinFlags = Utils.getShort(buf, 1) & mask;

        t = t.next;
        pinType = t.getEnumerated();

        t = t.next;
        minLength = t.getInteger();

        t = t.next;
        storedLength = t.getInteger();

        t = t.next;
        if (t.type == TLV.INTEGER_TYPE) {
            maxLength = t.getInteger();
            t = t.next;
        } else {
            maxLength = storedLength;
        }

        // this entry is optional, default value is 0
        if (t.type == 0x80) {
            pinReference = t.getInteger();
            t = t.next;
        }

        padChar = t.getId();
    }

    /**
     * Returns true if this PIN is a number.
     * @return true if this PIN is a number.
     */
    public boolean isNumeric() {
        return (pinType != TYPE_UTF);
    }

    /**
     * Returns maximum PIN length in characters.
     * @return maximum PIN length in characters.
     */
    public int getMaxLength() {

        if ((pinFlags & FLAG_NEEDS_PADDING) == 0) {
            return maxLength;
        }

        if (pinType == TYPE_BCD) {
            return storedLength * 2;
        }

        // UTF symbol may occupy 1 or 2 bytes, additional check is necessary

        return storedLength;
    }

    /**
     * Verifies if the specified operation can be performed on this PIN.
     * @param action operation identifier.
     * @return true if the specified operation can be performed on this PIN.
     */
    public boolean check(int action) {

        if (action == ACLPermissions.CMD_CHANGE) {
            return (pinFlags & FLAG_CHANGE_DISABLED) == 0;
        }

        if (action == ACLPermissions.CMD_DISABLE) {
            return (pinFlags & FLAG_DISABLE_ALLOWED) != 0;
        }

        if (action == ACLPermissions.CMD_UNBLOCK) {
            return (pinFlags & FLAG_UNBLOCK_DISABLED) == 0;
        }

        return true;
    }

    /**
     * Verifies if this PIN can be used to unblock other PINs.
     * @return true if this PIN can be used to unblock other PINs.
     */
    public boolean isUnblockingPIN() {
        return (pinFlags & FLAG_UNBLOCKING_PIN) != 0;
    }

    /**
     * Transforms string entered by user according to PIN attributes.
     * @param s the value enterd by user.
     * @return converted and padded PIN value.
     */
    public byte[] transform(String s) {

        if (s.length() < minLength) {
            return null;
        }

        byte[] data = null;

        if (pinType == TYPE_UTF) {

            if ((pinFlags & FLAG_CASE_SENSITIVE) == 0) {
                s = s.toUpperCase();  // locale?
            }

            data = Utils.stringToBytes(s);

            if (data.length > getMaxLength()) {
                return null;
            }

        } else {

            byte[] tmp = new byte[s.length()];
            for (int i = 0; i < tmp.length; i++) {
                tmp[i] = (byte) (s.charAt(i));
            }

            if (pinType == TYPE_ASCII || pinType == TYPE_ISO) {
                data = tmp;
            } else {
                if (pinType == TYPE_HN) {
                    data = tmp;
                    for (int i = 0; i < data.length; i++) {
                        data[i] = (byte) (0xf0 | (data[i] - 0x30));
                    }
                } else {   // type == TYPE_BCD

                    data = new byte[(tmp.length + 1) / 2];

                    for (int i = 0; i < data.length; i++) {

                        int l = i * 2;
                        int b1 = tmp[l] - 0x30;
                        int b2;
                        if (l + 1 == tmp.length) {
                            b2 = padChar;
                        } else {
                            b2 = tmp[l + 1] - 0x30;
                        }
                        data[i] = (byte) ((b1 << 4) | (b2 & 0xf));
                    }

                }
            }
        }

        if (((pinFlags & FLAG_NEEDS_PADDING) == 0) ||
             (data.length == storedLength)) {
            return data;
        }

        byte[] r = new byte[storedLength];
        System.arraycopy(data, 0, r, 0, data.length);
        for (int i = data.length; i < storedLength; i++) {
            r[i] = (byte) padChar;
        }

        return r;
    }
}
