/*
 *  ---------
 * |.##> <##.|  Open Smart Card Development Platform (www.openscdp.org)
 * |#       #|  
 * |#       #|  Copyright (c) 1999-2006 CardContact Software & System Consulting
 * |'##> <##'|  Andreas Schwier, 32429 Minden, Germany (www.cardcontact.de)
 *  --------- 
 *
 *  This file is part of OpenSCDP.
 *
 *  OpenSCDP is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation.
 *
 *  OpenSCDP 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 for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with OpenSCDP; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

package de.cardcontact.opencard.service.globalplatform;

import opencard.core.service.CardChannel;
import opencard.core.service.CardService;
import opencard.core.service.CardServiceException;
import opencard.core.service.CardServiceScheduler;
import opencard.core.service.SmartCard;
import opencard.core.terminal.CardTerminalException;
import opencard.core.terminal.CommandAPDU;
import opencard.core.terminal.ResponseAPDU;
import opencard.core.util.Tracer;
import opencard.opt.applet.AppletID;
import de.cardcontact.opencard.service.isocard.IsoConstants;
import de.cardcontact.opencard.utils.CapFile;

/**
 * Class implementing a Global Platform Security Domain card service
 * 
 * @author Andreas Schwier (info@cardcontact.de)
 */
public class SecurityDomainCardService extends CardService {

    private final static Tracer ctracer = new Tracer(SecurityDomainCardService.class);

    /**
     * Create the IsoCardState object in the card channel if it
     * does not yet exist.
     * 
     * Overwrites #opencard.core.service.CardService#initialize
     */
    protected void initialize(CardServiceScheduler scheduler,
                              SmartCard smartcard,
                              boolean blocking)
                       throws CardServiceException {
    
        super.initialize(scheduler, smartcard, blocking);

        ctracer.debug("initialize", "called");

        try {
            allocateCardChannel();
/*            
            CardState cardState = (CardState)getCardChannel().getState();
            
            if (cardState == null) {
                cardState = new CardState();
                cardState.setPath(root_path);
                getCardChannel().setState(cardState);
            }
*/
        }
        
        finally {
            releaseCardChannel();
        }
    }
    
    
    
    /**
     * Select applet using SELECT command and application identifier passed in id
     * 
     * If the argument id is set to null, then the issuer security domain is selected
     * 
     * @param id Applet Id (AID) or null
     * @param next True to select next matching AID
     * @return Response for SELECT APDU
     * 
     * @throws CardTerminalException
     */
    public ResponseAPDU select(AppletID id, boolean next) throws CardTerminalException {
        CardChannel channel;
        CommandAPDU com = new CommandAPDU(64);
        ResponseAPDU res = new ResponseAPDU(258);

        try {
            allocateCardChannel();
            channel = getCardChannel();
            
            com.setLength(0);
            com.append(IsoConstants.CLA_ISO);
            com.append(IsoConstants.INS_SELECT_FILE);
            com.append(IsoConstants.SC_AID);
            com.append((byte)(next ? IsoConstants.RC_NEXT : 0));
            
            if (id != null) {
                byte[] aid = id.getBytes();
                com.append((byte)aid.length);
                com.append(aid);
            }
            
            com.append((byte)0x00);
            
            res = channel.sendCommandAPDU(com);

        } finally {
            releaseCardChannel();
        }

        return res;
    }


    
    /**
     * Issue INSTALL command with INSTALL FOR INSTALL and SELECTABLE option to security domain
     * 
     * @param loadFileAID AID for load file in card
     * @param execModAID AID for module contained in load file
     * @param appInsAID AID for application instance
     * @param privileges Privileges for application
     * @param installParam Install parameter for application
     * @param installToken Install tokens
     * @return ResponseAPDU from card
     */
    public ResponseAPDU installForInstallAndSelectable(
                                       byte[] loadFileAID, 
                                       byte[] execModAID, 
                                       byte[] appInsAID, 
                                       byte[] privileges, 
                                       byte[] installParam,
                                       byte[] installToken) throws CardTerminalException {
                
        CardChannel channel;
        CommandAPDU com = new CommandAPDU(262);
        ResponseAPDU res = new ResponseAPDU(258);

        try {
            allocateCardChannel();
            channel = getCardChannel();

            com.setLength(0);
            com.append(IsoConstants.CLA_EMV);
            com.append(IsoConstants.INS_INSTALL);
            com.append((byte)0x0C);
            com.append((byte)0x00);
            
            com.append((byte)0x00);
            
            com.append((byte)loadFileAID.length);
            com.append(loadFileAID);

            com.append((byte)execModAID.length);
            com.append(execModAID);

            com.append((byte)appInsAID.length);
            com.append(appInsAID);

            com.append((byte)privileges.length);
            com.append(privileges);

            com.append((byte)installParam.length);
            com.append(installParam);

            if (installToken != null) {
                com.append((byte)installToken.length);
                com.append(installToken);
            } else {
                com.append((byte)0x00);
            }

            com.setByte(4, com.getLength() - 5);
            com.append((byte)0x00);
                
            res = channel.sendCommandAPDU(com);
        } finally {
            releaseCardChannel();
        }

        return res;
    }
            

    
    /**
     * Issue INSTALL command with INSTALL FOR LOAD option to security domain
     * 
     * @param loadFileAID
     * @param secDomAID
     * @param loadDBHash
     * @param loadParam
     * @param loadToken
     * @return ResponseAPDU from card
     * @throws CardTerminalException
     */
    public ResponseAPDU installForLoad(byte[] loadFileAID, 
                                       byte[] secDomAID, 
                                       byte[] loadDBHash, 
                                       byte[] loadParam, 
                                       byte[] loadToken) throws CardTerminalException {
        
        CardChannel channel;
        CommandAPDU com = new CommandAPDU(262);
        ResponseAPDU res = new ResponseAPDU(258);

        
        try {
            allocateCardChannel();
            channel = getCardChannel();

            com.setLength(0);
            com.append(IsoConstants.CLA_EMV);
            com.append(IsoConstants.INS_INSTALL);
            com.append((byte)0x02);
            com.append((byte)0x00);
            
            com.append((byte)0x00);
            
            com.append((byte)loadFileAID.length);
            com.append(loadFileAID);
            
            if (secDomAID != null) {
                com.append((byte)secDomAID.length);
                com.append(secDomAID);
            } else {
                com.append((byte)0x00);
            }

            if (loadDBHash != null) {
                com.append((byte)loadDBHash.length);
                com.append(loadDBHash);
            } else {
                com.append((byte)0x00);
            }
            
            if (loadParam != null) {
                com.append((byte)loadParam.length);
                com.append(loadParam);
            } else {
                com.append((byte)0x00);
            }
            
            if (loadToken != null) {
                com.append((byte)loadToken.length);
                com.append(loadToken);
            } else {
                com.append((byte)0x00);
            }

            com.setByte(4, com.getLength() - 5);
            com.append((byte)0x00);
                
            res = channel.sendCommandAPDU(com);
        } finally {
            releaseCardChannel();
        }

        return res;
    }
            

    
    /**
     * Load load file into card using a sequence of LOAD apdus
     * 
     * @param capFile Load file with cap components
     * 
     * @return Response from last LOAD commands
     * 
     * @throws CardTerminalException
     */
    public ResponseAPDU load(CapFile capFile) throws CardTerminalException {
        CardChannel channel;
        CommandAPDU com = new CommandAPDU(262);
        ResponseAPDU res = new ResponseAPDU(258);

//        byte[] loadFile = capFile.getLoadFile(CapFile.CAPSEQUENCE_JCOP);
        byte[] loadFile = capFile.getLoadFile(CapFile.CAPSEQUENCE);
        int length = loadFile.length;
        int offset = 0;
        int block;
        int count = 0;
        
        try {
            allocateCardChannel();
            channel = getCardChannel();

            while(length > 0) {
                block = length;
                if (block > 255) {
                    block = 255;
                }
                length -= block;
                
                com.setLength(0);
                com.append(IsoConstants.CLA_EMV);
                com.append(IsoConstants.INS_LOAD);
                com.append((byte)(length > 0 ? 0x00 : 0x80));   // Last block ?
                com.append((byte)count);
                com.append((byte)block);
                
                System.arraycopy(loadFile, offset, com.getBuffer(), 5, block);
                com.setLength(block + 5);
                
                com.append((byte)0x00);
                
                res = channel.sendCommandAPDU(com);
                
                if (res.sw() != IsoConstants.RC_OK) {
                    break;
                }
                count++;
                offset += block;
            }
        } finally {
            releaseCardChannel();
        }

        return res;
    }
    
    
    
    /**
     * Issue DELETE command to remove package with given AID from card
     * 
     * @param aid
     * @return ResponseAPDU from card
     * @throws CardTerminalException
     */
    public ResponseAPDU deleteAID(byte[] aid) throws CardTerminalException {
        
        CardChannel channel;
        CommandAPDU com = new CommandAPDU(262);
        ResponseAPDU res = new ResponseAPDU(258);
        
        try {
            allocateCardChannel();
            channel = getCardChannel();

            com.setLength(0);
            com.append(IsoConstants.CLA_EMV);
            com.append(IsoConstants.INS_DELETE_FILE);
            com.append((byte)0x00);
            com.append((byte)0x00);

            com.append((byte)(aid.length + 2));

            com.append((byte)0x4F);
            com.append((byte)aid.length);
            com.append(aid);
            
            com.append((byte)0x00);
                
            res = channel.sendCommandAPDU(com);
        } finally {
            releaseCardChannel();
        }

        return res;
    }
}
