/*
* Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code 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 General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.max.jdwp.server;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.max.jdwp.data.CommandHandler;
import com.sun.max.jdwp.data.CommandHandlerRegistry;
import com.sun.max.jdwp.data.IncomingData;
import com.sun.max.jdwp.data.IncomingPacket;
import com.sun.max.jdwp.data.JDWPException;
import com.sun.max.jdwp.data.JDWPIncomingPacketException;
import com.sun.max.jdwp.data.JDWPInputStream;
import com.sun.max.jdwp.data.JDWPOutputStream;
import com.sun.max.jdwp.data.JDWPSender;
import com.sun.max.jdwp.data.OutgoingData;
import com.sun.max.jdwp.data.ReplyPacket;
class JDWPStream implements JDWPSender {
private static final Logger LOGGER = Logger.getLogger(JDWPStream.class.getName());
private static final String HANDSHAKE = "JDWP-Handshake";
private static final int HEADER_SIZE = 11;
private DataInputStream in;
private DataOutputStream out;
// Counter that is increased for each sent outgoing command.
private int outgoingID;
JDWPStream(InputStream is, OutputStream os) {
in = new DataInputStream(is);
out = new DataOutputStream(os);
}
public synchronized void sendCommand(OutgoingData outgoingData) throws IOException {
outgoingID++;
send(outgoingID, outgoingData);
}
private void send(int id, OutgoingData outgoingData) throws IOException {
LOGGER.info("***************************************************************************");
LOGGER.info("Sending eventPacket with id=" + id);
LOGGER.info(outgoingData.toString());
final byte[] dataBytes = toByteArray(outgoingData);
final int length = HEADER_SIZE + dataBytes.length;
out.writeInt(length);
out.writeInt(id);
out.writeByte(0);
out.writeByte(outgoingData.getCommandSetId());
out.writeByte(outgoingData.getCommandId());
out.write(dataBytes);
}
private byte[] toByteArray(OutgoingData outgoingData) {
if (outgoingData == null) {
return new byte[0];
}
final ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
outgoingData.write(new JDWPOutputStream(out));
} catch (IOException ex) {
LOGGER.log(Level.SEVERE, null, ex);
}
return out.toByteArray();
}
/**
* Sends a reply packet to the client.
*
* @param packet the packet to be sent
* @throws IOException this exception is thrown when a problem occurred while writing the packet bytes
*/
public synchronized <IncomingData_Type extends IncomingData, OutgoingData_Type extends OutgoingData> void send(ReplyPacket<IncomingData_Type, OutgoingData_Type> packet) throws IOException {
LOGGER.info("Sending reply packet: " + packet);
final byte[] dataBytes = toByteArray(packet.getData());
final int length = HEADER_SIZE + dataBytes.length;
out.writeInt(length);
out.writeInt(packet.getId());
out.writeByte(packet.getFlags());
out.writeShort(packet.getErrorCode());
out.write(dataBytes);
}
/**
* Performs a JDWP handshake and throws an exception if the handshake fails.
*
* @throws IOException this exception is thrown if the handshake fails
*/
public void handshake() throws IOException {
if (readAndCheckStringAsBytes(HANDSHAKE)) {
writeStringAsBytes(HANDSHAKE);
} else {
throw new IOException("JDWP handshake failed");
}
}
private void writeStringAsBytes(String s) throws IOException {
for (int i = 0; i < s.length(); i++) {
assert ((byte) s.charAt(i)) == s.charAt(i) : "String may only consist of ASCII characters";
out.writeByte((byte) s.charAt(i));
}
}
private boolean readAndCheckStringAsBytes(String s) throws IOException {
for (int i = 0; i < s.length(); i++) {
assert ((byte) s.charAt(i)) == s.charAt(i) : "String may only consist of ASCII characters";
final byte b = in.readByte();
if (b != ((byte) s.charAt(i))) {
return false;
}
}
return true;
}
/**
* Tries to receive a new JDWP packet and decode it by lookup up a command handler in the given command handler
* registry.
*
* @param registry the registry that is used to lookup the command handler based on the bytes in the packet header
* @return a newly created IncomingPacket object representing the read packet
* @throws IOException this exception is thrown, when a problem occurred while reading the packet bytes
* @throws JDWPIncomingPacketException this exception is thrown, when a problem occurred while translating the
* packet bytes
*/
public IncomingPacket<? extends IncomingData, ? extends OutgoingData> receive(CommandHandlerRegistry registry) throws IOException, JDWPIncomingPacketException {
final int length = in.readInt();
final int id = in.readInt();
final byte flags = in.readByte();
final byte commandSetId = in.readByte();
final byte commandId = in.readByte();
final byte[] data = new byte[length - HEADER_SIZE];
in.read(data);
final CommandHandler<? extends IncomingData, ? extends OutgoingData> handler = registry.findCommandHandler(commandSetId, commandId);
if (handler == null) {
final IncomingPacket<? extends IncomingData, ? extends OutgoingData> result = createIncomingPacket(length, id, flags, commandSetId, commandId, null, null);
return result;
}
assert handler.getCommandId() == commandId;
assert handler.getCommandSetId() == commandSetId;
try {
final IncomingData incomingData = handler.createIncomingDataObject();
@SuppressWarnings("unchecked")
final CommandHandler<IncomingData, OutgoingData> handlerDownCast = (CommandHandler<IncomingData, OutgoingData>) handler;
incomingData.read(new JDWPInputStream(new ByteArrayInputStream(data), handlerDownCast, incomingData));
final IncomingPacket<? extends IncomingData, ? extends OutgoingData> p = createIncomingPacket(length, id, flags, commandSetId, commandId, incomingData, handler);
LOGGER.info("#####################################################################################");
LOGGER.info(CommandHandler.Static.getCommandName(handler) + ": " + p);
return p;
} catch (JDWPException e) {
throw new JDWPIncomingPacketException(e, createIncomingPacket(length, id, flags, commandSetId, commandId, null, handler));
}
}
@SuppressWarnings("unchecked")
private IncomingPacket<? extends IncomingData, ? extends OutgoingData> createIncomingPacket(int length, int id, byte flags, byte commandSetId, byte commandId, IncomingData data,
CommandHandler handler) {
final IncomingPacket incomingPacket = new IncomingPacket<IncomingData, OutgoingData>(length, id, flags, commandSetId, commandId, data, handler);
final IncomingPacket<? extends IncomingData, ? extends OutgoingData> p = incomingPacket;
return p;
}
}