/* * This file is part of AtomicRNG. * (c) 2015 Thomas "V10lator" Rohloff * * AtomicRNG is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * AtomicRNG is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. */ package de.V10lator; import gnu.crypto.hash.BaseHash; import gnu.crypto.hash.Sha512; import gnu.crypto.hash.Whirlpool; import java.awt.Color; import java.awt.Font; import java.awt.Graphics; import java.awt.image.BufferedImage; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Iterator; import java.util.Random; import java.util.concurrent.atomic.AtomicBoolean; import javax.imageio.ImageIO; import org.bytedeco.javacv.CanvasFrame; import org.bytedeco.javacv.FFmpegFrameRecorder; import org.bytedeco.javacv.OpenCVFrameGrabber; import org.bytedeco.javacpp.avcodec; import org.bytedeco.javacpp.avutil; import org.bytedeco.javacpp.opencv_core.IplImage; public class AtomicRNG { private static OpenCVFrameGrabber atomicRNGDevice; private static String version; static Random rand = new Random(); private static boolean randSecure = false; private static int hashCount = 0; private static long byteCount = 0; static ImageScanner[][] scanners; private static FFmpegFrameRecorder videoOut = null; private static float ts = 0.0f; static int height = 0; static int width = 0; private static int statXoffset = 0; private static int statWidth = 0; static boolean firstRun = true; private static final ArrayList<Pixel> crosses = new ArrayList<Pixel>(); private static final ArrayList<Pixel> randomImagePixels = new ArrayList<Pixel>(); private static long randomImageNumber = 0L; private static long lastRandomImageSlice; static boolean stopped = false; private static BaseHash[] hashAlgos = { new Sha512(), new Whirlpool() }; /** * This hashes the number with a hashing algorithm based on xxHash:<br> * First the number is hashed with a random seed. After that it's * hashed again with a new random seed. Both numbers get mixed to * minimalize collisions and as a result maximum randomness.<br> * <br> * This function doesn't always handle the hash to /dev/random but uses * it internally to re-seed the internal RNG (used to get the seeds for * xxHash) from time to time. This is to ensure the Java pseudo RNG * produces true random numbers. * @param number The number to hash and feed to /dev/random */ //private static ByteBuffer byteBuffer = null; //private static ByteBuffer byteBuffer2 = null; private static void addZeroesToHash() { while(rand.nextInt(100) < 10) hash.update((byte) 0); } private static BaseHash hash; static void toOSrng(int number) { /* * If this is the first number we got use it as seed for the internal RNG and exit. */ if(!randSecure) { rand.setSeed(System.currentTimeMillis() * number); randSecure = true; return; } /* * Hash the numbers. */ if(hash == null) hash = hashAlgos[rand.nextInt(hashAlgos.length)]; ByteBuffer tmpBuffer = ByteBuffer.allocate(Integer.SIZE >> 3); tmpBuffer.putInt(number); tmpBuffer.flip(); boolean fb = false; byte b; while(tmpBuffer.hasRemaining()) { b = tmpBuffer.get(); if(!fb) { if(b == 0x00) continue; else fb = true; } addZeroesToHash(); hash.update(b); } if(!fb) { // We removed all bytes, so the number we got was zero, re-add. addZeroesToHash(); hash.update((byte) 0x00); } if(rand.nextInt(100) > 10) return; byte[] bytes = hash.digest(); hash.reset(); hash = null; hashCount++; /* * From time to time use the result to re-seed the internal RNG and exit. */ if(rand.nextInt(100) < 1) { rand.setSeed(ByteBuffer.wrap(bytes).getLong()); return; } EntropyQueue.add(bytes); byteCount += bytes.length; } /** * This restarts the Alpha-Ray-Vistualizer.<br> * Normally this should never be used. After 3 * failed restarts in a row it will quit the program. */ private static void restartAtomicRNGDevice(Exception why) { Exception trace = null; System.out.print("Restarting ARV device... "); int i; for(i = 0; i < 3; i++) { try { atomicRNGDevice.restart(); trace = null; break; } catch (org.bytedeco.javacv.FrameGrabber.Exception e) { e.addSuppressed(why); trace = e; } try { Thread.sleep(500L); } catch (InterruptedException e) {} } if(trace != null) { System.out.println("failed! ("+(i+1)+" tries)"); trace.printStackTrace(); System.exit(1); } System.out.println("done!"); why.printStackTrace(); } private static final AtomicBoolean lock = new AtomicBoolean(false); /** * This is to get the lock.<b> * This blocks till the lock could be aquired! * @param aggressive This should normally be false as it needs way more CPU power. */ private static boolean getLock(boolean aggressive) { long c = 0; while(!lock.compareAndSet(false, true)) { if(!aggressive) { try { Thread.sleep(2L); } catch (InterruptedException e) { e.printStackTrace(); } } else if(++c > 1000000000L) { return false; //TODO: Danger } } return true; } /** * Internal class trapped by Runtime.getRuntime().addShutdownHook();<br> * <br> * TODO: Make all actions happening in run() threadsafe as currently there are collisions. * @author v10lator * */ private static class Cleanup extends Thread { public void run() { System.out.print(System.lineSeparator()+ "Cleaning up... "); stopped = true; EntropyQueue.cleanup(); /* * Flush and close /dev/random. */ if(!getLock(true)) { // Assume crash System.err.println("error!"); return; // And do nothing; } if(videoOut != null) { lock.set(false); toggleRecording(); } else lock.set(false); if(randomImageNumber > 0) toggleRandomImage(); if(EntropyQueue.f != null) try { EntropyQueue.f.flush(); EntropyQueue.f.close(); } catch(IOException e) { e.printStackTrace(); } try { Thread.sleep(20L); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("done!"); } } static void toggleRecording() { getLock(false); try { if(videoOut == null) { System.out.println("Video: "+statWidth+" / "+height); videoOut = new FFmpegFrameRecorder("AtomicRNG.avi", statWidth, height); videoOut.setVideoCodec(avcodec.AV_CODEC_ID_HUFFYUV); videoOut.setAudioCodec(avcodec.AV_CODEC_ID_NONE); videoOut.setFormat("avi"); videoOut.setPixelFormat(avutil.AV_PIX_FMT_RGB32); //videoOut.setSampleRate(9); videoOut.setFrameRate(9); //TODO: Don't hardcode. videoOut.setVideoBitrate(10 * 1024 * 1024); //videoOut.setVideoQuality(1.0d); videoOut.start(); } else { videoOut.stop(); videoOut.release(); ts = 0.0f; videoOut = null; } } catch (org.bytedeco.javacv.FrameRecorder.Exception e) { lock.set(false); e.printStackTrace(); System.exit(1); } lock.set(false); } static void toggleRandomImage() { if(randomImageNumber > 0) { paintRandomImage(); randomImageNumber = 0; } else { lastRandomImageSlice = System.currentTimeMillis(); randomImageNumber++; } } private static void paintRandomImage() { BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); Graphics graphics = img.getGraphics(); graphics.setColor(Color.BLACK); graphics.fillRect(0, 0, width, height); long oldSeed = rand.nextLong(); for(Pixel pixel: randomImagePixels) { rand.setSeed(pixel.power); img.setRGB(pixel.x, pixel.y, new Color(rand.nextInt(256), rand.nextInt(256), rand.nextInt(256)).getRGB()); } rand.setSeed(oldSeed); randomImagePixels.clear(); try { File out = new File("Random image #"+(randomImageNumber++)+".png"); ImageIO.write(img, "png", out); } catch(IOException e) { e.printStackTrace(); } lastRandomImageSlice = System.currentTimeMillis(); } private static Font smallFont = new Font("Arial", Font.PLAIN, 9); private static void paintCross(Graphics g, Pixel pixel) { int x = statXoffset + pixel.x; g.drawOval(x - 7, pixel.y - 7, 14, 14); g.drawLine(x - 6, pixel.y, x - 4, pixel.y); g.drawLine(x + 4, pixel.y, x + 6, pixel.y); g.drawLine(x, pixel.y - 6, x, pixel.y - 4); g.drawLine(x, pixel.y + 4, x, pixel.y + 6); g.setFont(smallFont); g.drawString(String.valueOf(pixel.power), x + 10, pixel.y + 4); } /** * The main function called by the JVM.<br> * Most of the action happens in here. * @param args */ public static void main(String[] args) { org.bytedeco.javacpp.Loader.load(org.bytedeco.javacpp.avcodec.class); // Workaround for java.lang.NoClassDefFoundError: Could not initialize class org.bytedeco.javacpp.avcodec /* * Automagically get the version from maven. */ BufferedReader reader = new BufferedReader(new InputStreamReader(AtomicRNG.class.getResourceAsStream("/META-INF/maven/"+AtomicRNG.class.getPackage().getName()+"/AtomicRNG/pom.properties"))); String line; try { while((line = reader.readLine()) != null) if(line.substring(0, 8).equals("version=")) { version = line.substring(8); break; } reader.close(); } catch (IOException e) { e.printStackTrace(); System.exit(1); } /* * Startup: Print program name and copyright. */ System.out.println("AtomicRNG v"+version+System.lineSeparator()+ "(c) 2015 by Thomas \"V10lator\" Rohloff."+System.lineSeparator()); /* * Parse commandline arguments. */ boolean quiet = false, doubleView = false/*, experimentalFilter = false*/; for(String arg: args) { switch(arg) { case("-q"): quiet = true; break; case("-f"): if(EntropyQueue.f == null) // No multiple inits for multiple args. EntropyQueue.fileInit(); break; case("-d"): doubleView = true; break; /* case("-ef"): experimentalFilter = true; System.out.println("WARNING: Experimental noise filter activated!"+System.lineSeparator()); break;*/ case "-h": System.out.println("Arguments:"+System.lineSeparator()+ " -q : Be quiet."+System.lineSeparator()+ " -f : Enable file output."+System.lineSeparator()+ " -d : Enable double view."+System.lineSeparator()+ // " -ef : Enable experimental filter."+System.lineSeparator()+ " -v : Enable video recorder."+System.lineSeparator()+ " -h : Show this help."+System.lineSeparator()); return; default: System.out.println("Unknown argument: "+arg+System.lineSeparator()+System.lineSeparator()); System.exit(1); } } /* * Tell the user we're going to initialize the ARV device. */ System.out.print("Initializing Alpha Ray Visualizer... "); /* * Extract native libraries for use with JNA * try { /* * Create tmp dir and register it as JNAs library path. * FileAttribute<?>[] empty = {}; Path tmpDir = Files.createTempDirectory("AtomicRNG_", empty); tmpDir.toFile().deleteOnExit(); System.setProperty("jna.library.path", tmpDir.toString()); tmpDir.toFile().deleteOnExit(); // Delete tmp dir on exit. JarFile file = new JarFile(AtomicRNG.class.getProtectionDomain().getCodeSource().getLocation().getFile()); // Open our jar. /* * Extract all files. * String[] files = { "xxhash" }; String[] prefixes = { ".so", "__LICENSE.txt" }; String jarDir = "resources/"; Path jarFile; String fileName; InputStream inStream; for(String suffix: files) for(String prefix: prefixes) { fileName = suffix+prefix; inStream = file.getInputStream(file.getJarEntry(jarDir+fileName)); jarFile = tmpDir.resolve(fileName); jarFile.toFile().deleteOnExit(); Files.copy(inStream, jarFile); inStream.close(); } file.close(); // close jar. } catch (Exception e) { System.err.println("error!"); e.printStackTrace(); System.exit(1); } /* * Trap Cleanup().run() to be called when the JVM exits. */ Runtime.getRuntime().addShutdownHook(new Cleanup()); /* * Open and start the webcam inside of the ARV device. */ atomicRNGDevice = new OpenCVFrameGrabber(0); try { atomicRNGDevice.start(); } catch(Exception e) { restartAtomicRNGDevice(e); } /* * Throw away the first frame cause of hardware init. * The noise filters will handle the rest. */ try { atomicRNGDevice.grab().release(); } catch (org.bytedeco.javacv.FrameGrabber.Exception e) { restartAtomicRNGDevice(e); } /* * Open the Linux RNG and keep it open all the time. * We close it in Cleanup().run(). */ EntropyQueue.resetOSrng(); new EntropyQueue().start(); /* * In case we should draw the window initialize it and set its title. */ String[] stat = null, statOut = null; CanvasFrame canvasFrame = null; if(!quiet) { canvasFrame = new CanvasFrame("AtomicRNG v"+version); stat = new String[3]; stat[0] = "FPS: %1"; stat[1] = "%2 kb/s (%3 hashes/sec)"; stat[2] = "Queue: %4"; statOut = new String[3]; statOut[0] = "FPS: N/A"; statOut[1] = "N/A kb/s (N/A hashes/sec)"; statOut[2] = "Queue: N/A"; canvasFrame.setDefaultCloseOperation(CanvasFrame.EXIT_ON_CLOSE); canvasFrame.getCanvas().addMouseListener(new AtomicMouseListener()); } /* * We initialized everything. * Tell the user we're ready! */ System.out.println("done!"); /* * A few Variables we'll need inside of the main loop. */ int fpsCount = 0, avgFPS = 0; Color yellow = new Color(1.0f, 1.0f, 0.0f, 0.1f); BufferedImage statImg = null; Font font = new Font("Arial Black", Font.PLAIN, 18); long lastFound = System.currentTimeMillis(); long lastSlice = lastFound; /* * All right, let's enter the matrix, eh, the main loop I mean... */ while(true) { /* * Grab a frame from the webcam. */ IplImage img = null; try { /* * Grab a frame from the webcam. */ img = atomicRNGDevice.grab(); } catch(Exception e) { restartAtomicRNGDevice(e); } if(img != null && !img.isNull()) { /* * First get the start time of that loop run. */ long start = System.currentTimeMillis(); if(!quiet) fpsCount++; /* * After each 4 seconds... */ if(start - lastSlice >= 4000L) { /* * ...update the windows title with the newest statistics... */ if(!quiet) { avgFPS = fpsCount >> 2; if(((float)fpsCount/4.0f) % 2 != 0) avgFPS++; String es = EntropyQueue.getStats(); for(int i = 0; i < stat.length; i++) statOut[i] = stat[i]. replaceAll("%1", String.valueOf(avgFPS)). replaceAll("%2", String.valueOf((float)(byteCount >> 7)/4.0f)). replaceAll("%3", String.valueOf((float)hashCount/4.0f)). replaceAll("%4", es); byteCount = hashCount = fpsCount = 0; } /* * prepare to count the next 10 seconds and flush /dev/random. */ lastSlice = start; } if(randomImageNumber > 0 && start - lastRandomImageSlice >= 3600000L) paintRandomImage(); /* * The width is static, so if it's zero we never asked for it and other infos. * Let's do that. */ if(firstRun) { width = img.width(); height = img.height(); int rows = height >> 6, columns = width >> 6; int cw = width / columns, ch = height / rows, yi; scanners = new ImageScanner[rows][columns]; for(int y = 0; y < rows; y++) { yi = y * ch; for(int x = 0; x < columns; x++) scanners[y][x] = new ImageScanner(x * cw, yi); } /* * Calculate the needed window size and paint the red line in the middle. */ if(!quiet) { if(doubleView) { statXoffset = width + 2; statWidth = statXoffset + width; } else statWidth = width; statImg = new BufferedImage(statWidth, height, BufferedImage.TYPE_3BYTE_BGR); if(doubleView) for(int x = width; x < statXoffset; x++) for(int y = 0; y < height; y++) statImg.setRGB(x, y, Color.RED.getRGB()); canvasFrame.setCanvasSize(statWidth, height); } } Graphics graphics = null; if(!quiet) { graphics = statImg.getGraphics(); graphics.drawImage(img.getBufferedImage(), 0, 0, null); if(doubleView) { graphics.setColor(Color.BLACK); graphics.fillRect(statXoffset, 0, statXoffset + width, height); } } /* * Wrap the frame to a Java BufferedImage and parse it pixel by pixel. */ ByteBuffer buffer = img.getByteBuffer(); boolean[][] ignoredPixels = new boolean[width][height]; for(int x = 0; x < width; x++) for(int y = 0; y < height; y++) ignoredPixels[x][y] = false; ArrayList<Pixel>[] impacts = new ArrayList[(height >> 6) * (width >> 6)]; int c = 0; for(ImageScanner[] isa: scanners) for(ImageScanner is: isa) impacts[c++] = is.scan(buffer, img.widthStep(), img.nChannels(), start, ignoredPixels); boolean impact = false; for(ArrayList<Pixel> list: impacts) if(!list.isEmpty()) { for(Pixel pix: list) { toOSrng(pix.x); toOSrng(pix.power); toOSrng(pix.y); if(!quiet) { crosses.add(pix); if(randomImageNumber > 0) randomImagePixels.add(pix); } } toOSrng((int)(start - lastFound)); impact = true; } if(!quiet) { Iterator<Pixel> iter = crosses.iterator(); Pixel pix; graphics.setColor(Color.RED); //boolean imp = false; while(iter.hasNext()) { pix = iter.next(); if(start - pix.found > 2000L) { iter.remove(); continue; } paintCross(graphics, pix); //imp = true; } /* TODO: Debugging stuff if(imp) try { String fn = String.valueOf(in++); while(fn.length() < 5) fn = "0"+fn; File out = new File("debug/"+fn+".png"); ImageIO.write(statImg, "png", out); } catch(IOException e) { e.printStackTrace(); }*/ } /* * Write the yellow, transparent text onto the window and update it. */ if(!quiet) { graphics.setColor(yellow); if(doubleView) { graphics.setFont(font); graphics.drawString("Raw", width / 2 - 25, 25); graphics.drawString("Filtered", statXoffset + (width / 2 - 50), 25); } graphics.setFont(smallFont); int ty = 1; for(String st: statOut) graphics.drawString(st, 5, ty++ * 10); graphics.setColor(Color.RED); getLock(false); if(videoOut != null) { try { ts += avgFPS == 0.0f ? start - lastFound : avgFPS; videoOut.setTimestamp((int) ts); //TODO: DEBUG! videoOut.record(IplImage.createFrom(statImg)); lock.set(false); } catch (org.bytedeco.javacv.FrameRecorder.Exception e) { lock.set(false); e.printStackTrace(); toggleRecording(); } graphics.fillOval(statXoffset + width - 25, height - 25, 20, 20); } else { lock.set(false); graphics.drawOval(statXoffset + width - 25, height - 25, 20, 20); } graphics.setColor(Color.GREEN); if(randomImageNumber > 0) graphics.fillOval(statXoffset + width - 50, height - 25, 20, 20); else graphics.drawOval(statXoffset + width - 50, height - 25, 20, 20); if(!quiet) canvasFrame.showImage(statImg); } if(impact) lastFound = start; /* * Release the resources of the frame. */ img.release(); firstRun = false; } try { /* * Don't let us burn all CPU in case we're under heavy load. */ Thread.sleep(2L); } catch (InterruptedException e) {} } } //static int in = 0; static boolean isVideoButton(int x, int y) { int vX = statXoffset + width - 25; int vY = height - 25; return x >= vX && x <= vX + 20 && y >= vY && y <= vY + 20; } static boolean isImageButton(int x, int y) { int vX = statXoffset + width - 50; int vY = height - 25; return x >= vX && x <= vX + 20 && y >= vY && y <= vY + 20; } }