/*--- formatted by Jindent 2.1, (www.c-lab.de/~jindent) ---*/ /** * *************************************************************** * The LEAP libraries, when combined with certain JADE platform components, * provide a run-time environment for enabling FIPA agents to execute on * lightweight devices running Java. LEAP and JADE teams have jointly * designed the API for ease of integration and hence to take advantage * of these dual developments and extensions so that users only see * one development platform and a * single homogeneous set of APIs. Enabling deployment to a wide range of * devices whilst still having access to the full development * environment and functionalities that JADE provides. * Copyright (C) 2001 Telecom Italia LAB S.p.A. * Copyright (C) 2001 Motorola. * * GNU Lesser General Public License * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation, * version 2.1 of the License. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * ************************************************************** */ package jade.imtp.leap.JICP; //#MIDP_EXCLUDE_FILE import jade.core.Profile; import jade.imtp.leap.*; import jade.security.JADESecurityException; import jade.util.Logger; import jade.util.leap.Properties; import java.io.EOFException; import java.io.IOException; import java.io.InterruptedIOException; import java.net.*; import java.util.Enumeration; import java.util.Hashtable; import java.util.Vector; /** * Class declaration * @author Giovanni Caire - TILAB * @author Ronnie Taib - Motorola * @author Nicolas Lhuillier - Motorola * @author Steffen Rusitschka - Siemens */ public class JICPServer extends Thread //#J2ME_EXCLUDE_BEGIN implements PDPContextManager.Listener, JICPMediatorManager //#J2ME_EXCLUDE_END { private static final int INIT = 0; private static final int REQUEST_READ = 1; private static final int REQUEST_SERVED = 2; private static final int RESPONSE_SENT = 3; public static final String ACCEPT_LOCAL_HOST_ONLY = "jade_imtp_leap_JICP_JICPServer_acceptlocalhostonly"; public static final String UNCHECK_LOCAL_HOST = "jade_imtp_leap_JICP_JICPServer_unchecklocalhost"; private static final int LISTENING = 0; private static final int TERMINATING = 1; private int state = LISTENING; private String host; private ServerSocket server; private ICP.Listener cmdListener; private int mediatorCnt = 1; private Hashtable mediators = new Hashtable(); //#J2ME_EXCLUDE_BEGIN public static final String ACCEPT_MEDIATORS = "jade_imtp_leap_JICP_JICPServer_acceptmediators"; private boolean acceptMediators = true; private Properties leapProps = new Properties(); private PDPContextManager myPDPContextManager; //#J2ME_EXCLUDE_END private int maxHandlers; private Vector connectionHandlers; private ConnectionFactory connFactory; private Logger myLogger; /** * Constructor declaration */ public JICPServer(Profile p, JICPPeer myPeer, ICP.Listener l, ConnectionFactory f, int max) throws ICPException { connectionHandlers = new Vector(); cmdListener = l; connFactory = f; maxHandlers = max; myLogger = Logger.getMyLogger(getClass().getName()); StringBuffer sb = null; int idLength; String peerID = myPeer.getID(); if (peerID != null) { sb = new StringBuffer(peerID); sb.append('-'); idLength = sb.length(); } else { sb = new StringBuffer(); idLength = 0; } // Local host sb.append(JICPProtocol.LOCAL_HOST_KEY); host = p.getParameter(sb.toString(), null); boolean acceptLocalHostOnly = false; if (host == null || host.equals(Profile.LOCALHOST_CONSTANT)) { // Local host not specified --> Get it automatically sb.setLength(idLength); sb.append(JICPProtocol.REMOTE_URL_KEY); String remoteURL = p.getParameter(sb.toString(), null); if (remoteURL != null) { // Retrieve the local host address by means of the GET_ADDRESS JICP functionality host = myPeer.getAddress(remoteURL); } else { // Retrieve the host address/name from the underlying operating system host = Profile.getDefaultNetworkName(); } } else { // Unless the UNCKECK_LOCAL_HOST property is set, if a local-host is explicitly specified check // that it is a valid local address if (!p.getBooleanProperty(UNCHECK_LOCAL_HOST, false) && !Profile.isLocalHost(host)) { throw new ICPException("Error: Not possible to launch JADE on a remote host ("+host+"). Check the -host and -local-host options."); } // Then if the ACCEPT_LOCAL_HOST_ONLY property is specified, // we will accept connections only on the specified local network address acceptLocalHostOnly = p.getBooleanProperty(ACCEPT_LOCAL_HOST_ONLY, false); } // Local port: a peripheral container can change it if busy... int port = JICPProtocol.DEFAULT_PORT; boolean changePortIfBusy = !p.getBooleanProperty(Profile.MAIN, true) || p.getBooleanProperty(LEAPIMTPManager.CHANGE_PORT_IF_BUSY, false); sb.setLength(idLength); sb.append(JICPProtocol.LOCAL_PORT_KEY); String strPort = p.getParameter(sb.toString(), null); try { port = Integer.parseInt(strPort); } catch (Exception e) { // Try to use the Peer-ID as the port number try { port = Integer.parseInt(peerID); } catch (Exception e1) { // Keep default } } //#J2ME_EXCLUDE_BEGIN // Get the accept-mediators option acceptMediators = p.getBooleanProperty(ACCEPT_MEDIATORS, true); if (acceptMediators) { // Read the LEAP configuration properties sb.setLength(idLength); sb.append(LEAP_PROPERTY_FILE); String fileName = p.getParameter(sb.toString(), LEAP_PROPERTY_FILE_DEFAULT); try { leapProps.load(fileName); } catch (Exception e) { myLogger.log(Logger.FINE, "Can't read LEAP property file " + fileName + ". " + e); // Ignore: no back end properties specified } // Initialize the PDPContextManager if specified String pdpContextManagerClass = leapProps.getProperty(PDP_CONTEXT_MANAGER_CLASS); if (pdpContextManagerClass != null) { try { myLogger.log(Logger.INFO, "Loading PDPContextManager of class " + pdpContextManagerClass); myPDPContextManager = (PDPContextManager) Class.forName(pdpContextManagerClass).newInstance(); myPDPContextManager.init(leapProps); myPDPContextManager.registerListener(this); } catch (Throwable t) { t.printStackTrace(); myPDPContextManager = null; } } } //#J2ME_EXCLUDE_END // Create the ServerSocket. server = myPeer.getServerSocket((acceptLocalHostOnly ? host : null), port, changePortIfBusy); setDaemon(true); setName("JICPServer-" + getLocalPort()); } public int getLocalPort() { return server.getLocalPort(); } public String getLocalHost() { // If a local-host was not specified, we accept connection on all local network addresses, // but we expose the local host address we "prefer". return host; } /** Shut down this JICP server */ public synchronized void shutdown() { if(myLogger.isLoggable(Logger.FINE)) myLogger.log(Logger.FINE,"Shutting down JICPServer..."); state = TERMINATING; try { // Force the listening thread (this) to exit from the accept() // Calling this.interrupt(); should be the right way, but it seems // not to work...so do that by closing the server socket. server.close(); // Wait for the listening thread to complete this.join(); } catch (IOException ioe) { ioe.printStackTrace(); } catch (InterruptedException ie) { ie.printStackTrace(); } } /** * JICPServer thread entry point. Accept incoming connections * and for each of them start a ConnectionHandler that handles it. */ public void run() { while (state != TERMINATING) { try { // Accept connection Socket s = server.accept(); InetAddress addr = s.getInetAddress(); int port = s.getPort(); if(myLogger.isLoggable(Logger.FINEST)) myLogger.log(Logger.FINEST,"Incoming connection from "+addr+":"+port); Connection c = connFactory.createConnection(s); ConnectionHandler ch = new ConnectionHandler(c, addr, port); if(myLogger.isLoggable(Logger.FINEST)) myLogger.log(Logger.FINEST,"Create new ConnectionHandler ("+ch+")"); connectionHandlers.addElement(ch); ch.start(); // start a handler and go back to listening } catch (InterruptedIOException e) { // These can be generated by socket timeout (just ignore // the exception) or by a call to the shutdown() // method (the state has been set to TERMINATING and the // server will exit). } catch (Exception e) { if (state == LISTENING) { if(myLogger.isLoggable(Logger.WARNING)) myLogger.log(Logger.WARNING,"Problems accepting a new connection"); e.printStackTrace(); // Stop listening state = TERMINATING; } } } // END of while(listen) if(myLogger.isLoggable(Logger.FINE)) myLogger.log(Logger.FINE,"JICPServer terminated"); // release socket try { server.close(); } catch (IOException io) { if(myLogger.isLoggable(Logger.WARNING)) myLogger.log(Logger.WARNING,"I/O error closing the server socket"); io.printStackTrace(); } server = null; // Close all connection handler synchronized (connectionHandlers) { ConnectionHandler ch; Enumeration en = connectionHandlers.elements(); while(en.hasMoreElements()) { ch = (ConnectionHandler) en.nextElement(); ch.close(); } } //#J2ME_EXCLUDE_BEGIN // Close all mediators Enumeration e = mediators.elements(); while (e.hasMoreElements()) { JICPMediator m = (JICPMediator) e.nextElement(); m.kill(); } mediators.clear(); //#J2ME_EXCLUDE_END } /** Called by the JICPPeer ticker at each tick */ public void tick(long currentTime) { //#J2ME_EXCLUDE_BEGIN synchronized (mediators) { Enumeration e = mediators.elements(); while (e.hasMoreElements()) { JICPMediator m = (JICPMediator) e.nextElement(); m.tick(currentTime); } } //#J2ME_EXCLUDE_END } /** Inner class ConnectionHandler. Handle a connection accepted by this JICPServer */ class ConnectionHandler extends Thread { private Connection c; private InetAddress addr; private int port; private boolean loop = false; private int status = INIT; private boolean closeConnection = true; /** * Constructor declaration * @param s */ public ConnectionHandler(Connection c, InetAddress addr, int port) { this.c = c; this.addr = addr; this.port = port; } /** * close connection handler */ public void close() { if (status != RESPONSE_SENT) { // We are serving a request --> Prepare to close connection handler loop = false; closeConnection = true; if(myLogger.isLoggable(Logger.FINEST)) myLogger.log(Logger.FINEST,"Predispose to close connection handler ("+this+")"); } else { // We are waiting for the next request --> Close connection to force connection handler termination try { if(myLogger.isLoggable(Logger.FINEST)) myLogger.log(Logger.FINEST,"Close connection socket to force exit from connection handler ("+this+")"); c.close(); } catch (IOException e) { if(myLogger.isLoggable(Logger.FINEST)) myLogger.log(Logger.FINEST,"Exception closing connection with "+addr+":"+port); } } } /** * Thread entry point */ public void run() { if(myLogger.isLoggable(Logger.FINEST)) myLogger.log(Logger.FINEST,"CommandHandler started"); byte type = (byte) 0; try { do { // Read the incoming JICPPacket JICPPacket pkt = c.readPacket(); JICPPacket reply = null; status = REQUEST_READ; type = pkt.getType(); switch (type) { case JICPProtocol.COMMAND_TYPE: case JICPProtocol.RESPONSE_TYPE: // Get the right recipient and let it process the command. String recipientID = pkt.getRecipientID(); if(myLogger.isLoggable(Logger.FINEST)) myLogger.log(Logger.FINEST,"Recipient: "+recipientID); if (recipientID != null) { //#J2ME_EXCLUDE_BEGIN // The recipient is one of the mediators JICPMediator m = (JICPMediator) mediators.get(recipientID); if (m != null) { if(myLogger.isLoggable(Logger.FINEST)) myLogger.log(Logger.FINEST,"Passing incoming packet to mediator "+recipientID); reply = m.handleJICPPacket(pkt, addr, port); } else { // If the packet is a response we don't need to reply if (type == JICPProtocol.COMMAND_TYPE) { reply = new JICPPacket("Unknown recipient "+recipientID, null); } } //#J2ME_EXCLUDE_END } else { // The recipient is my ICP.Listener (the local CommandDispatcher) loop = true; if (type == JICPProtocol.COMMAND_TYPE) { if(myLogger.isLoggable(Logger.FINEST)) myLogger.log(Logger.FINEST,"Passing incoming COMMAND to local listener"); byte[] rsp = cmdListener.handleCommand(pkt.getData()); byte dataInfo = JICPProtocol.DEFAULT_INFO; if (connectionHandlers.size() >= maxHandlers) { // Too many connections open --> close the connection as soon as the command has been served dataInfo |= JICPProtocol.TERMINATED_INFO; loop = false; } reply = new JICPPacket(JICPProtocol.RESPONSE_TYPE, dataInfo, rsp); } if ((pkt.getInfo() & JICPProtocol.TERMINATED_INFO) != 0) { loop = false; } } break; //#J2ME_EXCLUDE_BEGIN case JICPProtocol.GET_ADDRESS_TYPE: // Respond sending back the caller address if(myLogger.isLoggable(Logger.INFO)) myLogger.log(Logger.INFO,"Received a GET_ADDRESS request from "+addr+":"+port); reply = new JICPPacket(JICPProtocol.GET_ADDRESS_TYPE, JICPProtocol.DEFAULT_INFO, addr.getHostAddress().getBytes()); break; case JICPProtocol.CREATE_MEDIATOR_TYPE: if (acceptMediators) { if(myLogger.isLoggable(Logger.INFO)) myLogger.log(Logger.INFO,"Received a CREATE_MEDIATOR request from "+ addr + ":" + port); // Starts a new Mediator and sends back its ID String s = new String(pkt.getData()); Properties p = FrontEndStub.parseCreateMediatorRequest(s); // If the platform-name is specified refuse the request: JICPServer does not accept this kind of mediator creation request String pn = p.getProperty(Profile.PLATFORM_ID); if (pn != null) { myLogger.log(Logger.WARNING, "CREATE_MEDIATOR request with specified platform-name: "+pn); reply = new JICPPacket(JICPProtocol.NOT_AUTHORIZED_ERROR, new JADESecurityException("Platform-name specified")); break; } // If there is a PDPContextManager add the PDP context properties if (myPDPContextManager != null) { try{ //FIXME: Get username and password specified among the CREATE_MEDIATOR properties Properties pdpContextInfo = myPDPContextManager.getPDPContextInfo(addr, p.getProperty(JICPProtocol.OWNER_KEY)); myLogger.log(Logger.FINE, "PDPContext properties = "+pdpContextInfo); mergeProperties(p, pdpContextInfo); }catch(JADESecurityException jse){ if(myLogger.isLoggable(Logger.WARNING)) myLogger.log(Logger.WARNING,"CREATE_MEDIATOR request from non authorized address: "+addr); reply = new JICPPacket(JICPProtocol.NOT_AUTHORIZED_ERROR, jse); break; } } // Get mediator ID from the passed properties (if present) String id = p.getProperty(JICPProtocol.MEDIATOR_ID_KEY); String msisdn = p.getProperty(PDPContextManager.MSISDN); if(id != null) { if (msisdn != null && !msisdn.equals(id)) { // Security attack: Someone is pretending to be someone other if(myLogger.isLoggable(Logger.WARNING)) myLogger.log(Logger.WARNING,"CREATE_MEDIATOR request with mediator-id != MSISDN. Address is: "+addr); reply = new JICPPacket(JICPProtocol.NOT_AUTHORIZED_ERROR, new JADESecurityException("Inconsistent mediator-id and msisdn")); break; } // An existing front-end whose back-end was lost. The BackEnd must resynch p.setProperty(jade.core.BackEndContainer.RESYNCH, "true"); } else { // Use the MSISDN (if present) id = msisdn; if (id == null) { // Construct a default id using the string representation of the server's TCP endpoint id = "BE-"+getLocalHost() + ':' + getLocalPort() + '-' + String.valueOf(mediatorCnt++); } } // If last connection from the same device aborted, the old // BackEnd may still exist as a zombie. In case ids are assigned // using the MSISDN the new name is equals to the old one. if (id.equals(msisdn)) { JICPMediator old = (JICPMediator) mediators.get(id); if (old != null) { // This is a zombie mediator --> kill it myLogger.log(Logger.INFO, "Replacing old mediator "+id); old.kill(); // Be sure the zombie container has been removed waitABit(1000); } } // Start the mediator JICPMediator m = startMediator(id, p); closeConnection = !m.handleIncomingConnection(c, pkt, addr, port); mediators.put(m.getID(), m); // Create an ad-hoc reply including the assigned mediator-id and the IP address p.setProperty(JICPProtocol.MEDIATOR_ID_KEY, m.getID()); p.setProperty(JICPProtocol.LOCAL_HOST_KEY, addr.getHostAddress()); String replyMsg = FrontEndStub.encodeCreateMediatorResponse(p); reply = new JICPPacket(JICPProtocol.RESPONSE_TYPE, JICPProtocol.DEFAULT_INFO, replyMsg.getBytes()); reply.setSessionID((byte) 31); // Dummy session ID != from valid ones } else { myLogger.log(Logger.WARNING,"CREATE_MEDIATOR request received with accept-mediator option set to false. Address is: "+addr); reply = new JICPPacket(JICPProtocol.NOT_AUTHORIZED_ERROR, null); } break; case JICPProtocol.CONNECT_MEDIATOR_TYPE: if (acceptMediators) { // A mediated container is (re)connecting to its mediator recipientID = pkt.getRecipientID(); // FIXME: If there is a PDPContextManager check that the recipientID is the MSISDN if(myLogger.isLoggable(Logger.INFO)) myLogger.log(Logger.INFO,"Received a CONNECT_MEDIATOR request from "+addr+":"+port+". Mediator ID is "+recipientID); JICPMediator m = (JICPMediator) mediators.get(recipientID); if (m != null) { // Don't close the connection, but pass it to the proper // mediator. closeConnection = !m.handleIncomingConnection(c, pkt, addr, port); reply = new JICPPacket(JICPProtocol.RESPONSE_TYPE, JICPProtocol.DEFAULT_INFO, addr.getHostAddress().getBytes()); } else { if(myLogger.isLoggable(Logger.INFO)) myLogger.log(Logger.INFO,"Mediator "+recipientID+" not found"); reply = new JICPPacket(JICPProtocol.NOT_FOUND_ERROR, null); } } else { myLogger.log(Logger.WARNING,"CONNECT_MEDIATOR request received with accept-mediator option set to false. Address is: "+addr); reply = new JICPPacket(JICPProtocol.NOT_AUTHORIZED_ERROR, null); } break; //#J2ME_EXCLUDE_END default: // Send back an error response if(myLogger.isLoggable(Logger.WARNING)) myLogger.log(Logger.WARNING,"Uncorrect JICP data type: "+pkt.getType()); reply = new JICPPacket("Uncorrect JICP data type: "+pkt.getType(), null); } status = REQUEST_SERVED; // Send the actual response data if (reply != null) { //reply.writeTo(out); c.writePacket(reply); } status = RESPONSE_SENT; } while (loop); } catch (Exception e) { switch (status) { case INIT:{ if(myLogger.isLoggable(Logger.SEVERE)) myLogger.log(Logger.SEVERE,"Communication error reading incoming packet from "+addr+":"+port); e.printStackTrace(); } break; case REQUEST_READ: if(myLogger.isLoggable(Logger.SEVERE)) myLogger.log(Logger.SEVERE,"Error handling incoming packet"); e.printStackTrace(); // If the incoming packet was a command, try // to send back a generic error response if (type == JICPProtocol.COMMAND_TYPE && c != null) { try { c.writePacket(new JICPPacket("Unexpected error", e)); } catch (IOException ioe) { // Just print a warning if(myLogger.isLoggable(Logger.WARNING)) myLogger.log(Logger.WARNING,"Can't send back error indication "+ioe); } } break; case REQUEST_SERVED: if(myLogger.isLoggable(Logger.SEVERE)) myLogger.log(Logger.SEVERE,"Communication error writing return packet to "+addr+":"+port+" ["+e.toString()+"]"); break; case RESPONSE_SENT: // This is a re-used connection waiting for the next incoming packet if (e instanceof EOFException) { if(myLogger.isLoggable(Logger.FINE)) myLogger.log(Logger.FINE,"Client "+addr+":"+port+" has closed the connection."); } else { if(myLogger.isLoggable(Logger.FINE)) myLogger.log(Logger.FINE,"Unexpected client "+addr+":"+port+" termination. "+e.toString()); } } } finally { try { if (closeConnection) { // Close connection if(myLogger.isLoggable(Logger.FINEST)) myLogger.log(Logger.FINEST,"Closing connection with "+addr+":"+port); c.close(); } } catch (IOException io) { if(myLogger.isLoggable(Logger.INFO)) myLogger.log(Logger.INFO,"I/O error while closing the connection"); io.printStackTrace(); } connectionHandlers.remove(this); if(myLogger.isLoggable(Logger.FINEST)) myLogger.log(Logger.FINEST,"ConnectionHandler closed ("+this+")"); } } } // END of inner class ConnectionHandler //#J2ME_EXCLUDE_BEGIN /** * Called by a Mediator to notify that it is no longer active */ public void deregisterMediator(String id) { myLogger.log(Logger.FINE, "Deregistering mediator "+id); mediators.remove(id); } /** Called by the PDPContextManager (if any) */ public void handlePDPContextClosed(String id) { // FIXME: to be implemented } private void mergeProperties(Properties p1, Properties p2) { Enumeration e = p2.propertyNames(); while (e.hasMoreElements()) { String key = (String) e.nextElement(); p1.setProperty(key, p2.getProperty(key)); } } private JICPMediator startMediator(String id, Properties p) throws Exception { String className = p.getProperty(JICPProtocol.MEDIATOR_CLASS_KEY); if (className != null) { JICPMediator m = (JICPMediator) Class.forName(className).newInstance(); mergeProperties(p, leapProps); myLogger.log(Logger.FINE, "Initializing mediator "+id+" with properties "+p); m.init(this, id, p); return m; } else { throw new ICPException("No JICPMediator class specified."); } } private void waitABit(long t) { try { Thread.sleep(t); } catch (InterruptedException ie) { } } //#J2ME_EXCLUDE_END }