package net.i2p.router.tunnel;

import net.i2p.data.Hash;
import net.i2p.router.RouterContext;
import net.i2p.util.Log;
import net.i2p.util.SimpleByteCache;

/**
 * Receive the inbound tunnel message, removing all of the layers
 * added by earlier hops to recover the preprocessed data sent
 * by the gateway.  This delegates the crypto to the 
 * OutboundGatewayProcessor, since the tunnel creator does the 
 * same thing in both instances.
 *
 */
class InboundEndpointProcessor {
    private final RouterContext _context;
    private final Log _log;
    private final TunnelCreatorConfig _config;
    private final IVValidator _validator;    
    
    //static final boolean USE_ENCRYPTION = HopProcessor.USE_ENCRYPTION;
    
    /** @deprecated unused */
    public InboundEndpointProcessor(RouterContext ctx, TunnelCreatorConfig cfg) {
        this(ctx, cfg, DummyValidator.getInstance());
    }

    public InboundEndpointProcessor(RouterContext ctx, TunnelCreatorConfig cfg, IVValidator validator) {
        _context = ctx;
        _log = ctx.logManager().getLog(InboundEndpointProcessor.class);
        _config = cfg;
        _validator = validator;
    }
    
    public Hash getDestination() { return _config.getDestination(); }
    public TunnelCreatorConfig getConfig() { return _config; }
    
    /**
     * Undo all of the encryption done by the peers in the tunnel, recovering the
     * preprocessed data sent by the gateway.  
     *
     * @return true if the data was recovered (and written in place to orig), false
     *         if it was a duplicate or from the wrong peer.
     */
    public boolean retrievePreprocessedData(byte orig[], int offset, int length, Hash prev) {
        Hash last = _config.getPeer(_config.getLength()-2);
        if (!last.equals(prev)) {
            // shouldn't happen now that we have good dup ID detection in BuildHandler
            if (_log.shouldLog(Log.WARN))
                _log.warn("Attempted IBEP injection from " + prev 
                               + ", expected " + last);
            return false;
        }
        
        byte iv[] = SimpleByteCache.acquire(HopProcessor.IV_LENGTH);
        System.arraycopy(orig, offset, iv, 0, iv.length);
        //if (_config.getLength() > 1)
        //    _log.debug("IV at inbound endpoint before decrypt: " + Base64.encode(iv));

        boolean ok = _validator.receiveIV(iv, 0, orig, offset + HopProcessor.IV_LENGTH);
        if (!ok) {
            if (_log.shouldLog(Log.WARN)) 
                _log.warn("Invalid IV, dropping at IBEP " + _config);
            SimpleByteCache.release(iv);
            return false;
        }
        
        // inbound endpoints and outbound gateways have to undo the crypto in the same way
        //if (USE_ENCRYPTION)
            decrypt(_context, _config, iv, orig, offset, length);
        
        SimpleByteCache.release(iv);
        
        if (_config.getLength() > 0) {
            int rtt = 0; // dunno... may not be related to an rtt
            if (_log.shouldLog(Log.DEBUG))
                _log.debug("Received a " + length + "byte message through tunnel " + _config);
            for (int i = 0; i < _config.getLength(); i++)
                _context.profileManager().tunnelDataPushed(_config.getPeer(i), rtt, length);
            _config.incrementVerifiedBytesTransferred(length);
        }
        
        return true;
    }
    
    /**
     * Iteratively undo the crypto that the various layers in the tunnel added.
     */
    private void decrypt(RouterContext ctx, TunnelCreatorConfig cfg, byte iv[], byte orig[], int offset, int length) {
        //Log log = ctx.logManager().getLog(OutboundGatewayProcessor.class);
        byte cur[] = SimpleByteCache.acquire(HopProcessor.IV_LENGTH);
        for (int i = cfg.getLength()-2; i >= 0; i--) { // dont include the endpoint, since that is the creator
            OutboundGatewayProcessor.decrypt(ctx, iv, orig, offset, length, cur, cfg.getConfig(i));
            //if (log.shouldLog(Log.DEBUG)) {
                //log.debug("IV at hop " + i + ": " + Base64.encode(orig, offset, HopProcessor.IV_LENGTH));
                //log.debug("hop " + i + ": " + Base64.encode(orig, offset + HopProcessor.IV_LENGTH, length - HopProcessor.IV_LENGTH));
            //}
        }
        SimpleByteCache.release(cur);
    }
    
}
