package net.i2p.router.message;
/*
 * free (adj.): unencumbered; not under the control of others
 * Written by jrandom in 2003 and released into the public domain
 * with no warranty of any kind, either expressed or implied.
 * It probably won't make your computer catch on fire, or eat
 * your children, but it might.  Use at your own risk.
 *
 */

import java.util.Date;

import net.i2p.crypto.SessionKeyManager;
import net.i2p.data.Certificate;
import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper;
import net.i2p.data.PrivateKey;
import net.i2p.data.i2np.GarlicClove;
import net.i2p.data.i2np.GarlicMessage;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;

/**
 * Read a GarlicMessage, decrypt it, and return the resulting CloveSet
 *
 */
class GarlicMessageParser {
    private final Log _log;
    private final RouterContext _context;
    
    /**
     *  Huge limit just to reduce chance of trouble. Typ. usage is 3.
     *  As of 0.9.12. Was 255.
     */
    private static final int MAX_CLOVES = 32;

    public GarlicMessageParser(RouterContext context) { 
        _context = context;
        _log = _context.logManager().getLog(GarlicMessageParser.class);
    }
    
    /** @param skm use tags from this session key manager */
    public CloveSet getGarlicCloves(GarlicMessage message, PrivateKey encryptionKey, SessionKeyManager skm) {
        byte encData[] = message.getData();
        byte decrData[] = null;
        try {
            if (_log.shouldLog(Log.DEBUG))
                _log.debug("Decrypting with private key " + encryptionKey);
            decrData = _context.elGamalAESEngine().decrypt(encData, encryptionKey, skm);
        } catch (DataFormatException dfe) {
            if (_log.shouldLog(Log.WARN))
                _log.warn("Error decrypting", dfe);
        }
        if (decrData == null) {
            // This is the usual error path and it's logged at WARN level in GarlicMessageReceiver
            if (_log.shouldLog(Log.INFO))
                _log.info("Decryption of garlic message failed", new Exception("Decrypt fail"));
            return null;
        } else {
            try {
                return readCloveSet(decrData); 
            } catch (DataFormatException dfe) {
                if (_log.shouldLog(Log.WARN))
                    _log.warn("Unable to read cloveSet", dfe);
                return null;
            }
        }
    }
    
    private CloveSet readCloveSet(byte data[]) throws DataFormatException {
        int offset = 0;
        
        int numCloves = (int)DataHelper.fromLong(data, offset, 1);
        offset++;
        if (_log.shouldLog(Log.DEBUG))
            _log.debug("# cloves to read: " + numCloves);
        if (numCloves <= 0 || numCloves > MAX_CLOVES)
            throw new DataFormatException("bad clove count " + numCloves);
        GarlicClove[] cloves = new GarlicClove[numCloves];
        for (int i = 0; i < numCloves; i++) {
            //if (_log.shouldLog(Log.DEBUG))
            //    _log.debug("Reading clove " + i);
                GarlicClove clove = new GarlicClove(_context);
                offset += clove.readBytes(data, offset);
                cloves[i] = clove;
            //if (_log.shouldLog(Log.DEBUG))
            //    _log.debug("After reading clove " + i);
        }
        //Certificate cert = new Certificate();
        //offset += cert.readBytes(data, offset);
        Certificate cert = Certificate.create(data, offset);
        offset += cert.size();
        long msgId = DataHelper.fromLong(data, offset, 4);
        offset += 4;
        //Date expiration = DataHelper.fromDate(data, offset);
        long expiration = DataHelper.fromLong(data, offset, 8);

        CloveSet set = new CloveSet(cloves, cert, msgId, expiration);
        return set;
    }
}
