import java.io.*; import java.net.*; import java.math.*; /** *

Netrand Project

*

Software Engineering - CS536

*

University of Wisconsin - Milwaukee

*

Authors:

* * File: BitPool.java
*

* As the clients collect entropy from the network, they send their results * back to the server in datagram packets. It is the BitPool class which * handles the datagram socket that accepts these packets and stores them * in an internal buffer. This operation is performed from within its own * thread. It is assumed that the clients are sending one byte of random * data at a time. The reason for only accepting one byte at a time is to * to prevent large strings of random data that originated from one source. * When a byte of data is received, a section of the current buffer is hashed * using the HashMD5 class. Each byte of the * resulting hash value is XORed together and then XORed with the new byte. * This helps to reduce biases in the incoming data.
* Note: this hashing behavior does not begin until the BitPool has * accumulated enough bytes in the internal buffer to hash. If there are not * enough bytes in the internal buffer as input to the hash function, the * the new bytes are added to the internal buffer as is. *

* There are two ways the BitPool class can be used: *

    *
  1. BitPool can be called by the RandomByteServer * class to collect data from the clients. When the RandomByteServer * receives a request for string of random bytes, it queries the BitPool for * the data. *
  2. BitPool can be called from the command line:
    * java BitPool [receiving_port] [output_file]
    * In this mode, the BitPool class collects the data received from the clients * and writes it to the output file. */ public class BitPool extends Thread { // --------- Class Variables ----------- // /** datagram receiving socket */ private DatagramSocket collector; /** holds the incoming datagram packets */ private DatagramPacket incoming; /** internal byte buffer */ private ByteVector bitPool; /** maximum size of the internal buffer */ private int maxPoolSize; /** maximum bytes available per request */ private int maxRequestSize; /** the output file when running in file-mode */ private FileOutputStream outputFile; // ---------- Class Methods ----------- // /** * Run when the BitPool class is involked from the command line. * @param args[0] - port the DatagramSocket should listen for data on * @param args[1] - the file to write the random bytes to */ public static void main( String[] args ) { if ( args.length != 2 ) { System.err.println( "BitPool [collection_port] [output_file]" ); System.exit(1); } try { int collection_port = Integer.parseInt(args[0]); BitPool bitPool = new BitPool(collection_port, args[1]); Thread bitPoolThread = new Thread(bitPool); bitPoolThread.setPriority(MAX_PRIORITY); bitPoolThread.start(); } catch ( Exception e ) { System.err.println(e); } } // ------------------------------------- // /** * constructor for the BitPool class - server mode * @param maxReqBytes - maximum number of bytes a user can take from buffer * at one time * @param poolSize - the size of the internal buffer which stores the * random bytes of data * @param collectionPort - the listening port for the datagram socket to * accept packets from the clients * @exception BitPoolException if an error occurs while initializing the * object */ public BitPool( int maxReqBytes, int poolSize, int collectionPort ) throws BitPoolException { initBitCollection( collectionPort ); initRandomBitPool( maxReqBytes, poolSize ); outputFile = null; } // ------------------------------------- // /** * constructor for the BitPool class - file mode * @param collectionPort - the listening port for the datagram socket to * accept packets from the clients * @param outFile - the name of file to write data to * @exception BitPoolException if an error occurs while initializing the * BitPool object */ public BitPool( int collectionPort, String outFile ) throws BitPoolException, IOException { initBitCollection( collectionPort ); initRandomBitPool( 1, 1 ); outputFile = new FileOutputStream( outFile ); } // ------------------------------------ // /** * overrides the Thread's run() method; * an infinite loop which receives datagram packets over the socket, * calls the appropriate functions to add the datagram packet's data to * the internal buffer */ public void run() { while ( true ) { try { collector.receive(incoming); addPacketDataToPool(incoming); System.out.println( bitPool ); Thread.yield(); } catch ( IOException e ) { System.err.println( e ); collector.close(); System.exit(1); } } } // ------------------------------------ // /** * initializes the DatagramSocket which receives the packets from the * clients * @param port - the port to listen for incoming packets on * @exception BitPoolException if the attempt to open the socket fails */ private void initBitCollection( int port ) throws BitPoolException { if ( port < 1 || port > 65535 ) throw new BitPoolException( port + " is not a valid port" ); try { collector = new DatagramSocket( port ); byte[] packetBuffer = new byte[65507]; incoming = new DatagramPacket(packetBuffer, packetBuffer.length); } catch ( SocketException se ) { System.err.println(se + ":DatagramSocket failed on port " + port); throw new BitPoolException("DatagramSocket failed--port=" + port); } } // ------------------------------------- // /** * initializes the internal buffer which stores the random bytes of data * Note: the internal buffer is actually be poolSize + numHashInputBytes * : this is done to prevent setting the maximum pool size to be * less than the number of bytes needed to hash the buffer * @param maxReqBytes - the maximum number of bytes a user and extract * from the buffer at one time * @param poolSize - the maximum visable size of the internal buffer * @exception BitPoolException if an error occured while initializing the * buffer */ private void initRandomBitPool( int maxReqBytes, int poolSize ) throws BitPoolException { if ( poolSize < 1 ) throw new BitPoolException( poolSize + ": size must be > 0" ); if ( maxReqBytes < 1 || maxReqBytes > poolSize ) throw new BitPoolException( maxReqBytes + ": invalid size for the maximum number of requestable bytes" ); bitPool = new ByteVector(); maxPoolSize = poolSize + HashMD5.requiredInputBytes(); maxRequestSize = maxReqBytes; } // -------------------------------------- // /** * determines if there are enough bytes in the internal buffer to fill * a request * @param numBytes - the number of bytes to request * @return true iff the removal of numBytes from the internal buffer * would not bring the size of the buffer below the number of bytes * needed as input to the hash function AND numBytes is less than * the maximum request size */ public boolean hasBytesAvailable( int numBytes ) { if ( (bitPool.size() >= numBytes + HashMD5.requiredInputBytes()) && (numBytes <= maxRequestSize) ) return true; return false; } // -------------------------------------- // /** * extracts the data from a packet and calls hashIntoBuffer() to add it * to the internal buffer. If BitPool is running in conjunction with * the server, when the internal buffer exceeds the maximum pool size, * the buffer is reduced to the maximum pool size by discarding the older * random bytes in the buffer. If BitPool is running in file mode, * when the internal buffer exceeds the maximum pool size, the access bytes * are written to the output file. * @param p - the datagram packet holding the data */ private synchronized void addPacketDataToPool( DatagramPacket p ) { byte[] newBytes = p.getData(); // since packets sent with BitSender class, they come one // byte at a time, so we only need first byte in array hashIntoBuffer(newBytes[0]); if ( bitPool.size() > maxPoolSize ) { if ( outputFile == null ) bitPool.truncFromFront( maxPoolSize ); else { int numOutputBytes = bitPool.size() - maxPoolSize; byte[] theBytes = new byte[numOutputBytes]; for ( int i = 0; i < numOutputBytes; i++ ) theBytes[i] = bitPool.byteAt(i); bitPool.truncFromFront( maxPoolSize ); try { outputFile.write(theBytes); } catch ( IOException ioe ) { System.err.println( ioe ); System.exit(1); } } } } // ---------------------------------- // /** * extracts bytes from the internal buffer * @param numBytes - the number of bytes to extract from the buffer * @return an array of bytes containing the requested data * @exception BitPoolException if the internal buffer cannot supply * the requested bytes */ public synchronized byte[] getBytesFromPool( int numBytes ) throws BitPoolException { if ( (bitPool.size() < HashMD5.requiredInputBytes()+numBytes) && (numBytes > maxRequestSize) ) throw new BitPoolException( "BitPool does not have " + numBytes + " of data available" ); byte[] randomBytes = new byte[numBytes]; bitPool.copyInto(randomBytes); bitPool.truncFromFront(bitPool.size()-numBytes); return ( randomBytes ); } // ----------------------------------- // /** * takes newly a newly received byte and adds it to the internal buffer. * if there are enough existing bytes in the buffer to be hashed by * the HashMD5 class, then oldest bytes in the internal buffer are * hashed. Each byte of the hash value is XORed together along with the * new byte. Then the new byte is added to the internal buffer. If the * internal buffer does not have enough bytes to hash, the new byte is * added to the buffer as is. * @param newByte - the new byte to add to the buffer */ private synchronized void hashIntoBuffer( byte newByte ) { System.out.println( "adding new byte: " + newByte ); if ( bitPool.size() < HashMD5.requiredInputBytes() ) { bitPool.addByte( newByte ); return; } byte[] partToHash = new byte[HashMD5.requiredInputBytes()]; bitPool.copyInto(partToHash); byte[] hashValue = HashMD5.doHash(partToHash); byte tmp = hashValue[0]; for ( int i = 1; i < hashValue.length; i++ ) tmp = (byte)((int)tmp ^ (int)hashValue[i]); newByte = (byte)((int)newByte ^ (int)tmp); bitPool.addByte(newByte); return; } }