import java.io.*;
import java.net.*;
import java.math.*;
/**
*
Netrand Project
* Software Engineering - CS536
* University of Wisconsin - Milwaukee
* Authors:
*
* - Spring 1998 - Francis William Kasper
*
* 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:
*
* - 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.
*
- 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;
}
}