/*
* Sun Public License
*
* The contents of this file are subject to the Sun Public License Version
* 1.0 (the "License"). You may not use this file except in compliance with
* the License. A copy of the License is available at http://www.sun.com/
*
* The Original Code is the SLAMD Distributed Load Generation Engine.
* The Initial Developer of the Original Code is Neil A. Wilson.
* Portions created by Neil A. Wilson are Copyright (C) 2004-2010.
* Some preexisting portions Copyright (C) 2002-2006 Sun Microsystems, Inc.
* All Rights Reserved.
*
* Contributor(s): Neil A. Wilson
*/
package com.slamd.tools.tcpreplay;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/**
* This class provides a data structure that may be used to hold data that has
* been captured in a form that can be replayed to generate load against a
* server.
*
*
* @author Neil A. Wilson
*/
public class CaptureData
{
// The data that was actually captured.
private byte[] data;
// The version number of this capture. The only currently-supported capture
// version is 1.
private int captureVersion;
// The time that this data was captured, recorded in milliseconds since
// January 1, 1970 (the format used by the System.currentTimeMillis() method.
private long captureTime;
/**
* Creates a new capture data object with the provided information.
*
* @param captureVersion The capture version used.
* @param captureTime The time that this data was captured.
* @param data The actual data that was captured.
*/
public CaptureData(int captureVersion, long captureTime, byte[] data)
{
this.captureVersion = captureVersion;
this.captureTime = captureTime;
this.data = data;
}
/**
* Retrieves the capture version used for this capture.
*
* @return The capture version used for this capture.
*/
public int getCaptureVersion()
{
return captureVersion;
}
/**
* Retrieves the time that the data was captured.
*
* @return The time that the data was captured.
*/
public long getCaptureTime()
{
return captureTime;
}
/**
* Retrieves the data that was captured.
*
* @return The data that was captured.
*/
public byte[] getData()
{
return data;
}
/**
* Reads an encoded form of this capture data from the provided input stream.
*
* @param inputStream The input stream from which to read the capture data
* to decode.
*
* @return The capture data decoded from the provided input stream, or
* <CODE>null</CODE> if there is no more data to read from the input
* stream.
*
* @throws IOException If a problem occurs while trying to read the data
* from the input stream.
*
* @throws CaptureException If a problem occurs while attempting to decode
* the data read from the input stream.
*/
public static CaptureData decodeFrom(InputStream inputStream)
throws IOException, CaptureException
{
// The first four bytes hold the capture version, which must be 1.
int captureVersion = 0;
for (int i=0; i < 4; i++)
{
int byteRead = inputStream.read();
if (byteRead < 0)
{
return null;
}
captureVersion = (captureVersion << 8) | (byteRead & 0xFF);
}
if (captureVersion != 1)
{
throw new CaptureException("The capture version must be 1, but a value " +
"of " + captureVersion + " was read.");
}
// The next eight bytes hold the timestamp.
long captureTime = 0;
for (int i=0; i < 8; i++)
{
captureTime= (captureTime << 8) | (inputStream.read() & 0xFF);
}
// The next four bytes hold the number of bytes in the capture.
int captureLength = 0;
for (int i=0; i < 4; i++)
{
captureLength = (captureLength << 8) | (inputStream.read() & 0xFF);
}
if ((captureLength < 0) || (captureLength > (1024*1024)))
{
throw new CaptureException("The capture length must not be greater " +
"than one megabyte (decoded a length of " +
captureLength + ").");
}
// Read the specified number of bytes.
byte[] data = new byte[captureLength];
int totalBytesRead = 0;
while (totalBytesRead < captureLength)
{
int bytesRead = inputStream.read(data, totalBytesRead,
(captureLength - totalBytesRead));
if (bytesRead < 0)
{
throw new IOException("The input stream was unexpectedly closed " +
"before all data could be read.");
}
totalBytesRead += bytesRead;
}
// Create and return the capture data.
return new CaptureData(captureVersion, captureTime, data);
}
/**
* Encodes this capture data and writes it to the provided output stream.
*
* @param outputStream The output stream to which the encoded data should be
* written.
*
* @throws IOException If a problem occurs while writing the data to the
* provided output stream.
*/
public void encodeTo(OutputStream outputStream)
throws IOException
{
// First, four bytes will be used to encode the capture version, which is 1.
outputStream.write((byte) 0x00);
outputStream.write((byte) 0x00);
outputStream.write((byte) 0x00);
outputStream.write((byte) 0x01);
// Next, use eight bytes for the timestamp.
outputStream.write((byte) ((captureTime >> 56) & 0xFF));
outputStream.write((byte) ((captureTime >> 48) & 0xFF));
outputStream.write((byte) ((captureTime >> 40) & 0xFF));
outputStream.write((byte) ((captureTime >> 32) & 0xFF));
outputStream.write((byte) ((captureTime >> 24) & 0xFF));
outputStream.write((byte) ((captureTime >> 16) & 0xFF));
outputStream.write((byte) ((captureTime >> 8) & 0xFF));
outputStream.write((byte) (captureTime & 0xFF));
// Next, use four bytes for the number of bytes in the capture.
outputStream.write((byte) ((data.length >> 24) & 0xFF));
outputStream.write((byte) ((data.length >> 16) & 0xFF));
outputStream.write((byte) ((data.length >> 8) & 0xFF));
outputStream.write((byte) (data.length & 0xFF));
// Finally, write the actual data.
outputStream.write(data);
}
/**
* Replays the captured data to the provided output stream.
*
* @param outputStream The output stream to which the data should be
* written.
*
* @throws IOException If a problem occurs while writing the data to the
* provided output stream.
*/
public void replayTo(OutputStream outputStream)
throws IOException
{
outputStream.write(data);
}
/**
* Replays the captured data to the provided socket channel.
*
* @param socketChannel The socket channel to which the data should be
* written.
*
* @throws IOException If a problem occurs while writing the data to the
* provided socket channel.
*/
public void replayTo(SocketChannel socketChannel)
throws IOException
{
ByteBuffer buffer = ByteBuffer.wrap(data);
int bytesWritten = socketChannel.write(buffer);
while (bytesWritten < data.length)
{
bytesWritten += socketChannel.write(buffer);
}
}
}