/************************************************************************** * Parts copyright (c) 2001 by Punch Telematix. All rights reserved. * * Parts copyright (c) 2009 by Chris Gray, /k/ Embedded Java Solutions. * * All rights reserved. * * * * Redistribution and use in source and binary forms, with or without * * modification, are permitted provided that the following conditions * * are met: * * 1. Redistributions of source code must retain the above copyright * * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * * notice, this list of conditions and the following disclaimer in the * * documentation and/or other materials provided with the distribution. * * 3. Neither the name of Punch Telematix or of /k/ Embedded Java Solutions* * nor the names of other contributors may be used to endorse or promote* * products derived from this software without specific prior written * * permission. * * * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED * * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * * IN NO EVENT SHALL PUNCH TELEMATIX, /K/ EMBEDDED JAVA SOLUTIONS OR OTHER * * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * **************************************************************************/ package wonka.rmi; import java.io.*; import java.net.*; import java.lang.reflect.Method; import java.rmi.*; import java.rmi.dgc.VMID; import java.rmi.server.*; import java.util.HashMap; import java.util.Hashtable; public class RMIConnection { private RMIConnection(){} /** integer indicating the debug level -1 = All ; the higher value the less debug you get. Values higher then 10 should not be used */ static final int DEBUG = 11; private static final byte[] headers = {0x4a, 0x52, 0x4d, 0x49, 0x00, 0x02, 0x4b}; private static final Class REMOTE_CLASS = java.rmi.Remote.class; /** the 1 and only VMID for this vm*/ static final VMID TheVMID = new VMID(); static final RMIPermission RMIPERMISSION = new RMIPermission(); static Hashtable exports = new Hashtable(3); static final Hashtable idData = new Hashtable(13); private native static long getStringHash(String name); private static long getMethodHash(Method method){ StringBuffer name = new StringBuffer(128); name.append(method.getName()); name.append('('); Class[] args = method.getParameterTypes(); for (int i=0 ; i < args.length ; i++){ name.append(class2String(args[i])); } name.append(")"); name.append(class2String(method.getReturnType())); long l = getStringHash(name.toString()); if(DEBUG < 5) {System.out.println("RMIConnection: METHOD NAME for hash '"+name+"' resulted in "+l);} return l; } private static String class2String(Class c){ String name; String prefix = ""; while(c.isArray()){ prefix = "[" + prefix; c = c.getComponentType(); } if(c.isPrimitive()){ if(c == Integer.TYPE){ name = "I"; } else if(c == Long.TYPE){ name = "J"; } else if(c == Double.TYPE){ name = "D"; } else if(c == Float.TYPE){ name = "F"; } else if(c == Short.TYPE){ name = "S"; } else if(c == Byte.TYPE){ name = "B"; } else if(c == Character.TYPE){ name = "C"; } else if(c == Boolean.TYPE){ name = "Z"; } else { name = "V"; } } else { name = "L"+c.getName()+";"; //the documentation says the name should be with dots but it only works with the slashes name = name.replace('.','/'); } return prefix + name; } /* ** public API needed by java.rmi.server.* classes */ public static String getClientHost() throws ServerNotActiveException { String name; synchronized(RMIRequestHandler.clientHostNames){ name = (String)RMIRequestHandler.clientHostNames.get(Thread.currentThread()); } if(name == null){ throw new ServerNotActiveException("current Thread is not handling any RMI requests"); } return name; } public static void setupTable(Hashtable map){ securityCheck(); exports = map; } public static void registerObjIDData(ObjID id, ObjIDData data) throws RemoteException { securityCheck(); Class[] interfaces = data.stub.getClass().getInterfaces(); int length = interfaces.length; if(length == 0){ throw new RemoteException("bad stub: no interfaces implemented"); } HashMap hashes2ID = new HashMap(13); boolean remoteOnly = true; for(int i = 0 ; i < length ; i++){ if(!REMOTE_CLASS.equals(interfaces[i])){ //all interface methods should be public ... remoteOnly = false; Method[] methods = interfaces[i].getMethods(); for(int k = 0 ; k < methods.length ; k++){ if(DEBUG < 5){System.out.println("method "+k+" = "+methods[k]);} hashes2ID.put(new Long(getMethodHash(methods[k])), methods[k]); } } } if(remoteOnly){ throw new RemoteException("bad stub: only implements the Remote interface"); } data.methods = hashes2ID; idData.put(id, data); } public static void deregisterObjIDData(ObjID id){ securityCheck(); idData.remove(id); } static void securityCheck(){ if (wonka.vm.SecurityConfiguration.ENABLE_SECURITY_CHECKS) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkPermission(RMIPERMISSION); } } } /* ** package protected methods contianing functions to handle the RMIStream protocol ... */ /** ** ask for an object to server (or void request if returns is false). This method uses the CALL scheme. */ static Object requestObject(Socket socket, long hash, int operation, ObjID id, Object arg, boolean returns) throws RemoteException { Object o = null; int rd = 0; try { handShake(socket); if(DEBUG < 6) {System.out.println("handshake complete");} OutputStream out = socket.getOutputStream(); out.write(RMIConstants.CALL); ObjectOutputStream oos = new RMIObjectOutputStream(new BufferedOutputStream(socket.getOutputStream())); id.write(oos); oos.writeInt(operation); oos.writeLong(hash); if(arg instanceof ParameterSet){ ((ParameterSet)arg).writeData(oos); } else { oos.writeObject(arg); } oos.flush(); if(DEBUG < 6) {System.out.println("wrote request");} InputStream in = socket.getInputStream(); rd = in.read(); if((rd & 0xff) != RMIConstants.RETURN_DATA){ if(DEBUG < 9) {System.out.println("Oops got :"+(rd & 0xff));} } if(DEBUG < 6) {System.out.println("reading request");} ObjectInputStream ois = new RMIObjectInputStream(new PushbackInputStream(in)); rd = ois.read(); if(DEBUG < 6) {System.out.println("read Header");} UID uid = UID.read(ois); if(DEBUG < 6) {System.out.println("UID = "+uid);} if(returns || rd == RMIConstants.EXCEPTION){ o = ois.readObject(); } else { return null; } } catch(Exception e){ if(DEBUG < 9) {System.out.println("CAUGTH EXCEPTION WHILE CONTACTING SERVER");} throw new RemoteException("oops",e); } if(DEBUG < 6) {System.out.println("verifying object");} return verifyObject(o, rd); } /** ** ask for a primitive result to the server and wraps it up (void not allowed). This method uses the CALL scheme. */ static Object requestPrimitive(Socket socket, long hash, int operation, ObjID id, Object arg, Class primitiveClass) throws RemoteException { Object o = null; int rd = 0; try { handShake(socket); OutputStream out = socket.getOutputStream(); out.write(RMIConstants.CALL); ObjectOutputStream oos = new RMIObjectOutputStream(socket.getOutputStream()); id.write(oos); oos.writeInt(operation); oos.writeLong(hash); if(arg instanceof ParameterSet){ ((ParameterSet)arg).writeData(oos); } else { oos.writeObject(arg); } oos.flush(); InputStream in = socket.getInputStream(); rd = in.read(); if((rd & 0xff) != RMIConstants.RETURN_DATA){ if(DEBUG < 8) {System.out.println("Oops got :"+(rd & 0xff));} } ObjectInputStream ois = new RMIObjectInputStream(new PushbackInputStream(in)); rd = ois.read(); if(DEBUG < 6) {System.out.println("type is "+(rd & 0xff));} UID uid = UID.read(ois); if(DEBUG < 6) {System.out.println("UID = "+uid);} if(rd == RMIConstants.EXCEPTION){ o = ois.readObject(); } else { return ParameterSet.readPrimitive(primitiveClass, ois); } } catch(Exception e){ if(DEBUG < 6) {System.out.println("CAUGTH EXCEPTION WHILE CONTACTING SERVER");} throw new RemoteException("oops",e); } return verifyObject(o, rd); } /** ** ping another VM for liveness */ static void pingVM(Socket socket) throws RemoteException { int rd = 0; try { handShake(socket); OutputStream out = socket.getOutputStream(); out.write(RMIConstants.PING); InputStream in = socket.getInputStream(); rd = in.read(); } catch(Exception e){ if(DEBUG < 9) {System.out.println("CAUGTH EXCEPTION WHILE CONTACTING SERVER");} throw new RemoteException("oops",e); } if((rd & 0xff) != RMIConstants.PING_ACK){ throw new RemoteException("bad response "+rd); } } /** ** implements the DGCAck scheme ... */ static Object sendDGCAck(Socket socket, UID uid) throws RemoteException { Object o = null; int rd = 0; try { handShake(socket); OutputStream out = socket.getOutputStream(); out.write(RMIConstants.DGC_ACK); uid.write(new DataOutputStream(out)); out.flush(); InputStream in = socket.getInputStream(); rd = in.read(); if((rd & 0xff) != RMIConstants.RETURN_DATA){ if(DEBUG < 8) {System.out.println("Oops got :"+(rd & 0xff));} } ObjectInputStream ois = new RMIObjectInputStream(new PushbackInputStream(in)); rd = ois.read(); uid = UID.read(ois); if(DEBUG < 6) {System.out.println("UID = "+uid);} o = ois.readObject(); } catch(Exception e){ if(DEBUG < 6) {System.out.println("CAUGTH EXCEPTION WHILE CONTACTING SERVER");} throw new RemoteException("oops",e); } return verifyObject(o, rd); } /** ** writes a void response to out ... */ static void writeVoidResponse(OutputStream out){ try { if(DEBUG < 8) {System.out.println("writing a void response to the stream");} out.write(RMIConstants.RETURN_DATA); ObjectOutputStream oout = new RMIObjectOutputStream(out); oout.write(RMIConstants.RETURN_VALUE); new UID().write(oout); oout.flush(); } catch(IOException ioe){ ioe.printStackTrace(); } } /** ** writes Object response to out ... */ static void writeResponse(OutputStream out, Object response){ try { out.write(RMIConstants.RETURN_DATA); ObjectOutputStream oout = new RMIObjectOutputStream(out); oout.write(RMIConstants.RETURN_VALUE); new UID().write(oout); oout.writeObject(response); oout.flush(); } catch(IOException ioe){ } } /** ** writes an Exception (with message as message) to out ... */ static void writeException(OutputStream out, String message) { try { out.write(RMIConstants.RETURN_DATA); ObjectOutputStream oout = new RMIObjectOutputStream(out); oout.write(RMIConstants.EXCEPTION); new UID().write(oout); oout.writeObject(new RemoteException(message)); oout.flush(); } catch(IOException ioe){ } } /** ** writes an RemoteException to out ... */ static void writeException(OutputStream out, RemoteException re) { try { out.write(RMIConstants.RETURN_DATA); ObjectOutputStream oout = new RMIObjectOutputStream(out); oout.write(RMIConstants.EXCEPTION); new UID().write(oout); oout.writeObject(re); oout.flush(); } catch(IOException ioe){ } } /** ** wrap an Exception into a RemoteException and then writes it to out ... */ static void wrapException(OutputStream out, Throwable t){ try { out.write(RMIConstants.RETURN_DATA); ObjectOutputStream oout = new RMIObjectOutputStream(out); oout.write(RMIConstants.EXCEPTION); new UID().write(oout); oout.writeObject(new RemoteException("RMI invocation fialed",t)); oout.flush(); } catch(IOException ioe){ } } /** ** initiates the serverside hand shake ... */ static void serverSideHandShake(Socket socket) throws IOException { InputStream in = socket.getInputStream(); byte[] bytes = new byte[7]; int len = in.read(bytes); if(len == -1){ throw new IOException("no data available"); } while(len < 7){ int rd = in.read(bytes,len, 7-len); if(rd == - 1){ throw new IOException("header is to short"); } len += rd; } if(DEBUG < 5) {System.out.println("RESPONSE IS '"+new String(bytes,0,len)+"'");} if(!java.util.Arrays.equals(bytes, headers)){ throw new RemoteException("invalid header"); } DataOutputStream out = new DataOutputStream(socket.getOutputStream()); out.write(RMIConstants.PROTOCOL_ACK); out.writeUTF(socket.getInetAddress().getHostAddress()); out.writeInt(socket.getPort()); } /** ** writes the RMI headers to the stream and handles the client side handshake */ static void handShake(Socket socket) throws IOException { OutputStream out = socket.getOutputStream(); out.write(headers); DataInputStream in = new DataInputStream(socket.getInputStream()); int answer = in.read(); if(answer != RMIConstants.PROTOCOL_ACK){ throw new IOException("GAME OVER"); } /** the Server responds by sending the address and the port of the socket connected to the server */ String address = in.readUTF(); if(DEBUG < 6) {System.out.println("address = '"+address+"'");} int port = in.readInt(); if(DEBUG < 6) {System.out.println("port = '"+port+"'"); } /** we don't check this information, the only thing we could verify is the port */ DataOutputStream sout = new DataOutputStream(out); sout.writeUTF(socket.getInetAddress().getHostAddress()); sout.writeInt(socket.getPort()); sout.flush(); } /** ** verifies the server response ... */ private static Object verifyObject(Object o, int type) throws RemoteException { if(type == RMIConstants.RETURN_VALUE){ return o; } else if(type == RMIConstants.EXCEPTION && o instanceof Throwable){ //check if correct exception is thrown if(o instanceof RemoteException){ if(DEBUG < 9) {System.out.println("REPLY IS A REMOTEEXCEPTION");} throw (RemoteException)o; } if(o instanceof RuntimeException){ if(DEBUG < 9) {System.out.println("REPLY IS A RUNTIMEEXCEPTION");} throw (RuntimeException)o; } if(o instanceof Exception){ if(DEBUG < 9) {System.out.println("REPLY IS AN ERROR");} throw new UnexpectedException("Unexpected server Exception",(Exception)o); } if(o instanceof Error){ if(DEBUG < 9) {System.out.println("REPLY IS AN ERROR");} throw new ServerError("Unexpected server Error",(Error)o); } throw new RemoteException("UnexpectedExeption",(Throwable)o); } throw new RemoteException("invalid response"); } }