package net.i2p.router.tunnel;

import net.i2p.I2PAppContext;
import net.i2p.data.Hash;
import net.i2p.util.Log;

/**
 * Take a received tunnel message, verify that it isn't a 
 * duplicate, and translate it into what the next hop will 
 * want.  The hop processor works the same on all peers -
 * inbound and outbound participants, outbound endpoints,
 * and inbound gateways (with a small modification per 
 * InboundGatewayProcessor).  
 *
 */
class HopProcessor {
    protected final I2PAppContext _context;
    private final Log _log;
    protected final HopConfig _config;
    private final IVValidator _validator;
        
    /** helpful flag for debugging */
    //static final boolean USE_ENCRYPTION = true;
    /**
     * as of i2p 0.6, the tunnel crypto changed  to encrypt the IV both before 
     * and after using it at each hop so as to prevent a certain type of replay/confirmation 
     * attack.
     *
     * See: http://osdir.com/ml/network.i2p/2005-07/msg00031.html
     */
    //static final boolean USE_DOUBLE_IV_ENCRYPTION = true;
    static final int IV_LENGTH = 16;

    /**
     *  @deprecated used only by unit tests
     */
    @Deprecated
    HopProcessor(I2PAppContext ctx, HopConfig config) {
        this(ctx, config, createValidator());
    }
    
    public HopProcessor(I2PAppContext ctx, HopConfig config, IVValidator validator) {
        _context = ctx;
        _log = ctx.logManager().getLog(HopProcessor.class);
        _config = config;
        _validator = validator;
    }

    /**
     *  @deprecated used only by unit test constructor
     */
    @Deprecated
    private static IVValidator createValidator() { 
        // yeah, we'll use an O(1) validator later (e.g. bloom filter)
        return new HashSetIVValidator();
    }
    
    /**
     * Process the data for the current hop, overwriting the original data with
     * what should be sent to the next peer.  This also validates the previous 
     * peer and the IV, making sure its not a repeat and not a loop.
     *
     * @param orig IV+data of the message
     * @param offset index into orig where the IV begins
     * @param length how long after the offset does the message go for?
     * @param prev previous hop in the tunnel, or null if we are the gateway
     * @return true if the message was updated and valid, false if it was not.
     */
    public boolean process(byte orig[], int offset, int length, Hash prev) {
        // prev is null on gateways
        if (prev != null) {
            if (_config.getReceiveFrom() == null) {
                _config.setReceiveFrom(prev);
            } else if (!_config.getReceiveFrom().equals(prev)) {
                // shouldn't happen now that we have good dup ID detection in BuildHandler
                if (_log.shouldLog(Log.WARN))
                    _log.warn("Attempted mid-tunnel injection from " + prev 
                               + ", expected " + _config.getReceiveFrom());
                return false;
            }
        }
        
        boolean okIV = _validator.receiveIV(orig, offset, orig, offset + IV_LENGTH);
        if (!okIV) {
            if (_log.shouldLog(Log.WARN)) 
                _log.warn("Invalid IV, dropping at hop " + _config);
            return false;
        }
        
        if (_log.shouldLog(Log.DEBUG)) {
            //_log.debug("IV received: " + Base64.encode(iv));
            //_log.debug("Before:" + Base64.encode(orig, IV_LENGTH, orig.length - IV_LENGTH));
        }
        //if (USE_ENCRYPTION) {
            //if (USE_DOUBLE_IV_ENCRYPTION) 
                updateIV(orig, offset);
            encrypt(orig, offset, length);
            updateIV(orig, offset);
        //}
        //if (_log.shouldLog(Log.DEBUG)) {
            //_log.debug("Data after processing: " + Base64.encode(orig, IV_LENGTH, orig.length - IV_LENGTH));
            //_log.debug("IV sent: " + Base64.encode(orig, 0, IV_LENGTH));
        //}
        return true;
    }
    
    private final void encrypt(byte data[], int offset, int length) {
        for (int off = offset + IV_LENGTH; off < length; off += IV_LENGTH) {
            //DataHelper.xor(data, off - IV_LENGTH, data, off, data, off, IV_LENGTH);
            for (int j = 0; j < IV_LENGTH; j++) {
                data[off + j] ^= data[(off - IV_LENGTH) + j];
            }
            _context.aes().encryptBlock(data, off, _config.getLayerKey(), data, off);
        }
    }
    
    private final void updateIV(byte orig[], int offset) {
        _context.aes().encryptBlock(orig, offset, _config.getIVKey(), orig, offset);
    }

    /**
     *  @since 0.8.12
     */
    @Override
    public String toString() {
        return getClass().getSimpleName() + " for " + _config;
    }
}
