blob: 23b0249b9be628c589d6c0e82a60f9b010e237ae [file] [log] [blame]
/* //device/tools/ddms/libs/ddmlib/src/com/android/ddmlib/JdwpPacket.java
**
** Copyright 2007, The Android Open Source Project
**
** Licensed under the Apache License, Version 2.0 (the "License");
** you may not use this file except in compliance with the License.
** You may obtain a copy of the License at
**
** http://www.apache.org/licenses/LICENSE-2.0
**
** Unless required by applicable law or agreed to in writing, software
** distributed under the License is distributed on an "AS IS" BASIS,
** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
** See the License for the specific language governing permissions and
** limitations under the License.
*/
package com.android.ddmlib;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.channels.SocketChannel;
/**
* A JDWP packet, sitting at the start of a ByteBuffer somewhere.
*
* This allows us to wrap a "pointer" to the data with the results of
* decoding the packet.
*
* None of the operations here are synchronized. If multiple threads will
* be accessing the same ByteBuffers, external sync will be required.
*
* Use the constructor to create an empty packet, or "findPacket()" to
* wrap a JdwpPacket around existing data.
*/
final class JdwpPacket {
// header len
public static final int JDWP_HEADER_LEN = 11;
// results from findHandshake
public static final int HANDSHAKE_GOOD = 1;
public static final int HANDSHAKE_NOTYET = 2;
public static final int HANDSHAKE_BAD = 3;
// our cmdSet/cmd
private static final int DDMS_CMD_SET = 0xc7; // 'G' + 128
private static final int DDMS_CMD = 0x01;
// "flags" field
private static final int REPLY_PACKET = 0x80;
// this is sent and expected at the start of a JDWP connection
private static final byte[] mHandshake = {
'J', 'D', 'W', 'P', '-', 'H', 'a', 'n', 'd', 's', 'h', 'a', 'k', 'e'
};
public static final int HANDSHAKE_LEN = mHandshake.length;
private ByteBuffer mBuffer;
private int mLength, mId, mFlags, mCmdSet, mCmd, mErrCode;
private boolean mIsNew;
private static int sSerialId = 0x40000000;
/**
* Create a new, empty packet, in "buf".
*/
JdwpPacket(ByteBuffer buf) {
mBuffer = buf;
mIsNew = true;
}
/**
* Finish a packet created with newPacket().
*
* This always creates a command packet, with the next serial number
* in sequence.
*
* We have to take "payloadLength" as an argument because we can't
* see the position in the "slice" returned by getPayload(). We could
* fish it out of the chunk header, but it's legal for there to be
* more than one chunk in a JDWP packet.
*
* On exit, "position" points to the end of the data.
*/
void finishPacket(int payloadLength) {
assert mIsNew;
ByteOrder oldOrder = mBuffer.order();
mBuffer.order(ChunkHandler.CHUNK_ORDER);
mLength = JDWP_HEADER_LEN + payloadLength;
mId = getNextSerial();
mFlags = 0;
mCmdSet = DDMS_CMD_SET;
mCmd = DDMS_CMD;
mBuffer.putInt(0x00, mLength);
mBuffer.putInt(0x04, mId);
mBuffer.put(0x08, (byte) mFlags);
mBuffer.put(0x09, (byte) mCmdSet);
mBuffer.put(0x0a, (byte) mCmd);
mBuffer.order(oldOrder);
mBuffer.position(mLength);
}
/**
* Get the next serial number. This creates a unique serial number
* across all connections, not just for the current connection. This
* is a useful property when debugging, but isn't necessary.
*
* We can't synchronize on an int, so we use a sync method.
*/
private static synchronized int getNextSerial() {
return sSerialId++;
}
/**
* Return a slice of the byte buffer, positioned past the JDWP header
* to the start of the chunk header. The buffer's limit will be set
* to the size of the payload if the size is known; if this is a
* packet under construction the limit will be set to the end of the
* buffer.
*
* Doesn't examine the packet at all -- works on empty buffers.
*/
ByteBuffer getPayload() {
ByteBuffer buf;
int oldPosn = mBuffer.position();
mBuffer.position(JDWP_HEADER_LEN);
buf = mBuffer.slice(); // goes from position to limit
mBuffer.position(oldPosn);
if (mLength > 0)
buf.limit(mLength - JDWP_HEADER_LEN);
else
assert mIsNew;
buf.order(ChunkHandler.CHUNK_ORDER);
return buf;
}
/**
* Returns "true" if this JDWP packet has a JDWP command type.
*
* This never returns "true" for reply packets.
*/
boolean isDdmPacket() {
return (mFlags & REPLY_PACKET) == 0 &&
mCmdSet == DDMS_CMD_SET &&
mCmd == DDMS_CMD;
}
/**
* Returns "true" if this JDWP packet is tagged as a reply.
*/
boolean isReply() {
return (mFlags & REPLY_PACKET) != 0;
}
/**
* Returns "true" if this JDWP packet is a reply with a nonzero
* error code.
*/
boolean isError() {
return isReply() && mErrCode != 0;
}
/**
* Returns "true" if this JDWP packet has no data.
*/
boolean isEmpty() {
return (mLength == JDWP_HEADER_LEN);
}
/**
* Return the packet's ID. For a reply packet, this allows us to
* match the reply with the original request.
*/
int getId() {
return mId;
}
/**
* Return the length of a packet. This includes the header, so an
* empty packet is 11 bytes long.
*/
int getLength() {
return mLength;
}
/**
* Write our packet to "chan". Consumes the packet as part of the
* write.
*
* The JDWP packet starts at offset 0 and ends at mBuffer.position().
*/
void writeAndConsume(SocketChannel chan) throws IOException {
int oldLimit;
//Log.i("ddms", "writeAndConsume: pos=" + mBuffer.position()
// + ", limit=" + mBuffer.limit());
assert mLength > 0;
mBuffer.flip(); // limit<-posn, posn<-0
oldLimit = mBuffer.limit();
mBuffer.limit(mLength);
while (mBuffer.position() != mBuffer.limit()) {
chan.write(mBuffer);
}
// position should now be at end of packet
assert mBuffer.position() == mLength;
mBuffer.limit(oldLimit);
mBuffer.compact(); // shift posn...limit, posn<-pending data
//Log.i("ddms", " : pos=" + mBuffer.position()
// + ", limit=" + mBuffer.limit());
}
/**
* "Move" the packet data out of the buffer we're sitting on and into
* buf at the current position.
*/
void movePacket(ByteBuffer buf) {
Log.v("ddms", "moving " + mLength + " bytes");
int oldPosn = mBuffer.position();
mBuffer.position(0);
mBuffer.limit(mLength);
buf.put(mBuffer);
mBuffer.position(mLength);
mBuffer.limit(oldPosn);
mBuffer.compact(); // shift posn...limit, posn<-pending data
}
/**
* Consume the JDWP packet.
*
* On entry and exit, "position" is the #of bytes in the buffer.
*/
void consume()
{
//Log.d("ddms", "consuming " + mLength + " bytes");
//Log.d("ddms", " posn=" + mBuffer.position()
// + ", limit=" + mBuffer.limit());
/*
* The "flip" call sets "limit" equal to the position (usually the
* end of data) and "position" equal to zero.
*
* compact() copies everything from "position" and "limit" to the
* start of the buffer, sets "position" to the end of data, and
* sets "limit" to the capacity.
*
* On entry, "position" is set to the amount of data in the buffer
* and "limit" is set to the capacity. We want to call flip()
* so that position..limit spans our data, advance "position" past
* the current packet, then compact.
*/
mBuffer.flip(); // limit<-posn, posn<-0
mBuffer.position(mLength);
mBuffer.compact(); // shift posn...limit, posn<-pending data
mLength = 0;
//Log.d("ddms", " after compact, posn=" + mBuffer.position()
// + ", limit=" + mBuffer.limit());
}
/**
* Find the JDWP packet at the start of "buf". The start is known,
* but the length has to be parsed out.
*
* On entry, the packet data in "buf" must start at offset 0 and end
* at "position". "limit" should be set to the buffer capacity. This
* method does not alter "buf"s attributes.
*
* Returns a new JdwpPacket if a full one is found in the buffer. If
* not, returns null. Throws an exception if the data doesn't look like
* a valid JDWP packet.
*/
static JdwpPacket findPacket(ByteBuffer buf) {
int count = buf.position();
int length, id, flags, cmdSet, cmd;
if (count < JDWP_HEADER_LEN)
return null;
ByteOrder oldOrder = buf.order();
buf.order(ChunkHandler.CHUNK_ORDER);
length = buf.getInt(0x00);
id = buf.getInt(0x04);
flags = buf.get(0x08) & 0xff;
cmdSet = buf.get(0x09) & 0xff;
cmd = buf.get(0x0a) & 0xff;
buf.order(oldOrder);
if (length < JDWP_HEADER_LEN)
throw new BadPacketException();
if (count < length)
return null;
JdwpPacket pkt = new JdwpPacket(buf);
//pkt.mBuffer = buf;
pkt.mLength = length;
pkt.mId = id;
pkt.mFlags = flags;
if ((flags & REPLY_PACKET) == 0) {
pkt.mCmdSet = cmdSet;
pkt.mCmd = cmd;
pkt.mErrCode = -1;
} else {
pkt.mCmdSet = -1;
pkt.mCmd = -1;
pkt.mErrCode = cmdSet | (cmd << 8);
}
return pkt;
}
/**
* Like findPacket(), but when we're expecting the JDWP handshake.
*
* Returns one of:
* HANDSHAKE_GOOD - found handshake, looks good
* HANDSHAKE_BAD - found enough data, but it's wrong
* HANDSHAKE_NOTYET - not enough data has been read yet
*/
static int findHandshake(ByteBuffer buf) {
int count = buf.position();
int i;
if (count < mHandshake.length)
return HANDSHAKE_NOTYET;
for (i = mHandshake.length -1; i >= 0; --i) {
if (buf.get(i) != mHandshake[i])
return HANDSHAKE_BAD;
}
return HANDSHAKE_GOOD;
}
/**
* Remove the handshake string from the buffer.
*
* On entry and exit, "position" is the #of bytes in the buffer.
*/
static void consumeHandshake(ByteBuffer buf) {
// in theory, nothing else can have arrived, so this is overkill
buf.flip(); // limit<-posn, posn<-0
buf.position(mHandshake.length);
buf.compact(); // shift posn...limit, posn<-pending data
}
/**
* Copy the handshake string into the output buffer.
*
* On exit, "buf"s position will be advanced.
*/
static void putHandshake(ByteBuffer buf) {
buf.put(mHandshake);
}
}