
package fortuna;

/**
 *
 * @author Viliam Krizan
 */
import aalg.crypto.ByteArraySymmetricKey;
import aalg.crypto.IHashFunction;
import aalg.crypto.ISymmetricKey;
import aalg.crypto.Sha512;
import fortuna.acumulator.Acumulator;
import fortuna.entropySources.EntropySource;
import fortuna.generator.IGenerator;
import java.util.ArrayList;
import java.util.Arrays;


public class Fortuna {
    
    private Acumulator acumulator = new Acumulator(Sha512.class);
    private IHashFunction hash;
    private ArrayList<EntropySource> etropySource = new ArrayList<EntropySource>();
    private ISymmetricKey key;
    private IGenerator generator;
    private int reseedCounter = 1;
    //timer to check out time between reseeds
    private Timer timer;
    //minimal entropy needed to perform another safe reseed
    private final int minEntropyInPool0 = 100;
    
    public Fortuna(IGenerator generator){
        
        this.generator = generator;
        
        //key initialization to 0,0,0....
        byte[] keyBytes = new byte[32];
        for (int i = 1; i < 32; i++) {
            keyBytes[i]=(byte)0;
        }
        key = new ByteArraySymmetricKey(keyBytes);
        generator.setKey(key);
        hash = new Sha512();
        timer = new Timer();
    }
    
    public Fortuna(IGenerator generator, IHashFunction hash){
        this(generator);
        this.hash = hash;
    }
    
    public void addEntropySource(EntropySource  src){
        src.passAcumulatorToES(acumulator);
        Thread th = new Thread(src, "Entropy thread for " + src.getClass().getName());
        th.setDaemon(true);
        etropySource.add(src);
        th.start();
    }
    
    public void generateRandomData(byte[] result){
        generator.generate(result);
        byte[] newKey = new byte[32];
        generator.generate(newKey);
        key = new ByteArraySymmetricKey(newKey);
        generator.setKey(key);
    }
    
    public boolean reseed(){
        //check if we can reseed
        if(!(timer.enoughTimePassed())){
            return false;
        }
        if(!(acumulator.getPool(0).getSize() > minEntropyInPool0)){
            return false;
        }
        //update hash from the required pools
        for(int q = 0; q<32; q++){
                if(((2^q)%reseedCounter) == 0){
                    hash.update(acumulator.getPool(q).getEntropy(), 0,
                            acumulator.getPool(q).getEntropy().length);
                }
        }
        //cut hash from 64 bytes to 32    
        byte[] keyBytes = Arrays.copyOfRange(hash.finish(), 0, 32);
        //set the new key
        key = new ByteArraySymmetricKey(keyBytes);
        generator.setKey(key);
        //update reseed counter
        ++reseedCounter;
        if(reseedCounter == (2^32)){
            reseedCounter = 1;
        }
        timer.reset();
        return true;
    }
    
    public void finish(){
        //softly killing all entropy source threads
        for (int i = 0; i < etropySource.size(); i++) {
                etropySource.get(i).stopRunning();
        }
    }
}
