8002048: Protocol to discovery of manageable Java processes on a network
authordsamersoff
Sun, 03 Feb 2013 21:39:58 +0400
changeset 15531 071efc9f31ad
parent 15530 d918415aea3f
child 15532 859facd70580
8002048: Protocol to discovery of manageable Java processes on a network Summary: Introduce a protocol to discover manageble Java instances across a network subnet, JDP Reviewed-by: sla, dfuchs
jdk/src/share/classes/sun/management/Agent.java
jdk/src/share/classes/sun/management/jdp/JdpBroadcaster.java
jdk/src/share/classes/sun/management/jdp/JdpController.java
jdk/src/share/classes/sun/management/jdp/JdpException.java
jdk/src/share/classes/sun/management/jdp/JdpGenericPacket.java
jdk/src/share/classes/sun/management/jdp/JdpJmxPacket.java
jdk/src/share/classes/sun/management/jdp/JdpPacket.java
jdk/src/share/classes/sun/management/jdp/JdpPacketReader.java
jdk/src/share/classes/sun/management/jdp/JdpPacketWriter.java
jdk/src/share/classes/sun/management/jdp/package-info.java
jdk/test/sun/management/jdp/JdpClient.java
jdk/test/sun/management/jdp/JdpDoSomething.java
jdk/test/sun/management/jdp/JdpTest.sh
jdk/test/sun/management/jdp/JdpUnitTest.java
--- a/jdk/src/share/classes/sun/management/Agent.java	Sun Feb 03 18:20:24 2013 +0400
+++ b/jdk/src/share/classes/sun/management/Agent.java	Sun Feb 03 21:39:58 2013 +0400
@@ -22,7 +22,6 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
-
 package sun.management;
 
 import java.io.BufferedInputStream;
@@ -31,49 +30,55 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
-
+import java.lang.management.ManagementFactory;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
-import java.lang.management.ManagementFactory;
-
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
 import java.text.MessageFormat;
-
 import java.util.MissingResourceException;
 import java.util.Properties;
 import java.util.ResourceBundle;
 
 import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXServiceURL;
 
 import static sun.management.AgentConfigurationError.*;
 import sun.management.jmxremote.ConnectorBootstrap;
+import sun.management.jdp.JdpController;
+import sun.management.jdp.JdpException;
 import sun.misc.VMSupport;
 
 /**
- * This Agent is started by the VM when -Dcom.sun.management.snmp
- * or -Dcom.sun.management.jmxremote is set. This class will be
- * loaded by the system class loader. Also jmx framework could
- * be started by jcmd
+ * This Agent is started by the VM when -Dcom.sun.management.snmp or
+ * -Dcom.sun.management.jmxremote is set. This class will be loaded by the
+ * system class loader. Also jmx framework could be started by jcmd
  */
 public class Agent {
     // management properties
+
     private static Properties mgmtProps;
     private static ResourceBundle messageRB;
-
     private static final String CONFIG_FILE =
-        "com.sun.management.config.file";
+            "com.sun.management.config.file";
     private static final String SNMP_PORT =
-        "com.sun.management.snmp.port";
+            "com.sun.management.snmp.port";
     private static final String JMXREMOTE =
-        "com.sun.management.jmxremote";
+            "com.sun.management.jmxremote";
     private static final String JMXREMOTE_PORT =
-        "com.sun.management.jmxremote.port";
+            "com.sun.management.jmxremote.port";
+    private static final String RMI_PORT =
+            "com.sun.management.jmxremote.rmi.port";
     private static final String ENABLE_THREAD_CONTENTION_MONITORING =
-        "com.sun.management.enableThreadContentionMonitoring";
+            "com.sun.management.enableThreadContentionMonitoring";
     private static final String LOCAL_CONNECTOR_ADDRESS_PROP =
-        "com.sun.management.jmxremote.localConnectorAddress";
+            "com.sun.management.jmxremote.localConnectorAddress";
+    private static final String SNMP_ADAPTOR_BOOTSTRAP_CLASS_NAME =
+            "sun.management.snmp.AdaptorBootstrap";
 
-    private static final String SNMP_ADAPTOR_BOOTSTRAP_CLASS_NAME =
-        "sun.management.snmp.AdaptorBootstrap";
+    private static final String JDP_DEFAULT_ADDRESS = "239.255.255.225";
+    private static final int JDP_DEFAULT_PORT = 7095;
 
     // The only active agent allowed
     private static JMXConnectorServer jmxServer = null;
@@ -81,26 +86,25 @@
     // Parse string com.sun.management.prop=xxx,com.sun.management.prop=yyyy
     // and return property set if args is null or empty
     // return empty property set
-    private static Properties parseString(String args){
+    private static Properties parseString(String args) {
         Properties argProps = new Properties();
         if (args != null) {
-           for (String option : args.split(",")) {
-               String s[] = option.split("=", 2);
-               String name = s[0].trim();
-               String value = (s.length > 1) ? s[1].trim() : "";
+            for (String option : args.split(",")) {
+                String s[] = option.split("=", 2);
+                String name = s[0].trim();
+                String value = (s.length > 1) ? s[1].trim() : "";
 
-               if (!name.startsWith("com.sun.management.")) {
-                  error(INVALID_OPTION, name);
-               }
+                if (!name.startsWith("com.sun.management.")) {
+                    error(INVALID_OPTION, name);
+                }
 
-               argProps.setProperty(name, value);
-           }
+                argProps.setProperty(name, value);
+            }
         }
 
         return argProps;
     }
 
-
     // invoked by -javaagent or -Dcom.sun.management.agent.class
     public static void premain(String args) throws Exception {
         agentmain(args);
@@ -115,18 +119,18 @@
         Properties arg_props = parseString(args);
 
         // Read properties from the config file
-         Properties config_props = new Properties();
-         String fname = arg_props.getProperty(CONFIG_FILE);
-         readConfiguration(fname, config_props);
+        Properties config_props = new Properties();
+        String fname = arg_props.getProperty(CONFIG_FILE);
+        readConfiguration(fname, config_props);
 
-         // Arguments override config file
-         config_props.putAll(arg_props);
-         startAgent(config_props);
+        // Arguments override config file
+        config_props.putAll(arg_props);
+        startAgent(config_props);
     }
 
     // jcmd ManagementAgent.start_local entry point
     // Also called due to command-line via startAgent()
-    private static synchronized void startLocalManagementAgent(){
+    private static synchronized void startLocalManagementAgent() {
         Properties agentProps = VMSupport.getAgentProperties();
 
         // start local connector if not started
@@ -156,7 +160,7 @@
             throw new RuntimeException(getText(INVALID_STATE, "Agent already started"));
         }
 
-        Properties argProps    = parseString(args);
+        Properties argProps = parseString(args);
         Properties configProps = new Properties();
 
         // Load the management properties from the config file
@@ -169,7 +173,7 @@
         // management properties can be overridden by system properties
         // which take precedence
         Properties sysProps = System.getProperties();
-        synchronized(sysProps){
+        synchronized (sysProps) {
             configProps.putAll(sysProps);
         }
 
@@ -190,21 +194,26 @@
         // can specify this property inside config file, so enable optional
         // monitoring functionality if this property is set
         final String enableThreadContentionMonitoring =
-            configProps.getProperty(ENABLE_THREAD_CONTENTION_MONITORING);
+                configProps.getProperty(ENABLE_THREAD_CONTENTION_MONITORING);
 
         if (enableThreadContentionMonitoring != null) {
             ManagementFactory.getThreadMXBean().
-                setThreadContentionMonitoringEnabled(true);
+                    setThreadContentionMonitoringEnabled(true);
         }
 
         String jmxremotePort = configProps.getProperty(JMXREMOTE_PORT);
         if (jmxremotePort != null) {
             jmxServer = ConnectorBootstrap.
-                           startRemoteConnectorServer(jmxremotePort, configProps);
+                    startRemoteConnectorServer(jmxremotePort, configProps);
+
+            startDiscoveryService(configProps);
         }
     }
 
     private static synchronized void stopRemoteManagementAgent() throws Exception {
+
+        JdpController.stopDiscoveryService();
+
         if (jmxServer != null) {
             ConnectorBootstrap.unexportRegistry();
 
@@ -222,15 +231,15 @@
 
         // Enable optional monitoring functionality if requested
         final String enableThreadContentionMonitoring =
-            props.getProperty(ENABLE_THREAD_CONTENTION_MONITORING);
+                props.getProperty(ENABLE_THREAD_CONTENTION_MONITORING);
         if (enableThreadContentionMonitoring != null) {
             ManagementFactory.getThreadMXBean().
-                setThreadContentionMonitoringEnabled(true);
+                    setThreadContentionMonitoringEnabled(true);
         }
 
         try {
             if (snmpPort != null) {
-               loadSnmpAgent(snmpPort, props);
+                loadSnmpAgent(snmpPort, props);
             }
 
             /*
@@ -242,13 +251,14 @@
              * of this "local" server is exported as a counter to the jstat
              * instrumentation buffer.
              */
-             if (jmxremote != null || jmxremotePort != null) {
+            if (jmxremote != null || jmxremotePort != null) {
                 if (jmxremotePort != null) {
-                   jmxServer = ConnectorBootstrap.
-                               startRemoteConnectorServer(jmxremotePort, props);
+                    jmxServer = ConnectorBootstrap.
+                            startRemoteConnectorServer(jmxremotePort, props);
+                    startDiscoveryService(props);
                 }
                 startLocalManagementAgent();
-             }
+            }
 
         } catch (AgentConfigurationError e) {
             error(e.getError(), e.getParams());
@@ -257,6 +267,73 @@
         }
     }
 
+    private static void startDiscoveryService(Properties props)
+            throws IOException {
+        // Start discovery service if requested
+        String discoveryPort = props.getProperty("com.sun.management.jdp.port");
+        String discoveryAddress = props.getProperty("com.sun.management.jdp.address");
+        String discoveryShouldStart = props.getProperty("com.sun.management.jmxremote.autodiscovery");
+
+        // Decide whether we should start autodicovery service.
+        // To start autodiscovery following conditions should be met:
+        // autodiscovery==true OR (autodicovery==null AND jdp.port != NULL)
+
+        boolean shouldStart = false;
+        if (discoveryShouldStart == null){
+            shouldStart = (discoveryPort != null);
+        }
+        else{
+            try{
+               shouldStart = Boolean.parseBoolean(discoveryShouldStart);
+            } catch (NumberFormatException e) {
+                throw new AgentConfigurationError("Couldn't parse autodiscovery argument");
+            }
+        }
+
+        if (shouldStart) {
+            // port and address are required arguments and have no default values
+            InetAddress address;
+            try {
+                address = (discoveryAddress == null) ?
+                        InetAddress.getByName(JDP_DEFAULT_ADDRESS) : InetAddress.getByName(discoveryAddress);
+            } catch (UnknownHostException e) {
+                throw new AgentConfigurationError("Unable to broadcast to requested address", e);
+            }
+
+            int port = JDP_DEFAULT_PORT;
+            if (discoveryPort != null) {
+               try {
+                  port = Integer.parseInt(discoveryPort);
+               } catch (NumberFormatException e) {
+                 throw new AgentConfigurationError("Couldn't parse JDP port argument");
+               }
+            }
+
+            // Rebuilding service URL to broadcast it
+            String jmxremotePort = props.getProperty(JMXREMOTE_PORT);
+            String rmiPort = props.getProperty(RMI_PORT);
+
+            JMXServiceURL url = jmxServer.getAddress();
+            String hostname = url.getHost();
+
+            String jmxUrlStr = (rmiPort != null)
+                    ? String.format(
+                    "service:jmx:rmi://%s:%s/jndi/rmi://%s:%s/jmxrmi",
+                    hostname, rmiPort, hostname, jmxremotePort)
+                    : String.format(
+                    "service:jmx:rmi:///jndi/rmi://%s:%s/jmxrmi", hostname, jmxremotePort);
+
+            String instanceName = System.getProperty("com.sun.management.jdp.name");
+
+            try{
+               JdpController.startDiscoveryService(address, port, instanceName, jmxUrlStr);
+            }
+            catch(JdpException e){
+                throw new AgentConfigurationError("Couldn't start JDP service", e);
+            }
+        }
+    }
+
     public static Properties loadManagementProperties() {
         Properties props = new Properties();
 
@@ -268,22 +345,22 @@
         // management properties can be overridden by system properties
         // which take precedence
         Properties sysProps = System.getProperties();
-        synchronized(sysProps){
+        synchronized (sysProps) {
             props.putAll(sysProps);
         }
 
         return props;
-   }
+    }
 
-   public static synchronized Properties getManagementProperties() {
+    public static synchronized Properties getManagementProperties() {
         if (mgmtProps == null) {
             String configFile = System.getProperty(CONFIG_FILE);
             String snmpPort = System.getProperty(SNMP_PORT);
             String jmxremote = System.getProperty(JMXREMOTE);
             String jmxremotePort = System.getProperty(JMXREMOTE_PORT);
 
-            if (configFile == null && snmpPort == null &&
-                jmxremote == null && jmxremotePort == null) {
+            if (configFile == null && snmpPort == null
+                    && jmxremote == null && jmxremotePort == null) {
                 // return if out-of-the-management option is not specified
                 return null;
             }
@@ -297,22 +374,23 @@
             // invoke the following through reflection:
             //     AdaptorBootstrap.initialize(snmpPort, props);
             final Class<?> adaptorClass =
-                Class.forName(SNMP_ADAPTOR_BOOTSTRAP_CLASS_NAME,true,null);
+                    Class.forName(SNMP_ADAPTOR_BOOTSTRAP_CLASS_NAME, true, null);
             final Method initializeMethod =
                     adaptorClass.getMethod("initialize",
-                        String.class, Properties.class);
-            initializeMethod.invoke(null,snmpPort,props);
+                    String.class, Properties.class);
+            initializeMethod.invoke(null, snmpPort, props);
         } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException x) {
             // snmp runtime doesn't exist - initialization fails
-            throw new UnsupportedOperationException("Unsupported management property: " + SNMP_PORT,x);
+            throw new UnsupportedOperationException("Unsupported management property: " + SNMP_PORT, x);
         } catch (InvocationTargetException x) {
             final Throwable cause = x.getCause();
-            if (cause instanceof RuntimeException)
+            if (cause instanceof RuntimeException) {
                 throw (RuntimeException) cause;
-            else if (cause instanceof Error)
+            } else if (cause instanceof Error) {
                 throw (Error) cause;
+            }
             // should not happen...
-            throw new UnsupportedOperationException("Unsupported management property: " + SNMP_PORT,cause);
+            throw new UnsupportedOperationException("Unsupported management property: " + SNMP_PORT, cause);
         }
     }
 
@@ -353,8 +431,8 @@
                 } catch (IOException e) {
                     error(CONFIG_FILE_CLOSE_FAILED, fname);
                 }
-             }
-         }
+            }
+        }
     }
 
     public static void startAgent() throws Exception {
@@ -389,9 +467,9 @@
                 // invoke the premain(String args) method
                 Class<?> clz = ClassLoader.getSystemClassLoader().loadClass(cname);
                 Method premain = clz.getMethod("premain",
-                                               new Class<?>[] { String.class });
+                        new Class<?>[]{String.class});
                 premain.invoke(null, /* static */
-                               new Object[] { args });
+                        new Object[]{args});
             } catch (ClassNotFoundException ex) {
                 error(AGENT_CLASS_NOT_FOUND, "\"" + cname + "\"");
             } catch (NoSuchMethodException ex) {
@@ -400,8 +478,8 @@
                 error(AGENT_CLASS_ACCESS_DENIED);
             } catch (Exception ex) {
                 String msg = (ex.getCause() == null
-                                 ? ex.getMessage()
-                                 : ex.getCause().getMessage());
+                        ? ex.getMessage()
+                        : ex.getCause().getMessage());
                 error(AGENT_CLASS_FAILED, msg);
             }
         }
@@ -425,7 +503,6 @@
         }
     }
 
-
     public static void error(String key, String message) {
         String keyText = getText(key);
         System.err.print(getText("agent.err.error") + ": " + keyText);
@@ -447,7 +524,7 @@
     private static void initResource() {
         try {
             messageRB =
-                ResourceBundle.getBundle("sun.management.resources.agent");
+                    ResourceBundle.getBundle("sun.management.resources.agent");
         } catch (MissingResourceException e) {
             throw new Error("Fatal: Resource for management agent is missing");
         }
@@ -470,10 +547,9 @@
         }
         String format = messageRB.getString(key);
         if (format == null) {
-            format = "missing resource key: key = \"" + key + "\", " +
-                "arguments = \"{0}\", \"{1}\", \"{2}\"";
+            format = "missing resource key: key = \"" + key + "\", "
+                    + "arguments = \"{0}\", \"{1}\", \"{2}\"";
         }
         return MessageFormat.format(format, (Object[]) args);
     }
-
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/management/jdp/JdpBroadcaster.java	Sun Feb 03 21:39:58 2013 +0400
@@ -0,0 +1,124 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.management.jdp;
+
+import java.io.IOException;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
+import java.net.ProtocolFamily;
+import java.net.StandardProtocolFamily;
+import java.net.StandardSocketOptions;
+import java.nio.ByteBuffer;
+import java.nio.channels.DatagramChannel;
+import java.nio.channels.UnsupportedAddressTypeException;
+
+/**
+ * JdpBroadcaster is responsible for sending pre-built JDP packet across a Net
+ *
+ * <p> Multicast group address, port number and ttl have to be chosen on upper
+ * level and passed to broadcaster constructor. Also it's possible to specify
+ * source address to broadcast from. </p>
+ *
+ * <p>JdpBradcaster doesn't perform any validation on a supplied {@code port} and {@code ttl} because
+ * the allowed values depend on an operating system setup</p>
+ *
+ */
+public final class JdpBroadcaster {
+
+    private final InetAddress addr;
+    private final int port;
+    private final DatagramChannel channel;
+
+    /**
+     * Create a new broadcaster
+     *
+     * @param address - multicast group address
+     * @param srcAddress - address of interface we should use to broadcast.
+     * @param port - udp port to use
+     * @param ttl - packet ttl
+     * @throws IOException
+     */
+    public JdpBroadcaster(InetAddress address, InetAddress srcAddress, int port, int ttl)
+            throws IOException, JdpException {
+        this.addr = address;
+        this.port = port;
+
+        ProtocolFamily family = (address instanceof Inet6Address)
+                ? StandardProtocolFamily.INET6 : StandardProtocolFamily.INET;
+
+        channel = DatagramChannel.open(family);
+        channel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
+        channel.setOption(StandardSocketOptions.IP_MULTICAST_TTL, ttl);
+
+        // with srcAddress equal to null, this constructor do exactly the same as
+        // if srcAddress is not passed
+        if (srcAddress != null) {
+            // User requests particular interface to bind to
+            NetworkInterface interf = NetworkInterface.getByInetAddress(srcAddress);
+            try {
+                channel.bind(new InetSocketAddress(srcAddress, 0));
+            } catch (UnsupportedAddressTypeException ex) {
+                throw new JdpException("Unable to bind to source address");
+            }
+            channel.setOption(StandardSocketOptions.IP_MULTICAST_IF, interf);
+        }
+    }
+
+    /**
+     * Create a new broadcaster
+     *
+     * @param address - multicast group address
+     * @param port - udp port to use
+     * @param ttl - packet ttl
+     * @throws IOException
+     */
+    public JdpBroadcaster(InetAddress address, int port, int ttl)
+            throws IOException, JdpException {
+        this(address, null, port, ttl);
+    }
+
+    /**
+     * Broadcast pre-built packet
+     *
+     * @param packet - instance of JdpPacket
+     * @throws IOException
+     */
+    public void sendPacket(JdpPacket packet)
+            throws IOException {
+        byte[] data = packet.getPacketData();
+        // Unlike allocate/put wrap don't need a flip afterward
+        ByteBuffer b = ByteBuffer.wrap(data);
+        channel.send(b, new InetSocketAddress(addr, port));
+    }
+
+    /**
+     * Shutdown broadcaster and close underlying socket channel
+     *
+     * @throws IOException
+     */
+    public void shutdown() throws IOException {
+        channel.close();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/management/jdp/JdpController.java	Sun Feb 03 21:39:58 2013 +0400
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.management.jdp;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.UUID;
+
+/**
+ * JdpController is responsible to create and manage a broadcast loop
+ *
+ * <p> Other part of code has no access to broadcast loop and have to use
+ * provided static methods
+ * {@link #startDiscoveryService(InetAddress,int,String,String) startDiscoveryService}
+ * and {@link #stopDiscoveryService() stopDiscoveryService}</p>
+ * <p>{@link #startDiscoveryService(InetAddress,int,String,String) startDiscoveryService} could be called multiple
+ * times as it stops the running service if it is necessary. Call to {@link #stopDiscoveryService() stopDiscoveryService}
+ * ignored if service isn't run</p>
+ *
+ *
+ * </p>
+ *
+ * <p> System properties below could be used to control broadcast loop behavior.
+ * Property below have to be set explicitly in command line. It's not possible to
+ * set it in management.config file.  Careless changes of these properties could
+ * lead to security or network issues.
+ * <ul>
+ *     <li>com.sun.management.jdp.ttl         - set ttl for broadcast packet</li>
+ *     <li>com.sun.management.jdp.pause       - set broadcast interval in seconds</li>
+ *     <li>com.sun.management.jdp.source_addr - an address of interface to use for broadcast</li>
+ * </ul>
+  </p>
+ * <p>null parameters values are filtered out on {@link JdpPacketWriter} level and
+ * corresponding keys are not placed to packet.</p>
+ */
+public final class JdpController {
+
+    private static class JDPControllerRunner implements Runnable {
+
+        private final JdpJmxPacket packet;
+        private final JdpBroadcaster bcast;
+        private final int pause;
+        private volatile boolean shutdown = false;
+
+        private JDPControllerRunner(JdpBroadcaster bcast, JdpJmxPacket packet, int pause) {
+            this.bcast = bcast;
+            this.packet = packet;
+            this.pause = pause;
+        }
+
+        @Override
+        public void run() {
+            try {
+                while (!shutdown) {
+                    bcast.sendPacket(packet);
+                    try {
+                        Thread.sleep(this.pause);
+                    } catch (InterruptedException e) {
+                        // pass
+                    }
+                }
+
+            } catch (IOException e) {
+              // pass;
+            }
+
+            // It's not possible to re-use controller,
+            // nevertheless reset shutdown variable
+            try {
+                stop();
+                bcast.shutdown();
+            } catch (IOException ex) {
+                // pass - ignore IOException during shutdown
+            }
+        }
+
+        public void stop() {
+            shutdown = true;
+        }
+    }
+    private static JDPControllerRunner controller = null;
+
+    private JdpController(){
+        // Don't allow to instantiate this class.
+    }
+
+    // Utility to handle optional system properties
+    // Parse an integer from string or return default if provided string is null
+    private static int getInteger(String val, int dflt, String msg) throws JdpException {
+        try {
+            return (val == null) ? dflt : Integer.parseInt(val);
+        } catch (NumberFormatException ex) {
+            throw new JdpException(msg);
+        }
+    }
+
+    // Parse an inet address from string or return default if provided string is null
+    private static InetAddress getInetAddress(String val, InetAddress dflt, String msg) throws JdpException {
+        try {
+            return (val == null) ? dflt : InetAddress.getByName(val);
+        } catch (UnknownHostException ex) {
+            throw new JdpException(msg);
+        }
+    }
+
+    /**
+     * Starts discovery service
+     *
+     * @param address - multicast group address
+     * @param port - udp port to use
+     * @param instanceName - name of running JVM instance
+     * @param url - JMX service url
+     * @throws IOException
+     */
+    public static synchronized void startDiscoveryService(InetAddress address, int port, String instanceName, String url)
+            throws IOException, JdpException {
+
+        // Limit packet to local subnet by default
+        int ttl = getInteger(
+                System.getProperty("com.sun.management.jdp.ttl"), 1,
+                "Invalid jdp packet ttl");
+
+        // Broadcast once a 5 seconds by default
+        int pause = getInteger(
+                System.getProperty("com.sun.management.jdp.pause"), 5,
+                "Invalid jdp pause");
+
+        // Converting seconds to milliseconds
+        pause = pause * 1000;
+
+        // Allow OS to choose broadcast source
+        InetAddress sourceAddress = getInetAddress(
+                System.getProperty("com.sun.management.jdp.source_addr"), null,
+                "Invalid source address provided");
+
+        // Generate session id
+        UUID id = UUID.randomUUID();
+
+        JdpJmxPacket packet = new JdpJmxPacket(id, url);
+
+        // Don't broadcast whole command line for security reason.
+        // Strip everything after first space
+        String javaCommand = System.getProperty("sun.java.command");
+        if (javaCommand != null) {
+            String[] arr = javaCommand.split(" ", 2);
+            packet.setMainClass(arr[0]);
+        }
+
+        // Put optional explicit java instance name to packet, if user doesn't specify
+        // it the key is skipped. PacketWriter is responsible to skip keys having null value.
+        packet.setInstanceName(instanceName);
+
+        JdpBroadcaster bcast = new JdpBroadcaster(address, sourceAddress, port, ttl);
+
+        // Stop discovery service if it's already running
+        stopDiscoveryService();
+
+        controller = new JDPControllerRunner(bcast, packet, pause);
+
+        Thread t = new Thread(controller, "JDP broadcaster");
+        t.setDaemon(true);
+        t.start();
+    }
+
+    /**
+     * Stop running discovery service,
+     * it's safe to attempt to stop not started service
+     */
+    public static synchronized void stopDiscoveryService() {
+        if ( controller != null ){
+             controller.stop();
+             controller = null;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/management/jdp/JdpException.java	Sun Feb 03 21:39:58 2013 +0400
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.management.jdp;
+
+/**
+ * An Exception thrown if a JDP implementation encounters a problem.
+ */
+public final class JdpException extends Exception {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Construct a new JDP exception with a meaningful message
+     *
+     * @param msg - message
+     */
+    public JdpException(String msg) {
+        super(msg);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/management/jdp/JdpGenericPacket.java	Sun Feb 03 21:39:58 2013 +0400
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.management.jdp;
+
+/**
+ * JdpGenericPacket responsible to provide fields
+ * common for all Jdp packets
+ */
+public abstract class JdpGenericPacket implements JdpPacket {
+
+    /**
+     * JDP protocol magic. Magic allows a reader to quickly select
+     * JDP packets from a bunch of broadcast packets addressed to the same port
+     * and broadcast group. Any packet intended to be parsed by JDP client
+     * has to start from this  magic.
+     */
+    private static final int MAGIC = 0xC0FFEE42;
+
+    /**
+     * Current version of protocol. Any implementation of this protocol has to
+     * conform with the packet structure and the flow described in JEP-168
+     */
+    private static final short PROTOCOL_VERSION = 1;
+
+    /**
+     * Default do-nothing constructor
+     */
+    protected  JdpGenericPacket(){
+        // do nothing
+    }
+
+
+    /**
+     * Validate protocol header magic field
+     *
+     * @param magic - value to validate
+     * @throws JdpException
+     */
+    public static void checkMagic(int magic)
+            throws JdpException {
+        if (magic != MAGIC) {
+            throw new JdpException("Invalid JDP magic header: " + magic);
+        }
+    }
+
+    /**
+     * Validate protocol header version field
+     *
+     * @param version - value to validate
+     * @throws JdpException
+     */
+    public static void checkVersion(short version)
+            throws JdpException {
+
+        if (version > PROTOCOL_VERSION) {
+            throw new JdpException("Unsupported protocol version: " + version);
+        }
+    }
+
+    /**
+     *
+     * @return protocol magic
+     */
+    public static int getMagic() {
+        return MAGIC;
+    }
+
+    /**
+     *
+     * @return current protocol version
+     */
+    public static short getVersion() {
+        return PROTOCOL_VERSION;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/management/jdp/JdpJmxPacket.java	Sun Feb 03 21:39:58 2013 +0400
@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.management.jdp;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * A packet to broadcasts JMX URL
+ *
+ * Fields:
+ *
+ * <ul>
+ * <li>UUID - broadcast session ID, changed every time when we start/stop
+ * discovery service</li>
+ * <li>JMX_URL - URL to connect to JMX service</li>
+ * <li>MAIN_CLASS - optional name of main class, filled from sun.java.command stripped for
+ * security reason to first space</li>
+ * <li>INSTANCE_NAME - optional custom name of particular instance as provided by customer</li>
+ * </ul>
+ */
+public final class JdpJmxPacket
+       extends JdpGenericPacket
+       implements JdpPacket {
+
+    /**
+     * Session ID
+     */
+    public final static String UUID_KEY = "DISCOVERABLE_SESSION_UUID";
+    /**
+     * Name of main class
+     */
+    public final static String MAIN_CLASS_KEY = "MAIN_CLASS";
+    /**
+     * JMX service URL
+     */
+    public final static String JMX_SERVICE_URL_KEY = "JMX_SERVICE_URL";
+    /**
+     * Name of Java instance
+     */
+    public final static String INSTANCE_NAME_KEY = "INSTANCE_NAME";
+
+    private UUID id;
+    private String mainClass;
+    private String jmxServiceUrl;
+    private String instanceName;
+
+    /**
+     * Create new instance from user provided data. Set mandatory fields
+     *
+     * @param id - java instance id
+     * @param jmxServiceUrl - JMX service url
+     */
+    public JdpJmxPacket(UUID id, String jmxServiceUrl) {
+        this.id = id;
+        this.jmxServiceUrl = jmxServiceUrl;
+    }
+
+    /**
+     * Create new instance from network data Parse packet and set fields.
+     *
+     * @param data - raw packet data as it came from a Net
+     * @throws JdpException
+     */
+    public JdpJmxPacket(byte[] data)
+            throws JdpException {
+        JdpPacketReader reader;
+
+        reader = new JdpPacketReader(data);
+        Map<String, String> p = reader.getDiscoveryDataAsMap();
+
+        String sId = p.get(UUID_KEY);
+        this.id = (sId == null) ? null : UUID.fromString(sId);
+        this.jmxServiceUrl = p.get(JMX_SERVICE_URL_KEY);
+        this.mainClass = p.get(MAIN_CLASS_KEY);
+        this.instanceName = p.get(INSTANCE_NAME_KEY);
+    }
+
+    /**
+     * Set main class field
+     *
+     * @param mainClass - main class of running app
+     */
+    public void setMainClass(String mainClass) {
+        this.mainClass = mainClass;
+    }
+
+    /**
+     * Set instance name field
+     *
+     * @param instanceName - name of instance as provided by customer
+     */
+    public void setInstanceName(String instanceName) {
+        this.instanceName = instanceName;
+    }
+
+    /**
+     * @return id of discovery session
+     */
+    public UUID getId() {
+        return id;
+    }
+
+    /**
+     *
+     * @return main class field
+     */
+    public String getMainClass() {
+        return mainClass;
+    }
+
+    /**
+     *
+     * @return JMX service URL
+     */
+    public String getJmxServiceUrl() {
+        return jmxServiceUrl;
+    }
+
+    /**
+     *
+     * @return instance name
+     */
+    public String getInstanceName() {
+        return instanceName;
+    }
+
+    /**
+     *
+     * @return assembled packet ready to be sent across a Net
+     * @throws IOException
+     */
+    @Override
+    public byte[] getPacketData() throws IOException {
+        // Assemble packet from fields to byte array
+        JdpPacketWriter writer;
+        writer = new JdpPacketWriter();
+        writer.addEntry(UUID_KEY, (id == null) ? null : id.toString());
+        writer.addEntry(MAIN_CLASS_KEY, mainClass);
+        writer.addEntry(JMX_SERVICE_URL_KEY, jmxServiceUrl);
+        writer.addEntry(INSTANCE_NAME_KEY, instanceName);
+        return writer.getPacketBytes();
+    }
+
+    /**
+     *
+     * @return packet hash code
+     */
+    @Override
+    public int hashCode() {
+        int hash = 1;
+        hash = hash * 31 + id.hashCode();
+        hash = hash * 31 + jmxServiceUrl.hashCode();
+        return hash;
+    }
+
+    /**
+     * Compare two packets
+     *
+     * @param o - packet to compare
+     * @return either packet equals or not
+     */
+    @Override
+    public boolean equals(Object o) {
+
+        if (o == null || ! (o instanceof JdpJmxPacket) ){
+            return false;
+        }
+
+        JdpJmxPacket p = (JdpJmxPacket) o;
+        return  Objects.equals(id, p.getId()) && Objects.equals(jmxServiceUrl, p.getJmxServiceUrl());
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/management/jdp/JdpPacket.java	Sun Feb 03 21:39:58 2013 +0400
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.management.jdp;
+
+import java.io.IOException;
+
+/**
+ * Packet to broadcast
+ *
+ * <p>Each packet have to contain MAGIC and PROTOCOL_VERSION in order to be
+ * recognized as a valid JDP packet.</p>
+ *
+ * <p>Default implementation build packet as a set of UTF-8 encoded Key/Value pairs
+ * are stored as an ordered list of values, and are sent to the server
+ * in that order.</p>
+ *
+ * <p>
+ * Packet structure:
+ *
+ * 4 bytes JDP magic (0xC0FFE42)
+ * 2 bytes JDP protocol version (01)
+ *
+ * 2 bytes size of key
+ * x bytes key (UTF-8 encoded)
+ * 2 bytes size of value
+ * x bytes value (UTF-8 encoded)
+ *
+ * repeat as many times as necessary ...
+ * </p>
+  */
+public interface JdpPacket {
+
+    /**
+     * This method responsible to assemble packet and return a byte array
+     * ready to be sent across a Net.
+     *
+     * @return assembled packet as an array of bytes
+     * @throws IOException
+     */
+    public byte[] getPacketData() throws IOException;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/management/jdp/JdpPacketReader.java	Sun Feb 03 21:39:58 2013 +0400
@@ -0,0 +1,139 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.management.jdp;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * JdpPacketReader responsible for reading a packet <p>This class gets a byte
+ * array as it came from a Net, validates it and breaks a part </p>
+ */
+public final class JdpPacketReader {
+
+    private final DataInputStream pkt;
+    private Map<String, String> pmap = null;
+
+    /**
+     * Create packet reader, extract and check magic and version
+     *
+     * @param packet - packet received from a Net
+     * @throws JdpException
+     */
+    public JdpPacketReader(byte[] packet)
+            throws JdpException {
+        ByteArrayInputStream bais = new ByteArrayInputStream(packet);
+        pkt = new DataInputStream(bais);
+
+        try {
+            int magic = pkt.readInt();
+            JdpGenericPacket.checkMagic(magic);
+        } catch (IOException e) {
+            throw new JdpException("Invalid JDP packet received, bad magic");
+        }
+
+        try {
+            short version = pkt.readShort();
+            JdpGenericPacket.checkVersion(version);
+        } catch (IOException e) {
+            throw new JdpException("Invalid JDP packet received, bad protocol version");
+        }
+    }
+
+    /**
+     * Get next entry from packet
+     *
+     * @return the entry
+     * @throws EOFException
+     * @throws JdpException
+     */
+    public String getEntry()
+            throws EOFException, JdpException {
+
+        try {
+            short len = pkt.readShort();
+            // Artificial setting the "len" field to Short.MAX_VALUE may cause a reader to allocate
+            // to much memory. Prevent this possible DOS attack.
+            if (len < 1 && len > pkt.available()) {
+                throw new JdpException("Broken JDP packet. Invalid entry length field.");
+            }
+
+            byte[] b = new byte[len];
+            if (pkt.read(b) != len) {
+                throw new JdpException("Broken JDP packet. Unable to read entry.");
+            }
+            return new String(b, "UTF-8");
+
+        } catch (EOFException e) {
+            throw e;
+        } catch (UnsupportedEncodingException ex) {
+            throw new JdpException("Broken JDP packet. Unable to decode entry.");
+        } catch (IOException e) {
+            throw new JdpException("Broken JDP packet. Unable to read entry.");
+        }
+
+
+    }
+
+    /**
+     * return packet content as a key/value map
+     *
+     * @return map containing packet entries pair of entries treated as
+     * key,value
+     * @throws IOException
+     * @throws JdpException
+     */
+    public Map<String, String> getDiscoveryDataAsMap()
+            throws JdpException {
+        // return cached map if possible
+        if (pmap != null) {
+            return pmap;
+        }
+
+        String key = null, value = null;
+
+        final Map<String, String> tmpMap = new HashMap<>();
+        try {
+            while (true) {
+                key = getEntry();
+                value = getEntry();
+                tmpMap.put(key, value);
+            }
+        } catch (EOFException e) {
+            // EOF reached on reading value, report broken packet
+            // otherwise ignore it.
+            if (value == null) {
+                throw new JdpException("Broken JDP packet. Key without value." + key);
+            }
+        }
+
+        pmap = Collections.unmodifiableMap(tmpMap);
+        return pmap;
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/management/jdp/JdpPacketWriter.java	Sun Feb 03 21:39:58 2013 +0400
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package sun.management.jdp;
+
+import java.io.ByteArrayOutputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+/**
+ * JdpPacketWriter responsible for writing a packet
+ * <p>This class assembles a set of key/value pairs to valid JDP packet,
+ * ready to be sent across a Net</p>
+ */
+public final class JdpPacketWriter {
+
+    private final ByteArrayOutputStream baos;
+    private final DataOutputStream pkt;
+
+    /**
+     * Create a JDP packet, add mandatory magic and version headers
+     *
+     * @throws IOException
+     */
+    public JdpPacketWriter()
+            throws IOException {
+        baos = new ByteArrayOutputStream();
+        pkt = new DataOutputStream(baos);
+
+        pkt.writeInt(JdpGenericPacket.getMagic());
+        pkt.writeShort(JdpGenericPacket.getVersion());
+    }
+
+    /**
+     * Put string entry to packet
+     *
+     * @param entry - string to put (utf-8 encoded)
+     * @throws IOException
+     */
+    public void addEntry(String entry)
+            throws IOException {
+        pkt.writeShort(entry.length());
+        byte[] b = entry.getBytes("UTF-8");
+        pkt.write(b);
+    }
+
+    /**
+     * Put key/value pair to packet
+     *
+     * @param key - key to put (utf-8 encoded)
+     * @param val - value to put (utf-8 encoded)
+     * @throws IOException
+     */
+    public void addEntry(String key, String val)
+            throws IOException {
+        /* Silently skip key if value is null.
+         * We don't need to distinguish between key missing
+         * and key has no value cases
+         */
+        if (val != null) {
+            addEntry(key);
+            addEntry(val);
+        }
+    }
+
+    /**
+     * Return assembled packet as a byte array
+     *
+     * @return packet bytes
+     */
+    public byte[] getPacketBytes() {
+        return baos.toByteArray();
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/management/jdp/package-info.java	Sun Feb 03 21:39:58 2013 +0400
@@ -0,0 +1,55 @@
+/**
+ *  Summary
+ *  -------
+ *
+ *  Define a lightweight network protocol for discovering running and
+ *  manageable Java processes within a network subnet.
+ *
+ *
+ * Description
+ * -----------
+ *
+ * The protocol is lightweight multicast based, and works like a beacon,
+ * broadcasting the JMXService URL needed to connect to the external JMX
+ * agent if an application is started with appropriate parameters.
+ *
+ * The payload is structured like this:
+ *
+ *  4 bytes JDP magic (0xC0FFEE42)
+ *  2 bytes JDP protocol version (1)
+ *  2 bytes size of the next entry
+ *      x bytes next entry (UTF-8 encoded)
+ *  2 bytes size of next entry
+ *    ...   Rinse and repeat...
+ *
+ * The payload will be parsed as even entries being keys, odd entries being
+ * values.
+ *
+ * The standard JDP packet contains four entries:
+ *
+ * - `DISCOVERABLE_SESSION_UUID` -- Unique id of the instance; this id changes every time
+ *    the discovery protocol starts and stops
+ *
+ * - `MAIN_CLASS` -- The value of the `sun.java.command` property
+ *
+ * - `JMX_SERVICE_URL` -- The URL to connect to the JMX agent
+ *
+ * - `INSTANCE_NAME` -- The user-provided name of the running instance
+ *
+ * The protocol sends packets to 239.255.255.225:7095 by default.
+ *
+ * The protocol uses system properties to control it's behaviour:
+ * - `com.sun.management.jdp.port` -- override default port
+ *
+ * - `com.sun.management.jdp.address` -- override default address
+ *
+ * - `com.sun.management.jmxremote.autodiscovery` -- whether we should start autodiscovery or
+ * not. Autodiscovery starts if and only if following conditions are met: (autodiscovery is
+ * true OR (autodiscovery is not set AND jdp.port is set))
+ *
+ * - `com.sun.management.jdp.ttl`         -- set ttl for broadcast packet, default is 1
+ * - `com.sun.management.jdp.pause`       -- set broadcast interval in seconds default is 5
+ * - `com.sun.management.jdp.source_addr` -- an address of interface to use for broadcast
+ */
+
+package sun.management.jdp;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/management/jdp/JdpClient.java	Sun Feb 03 21:39:58 2013 +0400
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.IOException;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.NetworkInterface;
+import java.net.ProtocolFamily;
+import java.net.StandardProtocolFamily;
+import java.net.StandardSocketOptions;
+import java.nio.ByteBuffer;
+import java.nio.channels.DatagramChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.util.Collections;
+import java.util.Enumeration;
+import sun.management.jdp.JdpException;
+import sun.management.jdp.JdpJmxPacket;
+
+public class JdpClient {
+
+    private static class PacketListener implements Runnable {
+
+        private static final int BUFFER_LENGTH = 4096;
+        private final DatagramChannel channel;
+        private static int maxPacketCount = 1;
+        private static int maxEmptyPacketCount = 10;
+
+
+        PacketListener(DatagramChannel channel) {
+            this.channel = channel;
+        }
+
+        @java.lang.Override
+        public void run() {
+            try {
+                Selector sel;
+                sel = Selector.open();
+                channel.configureBlocking(false);
+                channel.register(sel, SelectionKey.OP_READ);
+                ByteBuffer buf = ByteBuffer.allocate(1024);
+
+                int count = 1;
+                int emptyPacketsCount = 1;
+
+                try {
+                    while (true) {
+
+                        sel.selectedKeys().clear();
+                        buf.rewind();
+
+                        sel.select(10 * 1000);
+                        channel.receive(buf);
+
+                        if (buf.position() == 0 ){
+                            if (JdpDoSomething.getVerbose()){
+                                System.err.println("Empty packet received");
+                            }
+                            if (++emptyPacketsCount > maxEmptyPacketCount){
+                                throw new RuntimeException("Test failed, maxEmptyPacketCount reached");
+                            }
+
+                            continue;
+                        }
+
+                        buf.flip();
+                        byte[] dgramData = new byte[buf.remaining()];
+                        buf.get(dgramData);
+
+                        try {
+                            JdpJmxPacket packet = new JdpJmxPacket(dgramData);
+                            JdpDoSomething.printJdpPacket(packet);
+                            if(++count > maxPacketCount){
+                                   break;
+                            }
+                        } catch (JdpException e) {
+                            e.printStackTrace();
+                            throw new RuntimeException("Test failed");
+                        }
+
+                    }
+
+                    System.out.println("OK: Test passed");
+
+                } finally {
+                    sel.close();
+                    channel.close();
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+                throw new RuntimeException("Test failed");
+            }
+        }
+    }
+
+    public static void main(String[] args) {
+        try {
+            String discoveryPort = System.getProperty("com.sun.management.jdp.port");
+            String discoveryAddress = System.getProperty("com.sun.management.jdp.address");
+            if (discoveryAddress == null || discoveryPort == null) {
+                System.out.println("Test failed. address and port must be specified");
+                return;
+            }
+
+            int port = Integer.parseInt(discoveryPort);
+            InetAddress address = InetAddress.getByName(discoveryAddress);
+
+
+            ProtocolFamily family = (address instanceof Inet6Address)
+                    ? StandardProtocolFamily.INET6 : StandardProtocolFamily.INET;
+
+            DatagramChannel channel;
+
+            channel = DatagramChannel.open(family);
+            channel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
+            channel.bind(new InetSocketAddress(port));
+
+            Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
+            for (NetworkInterface interf : Collections.list(nets)) {
+                if (interf.supportsMulticast()) {
+                    try {
+                        channel.join(address, interf);
+                    } catch (IOException e) {
+                        // Skip not configured interfaces
+                    }
+                }
+            }
+
+            PacketListener listener = new PacketListener(channel);
+            new Thread(listener, "Jdp Client").start();
+
+        } catch (RuntimeException e){
+            System.out.println("Test failed.");
+        } catch (Exception e) {
+            e.printStackTrace();
+            System.out.println("Test failed. unexpected error " + e);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/management/jdp/JdpDoSomething.java	Sun Feb 03 21:39:58 2013 +0400
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.Objects;
+
+import sun.management.jdp.JdpJmxPacket;
+import sun.management.jdp.JdpException;
+
+public class JdpDoSomething {
+
+    private static final String lockFileName = "JdpDoSomething.lck";
+    private static final boolean verbose=false;
+
+    public static boolean getVerbose(){
+        return verbose;
+    }
+
+    public static void printJdpPacket(JdpJmxPacket p) {
+        if (getVerbose()) {
+            try {
+                RandomAccessFile f = new RandomAccessFile("out.dmp", "rw");
+                f.write(p.getPacketData());
+                f.close();
+            } catch (IOException e) {
+                System.out.println("Can't write a dump file: " + e);
+            }
+
+            System.out.println("Id: " + p.getId());
+            System.out.println("Jmx: " + p.getJmxServiceUrl());
+            System.out.println("Main: " + p.getMainClass());
+            System.out.println("InstanceName: " + p.getInstanceName());
+
+            System.out.flush();
+        }
+    }
+
+    public static void compaireJdpPacketEx(JdpJmxPacket p1, JdpJmxPacket p2)
+    throws JdpException {
+
+        if (!Objects.equals(p1, p1)) {
+            throw new JdpException("Packet mismatch error");
+        }
+
+        if (!Objects.equals(p1.getMainClass(), p2.getMainClass())) {
+            throw new JdpException("Packet mismatch error (main class)");
+        }
+
+        if (!Objects.equals(p1.getInstanceName(), p2.getInstanceName())) {
+            throw new JdpException("Packet mismatch error (instance name)");
+        }
+    }
+
+    public static void doSomething() {
+        try {
+            File lockFile = new File(lockFileName);
+            lockFile.createNewFile();
+
+            while (lockFile.exists()) {
+                long datetime = lockFile.lastModified();
+                long epoch = System.currentTimeMillis() / 1000;
+
+                // Don't allow test app to run more than an hour
+                if (epoch - datetime > 3600) {
+                    System.err.println("Lock is too old. Aborting");
+                    return;
+                }
+                Thread.sleep(1);
+            }
+
+        } catch (Throwable e) {
+            System.err.println("Something bad happens:" + e);
+        }
+    }
+
+    public static void main(String args[]) throws Exception {
+        System.err.println("main enter");
+        doSomething();
+        System.err.println("main exit");
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/management/jdp/JdpTest.sh	Sun Feb 03 21:39:58 2013 +0400
@@ -0,0 +1,323 @@
+#!/bin/sh
+
+# Copyright (c) 2011, 2012 Oracle and/or its affiliates. All rights reserved.
+# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+# 
+# This code is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License version 2 only, as
+# published by the Free Software Foundation.
+# 
+# This code 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 General Public License
+# version 2 for more details (a copy is included in the LICENSE file that
+# accompanied this code).
+# 
+# You should have received a copy of the GNU General Public License version
+# 2 along with this work; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+# or visit www.oracle.com if you need additional information or have any
+# questions.
+
+# @test
+# @bug 7169888 
+# @build JdpUnitTest JdpClient JdpDoSomething 
+# @run shell JdpTest.sh --jtreg --no-compile
+# @summary No word Failed expected in the test output
+
+_verbose=no
+_jtreg=no
+_compile=yes
+
+# temporary disable jcmd related tests
+# _testsuite="01,02,03,04,05"
+_testsuite="01,02,04"
+
+_pwd=`pwd`
+
+_testclasses=".classes"
+_testsrc="${_pwd}"
+_lockFileName="JdpDoSomething.lck"
+
+_logname=".classes/output.txt"
+_last_pid=""
+
+
+_compile(){
+
+    if [ ! -e ${_testclasses} ]
+    then
+	  mkdir -p ${_testclasses}
+    fi   
+
+    rm -f ${_testclasses}/*.class
+
+    # Compile testcase
+    ${TESTJAVA}/bin/javac -d ${_testclasses} JdpUnitTest.java \
+                                             JdpDoSomething.java  \
+                                             JdpClient.java
+
+   
+    if [ ! -e ${_testclasses}/JdpDoSomething.class -o ! -e ${_testclasses}/JdpClient.class -o ! -e ${_testclasses}/JdpUnitTest.class ]
+    then
+      echo "ERROR: Can't compile"
+      exit -1
+    fi
+}
+
+
+_app_start(){
+
+  testappname=$1
+  shift
+
+  ${TESTJAVA}/bin/java -server $* -cp ${_testclasses} ${testappname}  >> ${_logname} 2>&1 &
+ _last_pid=$!
+
+  npid=`_get_pid`
+  if [ "${npid}" = "" ]
+  then
+     echo "ERROR: Test app not started"
+     if [ "${_jtreg}" = "yes" ]
+     then
+       exit -1
+     fi
+  fi
+}
+
+_get_pid(){
+    ${TESTJAVA}/bin/jps | sed -n "/Jdp/s/ .*//p"
+}
+
+_app_stop(){
+   rm ${_lockFileName}
+
+# wait until VM is actually shuts down
+  while true 
+  do
+    npid=`_get_pid`
+    if [ "${npid}" = "" ] 
+    then
+      break
+    fi
+    sleep 1
+  done 
+}
+   
+_testme(){
+  ${TESTJAVA}/bin/java \
+  -cp ${_testclasses} \
+  $* \
+    -Dcom.sun.management.jdp.port=7095 \
+    -Dcom.sun.management.jdp.address=239.255.255.225 \
+  JdpClient 
+
+}   
+
+
+_jcmd(){
+    ${TESTJAVA}/bin/jcmd JdpDoSomething $* > /dev/null 2>/dev/null
+} 
+
+
+_echo(){
+    echo "$*"
+    echo "$*" >> ${_logname}
+}
+   
+# ============= TESTS ======================================
+   
+test_01(){
+		
+    _echo "**** Test one ****"		
+
+    _app_start JdpUnitTest \
+    -Dcom.sun.management.jdp.port=7095 \
+    -Dcom.sun.management.jdp.address=239.255.255.225 \
+    -Dcom.sun.management.jdp.pause=5
+
+    res=`_testme`
+
+    case "${res}" in 
+     OK*)  
+	_echo "Passed"
+     ;;
+     *)
+	_echo "Failed!"
+     ;;
+    esac
+
+    _app_stop
+}  
+
+test_02(){
+		
+    _echo "**** Test two ****"		
+
+    _app_start JdpDoSomething \
+     -Dcom.sun.management.jdp.port=7095 \
+     -Dcom.sun.management.jdp.address=239.255.255.225 \
+     -Dcom.sun.management.jdp.pause=5 \
+     -Dcom.sun.management.jmxremote.port=4545 \
+     -Dcom.sun.management.jmxremote.authenticate=false \
+     -Dcom.sun.management.jmxremote.ssl=false
+
+    res=`_testme`
+
+    case "${res}" in 
+     OK*)  
+	_echo "Passed"
+     ;;
+     *)
+	_echo "Failed!"
+     ;;
+    esac
+
+    _app_stop
+}  
+
+test_03(){
+		
+    _echo "**** Test three ****"
+
+    _app_start JdpDoSomething
+    
+    _jcmd  ManagementAgent.start\
+                jdp.port=7095 \
+                jdp.address=239.255.255.225 \
+                jdp.pause=5 \
+                jmxremote.port=4545 \
+                jmxremote.authenticate=false \
+                jmxremote.ssl=false
+
+    res=`_testme`
+
+    case "${res}" in 
+     OK*)  
+	_echo "Passed"
+     ;;
+     *)
+	_echo "Failed!"
+     ;;
+    esac
+
+    _app_stop
+}  
+
+test_04(){
+
+    _echo "**** Test four ****"
+
+    _app_start JdpDoSomething \
+     -Dcom.sun.management.jmxremote.autodiscovery=true \
+     -Dcom.sun.management.jmxremote.port=4545 \
+     -Dcom.sun.management.jmxremote.authenticate=false \
+     -Dcom.sun.management.jmxremote.ssl=false
+
+    res=`_testme`
+
+    case "${res}" in
+     OK*)
+	_echo "Passed"
+     ;;
+     *)
+	_echo "Failed!"
+     ;;
+    esac
+
+    _app_stop
+}
+
+test_05(){
+
+    _echo "**** Test five ****"
+
+    _app_start JdpDoSomething
+
+    _jcmd  ManagementAgent.start\
+                jmxremote.autodiscovery=true \
+                jmxremote.port=4545 \
+                jmxremote.authenticate=false \
+                jmxremote.ssl=false
+
+
+    res=`_testme`
+
+    case "${res}" in
+     OK*)
+	_echo "Passed"
+     ;;
+     *)
+	_echo "Failed!"
+     ;;
+    esac
+
+    _app_stop
+}
+
+
+# ============= MAIN =======================================
+
+if [ "x${TESTJAVA}" = "x" ]
+then
+  echo "TESTJAVA env have to be set"
+  exit
+fi
+
+#------------------------------------------------------------------------------
+# reading parameters 
+
+for parm in "$@"  
+do
+   case $parm in
+  --verbose)      _verbose=yes  ;;
+  --jtreg)        _jtreg=yes    ;;
+  --no-compile)   _compile=no   ;;
+  --testsuite=*)  _testsuite=`_echo $parm | sed "s,^--.*=\(.*\),\1,"`  ;;
+  *) 
+     echo "Undefined parameter $parm. Try --help for help" 
+     exit 
+   ;;
+ esac 
+done
+
+if [ ${_compile} = "yes" ]
+then
+ _compile
+fi
+
+if [ ${_jtreg} = "yes" ]
+then
+ _testclasses=${TESTCLASSES}
+ _testsrc=${TESTSRC}
+ _logname="output.txt"
+fi
+
+# Make sure _tesclasses is absolute path
+tt=`echo ${_testclasses} | sed -e 's,/,,'`
+if [ ${tt} = ${_testclasses} ]
+then
+  _testclasses="${_pwd}/${_testclasses}"
+fi
+
+_policyname="${_testclasses}/policy"
+
+rm -f ${_logname}
+rm -f ${_policyname}
+
+if [ -e ${_testsrc}/policy.tpl ]
+then
+
+cat ${_testsrc}/policy.tpl | \
+     sed -e "s,@_TESTCLASSES@,${_testclasses},g" -e "s,@TESTJAVA@,${TESTJAVA},g" \
+ > ${_policyname}
+ 
+fi
+ 
+# Local mode tests
+for i in `echo ${_testsuite} | sed -e "s/,/ /g"`
+do
+  test_${i} 
+done
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/management/jdp/JdpUnitTest.java	Sun Feb 03 21:39:58 2013 +0400
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code 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 General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.UUID;
+
+import sun.management.jdp.JdpController;
+import sun.management.jdp.JdpPacket;
+import sun.management.jdp.JdpJmxPacket;
+import sun.management.jdp.JdpException;
+
+public class JdpUnitTest {
+
+    /**
+     * This test tests that complete packet is build correctly
+     */
+    public static void PacketBuilderTest()
+        throws IOException, JdpException {
+
+        /* Complete packet test */
+        {
+            JdpJmxPacket p1 = new JdpJmxPacket(UUID.randomUUID(), "fake://unit-test");
+            p1.setMainClass("FakeUnitTest");
+            p1.setInstanceName("Fake");
+            byte[] b = p1.getPacketData();
+
+            JdpJmxPacket p2 = new JdpJmxPacket(b);
+            JdpDoSomething.printJdpPacket(p1);
+            JdpDoSomething.compaireJdpPacketEx(p1, p2);
+        }
+
+        /*Missed field packet test*/
+        {
+            JdpJmxPacket p1 = new JdpJmxPacket(UUID.randomUUID(), "fake://unit-test");
+            p1.setMainClass("FakeUnitTest");
+            p1.setInstanceName(null);
+            byte[] b = p1.getPacketData();
+
+            JdpJmxPacket p2 = new JdpJmxPacket(b);
+            JdpDoSomething.printJdpPacket(p1);
+            JdpDoSomething.compaireJdpPacketEx(p1, p2);
+        }
+
+         System.out.println("OK: Test passed");
+
+    }
+
+    public static void startFakeDiscoveryService()
+            throws IOException, JdpException {
+
+        String discoveryPort = System.getProperty("com.sun.management.jdp.port");
+        String discoveryAddress = System.getProperty("com.sun.management.jdp.address");
+        InetAddress address = InetAddress.getByName(discoveryAddress);
+        int port = Integer.parseInt(discoveryPort);
+        JdpController.startDiscoveryService(address, port, "FakeDiscovery", "fake://unit-test");
+    }
+
+    public static void main(String[] args) {
+        try {
+            PacketBuilderTest();
+            startFakeDiscoveryService();
+            JdpDoSomething.doSomething();
+
+        } catch (Throwable e) {
+            e.printStackTrace();
+            System.out.println("Test failed. unexpected error " + e);
+        }
+    }
+}