package lejos.pc.tools; import java.io.*; import java.net.*; import js.tinyvm.TinyVMException; import org.apache.commons.cli.CommandLine; import org.apache.commons.cli.GnuParser; import org.apache.commons.cli.HelpFormatter; import org.apache.commons.cli.Option; import org.apache.commons.cli.Options; import org.apache.commons.cli.ParseException; import lejos.pc.comm.*; /** * Socket Proxy for NXT * Has two main functions. The first is to connect to an existing * server socket on the specified port. The second function is to * create a socket server. In this case the proxy assumes that the * NXT will then send a command to inform the proxy of the next action * to take * * Currently only supports TCP connections * * @author Ranulf Green and Lawrie Griffiths */ public class SocketProxy { private String host; private int port; private DataInputStream inFromNXT; private DataOutputStream outToNXT; private ServerSocket serverSocket; private Socket sock; /** * Run the Socket Proxy. * An instance of SocketProxy will allow for transparent forwarding * of messages between server and NXT using a socket connection */ public void run(String[] args) throws TinyVMException { try { int protocols = 0; ProxyCommandLineParser fParser = new ProxyCommandLineParser(); CommandLine commandLine = fParser.parse(args); boolean blueTooth = commandLine.hasOption("b"); boolean usb = commandLine.hasOption("u"); String name = commandLine.getOptionValue("n"); String address = commandLine.getOptionValue("d"); NXTConnector conn = new NXTConnector(); conn.addLogListener(new ToolsLogger()); if (blueTooth) protocols |= NXTCommFactory.BLUETOOTH; if (usb) protocols |= NXTCommFactory.USB; if (protocols == 0) protocols = NXTCommFactory.ALL_PROTOCOLS; boolean connected = conn.connectTo(name, address, protocols); if (!connected) { System.err.println("Failed to connect to NXT"); System.exit(1); } inFromNXT = conn.getDataIn(); outToNXT = conn.getDataOut(); // Check to see if socket is a server or a client boolean isServer = inFromNXT.readBoolean(); if (isServer) { newSocketServer(); } else { newSocketConnection(); } } catch (UnknownHostException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} } /** * Creates a new socket server if instructed by the NXT * @throws IOException */ private void newSocketServer() throws IOException { int port = inFromNXT.readInt(); System.out.println("Waiting on " + port); serverSocket = new ServerSocket(port); while (true) { // Wait for command from NXT byte command = inFromNXT.readByte(); if(command == 1){ waitForConnection(); } } } /** * Allows negotiation of the accept() method of Socket server * Executes a single accept and waits until the Socket is closed * * @throws IOException */ private void waitForConnection() throws IOException { sock = serverSocket.accept(); //System.out.println("Accepted"); // inform the NXT of the new Connection outToNXT.writeBoolean(true); outToNXT.flush(); DataInputStream inFromSocket = new DataInputStream(sock.getInputStream()); DataOutputStream outToSocket = new DataOutputStream(sock.getOutputStream()); // Listen for incoming data from socket new Forward(sock, inFromSocket, outToNXT); // Listen for incoming data from NXT new ForwardNXT(sock, inFromNXT, outToSocket); // Wait for socket to close while (!sock.isClosed()) Thread.yield(); } /** * Allows for a connection to be made using the details supplied from the NXT * @throws UnknownHostException * @throws IOException */ private void newSocketConnection() throws UnknownHostException, IOException { // The first byte from the NXT contains the length of the host name in chars int len = inFromNXT.readByte(); char[] hostChars = new char[len]; // Following the first byte the host name is transmitted for(int i=0;i<len;i++){ hostChars[i] = inFromNXT.readChar(); } // Following the host name an int containing the port number of the socket to connect to // is transmitted port = inFromNXT.readInt(); host = new String(hostChars); System.out.println("Host: " + host + " port: " + port); // Create a socket connection with the specified host using the specified port sock = new Socket(host, port); outToNXT.writeBoolean(true); outToNXT.flush(); DataInputStream inFromSocket = new DataInputStream(sock.getInputStream()); DataOutputStream outToSocket = new DataOutputStream(sock.getOutputStream()); // Listen for incoming data from socket new Forward(sock, inFromSocket, outToNXT); // Listen for incoming data from NXT new ForwardNXT(sock, inFromNXT, outToSocket); } /** * Allows for the forwarding of messages from Socket to NXT * @author Ranulf Green */ private class Forward extends Thread{ private DataOutputStream dout; private DataInputStream din; private Socket sock; /** * Constructor. * @param sock the socket with which the connection is made * @param dis the input stream to read * @param dos the output stream to forward to */ public Forward(Socket sock, DataInputStream dis, DataOutputStream dos){ super(); din=dis; dout=dos; this.sock = sock; start(); } /** * Causes a new thread to be invoked */ public void run(){ try{ while(true){ int in = din.read(); if(in<0){ sock.close(); return; } dout.writeByte(in); dout.flush(); } } catch (IOException ioe) {} } } /** * Class to forward messages from NXT to socket * @author Ranulf Green * */ private class ForwardNXT extends Thread { private static final byte ESCAPE = (byte) 0xFF; private static final byte ESCAPE_CLOSE = 1; private DataOutputStream dout; private DataInputStream din; private Socket sock; /** * Constructor. * @param sock * @param dis input stream from NXT * @param dos output stream to socket */ public ForwardNXT(Socket sock, DataInputStream dis, DataOutputStream dos){ super(); din=dis; dout=dos; this.sock = sock; start(); } /** * Causes a new thread to be invoked */ public void run() { try { while(true) { int in = din.read(); if (in < 0) { sock.close(); return; } // Process ESCAPE sequence if ((byte) in == ESCAPE) { in = din.read(); if ((byte) in == ESCAPE_CLOSE) { sock.close(); return; } else in = ESCAPE; } dout.writeByte(in); dout.flush(); } } catch (IOException ioe) {ioe.printStackTrace();} } } public static void main(String[] args) { try { (new SocketProxy()).run(args); } catch (Throwable t) { System.err.println("An error has occurred: " + t.getMessage()); } } /** * CommandLineParser */ class ProxyCommandLineParser { /** * Parse commandline. * * @param args command line * @throws TinyVMException */ public CommandLine parse (String[] args) throws TinyVMException { assert args != null: "Precondition: args != null"; Options options = new Options(); options.addOption("h", "help", false, "help"); options.addOption("b", "bluetooth", false, "use bluetooth"); options.addOption("u", "usb", false, "use usb"); Option nameOption = new Option("n", "name", true,"look for named NXT"); nameOption.setArgName("name"); options.addOption(nameOption); Option addressOption = new Option("d", "address", true, "look for NXT with given address"); addressOption.setArgName("address"); options.addOption(addressOption); CommandLine result; try { try { result = new GnuParser().parse(options, args); } catch (ParseException e) { throw new TinyVMException(e.getMessage(), e); } if (result.hasOption("h")) { throw new TinyVMException("Help:"); } } catch (TinyVMException e) { StringWriter writer = new StringWriter(); PrintWriter printWriter = new PrintWriter(writer); printWriter.println(e.getMessage()); String commandName = System.getProperty("COMMAND_NAME", "lejos.pc.tools.SocketProxy"); String usage = commandName + " [options]"; new HelpFormatter().printHelp(printWriter, 80, usage.toString(), null, options, 0, 2, null); throw new TinyVMException(writer.toString()); } assert result != null: "Postconditon: result != null"; return result; } } }