/*******************************************************************************
 * Copyright (c) 2000, 2006 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.jdi.internal.spy;


import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import com.ibm.icu.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;

/**
 * This class can be used to spy all JDWP packets. It should be configured 'in between' the debugger
 * application and the VM (or J9 debug proxy).
 * Its parameters are:
 *  1) The port number to which the debugger application connects;
 *  2) The name of the host on which the VM or proxy waits for a JDWP connection;
 *  3) The port number on which the VM or proxy waits for a JDWP connection;
 *  4) The file where the trace is written to.
 *
 * Note that if this program is used for tracing JDWP activity of Leapfrog, the
 * 'debug remote program' option must be used, and the J9 proxy must first be started up by hand
 * on the port to which Leapfrog will connect.
 * The J9 proxy that is started up by Leapfrog is not used and will return immediately.
 */
public class TcpipSpy extends Thread {

	private static final byte[] handshakeBytes= "JDWP-Handshake".getBytes(); //$NON-NLS-1$
	private boolean fVMtoDebugger;
	private DataInputStream fDataIn;
	private DataOutputStream fDataOut;

	private static VerbosePacketStream out= new VerbosePacketStream(System.out);
	private static Map fPackets= new HashMap();

	private static int fFieldIDSize;
	private static int fMethodIDSize;
	private static int fObjectIDSize;
	private static int fReferenceTypeIDSize;
	private static int fFrameIDSize;
	private static boolean fHasSizes;

	public TcpipSpy(boolean VMtoDebugger, InputStream in, OutputStream out) {
		fVMtoDebugger= VMtoDebugger;
		fDataIn= new DataInputStream(new BufferedInputStream(in));
		fDataOut= new DataOutputStream(new BufferedOutputStream(out));
		fHasSizes= false;
	}

	public static void main(String[] args) {
		int inPort= 0;
		String serverHost= null;
		int outPort= 0;
		String outputFile= null;
		try {
			inPort= Integer.parseInt(args[0]);
			serverHost= args[1];
			outPort= Integer.parseInt(args[2]);
			if (args.length > 3) {
				outputFile= args[3];
			}
		} catch (Exception e) {
			out.println(TcpIpSpyMessages.TcpipSpy_usage__TcpipSpy__client_port___server_host___server_port____output_file___1); 
			System.exit(-1);
		}

		if (outputFile != null) {
			File file= new File(outputFile);
			out.println(MessageFormat.format(TcpIpSpyMessages.TcpipSpy_Writing_output_to__0__2, new String[] {file.getAbsolutePath()})); 
			try {
				out= new VerbosePacketStream(new BufferedOutputStream(new FileOutputStream(file)));
			} catch (FileNotFoundException e) {
				out.println(MessageFormat.format(TcpIpSpyMessages.TcpipSpy_Could_not_open__0____Using_stdout_instead_3, new String[] {file.getAbsolutePath()})); 
			}
		}
		out.println();
		try {
			ServerSocket serverSock= new ServerSocket(inPort);
			Socket inSock= serverSock.accept();
			Socket outSock= new Socket(InetAddress.getByName(serverHost), outPort);
			new TcpipSpy(false, inSock.getInputStream(), outSock.getOutputStream()).start(); 
			new TcpipSpy(true, outSock.getInputStream(), inSock.getOutputStream()).start(); 
		} catch (Exception e) {
			out.println(e);
		}
	}

	public void run() {
		try {
			// Skip handshake.
			int handshakeLength;

			handshakeLength= handshakeBytes.length;
			while (handshakeLength-- > 0) {
				int b= fDataIn.read();
				fDataOut.write(b);
			}
			fDataOut.flush();

			// Print all packages.
			while (true) {
				JdwpPacket p= JdwpPacket.read(fDataIn);
				// we need to store conversation only for command send by the debugger,
				// as there is no answer from the debugger to VM commands.
				if (!(fVMtoDebugger && (p.getFlags() & JdwpPacket.FLAG_REPLY_PACKET) == 0)) {
					store(p);
				}
				out.print(p, fVMtoDebugger);
				out.flush();
				p.write(fDataOut);
				fDataOut.flush();
			}
		} catch (EOFException e) {
		} catch (SocketException e) {
		} catch (IOException e) {
			out.println(MessageFormat.format(TcpIpSpyMessages.TcpipSpy_Caught_exception___0__5, new String[] {e.toString()})); 
			e.printStackTrace(out);
		} finally {
			try {
				fDataIn.close();
				fDataOut.close();
			} catch (IOException e) {
			}
			out.flush();
		}
	}

	public static JdwpCommandPacket getCommand(int id) {
		JdwpConversation conversation= (JdwpConversation) fPackets.get(new Integer(id));
		if (conversation != null)
			return conversation.getCommand();
		return null;
	}

	protected static void store(JdwpPacket packet) {
		int id= packet.getId();
		JdwpConversation conversation= (JdwpConversation) fPackets.get(new Integer(id));
		if (conversation == null) {
			conversation= new JdwpConversation(id);
			fPackets.put(new Integer(id), conversation);
		}

		if ((packet.getFlags() & JdwpPacket.FLAG_REPLY_PACKET) != 0) {
			conversation.setReply((JdwpReplyPacket) packet);
		} else {
			conversation.setCommand((JdwpCommandPacket) packet);
		}
	}

	public static int getCommand(JdwpPacket packet) throws UnableToParseDataException {
		JdwpCommandPacket command= null;
		if (packet instanceof JdwpCommandPacket) {
			command= (JdwpCommandPacket) packet;
		} else {
			command= getCommand(packet.getId());
			if (command == null) {
				throw new UnableToParseDataException(TcpIpSpyMessages.TcpIpSpy_This_packet_is_marked_as_reply__but_there_is_no_command_with_the_same_id__1, null); 
			}
		}
		return command.getCommand();
	}

	public static boolean hasSizes() {
		return fHasSizes;
	}

	public static void setHasSizes(boolean value) {
		fHasSizes= value;
	}

	public static void setFieldIDSize(int fieldIDSize) {
		fFieldIDSize= fieldIDSize;
	}

	public static int getFieldIDSize() {
		return fFieldIDSize;
	}

	public static void setMethodIDSize(int methodIDSize) {
		fMethodIDSize= methodIDSize;
	}

	public static int getMethodIDSize() {
		return fMethodIDSize;
	}

	public static void setObjectIDSize(int objectIDSize) {
		fObjectIDSize= objectIDSize;
	}

	public static int getObjectIDSize() {
		return fObjectIDSize;
	}

	public static void setReferenceTypeIDSize(int referenceTypeIDSize) {
		fReferenceTypeIDSize= referenceTypeIDSize;
	}

	public static int getReferenceTypeIDSize() {
		return fReferenceTypeIDSize;
	}

	public static void setFrameIDSize(int frameIDSize) {
		fFrameIDSize= frameIDSize;
	}

	public static int getFrameIDSize() {
		return fFrameIDSize;
	}
}
