blob: 42b37248dd40a8dfa500baecff4c9e1a52b5986c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - Initial API and implementation
*******************************************************************************/
package org.eclipse.ptp.proxy.packet;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import org.eclipse.ptp.proxy.command.IProxyCommand;
import org.eclipse.ptp.proxy.event.IProxyEvent;
import org.eclipse.ptp.proxy.messages.Messages;
import org.eclipse.ptp.proxy.util.ProtocolUtil;
public class ProxyPacket {
public static final int PACKET_LENGTH_SIZE = 8;
public static final int PACKET_CHANNEL_SIZE = 2;
public static final int PACKET_ID_SIZE = 4;
public static final int PACKET_TRANS_ID_SIZE = 8;
public static final int PACKET_NARGS_SIZE = 8;
public static final int PACKET_ARG_LEN_SIZE = 8;
private boolean debug = false;
private int packetID;
private int packetTransID;
private String[] packetArgs;
private Charset charset = Charset.forName("US-ASCII"); //$NON-NLS-1$
private CharsetEncoder encoder = charset.newEncoder();
private CharsetDecoder decoder = charset.newDecoder();
public ProxyPacket() {
}
public ProxyPacket(IProxyCommand cmd) {
this.packetID = cmd.getCommandID();
this.packetTransID = cmd.getTransactionID();
this.packetArgs = cmd.getArguments();
}
public ProxyPacket(IProxyEvent event) {
this.packetID = event.getEventID();
this.packetTransID = event.getTransactionID();
this.packetArgs = event.getAttributes();
}
/**
* Character set decoder
*
* @return decoder
*/
public CharsetDecoder decoder() {
return decoder;
}
/**
* Character set encoder
*
* @return encoder
*/
public CharsetEncoder encoder() {
return encoder;
}
/**
* Get the arguments
*
* @return arguments
*/
public String[] getArgs() {
return packetArgs;
}
/**
* Get the packet type
*
* @return packet type
*/
public int getID() {
return packetID;
}
/**
* Get the transaction ID for this packet
*
* @return transaction ID
*/
public int getTransID() {
return packetTransID;
}
/**
* Process packets from the wire. Each packet comprises a length, header and a body
* formatted as follows:
*
* LENGTH HEADER BODY
*
* where:
*
* LENGTH is an PACKET_LENGTH_SIZE hexadecimal number representing
* the total length of the HEADER and BODY sections.
*
* HEADER consists of the following fields:
*
* ' ' CMD_ID ':' TRANS_ID ':' NUM_ARGS
*
* where:
*
* CMD_ID is an PACKET_ID_SIZE hexadecimal number representing
* the type of this command.
* TRANS_ID is an PACKET_TRANS_ID_SIZE hexadecimal number representing
* the transaction ID of the command.
* NUM_ARGS is an PACKET_ARGS_LEN_SIZE hexadecimal number representing
* the number of arguments.
*
* The command body is formatted as a list of NUM_ARGS string arguments, each
* preceded by a space (0x20) characters as follows:
*
* ' ' LENGTH ':' BYTES ... ' ' LENGTH ':' BYTES
*
* where:
*
* LENGTH is an PACKET_ARGS_LEN_SIZE hexadecimal number representing
* the length of the string.
* BYTES are LENGTH bytes of the string. Any characters are permitted,
* including spaces
*
* @return false if a protocol error occurs
* @throws IOException if the connection is terminated (read returns < 0)
*
*/
public boolean read(ReadableByteChannel channel) throws IOException {
/*
* First EVENT_LENGTH_SIZE bytes are the length of the event
*/
ByteBuffer lengthBytes = ByteBuffer.allocate(PACKET_LENGTH_SIZE);
fullRead(channel, lengthBytes);
CharBuffer len_str = decoder.decode(lengthBytes);
if (debug) {
System.out.print("RECEIVE:[" + len_str); //$NON-NLS-1$
}
int len;
try {
len = Integer.parseInt(len_str.subSequence(0, PACKET_LENGTH_SIZE).toString(), 16);
} catch (NumberFormatException e) {
if (debug) {
System.out.println("] BAD PACKET LENGTH"); //$NON-NLS-1$
} else {
System.out.println("BAD PACKET LENGTH: \"" + len_str + "\""); //$NON-NLS-1$ //$NON-NLS-2$
}
throw new IOException(Messages.ProxyPacket_0);
}
/*
* Read len bytes of rest of packet
*/
ByteBuffer packetBytes = ByteBuffer.allocate(len);
fullRead(channel, packetBytes);
CharBuffer packetBuf = decoder.decode(packetBytes);
if (debug) {
System.out.println(packetBuf + "]"); //$NON-NLS-1$
}
/*
* Extract transaction ID and event type
*/
int idStart = 1; // Skip ' '
int idEnd = idStart + PACKET_ID_SIZE;
int transStart = idEnd + 1; // Skip ':'
int transEnd = transStart + PACKET_TRANS_ID_SIZE;
int numArgsStart = transEnd + 1; // Skip ':'
int numArgsEnd = numArgsStart + PACKET_NARGS_SIZE;
try {
packetID = Integer.parseInt(packetBuf.subSequence(idStart, idEnd).toString(), 16);
packetTransID = Integer.parseInt(packetBuf.subSequence(transStart, transEnd).toString(), 16);
int packetNumArgs = Integer.parseInt(packetBuf.subSequence(numArgsStart, numArgsEnd).toString(), 16);
/*
* Extract rest of the arguments. Each argument is an 8 byte hex length, ':' and
* then the characters of the argument.
*/
packetArgs = new String[packetNumArgs];
int argPos = numArgsEnd + 1;
for (int i = 0; i < packetNumArgs; i++) {
packetArgs[i] = ProtocolUtil.decodeString(packetBuf, argPos);
argPos += packetArgs[i].length() + PACKET_ARG_LEN_SIZE + 2;
}
} catch (NumberFormatException e) {
System.out.println("BAD PACKET FORMAT: \"" + packetBuf + "\""); //$NON-NLS-1$ //$NON-NLS-2$
throw new IOException(Messages.ProxyPacket_1);
} catch (IndexOutOfBoundsException e1) {
return false;
}
return true;
}
public void send(WritableByteChannel channel) throws IOException {
String body = ProtocolUtil.encodeIntVal(packetID, PACKET_ID_SIZE)
+ ":" + ProtocolUtil.encodeIntVal(packetTransID, PACKET_TRANS_ID_SIZE) //$NON-NLS-1$
+ ":" + ProtocolUtil.encodeIntVal(packetArgs.length, PACKET_ARG_LEN_SIZE); //$NON-NLS-1$
for (String arg : packetArgs) {
body += " " + ProtocolUtil.encodeString(arg); //$NON-NLS-1$
}
/*
* Note: command length includes the first space!
*/
String packet = ProtocolUtil.encodeIntVal(body.length() + 1,
PACKET_LENGTH_SIZE) + " " + body; //$NON-NLS-1$
if (debug) {
System.out.println("SEND:[" + packet + "]"); //$NON-NLS-1$ //$NON-NLS-2$
}
fullWrite(channel, encoder.encode(CharBuffer.wrap(packet)));
}
/**
* Enable/disable protocol debugging
*
* @param logging
*/
public void setDebug(boolean debug) {
this.debug = debug;
}
/**
* Read a full buffer from the socket. Guaranteed to read buf.remaining() bytes
* from the channel.
*
* FIXME: Can this block if there is nothing available on the channel? If so, then
* there should be some kind of timeout to prevent the UI from hanging.
*
* @throws IOException if EOF
*/
private void fullRead(ReadableByteChannel channel, ByteBuffer buf) throws IOException {
buf.clear();
while (buf.hasRemaining()) {
int n = channel.read(buf);
if (n < 0) {
throw new IOException(Messages.ProxyPacket_2);
}
}
buf.flip();
}
/**
* Write a full buffer to the socket. Guaranteed to write buf.remaingin() bytes to
* the channel.
*
* FIXME: Can this block? If so, then there should be some kind of timeout to prevent the UI from hanging.
*
* @param buf
* @throws IOException
*/
private void fullWrite(WritableByteChannel channel, ByteBuffer buf) throws IOException {
while (buf.hasRemaining()) {
int n = channel.write(buf);
if (n < 0) {
throw new IOException(Messages.ProxyPacket_3);
}
}
}
}