/*******************************************************************************
An implementation of the Java Debug Wire Protocol (JDWP) for JOP
Copyright (C) 2007 Paulo Abadie Guedes
This library 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 2.1 of the License, or (at your option) any later version.
This library 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.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*******************************************************************************/
package debug.io;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import debug.constants.CommandConstants;
import debug.constants.ErrorConstants;
/**
* This class model a JDWP packet for JOP.
* It is used both to receive and to send JDWP packets.
*
* JavaDebugPacket.java
*
* @author Paulo Abadie Guedes
*
* 04/12/2007 - 16:50:43
*
*/
public final class JavaDebugPacket
{
private static final byte REPLY_FLAG = (byte) 0x80;
// a static counter to create packet ID's as needed.
private static int idCounter = 0;
private static final int LENGTH_INDEX = 0;
private static final int LENGTH_SIZE = 4;
private static final int ID_INDEX = (LENGTH_INDEX + LENGTH_SIZE);
private static final int ID_SIZE = 4;
private static final int FLAGS_INDEX = (ID_INDEX + ID_SIZE);
private static final int FLAGS_SIZE = 1;
private static final int COMMAND_SET_INDEX = (FLAGS_INDEX + FLAGS_SIZE);
private static final int COMMAND_SET_SIZE = 1;
private static final int COMMAND_INDEX = (COMMAND_SET_INDEX + COMMAND_SET_SIZE);
private static final int COMMAND_SIZE = 1;
private static final int ERROR_CODE_INDEX = COMMAND_SET_INDEX;
private static final int ERROR_CODE_SIZE = 2;
// the size of a JDWP packet header
private static final int JDWP_HEADER_SIZE = 11;
// varibles to hold the packet content
private RandomAccessByteArrayOutputStream arrayOutputStream;
// an internal index to be used when it's necessary to read data
// from this stream.
private int readIndex;
// a buffer to read packets without creating too many new objects
// for every byte read.
private static final int READ_BUFFER_SIZE = 512;
private byte readBuffer[];
/**
* The default constructor for a debug packet.
*/
public JavaDebugPacket()
{
arrayOutputStream = new RandomAccessByteArrayOutputStream();
readBuffer = new byte[READ_BUFFER_SIZE];
// allocate at least an empty header
createEmptyHeader();
rewindReadIndexToDataSection();
}
/**
* Set the read index to the beginning of the data section.
*/
private void rewindReadIndexToDataSection()
{
readIndex = JDWP_HEADER_SIZE;
}
/**
* Create an empty header for this packet
*/
public synchronized void createEmptyHeader()
{
clear();
// Write a dummy value for the header size.
// An empty JDWP packet has at least the header (11 bytes).
writeInt(JDWP_HEADER_SIZE);
// the ID
writeInt(0);
//the flags, command set and command
writeShort(0);
writeByte(0);
}
/**
* Write an int value into this packet content.
*
* @param value
*/
public synchronized void writeInt(int value)
{
arrayOutputStream.writeInt(value);
}
/**
* Write a short value into this packet content.
*
* @param value
*/
public synchronized void writeShort(int value)
{
arrayOutputStream.writeShort(value);
}
/**
* Write a byte value into this packet content.
*
* @param value
*/
public synchronized void writeByte(int value)
{
arrayOutputStream.write(value);
}
/**
* Read an int value from this packet content.
*
* @param value
*/
public synchronized int readInt()
{
int value;
value = arrayOutputStream.readInt(this.readIndex);
readIndex += 4;
//JopDebugKernel.debugPrint("ReadInt: ");
//JopDebugKernel.debugPrintln(value);
return value;
}
/**
* Read a short value from this packet content.
*
* @param value
*/
public synchronized int readShort()
{
int value;
value = arrayOutputStream.readShort(this.readIndex);
readIndex += 2;
return value;
}
/**
* Read a byte value from this packet content.
*
* @param value
*/
public synchronized int readByte()
{
int value;
value = arrayOutputStream.readByte(this.readIndex);
readIndex += 1;
return value;
}
/**
* Skip some bytes from the packet content.
* The numBytes parameter should be positive or will be ignored.
*
* @param numBytes
*/
public synchronized void skipBytes(int numBytes)
{
if(numBytes > 0)
{
readIndex += numBytes;
}
}
/**
* Read an entire packet from the given input and store its content
* in memory for later access. Discard previous data.
*
* Set the read index to the beginning of the packet data area.
*
* @param input
* @throws IOException
*/
public synchronized final void readPacket(DataInputStream input) throws IOException
{
int size, count, data;
int num;
count = 0;
clear();
// read the size
size = input.readInt();
// store the packet size
arrayOutputStream.writeInt(size);
// read the content. We got already 4 bytes (the size, above).
// So, start with 4.
for(count = 4; count < size;)
{
// calculate how many bytes are still left to be read
num = size - count;
if(num > READ_BUFFER_SIZE)
{
num = READ_BUFFER_SIZE;
}
// try to read another set of bytes from the input.
// throw an exception if can't read it.
data = input.read(readBuffer, 0, num);
if(data == -1)
{
throw new IOException();
}
// copy the newly read chunk into the packet content
arrayOutputStream.write(readBuffer, 0, data);
count = count + data;
}
// set the read index to the beginning of the data section.
rewindReadIndexToDataSection();
}
/**
* This method write the internal content to an output stream.
* It's used to avoid allocating and releasing new objects for every
* packet sent.
*
* @param outputStream
* @throws IOException
*/
public synchronized final void writePacket(OutputStream outputStream)
throws IOException
{
// update the "size" field
updateSize();
// write the packet content to the output stream
arrayOutputStream.writeContent(outputStream);
}
/**
* Reset the internal buffer, so this object can be reused.
*/
public synchronized void clear()
{
arrayOutputStream.reset();
}
/**
* Return the buffer size.
*
* @return
*/
private synchronized int getInternalBufferSize()
{
return arrayOutputStream.size();
}
/**
* Update the "length" field inside the JDWP packet.
*/
private synchronized void updateSize()
{
int size = getInternalBufferSize();
arrayOutputStream.overwriteInt(size, LENGTH_INDEX);
}
/**
* Create a new packet ID.
* Set the ID field inside the JDWP packet.
*/
public synchronized final void createNewPacketId()
{
int id = getNextId();
setPacketId(id);
}
/**
* Set the content of the "ID" field.
*
* @param id
*/
private synchronized void setPacketId(int id)
{
arrayOutputStream.overwriteInt(id, ID_INDEX);
}
/**
* Return the next packet ID and increment the internal
* ID counter.
*
* @return
*/
private synchronized static int getNextId()
{
int nextId;
nextId = idCounter;
idCounter++;
return nextId;
}
/**
* Clear the packet flags.
*
* @param i
*/
public synchronized void clearFlags()
{
setFlags(0);
}
/**
* Set the reply flag.
*
* @param i
*/
public synchronized void setReplyPacket()
{
int flags;
flags = getFlags();
setFlags(flags | REPLY_FLAG);
}
/**
* Check if this is a reply packet.
*
* @return
*/
public synchronized boolean isReply()
{
int flags;
flags = getFlags();
// check if the reply flag is set
return ((flags & REPLY_FLAG) != 0);
}
/**
* Set the "flags" field.
*
* @param value
*/
public synchronized void setFlags(int value)
{
arrayOutputStream.overwriteByte(value, FLAGS_INDEX);
}
/**
* Set the "Command Set" field.
*
* @param value
*/
public synchronized void setCommandSet(int value)
{
arrayOutputStream.overwriteByte(value, COMMAND_SET_INDEX);
}
/**
* Set the "Command" field.
*
* @param value
*/
public synchronized void setCommand(int value)
{
arrayOutputStream.overwriteByte(value, COMMAND_INDEX);
}
/**
* Set the content of the "Error code" field of this packet.
* Beware: the error code is present *only* in reply packets.
*
* For regular packets, this method is not valid.
* Instead, use the "setCommand" and
* "setCommandSet" methods.
*
* @return
*/
public synchronized void setErrorCode(int errorCode)
{
overwriteBytes(ERROR_CODE_INDEX, ERROR_CODE_SIZE, errorCode);
}
/**
* Check if this is a reply package which has no error set.
*
* This method can be used only for reply packets.
* It has no meaning for regular request packets: in this
* case, it will always return "false".
*
* @return
*/
public boolean hasNoError()
{
// check if it's a reply packet and also if there's no error set.
return isReply() && (getErrorCode() == ErrorConstants.ERROR_NONE);
}
/**
* Read a set of bytes from the buffer.
*
* @param location
* @param size
* @return
*/
private synchronized int readBytes(int location, int size)
{
int value = 0;
switch(size)
{
case 4:
{
value = arrayOutputStream.readInt(location);
break;
}
case 2:
{
value = arrayOutputStream.readShort(location);
break;
}
case 1:
{
value = arrayOutputStream.readByte(location);
break;
}
default:
{
throw new ArrayIndexOutOfBoundsException("Wrong size! " + size);
}
}
return value;
}
/**
* Write a set of bytes on the buffer.
*
* Be careful: it does not write beyond the current size.
* Should be used to OVERWRITE ONLY.
*
* @param location
* @param size
* @return
*/
private synchronized void overwriteBytes(int location, int size, int value)
{
switch(size)
{
case 4:
{
arrayOutputStream.overwriteInt(value, location);
break;
}
case 2:
{
arrayOutputStream.overwriteShort(value, location);
break;
}
case 1:
{
arrayOutputStream.overwriteByte(value, location);
break;
}
default:
{
throw new ArrayIndexOutOfBoundsException("Wrong size! " + size);
}
}
}
/**
* Return the content of the "Length" field of this packet.
* For complete packets, it's the total number of bytes.
*
* In particular, it does *not* inform the actual length
* of the internal buffer, nor how many bytes were
* written in case of a partial (not complete)
* packet.
*
* @return
*/
public synchronized int getLength()
{
return arrayOutputStream.readInt(LENGTH_INDEX);
}
/**
* Return the content of the "ID" field of this packet.
*
* @return
*/
public synchronized int getId()
{
return arrayOutputStream.readInt(ID_INDEX);
}
/**
* Return the content of the "Flags" field of this packet.
*
* @return
*/
public synchronized int getFlags()
{
return arrayOutputStream.readByte(FLAGS_INDEX);
}
/**
* Return the content of the "Error code" field of this packet.
* Beware: the error code is present *only* in reply packets.
*
* For regular packets, this method is not valid.
* Instead, use the "getCommand" and
* "getCommandSet" methods.
*
* @return
*/
public synchronized int getErrorCode()
{
//return arrayOutputStream.readShort(ERROR_CODE_INDEX);
return readBytes(ERROR_CODE_INDEX, ERROR_CODE_SIZE);
}
/**
* Return the content of the "Command Set" field of this packet.
*
* @return
*/
public synchronized int getCommandSet()
{
return readBytes(COMMAND_SET_INDEX, COMMAND_SET_SIZE);
}
/**
* Return the content of the "Command" field of this packet.
*
* @return
*/
public synchronized int getCommand()
{
return readBytes(COMMAND_INDEX, COMMAND_SIZE);
}
/**
* Create a header for a debug packet with a brand new ID,
* the "Event" command set and the "Composite" command.
*/
public synchronized void createEventHeader()
{
int id, commandSet, command;
id = getNextId();
commandSet = CommandConstants.Event_Command_Set;
command = CommandConstants.Event_Composite;
createHeader(id, 0, commandSet, command);
}
/**
* Create a new packet header, with all the given fields.
*
* @param id the packet ID.Can be new or old (from the server), for reply packets.
* @param flags the flags field.
* @param commandSet the command set.
* @param command the command.
*/
public synchronized void createHeader(int id, int flags, int commandSet, int command)
{
// clear the packet and create an empty header
createEmptyHeader();
// create a new ID for the packet
setPacketId(id);
// set the flags field
setFlags(flags);
// set the "command set" and "command" fields
setCommandSet(commandSet);
setCommand(command);
}
/**
* Create a reply header based on the given parameters
*
* @param id
* @param commandSet
* @param command
*/
private synchronized void createReplyHeader(int id, int commandSet, int command)
{
createHeader(id, REPLY_FLAG, commandSet, command);
}
/**
* Create a header for reply packet, based on the given packet.
*
* @param packet
*/
public synchronized void createReplyHeader(JavaDebugPacket packet)
{
int id;
int commandSet;
int command;
id = packet.getId();
commandSet = packet.getCommandSet();
command = packet.getCommand();
createReplyHeader(id, commandSet, command);
}
public void printPacketHeader(PrintStream out)
{
printPacketHeader(this, out);
}
public static void printPacketHeader(JavaDebugPacket packet, PrintStream out)
{
int id;
int commandSet;
int command;
int size;
int flags;
size = packet.getLength();
id = packet.getId();
flags = packet.getFlags();
commandSet = packet.getCommandSet();
command = packet.getCommand();
out.print(" Size: ");
out.print(size);
out.print(" ID: ");
out.print(id);
out.print(" Flags: ");
out.print(flags);
if(packet.isReply() == false)
{
out.print(" Command Set: ");
out.print(commandSet);
out.print(" ");
out.print(CommandConstants.getSetDescription(commandSet));
out.print(" Command: ");
out.print(command);
out.print(" ");
out.println(CommandConstants.getCommandDescription(commandSet, command));
}
else
{
out.print(" Error code: ");
if(packet.hasNoError())
{
out.println("None");
}
else
{
int error = packet.getErrorCode();
out.println(error);
}
}
out.println();
}
}