| /* //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); |
| } |
| } |
| |