/* * * * Copyright 2014 http://Bither.net * * * * Licensed under the Apache License, Version 2.0 (the "License"); * * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * * See the License for the specific language governing permissions and * * limitations under the License. * */ package net.bither.xrandom; import com.google.common.primitives.Ints; import net.bither.bitherj.utils.Sha256Hash; import java.io.IOException; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.util.Arrays; import java.util.HashSet; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class UEntropyCollector implements IUEntropy, IUEntropySource { public static final int POOL_SIZE = 32 * 200; private static final int ENTROPY_XOR_MULTIPLIER = (int) Math.pow(2, 4); public static interface UEntropyCollectorListener { public void onUEntropySourceError(Exception e, IUEntropySource source); } private boolean shouldCollectData; private UEntropyCollectorListener listener; private PipedInputStream in; private PipedOutputStream out; private HashSet<IUEntropySource> sources; private boolean paused; private ExecutorService executor; public UEntropyCollector(UEntropyCollectorListener listener) { this.listener = listener; paused = true; sources = new HashSet<IUEntropySource>(); executor = Executors.newSingleThreadExecutor(); } public void onNewData(final byte[] data, final UEntropySource source) { if (!shouldCollectData()) { return; } executor.submit(new Runnable() { @Override public void run() { if (!shouldCollectData()) { return; } byte[] processedData = source.processData(data); try { int available = in.available(); int extraBytes = available + processedData.length - POOL_SIZE; if (extraBytes <= 0) { out.write(processedData); } else if (extraBytes < processedData.length) { out.write(Arrays.copyOf(processedData, processedData.length - extraBytes)); } } catch (IOException e) { e.printStackTrace(); } } }); } public void onError(Exception e, IUEntropySource source) { if (sources.contains(source)) { source.onPause(); sources.remove(source); } if (listener != null) { listener.onUEntropySourceError(e, source); } } public void start() throws IOException { if (shouldCollectData) { return; } shouldCollectData = true; in = new PipedInputStream(POOL_SIZE); out = new PipedOutputStream(in); } public void stop() { if (!shouldCollectData) { return; } shouldCollectData = false; try { out.close(); in.close(); out = null; in = null; } catch (IOException e) { e.printStackTrace(); } } public boolean shouldCollectData() { return shouldCollectData; } @Override public byte[] nextBytes(int length) { byte[] bytes = null; if (!shouldCollectData()) { throw new IllegalStateException("UEntropyCollector is not running"); } try { for (int i = 0; i < ENTROPY_XOR_MULTIPLIER; i++) { byte[] itemBytes = new byte[length]; while (in.available() < itemBytes.length) { if (!shouldCollectData()) { throw new IllegalStateException("UEntropyCollector is not running"); } } in.read(itemBytes); if (i == ENTROPY_XOR_MULTIPLIER - 1) { itemBytes = Sha256Hash.create(itemBytes).getBytes(); } if (bytes == null) { bytes = itemBytes; } else { for (int k = 0; k < bytes.length && k < itemBytes.length; k++) { bytes[k] = (byte) (bytes[k] ^ itemBytes[k]); } } } } catch (IOException e) { e.printStackTrace(); return new byte[length]; } return bytes; } public enum UEntropySource { Unknown, Mouse(3), Keyboard(3); private int bytesInOneBatch; UEntropySource(int bytesInOneBatch) { this.bytesInOneBatch = bytesInOneBatch; } UEntropySource() { this(1); } public byte[] processData(byte[] data) { if (data.length <= bytesInOneBatch) { return data; } byte[] result = new byte[bytesInOneBatch]; byte[] locatorBytes; for (int i = 0; i < bytesInOneBatch; i++) { int position = (int) (Math.random() * data.length); try { locatorBytes = URandom.nextBytes(Ints.BYTES); int value = Math.abs(Ints.fromByteArray(locatorBytes)); position = (int) (((float) value / (float) Integer.MAX_VALUE) * data.length); } catch (Exception e) { e.printStackTrace(); } position = Math.min(Math.max(position, 0), data.length - 1); result[i] = data[position]; } return result; } } public void addSource(IUEntropySource source) { sources.add(source); if (!paused) { source.onResume(); } } public void addSources(IUEntropySource... sources) { for (IUEntropySource source : sources) { addSource(source); } } @Override public void onResume() { paused = false; for (IUEntropySource source : sources) { source.onResume(); } } @Override public void onPause() { paused = true; for (IUEntropySource source : sources) { source.onPause(); } } @Override public UEntropySource type() { return UEntropySource.Unknown; } public HashSet<IUEntropySource> sources() { return sources; } }