package com.musclecard.CardEdge;

import com.musclecard.CardEdge.MemoryManager;

import javacard.framework.Util;
import javacard.framework.ISOException;

/** Object Manager Class <p>
 * 
 * Objects are linked in a list in the
 * dynamic memory. No smart search is done
 * at the moment.<p>
 *
 * Object fields:
 * <pre>
 *   short next
 *   short obj_class
 *   short obj_id
 *   short obj_size
 *   byte[] data
 * </pre>
 *
 * TODO
 *   - Could we definitively avoid a map enforcing the ID
 *     (equal to the memory address, i.e.) - security implications ?
 *   
 */
import javacard.framework.ISO7816;
import javacard.framework.ISOException;
import javacard.security.RandomData;
import javacard.framework.ISO7816;
import javacard.framework.JCSystem;
import javacard.security.CryptoException;
import javacard.framework.ISOException;
public class ObjectManager { 
	private short fault_resistant_short_g[] = new short[2];	/* help array to store negations of global short variables*/

    public final static byte OBJ_ACL_SIZE = (byte) 6;
    private final static byte OBJ_HEADER_SIZE = (byte) (6 + OBJ_ACL_SIZE + 2);
    private final static byte OBJ_H_NEXT = (byte) 0; // Short size;
    private final static byte OBJ_H_CLASS = (byte) 2; // Short ocj_class;
    private final static byte OBJ_H_ID = (byte) 4; // Short obj_id;
    private final static byte OBJ_H_ACL = (byte) 6; // Byte[OBJ_ACL_SIZE] acl;
    private final static byte OBJ_H_SIZE = (byte) 12; // Short size;
    private final static byte OBJ_H_DATA = (byte) 14;
    /** There have been memory problems on the card */
    public final static short SW_NO_MEMORY_LEFT = (short) 0x9C01;
    /** Size of an Object Record filled by getFirstRecord()
     *    or getNextRecord(): ID, Size, ACL */
    public final static short RECORD_SIZE = (short) (4 + 4 + OBJ_ACL_SIZE);
    /** Iterator on objects. Stores the offset of the last retrieved object's record. */
    private short it=__set_short_g((short)(0),(short)0);
    /** The Memory Manager object */
    private MemoryManager mem = null;
    /** Map for fast search of objects (unimplemented) */
    // static Map map;
    /** Head of the objects' list */
    private short obj_list_head = __set_short_g((short)(MemoryManager.NULL_OFFSET),(short)1);

    /** Constructor for the ObjectManager class.
     *    @param mem_ref	The MemoryManager object to be used
     *				to allocate objects' memory.
     */
    public ObjectManager(MemoryManager mem_ref) {
        mem = mem_ref;
        // map = new Map();
        obj_list_head=__set_short_g((short)(MemoryManager.NULL_OFFSET),(short)1);
    }

    /** Creates an object with specified parameters. Throws a SW_NO_MEMORY_LEFT
     *  exception if cannot allocate the memory. Does not check if object exists.
     *    @param type		Object Type
     *    @param id		Object ID (Type and ID form a generic 4 bytes identifier)
     *    @param acl_buf	Java byte array containing the ACL for the new object
     *    @param acl_offset	Offset at which the ACL starts in acl_buf[]
     *    @return		The memory base address for the object. It can be used
     *				in successive calls to xxxFromAddress() methods.
     */
    public short createObject(short type, short id, short size,
            byte[] acl_buf, short acl_offset) {
        /* Allocate memory for new object */
        short base = mem.alloc((short) (size + OBJ_HEADER_SIZE));
        if (base == MemoryManager.NULL_OFFSET) {
            ISOException.throwIt(SW_NO_MEMORY_LEFT);
        }
        /* New obj will be inserted in the head of the list */
        mem.setShort(base, OBJ_H_NEXT, __get_short_g(obj_list_head,(short)1));
        mem.setShort(base, OBJ_H_CLASS, type);
        mem.setShort(base, OBJ_H_ID, id);
        mem.setShort(base, OBJ_H_SIZE, size);
        mem.setBytes(base, OBJ_H_ACL, acl_buf, acl_offset, OBJ_ACL_SIZE);
        obj_list_head=__set_short_g((short)(base),(short)1);

        /* Add to the map */
        // map.addEntry(type, id, base);

        // Return base address
        return (short) (base + OBJ_HEADER_SIZE);
    }

    /** Creates an object with the maximum available size */
    public short createObjectMax(short type, short id,
            byte[] acl_buf, short acl_offset) {
        short obj_size = mem.getMaxSize();
        if (obj_size == (short) 0) {
            ISOException.throwIt(SW_NO_MEMORY_LEFT);
        }
        /* The object's real size must take into account that	*
         * extra bytes are needed for the header		*/
        return createObject(type, id, (short) (obj_size - OBJ_HEADER_SIZE),
                acl_buf, acl_offset);
    }

    /** Clamps an object freeing the unused memory
     *    @param type		Object Type
     *    @param id		Object ID (Type and ID form a generic 4 bytes identifier)
     *    @param new_size	The new object size (must be less than current size)
     *    @return		True if clamp was possible, false otherwise
     */
    public boolean clampObject(short type, short id, short new_size) {
        short base = getEntry(type, id);
        if (base == (short) MemoryManager.NULL_OFFSET) {
            ISOException.throwIt((short) 0x9C07);
        }
        // Delegate every check to the Memory Manager
        if (mem.realloc(base, (short) (new_size + OBJ_HEADER_SIZE))) {
            mem.setShort(base, OBJ_H_SIZE, new_size);
            return true;
        }
        return false;
    }

    /** Set the object's ACL. Unused at the moment. */
    private void setACL(short type, short id, byte[] acl_buf, short acl_offset) {
        short base = getEntry(type, id);
        mem.setBytes(base, OBJ_H_ACL, acl_buf, acl_offset, OBJ_ACL_SIZE);
    }

    /** Allow or unallow read on object given the logged identities
     *    @param base		The object base address as returned from getBaseAddress()
     *    @param logged_ids	The current logged in identities as stored in CardEdge.logged_ids
     */
    public boolean authorizeReadFromAddress(short base, short logged_ids) {
        return authorizeOp(mem.getShort(base, (short) (OBJ_H_ACL - OBJ_HEADER_SIZE)), logged_ids);
    }

    /** Allow or unallow write on object given the logged identities
     *    @param base		The object base address as returned from getBaseAddress()
     *    @param logged_ids	The current logged in identities as stored in CardEdge.logged_ids
     */
    public boolean authorizeWriteFromAddress(short base, short logged_ids) {
        return authorizeOp(mem.getShort(base, (short) (OBJ_H_ACL + (short) 2 - OBJ_HEADER_SIZE)), logged_ids);
    }

    /** Allow or unallow delete on object given the logged identities
     *    @param base		The object base address as returned from getBaseAddress()
     *    @param logged_ids	The current logged in identities as stored in CardEdge.logged_ids
     */
    public boolean authorizeDeleteFromAddress(short base, short logged_ids) {
        return authorizeOp(mem.getShort(base, (short) (OBJ_H_ACL + (short) 4 - OBJ_HEADER_SIZE)), logged_ids);
    }

    /** Check if logged in identities satisfy requirements for an operation
     *    @param required_ids	The required identities as from an ACL short
     *    @param logged_ids	The current logged in identities as stored in CardEdge.logged_ids
     */
    private boolean authorizeOp(short required_ids, short logged_ids) {
        return ((required_ids != (short) 0xFFFF)
                && (((short) (required_ids & logged_ids)) == required_ids));
    }

    /** Write data at the specified location in an object */
//      public void setObjectData(short type, short id, short dst_offset,
//  				      byte[] src_data, short src_offset,
//  				      short len) {
//  	// TODO: short dst_base = map.getEntry(type, id);
//  	short dst_base = getEntry(type, id);
//  	mem.setBytes(dst_base, dst_offset, src_data, src_offset, len);
//      }
//      /** Read data from the specified location in an object */
//      public void getObjectData(byte[] dst_data, short dst_offset,
//  				      short type, short id, short src_offset,
//  				      short len) {
//  	// TODO: short dst_base = map.getEntry(type, id);
//  	short src_base = getEntry(type, id);
//  	mem.getBytes(dst_data, dst_offset, src_base, src_offset, len);
//      }
    /** Destroy the specified object
     *    @param type		Object Type
     *    @param id		Object ID (Type and ID form a generic 4 bytes identifier)
     *    @param secure		If true, object memory is zeroed before being released.
     */
    public void destroyObject(short type, short id, boolean secure) {
        short base = __get_short_g(obj_list_head,(short)1);
        short prev = MemoryManager.NULL_OFFSET;
        boolean found = false;
        while ((!found) && (base != MemoryManager.NULL_OFFSET)) {
	/***** BEGIN ORIGINAL [IF_SWITCH_5] *****
	            if ((mem.getShort(base, OBJ_H_CLASS) == type)
	                    && (mem.getShort(base, OBJ_H_ID) == id)) {
	                found = true;
	            } else {
	                prev = base;
	                base = mem.getShort(base, OBJ_H_NEXT);
	            } 
	***** END ORIGINAL [IF_SWITCH_5] *****/ 
	/***** BEGIN REPLACE [IF_SWITCH_5] *****/ 
	IF_SWITCH_5: {
		switch (__getRandomBit()){
			case -1: 
				ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
			case 0:
				if (((mem.getShort(base, OBJ_H_CLASS) == type)
	                    && (mem.getShort(base, OBJ_H_ID) == id))) {
					{
					                found = true;
					            }
				} else {
					{
					                prev = base;
					                base = mem.getShort(base, OBJ_H_NEXT);
					            }
				}
	break IF_SWITCH_5;
			case 1:
				if (!((mem.getShort(base, OBJ_H_CLASS) == type)
	                    && (mem.getShort(base, OBJ_H_ID) == id))) {
					{
					                prev = base;
					                base = mem.getShort(base, OBJ_H_NEXT);
					            }
				} else {
					{
					                found = true;
					            }
				}
	break IF_SWITCH_5;
			default:
				ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
		}
	}
	/***** END REPLACE [IF_SWITCH_5] *****/ 
        }
        if (found) {
            // Unlink object from the list
            if (prev != MemoryManager.NULL_OFFSET) {
                mem.setShort(prev, OBJ_H_NEXT, mem.getShort(base, OBJ_H_NEXT));
            } else {
                obj_list_head=__set_short_g((short)(mem.getShort(base, OBJ_H_NEXT)),(short)1);
            }
            // Zero memory if required
            if (secure) {
                Util.arrayFillNonAtomic(mem.getBuffer(), (short) (base + OBJ_HEADER_SIZE),
                        mem.getShort(base, OBJ_H_SIZE), (byte) 0x00);
            }

            // Free memory
            mem.free(base);
        }
    }

    /** Returns the header base address (offset) for the specified object<p>
     *    Object header is found at the returned offset, while object
     *    data starts right after the header<p>
     *    This performs a linear search, so performance issues could arise
     *    as the number of objects grows
     *  If object is not found, then returns NULL_OFFSET
     *    @param type		Object Type
     *    @param id		Object ID (Type and ID form a generic 4 bytes identifier)
     *    @return		The starting offset of the object or NULL_OFFSET if the object
     *				is not found.
     */
    private short getEntry(short type, short id) {
        /* This is a stupid linear search. It's fine for a few objects.
         * TODO: Use a map for high number of objects */
        short base = __get_short_g(obj_list_head,(short)1);
        while (base != MemoryManager.NULL_OFFSET) {
            if ((mem.getShort(base, OBJ_H_CLASS) == type)
                    && (mem.getShort(base, OBJ_H_ID) == id)) {
                return base;
            }
            base = mem.getShort(base, OBJ_H_NEXT);
        }
        return MemoryManager.NULL_OFFSET;
    }

    /** Returns the data base address (offset) for an object.<p>
     *    The base address can be used for further calls to
     *    xxxFromAddress() methods<p>
     *    This function should only be used if performance
     *    issue arise. setObjectData() and getObjectData()
     *    should be used, instead.
     *    @param type		Object Type
     *    @param id		Object ID (Type and ID form a generic 4 bytes identifier)
     *    @return		The starting offset of the object. At this location
     */
    public short getBaseAddress(short type, short id) {
        short base = getEntry(type, id);
	/***** BEGIN ORIGINAL [IF_SWITCH_10] *****
	        if (base == MemoryManager.NULL_OFFSET) {
	            return MemoryManager.NULL_OFFSET;
	        } else {
	            return ((short) (base + OBJ_HEADER_SIZE));
	        } 
	***** END ORIGINAL [IF_SWITCH_10] *****/ 
	/***** BEGIN REPLACE [IF_SWITCH_10] *****/ 
	IF_SWITCH_10: {
		switch (__getRandomBit()){
			case -1: 
				ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
			case 0:
				if ((base == MemoryManager.NULL_OFFSET)) {
					{
					            return MemoryManager.NULL_OFFSET;
					        }
				} else {
					{
					            return ((short) (base + OBJ_HEADER_SIZE));
					        }
				}
			case 1:
				if (!(base == MemoryManager.NULL_OFFSET)) {
					{
					            return ((short) (base + OBJ_HEADER_SIZE));
					        }
				} else {
					{
					            return MemoryManager.NULL_OFFSET;
					        }
				}
			default:
				ISOException.throwIt(ISO7816.SW_CONDITIONS_NOT_SATISFIED);
		}
	return 0;
	}
	/***** END REPLACE [IF_SWITCH_10] *****/ 
    }

    /** Checks if an object exists
     *    @param type	The object type
     *    @param id	The object ID
     *    @return true if object exists
     */
    public boolean exists(short type, short id) {
        short base = getEntry(type, id);
        return (base != MemoryManager.NULL_OFFSET);
    }

    /** Returns object size from the base address */
    public short getSizeFromAddress(short base) {
        return mem.getShort((short) (base - OBJ_HEADER_SIZE + OBJ_H_SIZE));
    }

    /** Resets the objects iterator and retrieves the information
     *	record of the first object, if any.<p>
     *    @param buffer		The byte array into which the record will be copied
     *    @param offset		The offset in buffer[] at which the record will be copied
     *    @return		True if an object was found. False if there are no objects.
     *
     *    @see #getNextRecord
     */
    public boolean getFirstRecord(byte[] buffer, short offset) {
        it=__set_short_g((short)(__get_short_g(obj_list_head,(short)1)),(short)0);
        return getNextRecord(buffer, offset);
    }

    /** Retrieves the information record of the next object, if any.<p>
     *    @param buffer		The byte array into which the record will be copied
     *    @param offset		The offset in buffer[] at which the record will be copied
     *    @return		True if an object was found. False if there are no more
     *				objects to inspect.
     *    @see #getFirstRecord
     */
    public boolean getNextRecord(byte[] buffer, short offset) {
        if (__get_short_g(it,(short)0) == MemoryManager.NULL_OFFSET) {
            return false;
        }
        // Setting Object Class
        Util.setShort(buffer, offset, mem.getShort(__get_short_g(it,(short)0), OBJ_H_CLASS));
        // Setting Object ID
        Util.setShort(buffer, (short) (offset + 2), mem.getShort(__get_short_g(it,(short)0), OBJ_H_ID));
        // Setting Size's M.S.Short to zero.
        Util.setShort(buffer, (short) (offset + 4), (short) 0);
        // Setting Size's L.S.Short
        Util.setShort(buffer, (short) (offset + 6), mem.getShort(__get_short_g(it,(short)0), (short) OBJ_H_SIZE));
        // Setting ACL
        Util.arrayCopyNonAtomic(mem.getBuffer(), (short) (__get_short_g(it,(short)0) + OBJ_H_ACL),
                buffer, (short) (offset + 8), OBJ_ACL_SIZE);
        // Advance iterator
        it=__set_short_g((short)(mem.getShort(__get_short_g(it,(short)0), OBJ_H_NEXT)),(short)0);
        return true;
    }

    /**
     * Compare an object's ACL with the provided ACL.
     *   @param base	The object base address, as returned from getBaseAddress()
     *   @param acl	The buffer containing the ACL
     *   @return	True if the ACLs are equal
     */
    public boolean compareACLFromAddress(short base, byte[] acl) {
        return (Util.arrayCompare(mem.getBuffer(), (short) (base - OBJ_HEADER_SIZE + OBJ_H_ACL),
                acl, (short) 0, OBJ_ACL_SIZE) == (byte) 0);
    }
 
	/**
	 *	This method will compare negation of input value with stored negation. 
	 * 	In case of error an exception will be thrown. 
	 *@param value value, which is expected
	 *@param id id of variable in array of shadow copies
	 *@throw Exception if value is corrupted
	 */
	private short __get_short_g(short value, short id){
		if (fault_resistant_short_g[id] != (short)(value ^ (short)((1<<15)-1))) 
			ISOException.throwIt(ISO7816.SW_DATA_INVALID);
		return value;
	}	/**
	 *	This method will set value in help array to negation of original value.
	 *@param value value to set
	 *@param id id of variable in array of shadow copies
	 *@return value value, which was set
	 */
	private short __set_short_g(short value, short id){
		fault_resistant_short_g[id] = (short)(value ^ (short)((1<<15)-1));
		return value;
	}	/** Random number generator for IF_SWITCH transformation */
	private static RandomData __random = null;
	/** Helper array for random number generator */
	private static byte __randomData[] = null;
	/**
	 *	Returns random bit 0 or 1
	 */
	private static short __getRandomBit(){
		try {
			if (__random == null) __random = RandomData.getInstance(RandomData.ALG_SECURE_RANDOM);
			if (__randomData == null) __randomData = JCSystem.makeTransientByteArray((short)1, JCSystem.CLEAR_ON_DESELECT);
			__random.generateData(__randomData, (short)0, (short)1);
			return (byte)((__randomData[0] & 0xFF)%2);
		} catch (CryptoException ex) {
			// JC simulator sometimes doesn't allow to get instance of RandomData.. 
			// this fallback will return always 0 in case that random number couldn't be fetched 
			// and program will continue normally
			return (byte)0;
		}
	}} // class MemoryManager

