package jade.core; import jade.util.Logger; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.DatagramPacket; import java.net.InetAddress; import java.net.MulticastSocket; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.StringTokenizer; //#J2ME_EXCLUDE_FILE class MainDetectionManager { private final static Logger logger = Logger.getMyLogger(MainDetectionManager.class.getName()); /* * configuration options with defaults */ private final static String OPT_MCAST_ADDR = "jade_core_MainDetectionManager_mcastaddr"; private final static String OPT_MCAST_ADDR_DEFAULT = "239.255.10.99"; private final static String OPT_MCAST_PORT = "jade_core_MainDetectionManager_mcastport"; private final static String OPT_MCAST_PORT_DEFAULT = "1199"; private final static String OPT_MCAST_TTL = "jade_core_MainDetectionManager_mcastttl"; private final static String OPT_MCAST_TTL_DEFAULT = "4"; private final static String OPT_MCAST_FIRST_TIMEOUT = "jade_core_MainDetectionManager_mcastfirsttimeout"; private final static String OPT_MCAST_FIRST_TIMEOUT_DEFAULT = "500"; private final static String OPT_MCAST_TIMEOUT = "jade_core_MainDetectionManager_mcasttimeout"; private final static String OPT_MCAST_TIMEOUT_DEFAULT = "2500"; private final static String OPT_MCAST_RETRIES = "jade_core_MainDetectionManager_mcastretries"; private final static String OPT_MCAST_RETRIES_DEFAULT = "3"; /* * protocol data */ // version string public final static String PROTO_VERSION = " MJADE/1.0"; // charset used to encode/decode packets public final static String PROTO_ENCODING = "ISO-8859-1"; // protocol commands public final static String PROTO_CMD_GETMAIN = "get-main"; public final static String PROTO_CMD_PING = "ping"; // constants for get-main command public final static String PROTO_ADDRESSES_SEPARATOR = ";"; public final static String PROTO_ADDR_SEPARATOR = ":"; // protocol responses public final static String PROTO_RESP_OK = "200 OK "; public final static String PROTO_RESP_ERR = "500 Internal Server Error"; public final static String PROTO_RESP_NOTFOUND = "404 Not Found"; // buffer size private final static int DGRAM_BUF_LEN = 1024; // source port for datagram packets private final static int SRC_PORT = 1198; private static final String MATCH_ALL_PLATFORMS = "*"; /* * inner class used to parse and keep addresses from main */ private static class MainAddr { public String protocol; public String hostname; public int port; public MainAddr(String address) { StringTokenizer ast; /* * Address must be in form "proto:host:port" * If address has a bad format, the following code * will throw an exception. The behaviour is by design. */ ast = new StringTokenizer(address, PROTO_ADDR_SEPARATOR); protocol = ast.nextToken(); hostname = ast.nextToken(); port = Integer.parseInt(ast.nextToken()); } } /* * inner class used to get multicast parameters from Profile */ static class MulticastParams { String address; int port; int firstTimeout; int timeout; int retries; int ttl; private void checkTrue(boolean condition, String paramName, String paramValue) throws ProfileException { if (!condition) { throw new ProfileException("Bad value \""+paramValue+"\" for option "+paramName); } } private int parseInt(String paramName, String paramValue) throws ProfileException { try { return Integer.parseInt(paramValue); } catch (NumberFormatException nfe) { throw new ProfileException("Bad value \""+paramValue+"\" for option "+paramName+": integer value required", nfe); } } public MulticastParams(Profile p) throws ProfileException { String s; address = p.getParameter(OPT_MCAST_ADDR, OPT_MCAST_ADDR_DEFAULT); checkTrue(address != null && address.length() > 0, OPT_MCAST_ADDR, address); s = p.getParameter(OPT_MCAST_PORT, OPT_MCAST_PORT_DEFAULT); port = parseInt(OPT_MCAST_PORT, s); checkTrue(port > 0, OPT_MCAST_PORT, s); s = p.getParameter(OPT_MCAST_FIRST_TIMEOUT, OPT_MCAST_FIRST_TIMEOUT_DEFAULT); firstTimeout = parseInt(OPT_MCAST_FIRST_TIMEOUT, s); checkTrue(firstTimeout > 0, OPT_MCAST_FIRST_TIMEOUT, s); s = p.getParameter(OPT_MCAST_TIMEOUT, OPT_MCAST_TIMEOUT_DEFAULT); timeout = parseInt(OPT_MCAST_TIMEOUT, s); checkTrue(timeout >= 0, OPT_MCAST_TIMEOUT, s); s = p.getParameter(OPT_MCAST_RETRIES, OPT_MCAST_RETRIES_DEFAULT); retries = parseInt(OPT_MCAST_RETRIES, s); checkTrue(retries >= 0, OPT_MCAST_RETRIES, s); s = p.getParameter(OPT_MCAST_TTL, OPT_MCAST_TTL_DEFAULT); ttl = parseInt(OPT_MCAST_TTL, s); checkTrue(ttl > 0, OPT_MCAST_TTL, s); } } /* * decode data using the right protocol charset and throw away trailing '\0's */ public static String decodeData(byte[] data) { String result = null; try { // discard all null bytes at the end of the buffer int i; for (i = data.length-1; i >= 0; i--) { if (data[i] != 0) { break; } } if (i > 0) { result = new String(data, 0, i+1, PROTO_ENCODING); } } catch (UnsupportedEncodingException uee) { logger.log(Logger.SEVERE, "Cannot decode data with charset "+PROTO_ENCODING, uee); } return result; } /* * check protocol version at the end of request */ public static int checkProtocolVersion(String request) throws Exception { int i = request.lastIndexOf(PROTO_VERSION); if (i < 0) { throw new Exception("Bad message"); } if (i+PROTO_VERSION.length() != request.length()) { throw new Exception("Wrong protocol version"); } return i; } /* * extract Main address from the list got in response */ private static MainAddr extractAddress(String response, String proto) { logger.log(Logger.FINER, "MainDetectionManager::extractAddress(response=\""+response+"\", proto=\""+proto+"\")"); MainAddr result = null; // parse a list of addresses in form proto_1:hostname_1:port_1;...;proto_n:hostname_n:port_n StringTokenizer st = new StringTokenizer(response, PROTO_ADDRESSES_SEPARATOR); String address; MainAddr ma; while (st.hasMoreTokens()) { address = st.nextToken(); try { ma = new MainAddr(address); result = ma; /* * If either no protocol specified or main has an address with * desired protocol, then we are done, otherwise, go on searching * for the right protocol. */ if (proto == null || proto.equals(ma.protocol)) { break; } } catch (Exception e) { // skip malformed address... logger.log(Logger.WARNING, "Skipping malformed address", e); } } return result; } /* * manage responses for get-main query */ private static MainAddr manageGetMainResponses(List responses, String proto) { logger.log(Logger.FINER, "MainDetectionManager::manageGetMainResponses(responses.size()="+responses.size()+", proto=\""+proto+"\")"); MainAddr mainAddress = null; String response; String s; Iterator iter = responses.iterator(); byte[] data; InetAddress senderHost; int senderPort; while (iter.hasNext()) { DatagramPacket p = (DatagramPacket)iter.next(); data = p.getData(); senderHost = p.getAddress(); senderPort = p.getPort(); response = decodeData(data); try { if (response == null) { throw new Exception("Response cannot be decoded"); } s = response; // first of all, check protocol version in response int i = checkProtocolVersion(s); s = s.substring(0, i); // then, check for errors returned by Main i = s.indexOf(PROTO_RESP_OK); if (i != 0) { throw new Exception("Main container returned Error in response"); } s = s.substring(PROTO_RESP_OK.length()); mainAddress = extractAddress(s, proto); // bail out at first good answer break; } catch (Exception e) { logger.log(Logger.WARNING, "Error managing response \""+response+"\" from "+senderHost+":"+senderPort+"; response discarded", e); } } // at last, extract Main address return mainAddress; } /* * send multicast request, managing timeout and retries, and return decoded response */ private static List multicAsk(String request, Profile p) throws ProfileException { logger.log(Logger.FINER, "MainDetectionManager::multicAsk(...)"); List result = null; /* * usually we expect only one Main, but when more are active, * we collect all responses and choose the one we like more */ List responses = new ArrayList(3); // parse parameters from profile MulticastParams mcast = new MulticastParams(p); logger.log(Logger.FINER, "MainDetectionManager::multicAsk(): prepared msg=\""+request+"\""); InetAddress mcastGroupAddress; try { mcastGroupAddress = InetAddress.getByName(mcast.address); } catch (UnknownHostException e) { throw new ProfileException("Cannot resolve address "+mcast.address, e); } if (!mcastGroupAddress.isMulticastAddress()) { throw new ProfileException("Address "+mcast.address+" is not a multicast address"); } MulticastSocket socket = null; try { try { socket = new MulticastSocket(SRC_PORT); socket.setTimeToLive(mcast.ttl); socket.setSoTimeout(mcast.firstTimeout); } catch (IOException ioe) { throw new ProfileException("Error setting up multicast socket", ioe); } DatagramPacket packet; int retries = mcast.retries+1; do { try { logger.log(Logger.FINER, "MainDetectionManager::multicAsk(): sending msg =\""+request+"\" to "+mcast.address+":"+mcast.port); packet = new DatagramPacket(request.getBytes(PROTO_ENCODING), request.length(), mcastGroupAddress, mcast.port); socket.send(packet); do { try { // get responses byte[] buf = new byte[DGRAM_BUF_LEN]; DatagramPacket recv = new DatagramPacket(buf, buf.length); socket.receive(recv); logger.log(Logger.FINER, "MainDetectionManager::multicAsk(): received "+recv.getLength()+" bytes"); responses.add(recv); } catch (SocketTimeoutException ste) { socket.setSoTimeout(mcast.timeout); if (responses.size() > 0) { // we received at least one answer, it's enough break; } throw ste; } } while(true); if (responses.size() > 0) { // bail out break; } } catch (SocketTimeoutException ste) { // timeout, go for retry --retries; logger.log(Logger.FINER, "MainDetectionManager::multicAsk(): timeout, "+retries+" retries left"); } } while (retries > 0); if (responses.size() > 0) { // we got at least a response! result = responses; } } catch (Exception e) { throw new ProfileException("Error during multicast querying", e); } finally { if (socket != null) { socket.close(); } } return result; } /* * build request for get-main command */ private static String buildGetMainRequest(String platformName, String proto) { // build request StringBuffer msg = new StringBuffer(PROTO_CMD_GETMAIN); if (platformName != null) { msg.append('@'); msg.append(platformName); } if (proto != null) { msg.append(':'); msg.append(proto); } msg.append(PROTO_VERSION); return msg.toString(); } /* * search for a main container */ private static MainAddr getMainAddress(Profile profile) throws ProfileException { logger.log(Logger.FINER, "MainDetectionManager::getMainAddress(...)"); MainAddr result = null; // get platform name and protocol String platformName = profile.getParameter(Profile.PLATFORM_ID, null); if (platformName == null) { throw new ProfileException("platform id is mandatory when using automatic main detection; use \""+MATCH_ALL_PLATFORMS+"\" to match all"); } if (MATCH_ALL_PLATFORMS.equals(platformName)) { platformName = null; } String proto = profile.getParameter(Profile.MAIN_PROTO, null); // build request String msg = buildGetMainRequest(platformName, proto); // send multicast query to search for a main List responses = multicAsk(msg, profile); if (responses != null) { // parse the response result = manageGetMainResponses(responses, proto); } return result; } public static void detect(ProfileImpl profile) throws ProfileException { logger.log(Logger.FINER, "MainDetectionManager::detect(...)"); if (!profile.isFirstMain()) { MainAddr mainAddress = getMainAddress(profile); if (mainAddress == null) { // FIXME is the right exception to throw? throw new ProfileException("Cannot detect Main Container"); } logger.log(Logger.CONFIG, "setting "+Profile.MAIN_PROTO+"="+mainAddress.protocol); profile.setParameter(Profile.MAIN_PROTO, mainAddress.protocol); logger.log(Logger.CONFIG, "setting "+Profile.MAIN_HOST+"="+mainAddress.hostname); profile.setParameter(Profile.MAIN_HOST, mainAddress.hostname); logger.log(Logger.CONFIG, "setting "+Profile.MAIN_PORT+"="+mainAddress.port); profile.setParameter(Profile.MAIN_PORT, Integer.toString(mainAddress.port)); } } public static MulticastMainDetectionListener createListener(ProfileImpl profile, IMTPManager m) throws ProfileException { logger.log(Logger.FINER, "MainDetectionManager::export(...)"); MulticastMainDetectionListener listener = new MulticastMainDetectionListener(profile, m); Thread listenerThread = new Thread(listener); listenerThread.start(); return listener; } }