/*
 * Copyright (c) 2006-2007 Sun Microsystems, Inc.  All rights reserved.
 *
 *  The Sun Project JXTA(TM) Software License
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  1. Redistributions of source code must retain the above copyright notice,
 *     this list of conditions and the following disclaimer.
 *
 *  2. Redistributions in binary form must reproduce the above copyright notice,
 *     this list of conditions and the following disclaimer in the documentation
 *     and/or other materials provided with the distribution.
 *
 *  3. The end-user documentation included with the redistribution, if any, must
 *     include the following acknowledgment: "This product includes software
 *     developed by Sun Microsystems, Inc. for JXTA(TM) technology."
 *     Alternately, this acknowledgment may appear in the software itself, if
 *     and wherever such third-party acknowledgments normally appear.
 *
 *  4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA" must
 *     not be used to endorse or promote products derived from this software
 *     without prior written permission. For written permission, please contact
 *     Project JXTA at http://www.jxta.org.
 *
 *  5. Products derived from this software may not be called "JXTA", nor may
 *     "JXTA" appear in their name, without prior written permission of Sun.
 *
 *  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 *  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 *  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SUN
 *  MICROSYSTEMS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
 *  OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 *  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 *  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 *  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *  JXTA is a registered trademark of Sun Microsystems, Inc. in the United
 *  States and other countries.
 *
 *  Please see the license information page at :
 *  <http://www.jxta.org/project/www/license.html> for instructions on use of
 *  the license in source files.
 *
 *  ====================================================================
 *
 *  This software consists of voluntary contributions made by many individuals
 *  on behalf of Project JXTA. For more information on Project JXTA, please see
 *  http://www.jxta.org.
 *
 *  This license is based on the BSD license adopted by the Apache Foundation.
 */

package net.jxta.platform;

import net.jxta.document.Advertisement;
import net.jxta.document.AdvertisementFactory;
import net.jxta.document.MimeMediaType;
import net.jxta.document.StructuredDocumentFactory;
import net.jxta.document.StructuredDocumentUtils;
import net.jxta.document.XMLDocument;
import net.jxta.document.XMLElement;
import net.jxta.endpoint.EndpointAddress;
import net.jxta.id.ID;
import net.jxta.id.IDFactory;
import net.jxta.impl.membership.pse.PSEUtils;
import net.jxta.impl.membership.pse.PSEUtils.IssuerInfo;
import net.jxta.impl.peergroup.StdPeerGroup;
import net.jxta.impl.protocol.HTTPAdv;
import net.jxta.impl.protocol.PSEConfigAdv;
import net.jxta.impl.protocol.PeerGroupConfigAdv;
import net.jxta.impl.protocol.PlatformConfig;
import net.jxta.impl.protocol.RdvConfigAdv;
import net.jxta.impl.protocol.RdvConfigAdv.RendezVousConfiguration;
import net.jxta.impl.protocol.RelayConfigAdv;
import net.jxta.impl.protocol.TCPAdv;
import net.jxta.logging.Logging;
import net.jxta.peer.PeerID;
import net.jxta.peergroup.PeerGroup;
import net.jxta.peergroup.PeerGroupID;
import net.jxta.protocol.ConfigParams;
import net.jxta.protocol.TransportAdvertisement;
import javax.security.cert.CertificateException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import java.util.List;
import java.util.MissingResourceException;
import java.util.NoSuchElementException;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
import java.util.Set;
import java.util.logging.Logger;
import net.jxta.impl.protocol.MulticastAdv;

/**
 * NetworkConfigurator provides a simple programmatic interface for JXTA configuration.
 * <p/>
 * By default, it defines an edge configuration with TCP in auto mode w/port
 * range 9701-9799, multicast enabled on group "224.0.1.85", and port 1234,
 * HTTP transport with only outgoing enabled.
 * <p/>
 * By default a new PeerID is always generated. This can be overridden via
 * {@link NetworkConfigurator#setPeerID} method or loading a PlatformConfig via
 * {@link NetworkConfigurator#load}.
 * <p/>
 * A facility is provided to initialize a configuration by loading from an
 * existing configuration. This provides limited platform configuration lifecycle
 * management as well as configuration change management.
 * <p/>
 * Also by default, this class sets the default platform configurator to
 * {@link net.jxta.impl.peergroup.NullConfigurator}. <code>NullConfigurator<code>
 * is a no operation configurator intended to prevent any other configurators from
 * being invoked.
 * <p/>
 * NetworkConfigurator makes use of classes from the {@code net.jxta.impl.*}
 * packages. Applications are very strongly encouraged to avoid importing these
 * classes as their interfaces may change without notice in future JXTA releases.
 * The NetworkConfigurator API abstracts the configuration implementation details
 * and will provide continuity and stability i.e. the NetworkConfigurator API
 * won't change and it will automatically accommodate changes to service
 * configuration.
 * <p/>
 * <em> Configuration example :</em>
 * <pre>
 *     NetworkConfigurator config = new NetworkConfigurator();
 *     if (!config.exists()) {
 *         // Create a new configuration with a new name, principal, and pass
 *         config.setName("New Name");
 *         config.setPrincipal("username");
 *         config.setPassword("password");
 *         try {
 *             //persist it
 *             config.save();
 *         } catch (IOException io) {
 *             // deal with the io error
 *         }
 *      } else {
 *        // Load the pre-existing configuration
 *        File pc = new File(config.getHome(), "PlatformConfig");
 *        try {
 *            config.load(pc.toURI());
 *            // make changes if so desired
 *             ..
 *             ..
 *            // store the PlatformConfig under the default home
 *            config.save();
 *        } catch (CertificateException ce) {
 *            // In case the root cert is invalid, this creates a new one
 *            try {
 *                  //principal
 *                  config.setPrincipal("principal");
 *                  //password to encrypt private key with
 *                  config.setPassword("password");
 *                  config.save();
 *              } catch (Exception e) {
 *                  e.printStackTrace();
 *              }
 *        }
 * <p/>
 * </pre>
 *
 * @since JXTA JSE 2.4
 */
public class NetworkConfigurator {

    /**
     * Logger
     */
    private final static transient Logger LOG = Logger.getLogger(NetworkConfigurator.class.getName());

    // begin configuration modes

    /**
     * Relay off Mode
     */
    public final static int RELAY_OFF = 1 << 2;

    /**
     * Relay client Mode
     */
    public final static int RELAY_CLIENT = 1 << 3;

    /**
     * Relay Server Mode
     */
    public final static int RELAY_SERVER = 1 << 4;

    /**
     * Proxy Server Mode
     */
    public final static int PROXY_SERVER = 1 << 5;

    /**
     * TCP transport client Mode
     */
    public final static int TCP_CLIENT = 1 << 6;

    /**
     * TCP transport Server Mode
     */
    public final static int TCP_SERVER = 1 << 7;

    /**
     * HTTP transport client Mode
     */
    public final static int HTTP_CLIENT = 1 << 8;

    /**
     * HTTP transport server Mode
     */
    public final static int HTTP_SERVER = 1 << 9;

    /**
     * IP multicast transport Mode
     */
    public final static int IP_MULTICAST = 1 << 10;

    /**
     * RendezVousService Mode
     */
    public final static int RDV_SERVER = 1 << 11;

    /**
     * RendezVousService Client
     */
    public final static int RDV_CLIENT = 1 << 12;

    /**
     * RendezVousService Ad-Hoc mode
     */
    public final static int RDV_AD_HOC = 1 << 13;

    /**
     * HTTP2 (netty http tunnel) client
     */
    public final static int HTTP2_CLIENT = 1 << 14;

    /**
     * HTTP2 (netty http tunnel) server
     */
    public final static int HTTP2_SERVER = 1 << 15;

    /**
     * Default AD-HOC configuration
     */
    public final static int ADHOC_NODE = TCP_CLIENT | TCP_SERVER | IP_MULTICAST | RDV_AD_HOC | RELAY_OFF;

    /**
     * Default Edge configuration
     */
    public final static int EDGE_NODE = TCP_CLIENT | TCP_SERVER | HTTP_CLIENT | HTTP2_CLIENT | IP_MULTICAST | RDV_CLIENT | RELAY_CLIENT;

    /**
     * Default Rendezvous configuration
     */
    public final static int RDV_NODE = RDV_SERVER | TCP_CLIENT | TCP_SERVER | HTTP_SERVER | HTTP2_SERVER;

    /**
     * Default Relay configuration
     */
    public final static int RELAY_NODE = RELAY_SERVER | TCP_CLIENT | TCP_SERVER | HTTP_SERVER | HTTP2_SERVER;

//    /**
//     * Default Proxy configuration
//     *
//     * @since 2.6 Will be removed in a future release
//     */
//    @Deprecated
//    public final static int PROXY_NODE = PROXY_SERVER | RELAY_NODE;

//    /**
//     * Default Rendezvous/Relay/Proxy configuration
//     *
//     * @since 2.6 Will be removed in a future release
//     */
//    @Deprecated
//    public final static int RDV_RELAY_PROXY_NODE = RDV_NODE | PROXY_NODE;

    // end configuration modes

    /**
     * Default mode
     */
    protected transient int mode = EDGE_NODE;

    /**
     * Default PlatformConfig Peer Description
     */
    protected transient String description = "Platform Config Advertisement created by : " + NetworkConfigurator.class.getName();

    /**
     * The location which will serve as the parent for all stored items used
     * by JXTA.
     */
    private transient URI storeHome = null;

    /**
     * Default peer name
     */
    protected transient String name = "unknown";

    /**
     * AuthenticationType used by PSEMembership to specify the type of authentication.
     */
    protected transient String authenticationType = null;

    /**
     * Password value used to generate root Certificate and to protect the
     * Certificate's PrivateKey.
     */
    protected transient String password = null;

    /**
     * Default PeerID
     */
    protected transient PeerID peerid = null;

    /**
     * Principal value used to generate root certificate
     */
    protected transient String principal = null;

    /**
     * Public Certificate chain
     */
    protected transient X509Certificate[] cert = null;

    /**
     * Subject private key
     */
    protected transient PrivateKey subjectPkey = null;

    /**
     * Freestanding keystore location
     */
    protected transient URI keyStoreLocation = null;

    /**
     * Proxy Service Document
     */
    @Deprecated
    protected transient XMLElement proxyConfig;

    /**
     * Personal Security Environment Config Advertisement
     *
     * @see net.jxta.impl.membership.pse.PSEConfig
     */
    protected transient PSEConfigAdv pseConf;

    /**
     * Rendezvous Config Advertisement
     */
    protected transient RdvConfigAdv rdvConfig;

    /**
     * Default Rendezvous Seeding URI
     */
    protected URI rdvSeedingURI = null;

    /**
     * Relay Config Advertisement
     */
    protected transient RelayConfigAdv relayConfig;

    /**
     * Default Relay Seeding URI
     */
    protected transient URI relaySeedingURI = null;

    /**
     * TCP Config Advertisement
     */
    protected transient TCPAdv tcpConfig;

    /**
     * Multicating Config Advertisement
     */
    protected transient MulticastAdv multicastConfig;

    /**
     * Default TCP transport state
     */
    protected transient boolean tcpEnabled = true;

    /**
     * Default Multicast transport state
     */
    protected transient boolean multicastEnabled = true;

    /**
     * HTTP Config Advertisement
     */
    protected transient HTTPAdv httpConfig;

    /**
     * Default HTTP transport state
     */
    protected transient boolean httpEnabled = true;

    /**
     * HTTP2 Config Advertisement
     */
    protected transient TCPAdv http2Config;

    /**
     * Default HTTP2 transport state
     */
    protected transient boolean http2Enabled = true;

    /**
     * Infrastructure Peer Group Configuration
     */
    protected transient PeerGroupConfigAdv infraPeerGroupConfig;

    /**
     * Creates NetworkConfigurator instance with default AD-HOC configuration
     *
     * @param storeHome the URI to persistent store
     * @return NetworkConfigurator instance with default AD-HOC configuration
     */
    public static NetworkConfigurator newAdHocConfiguration(URI storeHome) {
        return new NetworkConfigurator(ADHOC_NODE, storeHome);
    }

    /**
     * Creates NetworkConfigurator instance with default Edge configuration
     *
     * @param storeHome the URI to persistent store
     * @return NetworkConfigurator instance with default AD-HOC configuration
     */
    public static NetworkConfigurator newEdgeConfiguration(URI storeHome) {
        return new NetworkConfigurator(EDGE_NODE, storeHome);
    }

    /**
     * Creates NetworkConfigurator instance with default Rendezvous configuration
     *
     * @param storeHome the URI to persistent store
     * @return NetworkConfigurator instance with default Rendezvous configuration
     */
    public static NetworkConfigurator newRdvConfiguration(URI storeHome) {
        return new NetworkConfigurator(RDV_NODE, storeHome);
    }

    /**
     * Creates NetworkConfigurator instance with default Relay configuration
     *
     * @param storeHome the URI to persistent store
     * @return NetworkConfigurator instance with default Relay configuration
     */
    public static NetworkConfigurator newRelayConfiguration(URI storeHome) {
        return new NetworkConfigurator(RELAY_NODE, storeHome);
    }

    /**
     * Creates NetworkConfigurator instance with default Rendezvous configuration
     *
     * @param storeHome the URI to persistent store
     * @return NetworkConfigurator instance with default Rendezvous configuration
     */
    public static NetworkConfigurator newRdvRelayConfiguration(URI storeHome) {
        return new NetworkConfigurator(RDV_NODE | RELAY_SERVER, storeHome);
    }

//    /**
//     * Creates NetworkConfigurator instance with default Proxy configuration
//     *
//     * @param storeHome the URI to persistent store
//     * @return NetworkConfigurator instance with defaultProxy configuration
//     *
//     * @since 2.6 Will be removed in a future release
//     */
//    @Deprecated
//    public static NetworkConfigurator newProxyConfiguration(URI storeHome) {
//        return new NetworkConfigurator(PROXY_NODE, storeHome);
//    }

//    /**
//     * Creates NetworkConfigurator instance with default Rendezvous, Relay, Proxy configuration
//     *
//     * @param storeHome the URI to persistent store
//     * @return NetworkConfigurator instance with default Rendezvous, Relay, Proxy configuration
//     *
//     * @since 2.6 It will be removed in a future release
//     */
//    @Deprecated
//    public static NetworkConfigurator newRdvRelayProxyConfiguration(URI storeHome) {
//        return new NetworkConfigurator(RDV_RELAY_PROXY_NODE, storeHome);
//    }

    /**
     * Creates the default NetworkConfigurator. The configuration is stored  with a default configuration mode of EDGE_NODE
     */
    public NetworkConfigurator() {
        this(EDGE_NODE, new File(".jxta").toURI());
    }

    /**
     * Creates a NetworkConfigurator with the default configuration of the
     * specified mode. <p/>Valid modes include ADHOC_NODE, EDGE_NODE, RDV_NODE
     * PROXY_NODE, RELAY_NODE, RDV_RELAY_PROXY_NODE, or any combination of
     * specific configuration.<p/> e.g. RDV_NODE | HTTP_CLIENT
     *
     * @param mode      the new configuration mode
     * @param storeHome the URI to persistent store
     * @see #setMode
     */
    public NetworkConfigurator(int mode, URI storeHome) {

        Logging.logCheckedFine(LOG, "Creating a default configuration");

        setStoreHome(storeHome);

        httpConfig = createHttpAdv();
        rdvConfig = createRdvConfigAdv();
        relayConfig = createRelayConfigAdv();
        // proxyConfig = createProxyAdv();
        tcpConfig = createTcpAdv();
        multicastConfig = createMulticastAdv();
        http2Config = createHttp2Adv();
        infraPeerGroupConfig = createInfraConfigAdv();

        setMode(mode);
    }

    /**
     * Sets PlaformConfig Peer Description element
     *
     * @param description the peer description
     */
    public void setDescription(String description) {
        this.description = description;
    }

    /**
     * Set the current directory for configuration and cache persistent store
     * <p/>(default is $CWD/.jxta)
     * <p/>
     * <dt>Simple example :</dt>
     * <pre>
     *  <code>
     *   //Create an application home
     *   File appHome = new File(System.getProperty("JXTA_HOME", ".cache"));
     *   //Create an instance home under the application home
     *   File instanceHome = new File(appHome, instanceName);
     *   jxtaConfig.setHome(instanceHome);
     *   </code>
     * </pre>
     *
     * @param home the new home value
     * @see #getHome
     */
    public void setHome(File home) {
        this.storeHome = home.toURI();
    }

    /**
     * Returns the current directory for configuration and cache persistent
     * store. This is the same location as returned by {@link #getStoreHome()}
     * which is more general than this method.
     *
     * @return Returns the current home directory
     * @see #setHome
     */
    public File getHome() {
        if ("file".equalsIgnoreCase(storeHome.getScheme())) {
            return new File(storeHome);
        } else {
            throw new UnsupportedOperationException("Home location is not a file:// URI : " + storeHome);
        }
    }

    /**
     * Returns the location which will serve as the parent for all stored items
     * used by JXTA.
     *
     * @return The location which will serve as the parent for all stored
     *         items used by JXTA.
     * @see net.jxta.peergroup.PeerGroup#getStoreHome()
     */
    public URI getStoreHome() {
        return storeHome;
    }

    /**
     * Sets the location which will serve as the parent for all stored items
     * used by JXTA.
     *
     * @param newHome new home directory URI
     * @see net.jxta.peergroup.PeerGroup#getStoreHome()
     */
    public void setStoreHome(URI newHome) {
        // Fail if the URI is not absolute.
        if (!newHome.isAbsolute()) {
            throw new IllegalArgumentException("Only absolute URIs accepted for store home location.");
        }

        // Fail if the URI is Opaque.
        if (newHome.isOpaque()) {
            throw new IllegalArgumentException("Only hierarchical URIs accepted for store home location.");
        }

        // FIXME this should be removed when 1488 is committed
        if (!"file".equalsIgnoreCase(newHome.getScheme())) {
            throw new IllegalArgumentException("Only file based URI currently supported");
        }

        // Adds a terminating /
        if (!newHome.toString().endsWith("/")) {
            newHome = URI.create(newHome.toString() + "/");
        }

        storeHome = newHome;
    }

    /**
     * Toggles HTTP transport state
     *
     * @param enabled if true, enables HTTP transport
     */
    public void setHttpEnabled(boolean enabled) {
        this.httpEnabled = enabled;
        if (!httpEnabled) {
            httpConfig.setClientEnabled(false);
            httpConfig.setServerEnabled(false);
        }
    }

    /**
     * Toggles the HTTP transport server (incoming) mode
     *
     * @param incoming toggles HTTP transport server mode
     */
    public void setHttpIncoming(boolean incoming) {
        httpConfig.setServerEnabled(incoming);
    }

    /**
     * Toggles the HTTP transport client (outgoing) mode
     *
     * @param outgoing toggles HTTP transport client mode
     */
    public void setHttpOutgoing(boolean outgoing) {
        httpConfig.setClientEnabled(outgoing);
    }

    /**
     * Sets the HTTP listening port (default 9901)
     *
     * @param port the new HTTP port value
     */
    public void setHttpPort(int port) {
        httpConfig.setPort(port);
    }

    /**
     * Sets the HTTP interface Address to bind the HTTP transport to
     * <p/>e.g. "192.168.1.1"
     *
     * @param address the new address value
     */
    public void setHttpInterfaceAddress(String address) {
        httpConfig.setInterfaceAddress(address);
    }

    /**
     * Returns the HTTP interface Address
     *
     * @param address the HTTP interface address
     */
    public String getHttpInterfaceAddress() {
        return httpConfig.getInterfaceAddress();
    }

    /**
     * Sets the HTTP JXTA Public Address
     * e.g. "192.168.1.1:9700"
     *
     * @param address   the HTTP transport public address
     * @param exclusive determines whether an address is advertised exclusively
     */
    public void setHttpPublicAddress(String address, boolean exclusive) {
        httpConfig.setServer(address);
        httpConfig.setPublicAddressOnly(exclusive);
    }

    public boolean isHttp2Enabled() {
    	return http2Enabled;
    }

    public void setHttp2Enabled(boolean enabled) {
    	http2Enabled = enabled;
    	
    	if(http2Enabled) {
    		http2Config.setClientEnabled(false);
    		http2Config.setServerEnabled(false);
    	}
    }

    public boolean getHttp2IncomingStatus() {
    	return http2Config.getServerEnabled();
    }

    public void setHttp2Incoming(boolean enabled) {
    	http2Config.setServerEnabled(enabled);
    }

    public boolean getHttp2OutgoingStatus() {
    	return http2Config.getClientEnabled();
    }

    public void setHttp2Outgoing(boolean enabled) {
    	http2Config.setClientEnabled(enabled);
    }

    public int getHttp2Port() {
    	return http2Config.getPort();
    }

    public void setHttp2Port(int port) {
    	http2Config.setPort(port);
    }

    public int getHttp2StartPort() {
    	return http2Config.getStartPort();
    }

    public void setHttp2StartPort(int startPort) {
    	http2Config.setStartPort(startPort);
    }

    public int getHttp2EndPort() {
    	return http2Config.getEndPort();
    }

    public void setHttp2EndPort(int endPort) {
    	http2Config.setEndPort(endPort);
    }

    public String getHttp2InterfaceAddress() {
    	return http2Config.getInterfaceAddress();
    }

    public void setHttp2InterfaceAddress(String address) {
    	http2Config.setInterfaceAddress(address);
    }

    public String getHttp2PublicAddress() {
    	return http2Config.getServer();
    }

    public boolean isHttp2PublicAddressExclusive() {
    	return http2Config.getPublicAddressOnly();
    }

    public void setHttp2PublicAddress(String address, boolean exclusive) {
    	http2Config.setServer(address);
        http2Config.setPublicAddressOnly(exclusive);
    }

    /**
     * Returns the HTTP JXTA Public Address
     *
     * @return exclusive determines whether an address is advertised exclusively
     */
    public String getHttpPublicAddress() {
        return httpConfig.getServer();
    }

    /**
     * Returns the HTTP JXTA Public Address exclusivity
     *
     * @return exclusive determines whether an address is advertised exclusively
     */
    public boolean isHttpPublicAddressExclusive() {
        return httpConfig.getPublicAddressOnly();
    }

    /**
     * Sets the ID which will be used for new net peer group instances.
     * <p/>
     * <p/>By Setting an alternate infrastructure PeerGroup ID (aka NetPeerGroup),
     * it prevents heterogeneous infrastructure PeerGroups from intersecting.
     * <p/>This is highly recommended practice for application deployment
     *
     * @param id the new infrastructure PeerGroupID as a string
     * @see net.jxta.peergroup.PeerGroupFactory#setNetPGID
     */
    public void setInfrastructureID(ID id) {
        if (id == null || id.equals(ID.nullID)) {
            throw new IllegalArgumentException("PeerGroupID can not be null");
        }
        infraPeerGroupConfig.setPeerGroupID(id);
    }

    /**
     * Sets the ID which will be used for new net peer group instances.
     * <p/>
     * <p/>By Setting an alternate infrastructure PeerGroup ID (aka NetPeerGroup),
     * it prevents heterogeneous infrastructure PeerGroups from intersecting.
     * <p/>This is highly recommended practice for application deployment
     *
     * @param idStr the new infrastructure PeerGroupID as a string
     * @see net.jxta.peergroup.PeerGroupFactory#setNetPGID
     */
    public void setInfrastructureID(String idStr) {
        if (idStr == null || idStr.length() == 0) {
            throw new IllegalArgumentException("PeerGroupID string can not be empty or null");
        }

        PeerGroupID pgid = (PeerGroupID) ID.create(URI.create(idStr));
        setInfrastructureID(pgid);
    }

    /**
     * Gets the ID which will be used for new net peer group instances.
     * <p/>
     *
     * @return the infrastructure PeerGroupID as a string
     */
    public String getInfrastructureIDStr() {
        return infraPeerGroupConfig.getPeerGroupID().toString();
    }

    /**
     * Sets the infrastructure PeerGroup name meta-data
     *
     * @param name the Infrastructure PeerGroup name
     * @see net.jxta.peergroup.PeerGroupFactory#setNetPGName
     */
    public void setInfrastructureName(String name) {
        infraPeerGroupConfig.setName(name);
    }

    /**
     * Gets the infrastructure PeerGroup name meta-data
     *
     * @return the Infrastructure PeerGroup name
     */
    public String getInfrastructureName() {
        return infraPeerGroupConfig.getName();
    }

    /**
     * Sets the infrastructure PeerGroup description meta-data
     *
     * @param description the infrastructure PeerGroup description
     * @see net.jxta.peergroup.PeerGroupFactory#setNetPGDesc
     */
    public void setInfrastructureDescriptionStr(String description) {
        infraPeerGroupConfig.setDescription(description);
    }

    /**
     * Returns the infrastructure PeerGroup description meta-data
     *
     * @return the infrastructure PeerGroup description meta-data
     */
    public String getInfrastructureDescriptionStr() {
        return infraPeerGroupConfig.getDescription();
    }

    /**
     * Sets the infrastructure PeerGroup description meta-data
     *
     * @param description the infrastructure PeerGroup description
     * @see net.jxta.peergroup.PeerGroupFactory#setNetPGDesc
     */
    public void setInfrastructureDesc(XMLElement description) {
        infraPeerGroupConfig.setDesc(description);
    }

    /**
     * Sets the current node configuration mode.
     * <p/>The default mode is EDGE, unless modified at construction time.
     * A node configuration mode defined a preset configuration
     * parameters based on a operating mode. i.e. an EDGE mode, enable
     * client/server side tcp, multicast, client side http, RelayService
     * client mode.
     * <p/> Valid modes include EDGE, RDV_SERVER,
     * RELAY_OFF, RELAY_CLIENT, RELAY_SERVER, PROXY_SERVER, or any combination
     * of which.<p/> e.g. RDV_SERVER + RELAY_SERVER
     *
     * @param mode the new configuration mode
     * @see #getMode
     */
    public void setMode(int mode) {
        this.mode = mode;
        if ((mode & PROXY_SERVER) == PROXY_SERVER && ((mode & RELAY_SERVER) != RELAY_SERVER)) {
            mode = mode | RELAY_SERVER;
        }

        // RELAY config
        relayConfig.setClientEnabled((mode & RELAY_CLIENT) == RELAY_CLIENT);
        relayConfig.setServerEnabled((mode & RELAY_SERVER) == RELAY_SERVER);

        // RDV_SERVER
        if ((mode & RDV_SERVER) == RDV_SERVER) {
            rdvConfig.setConfiguration(RendezVousConfiguration.RENDEZVOUS);
        } else if ((mode & RDV_CLIENT) == RDV_CLIENT) {
            rdvConfig.setConfiguration(RendezVousConfiguration.EDGE);
        } else if ((mode & RDV_AD_HOC) == RDV_AD_HOC) {
            rdvConfig.setConfiguration(RendezVousConfiguration.AD_HOC);
        }

        // TCP
        tcpConfig.setClientEnabled((mode & TCP_CLIENT) == TCP_CLIENT);
        tcpConfig.setServerEnabled((mode & TCP_SERVER) == TCP_SERVER);

        // HTTP
        httpConfig.setClientEnabled((mode & HTTP_CLIENT) == HTTP_CLIENT);
        httpConfig.setServerEnabled((mode & HTTP_SERVER) == HTTP_SERVER);

        // HTTP2
        http2Config.setClientEnabled((mode & HTTP2_CLIENT) == HTTP2_CLIENT);
        http2Config.setServerEnabled((mode & HTTP2_SERVER) == HTTP2_SERVER);

        // Multicast
        multicastConfig.setMulticastState((mode & IP_MULTICAST) == IP_MULTICAST);

        // EDGE
        if (mode == EDGE_NODE) {
            rdvConfig.setConfiguration(RendezVousConfiguration.EDGE);
        }
    }

    /**
     * Returns the current configuration mode
     * <p/>The default mode is EDGE, unless modified at construction time or through
     * Method {@link NetworkConfigurator#setMode}.  A node configuration mode defined a preset configuration
     * parameters based on a operating mode. i.e. an EDGE mode, enable
     * client/server side tcp, multicast, client side http, RelayService
     * client mode.
     *
     * @return mode  the current mode value
     * @see #setMode
     */
    public int getMode() {
        return mode;
    }

    /**
     * Sets the IP group multicast packet size
     *
     * @param size the new multicast packet
     */
    public void setMulticastSize(int size) {
        multicastConfig.setMulticastSize(size);
    }

    /**
     * Gets the IP group multicast packet size
     *
     * @return the multicast packet
     */
    public int getMulticastSize() {
        return multicastConfig.getMulticastSize();
    }

    /**
     * Sets the IP group multicast address (default 224.0.1.85)
     *
     * @param mcastAddress the new multicast group address
     * @see #setMulticastPort
     */
    public void setMulticastAddress(String mcastAddress) {
        multicastConfig.setMulticastAddr(mcastAddress);
    }

    /**
     * Gets the multicast network interface
     *
     * @return the multicast network interface, null if none specified
     */
    public String getMulticastInterface() {
        return multicastConfig.getMulticastInterface();
    }

    /**
     * Sets the multicast network interface
     *
     * @param interfaceAddress multicast network interface
     */
    public void setMulticastInterface(String interfaceAddress) {
        multicastConfig.setMulticastInterface(interfaceAddress);
    }

    /**
     * Sets the IP group multicast port (default 1234)
     *
     * @param port the new IP group multicast port
     * @see #setMulticastAddress
     */
    public void setMulticastPort(int port) {
        multicastConfig.setMulticastPort(port);
    }

    /**
     * Sets the group multicast thread pool size (default 10)
     *
     * @param size the new multicast thread pool size
     */
    public void setMulticastPoolSize(int size) {
        multicastConfig.setMulticastPoolSize(size);
    }

    /**
     * Sets the node name
     *
     * @param name node name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * Gets the node name
     *
     * @return node name
     */
    public String getName() {
        return this.name;
    }

    /**
     * Sets the Principal for the peer root certificate
     *
     * @param principal the new principal value
     * @see #setPassword
     * @see #getPrincipal
     * @see #setPrincipal
     */
    public void setPrincipal(String principal) {
        this.principal = principal;
    }

    /**
     * Gets the Principal for the peer root certificate
     *
     * @return principal  if a principal is set, null otherwise
     * @see #setPassword
     * @see #getPrincipal
     * @see #setPrincipal
     */
    public String getPrincipal() {
        return principal;
    }

    /**
     * Sets the public Certificate for this configuration.
     *
     * @param cert the new cert value
     */
    public void setCertificate(X509Certificate cert) {
        this.cert = new X509Certificate[]{cert};
    }

    /**
     * Returns the public Certificate for this configuration.
     *
     * @return X509Certificate
     */
    public X509Certificate getCertificate() {
        return (cert == null || cert.length == 0 ? null : cert[0]);
    }

    /**
     * Sets the public Certificate chain for this configuration.
     *
     * @param certificateChain the new Certificate chain value
     */
    public void setCertificateChain(X509Certificate[] certificateChain) {
        this.cert = certificateChain;
    }

    /**
     * Gets the public Certificate chain for this configuration.
     *
     * @return X509Certificate chain
     */
    public X509Certificate[] getCertificateChain() {
        return cert;
    }

    /**
     * Sets the Subject private key
     *
     * @param subjectPkey the subject private key
     */
    public void setPrivateKey(PrivateKey subjectPkey) {
        this.subjectPkey = subjectPkey;
    }

    /**
     * Gets the Subject private key
     *
     * @return the subject private key
     */
    public PrivateKey getPrivateKey() {
        return this.subjectPkey;
    }

    /**
     * Sets freestanding keystore location
     *
     * @param keyStoreLocation the absolute location of the freestanding keystore
     */
    public void setKeyStoreLocation(URI keyStoreLocation) {
        this.keyStoreLocation = keyStoreLocation;
    }

    /**
     * Gets the freestanding keystore location
     *
     * @return the location of the freestanding keystore
     */
    public URI getKeyStoreLocation() {
        return keyStoreLocation;
    }

    /**
     * Gets the authenticationType
     *
     * @return authenticationType the authenticationType value
     */
    public String getAuthenticationType() {
        return this.authenticationType;
    }

    /**
     * Sets the authenticationType
     *
     * @param authenticationType the new authenticationType value
     */
    public void setAuthenticationType(String authenticationType) {
        this.authenticationType = authenticationType;
    }

    /**
     * Sets the password used to sign the private key of the root certificate
     *
     * @param password the new password value
     * @see #setPassword
     * @see #getPrincipal
     * @see #setPrincipal
     */
    public void setPassword(String password) {
        this.password = password;
    }

    /**
     * Gets the password used to sign the private key of the root certificate
     *
     * @return password  if a password is set, null otherwise
     * @see #setPassword
     * @see #getPrincipal
     * @see #setPrincipal
     */
    public String getPassword() {
        return password;
    }

    /**
     * Sets the PeerID (by default, a new PeerID is generated).
     * <p/>Note: Persist the PeerID generated, or use load()
     * to avoid overridding a node's PeerID between restarts.
     *
     * @param peerid the new <code>net.jxta.peer.PeerID</code>
     */
    public void setPeerID(PeerID peerid) {
        this.peerid = peerid;
    }

    /**
     * Gets the PeerID
     *
     * @return peerid  the <code>net.jxta.peer.PeerID</code> value
     */
    public PeerID getPeerID() {
        return this.peerid;
    }

    /**
     * Sets Rendezvous Seeding URI
     *
     * @param seedURI Rendezvous service seeding URI
     */
    public void addRdvSeedingURI(URI seedURI) {
        rdvConfig.addSeedingURI(seedURI);
    }

//    /**
//     * Sets Rendezvous Access Control URI
//     * <p/>e.g. http://rdv.jxtahosts.net/cgi-bin/rendezvousACL.cgi?3
//     *
//     * @param aclURI Rendezvous Access Control URI
//     *
//     * @deprecated ACL seed lists are in functional conflict with 'UseOnlyRendezvousSeedsStatus'.
//     * They will be deprecated and removed in a future release.
//     */
//    @Deprecated
//    public void setRdvACLURI(URI aclURI) {
//        rdvConfig.setAclUri(aclURI);
//    }

//    /**
//     * Gets Rendezvous Access Control URI if set
//     * <p/>e.g. http://rdv.jxtahosts.net/cgi-bin/rendezvousACL.cgi?3
//     *
//     * @return aclURI Rendezvous Access Control URI
//     *
//     * @deprecated ACL seed lists are in functional conflict with 'UseOnlyRendezvousSeedsStatus'.
//     * They will be deprecated and removed in a future release.
//     */
//    @Deprecated
//    public URI getRdvACLURI() {
//        return rdvConfig.getAclUri();
//    }

//    /**
//     * Sets Relay Access Control URI
//     * <p/>e.g. http://rdv.jxtahosts.net/cgi-bin/relayACL.cgi?3
//     *
//     * @param aclURI Relay Access Control URI
//     *
//     * @deprecated ACL seed lists are in functional conflict with 'UseOnlyRelaySeedsStatus'.
//     * They will be deprecated and removed in a future release.
//     */
//    @Deprecated
//    public void setRelayACLURI(URI aclURI) {
//        relayConfig.setAclUri(aclURI);
//    }

//    /**
//     * Gets Relay Access Control URI if set
//     * <p/>e.g. http://rdv.jxtahosts.net/cgi-bin/relayACL.cgi?3
//     *
//     * @return aclURI Relay Access Control URI
//     *
//     * @deprecated ACL seed lists are in functional conflict with 'UseOnlyRelaySeedsStatus'.
//     * They will be deprecated and removed in a future release.
//     */
//    @Deprecated
//    public URI getRelayACLURI() {
//        return relayConfig.getAclUri();
//    }

    /**
     * Sets the RelayService maximum number of simultaneous relay clients
     *
     * @param relayMaxClients the new relayMaxClients value
     */
    public void setRelayMaxClients(int relayMaxClients) {
        if ((relayMaxClients != -1) && (relayMaxClients <= 0)) {
            throw new IllegalArgumentException("Relay Max Clients : " + relayMaxClients + " must be > 0");
        }
        relayConfig.setMaxClients(relayMaxClients);
    }

    /**
     * Sets the RelayService Seeding URI
     * <p/>A seeding URI (when read) is expected to provide a list of
     * physical endpoint addresse(s) to relay peers
     *
     * @param seedURI RelayService seeding URI
     */
    public void addRelaySeedingURI(URI seedURI) {
        relayConfig.addSeedingURI(seedURI);
    }

    /**
     * Sets the RendezVousService maximum number of simultaneous rendezvous clients
     *
     * @param rdvMaxClients the new rendezvousMaxClients value
     */
    public void setRendezvousMaxClients(int rdvMaxClients) {
        if ((rdvMaxClients != -1) && (rdvMaxClients <= 0)) {
            throw new IllegalArgumentException("Rendezvous Max Clients : " + rdvMaxClients + " must be > 0");
        }
        rdvConfig.setMaxClients(rdvMaxClients);
    }

    /**
     * Toggles TCP transport state
     *
     * @param enabled if true, enables TCP transport
     */
    public void setTcpEnabled(boolean enabled) {
        this.tcpEnabled = enabled;
        if (!tcpEnabled) {
            tcpConfig.setClientEnabled(false);
            tcpConfig.setServerEnabled(false);
        }
    }

    /**
     * Sets the TCP transport listening port (default 9701)
     *
     * @param port the new tcpPort value
     */
    public void setTcpPort(int port) {
        tcpConfig.setPort(port);
    }

    /**
     * Sets the lowest port on which the TCP Transport will listen if configured
     * to do so. Valid values are <code>-1</code>, <code>0</code> and
     * <code>1-65535</code>. The <code>-1</code> value is used to signify that
     * the port range feature should be disabled. The <code>0</code> specifies
     * that the Socket API dynamic port allocation should be used. For values
     * <code>1-65535</code> the value must be equal to or less than the value
     * used for end port.
     *
     * @param start the lowest port on which to listen.
     */
    public void setTcpStartPort(int start) {
        tcpConfig.setStartPort(start);
    }

    /**
     * Returns the highest port on which the TCP Transport will listen if
     * configured to do so. Valid values are <code>-1</code>, <code>0</code> and
     * <code>1-65535</code>. The <code>-1</code> value is used to signify that
     * the port range feature should be disabled. The <code>0</code> specifies
     * that the Socket API dynamic port allocation should be used. For values
     * <code>1-65535</code> the value must be equal to or greater than the value
     * used for start port.
     *
     * @param end the new TCP end port
     */
    public void setTcpEndPort(int end) {
        tcpConfig.setEndPort(end);
    }

    /**
     * Toggles TCP transport server (incoming) mode (default is on)
     *
     * @param incoming the new TCP server mode
     */
    public void setTcpIncoming(boolean incoming) {
        tcpConfig.setServerEnabled(incoming);
    }

    /**
     * Toggles TCP transport client (outgoing) mode (default is true)
     *
     * @param outgoing the new tcpOutgoing value
     */
    public void setTcpOutgoing(boolean outgoing) {
        tcpConfig.setClientEnabled(outgoing);
    }

    /**
     * Sets the TCP transport interface address
     * <p/>e.g. "192.168.1.1"
     *
     * @param address the TCP transport interface address
     */
    public void setTcpInterfaceAddress(String address) {
        tcpConfig.setInterfaceAddress(address);
    }

    /**
     * Sets the node public address
     * <p/>e.g. "192.168.1.1:9701"
     * <p/>This address is the physical address defined in a node's
     * AccessPointAdvertisement.  This often required for NAT'd/FW nodes
     *
     * @param address   the TCP transport public address
     * @param exclusive public address advertised exclusively
     */
    public void setTcpPublicAddress(String address, boolean exclusive) {
        tcpConfig.setServer(address);
        tcpConfig.setPublicAddressOnly(exclusive);
    }

    /**
     * Toggles whether to use IP group multicast (default is true)
     *
     * @param multicastOn the new useMulticast value
     */
    public void setUseMulticast(boolean multicastOn) {
        multicastConfig.setMulticastState(multicastOn);
    }

    /**
     * Determines whether to restrict RelayService leases to those defined in
     * the seed list. In other words, only registered endpoint address seeds
     * and seeds fetched from seeding URIs will be used.
     * </p>WARNING: Disabling 'use only relay seed' will cause this peer to
     * search and fetch RdvAdvertisements for use as relay candidates. Rdvs
     * are not necessarily relays.
     *
     * @param useOnlyRelaySeeds restrict RelayService lease to seed list
     */
    public void setUseOnlyRelaySeeds(boolean useOnlyRelaySeeds) {
        relayConfig.setUseOnlySeeds(useOnlyRelaySeeds);
    }

    /**
     * Determines whether to restrict RendezvousService leases to those defined in
     * the seed list. In other words, only registered endpoint address seeds
     * and seeds fetched from seeding URIs will be used.
     *
     * @param useOnlyRendezvouSeeds restrict RendezvousService lease to seed list
     */
    public void setUseOnlyRendezvousSeeds(boolean useOnlyRendezvouSeeds) {
        rdvConfig.setUseOnlySeeds(useOnlyRendezvouSeeds);
    }

    /**
     * Adds RelayService peer seed address
     * <p/>A RelayService seed is defined as a physical endpoint address
     * <p/>e.g. http://192.168.1.1:9700, or tcp://192.168.1.1:9701
     *
     * @param seedURI the relay seed URI
     */
    public void addSeedRelay(URI seedURI) {
        relayConfig.addSeedRelay(seedURI.toString());
    }

    /**
     * Adds Rendezvous peer seed, physical endpoint address
     * <p/>A RendezVousService seed is defined as a physical endpoint address
     * <p/>e.g. http://192.168.1.1:9700, or tcp://192.168.1.1:9701
     *
     * @param seedURI the rendezvous seed URI
     */
    public void addSeedRendezvous(URI seedURI) {
        rdvConfig.addSeedRendezvous(seedURI);
    }

    /**
     * Returns true if a PlatformConfig file exist under store home
     *
     * @return true if a PlatformConfig file exist under store home
     */
    public boolean exists() {

        URI platformConfig = storeHome.resolve("PlatformConfig");
        try {
            return null != read(platformConfig);
        } catch (IOException failed) {
            return false;
        }
    }

    /**
     * Sets the PeerID for this Configuration
     *
     * @param peerIdStr the new PeerID as a string
     */
    public void setPeerId(String peerIdStr) {
        this.peerid = (PeerID) ID.create(URI.create(peerIdStr));
    }

    /**
     * Sets the new RendezvousService seeding URI as a string.
     * <p/>A seeding URI (when read) is expected to provide a list of
     * physical endpoint address to rendezvous peers
     *
     * @param seedURIStr the new rendezvous seed URI as a string
     */
    public void addRdvSeedingURI(String seedURIStr) {
        rdvConfig.addSeedingURI(URI.create(seedURIStr));
    }

    /**
     * Sets the new RelayService seeding URI as a string.
     * <p/>A seeding URI (when read) is expected to provide a list of
     * physical endpoint address to relay peers
     *
     * @param seedURIStr the new RelayService seed URI as a string
     */
    public void addRelaySeedingURI(String seedURIStr) {
        relayConfig.addSeedingURI(URI.create(seedURIStr));
    }

    /**
     * Sets the List relaySeeds represented as Strings
     * <p/>A RelayService seed is defined as a physical endpoint address
     * <p/>e.g. http://192.168.1.1:9700, or tcp://192.168.1.1:9701
     *
     * @param seeds the Set RelayService seed URIs as a string
     */
    public void setRelaySeedURIs(List<String> seeds) {
        relayConfig.clearSeedRelays();
        for (String seedStr : seeds) {
            relayConfig.addSeedRelay(new EndpointAddress(seedStr));
        }
    }

    /**
     * Sets the relaySeeds represented as Strings
     * <p/>A seeding URI (when read) is expected to provide a list of
     * physical endpoint address to relay peers
     *
     * @param seedURIs the List relaySeeds represented as Strings
     */
    public void setRelaySeedingURIs(Set<String> seedURIs) {
        relayConfig.clearSeedingURIs();
        for (String seedStr : seedURIs) {
            relayConfig.addSeedingURI(URI.create(seedStr));
        }
    }

    /**
     * Clears the List of RelayService seeds
     */
    public void clearRelaySeeds() {
        relayConfig.clearSeedRelays();
    }

    /**
     * Clears the List of RelayService seeding URIs
     */
    public void clearRelaySeedingURIs() {
        relayConfig.clearSeedingURIs();
    }

    /**
     * Sets the List of RendezVousService seeds represented as Strings
     * <p/>A RendezvousService seed is defined as a physical endpoint address
     * <p/>e.g. http://192.168.1.1:9700, or tcp://192.168.1.1:9701
     *
     * @param seeds the Set of rendezvousSeeds represented as Strings
     */
    public void setRendezvousSeeds(Set<String> seeds) {
        rdvConfig.clearSeedRendezvous();
        for (String seedStr : seeds) {
            rdvConfig.addSeedRendezvous(URI.create(seedStr));
        }
    }

    /**
     * Sets the List of RendezVousService seeding URIs represented as Strings.
     * A seeding URI (when read) is expected to provide a list of
     * physical endpoint address to rendezvous peers.
     *
     * @param seedingURIs the List rendezvousSeeds represented as Strings.
     */
    public void setRendezvousSeedingURIs(List<String> seedingURIs) {
        rdvConfig.clearSeedingURIs();
        for (String seedStr : seedingURIs) {
            rdvConfig.addSeedingURI(URI.create(seedStr));
        }
    }

    /**
     * Clears the list of RendezVousService seeds
     */
    public void clearRendezvousSeeds() {
        rdvConfig.clearSeedRendezvous();
    }

    /**
     * Clears the list of RendezVousService seeding URIs
     */
    public void clearRendezvousSeedingURIs() {
        rdvConfig.clearSeedingURIs();
    }

    /**
     * Load a configuration from the specified store home uri
     * <p/>
     * e.g. file:/export/dist/EdgeConfig.xml, e.g. http://configserver.net/configservice?Edge
     *
     * @return The loaded configuration.
     * @throws IOException          if an i/o error occurs
     * @throws CertificateException if the MembershipService is invalid
     */
    public ConfigParams load() throws IOException, CertificateException {
        return load(storeHome.resolve("PlatformConfig"));
    }

    /**
     * Loads a configuration from a specified uri
     * <p/>
     * e.g. file:/export/dist/EdgeConfig.xml, e.g. http://configserver.net/configservice?Edge
     *
     * @param uri the URI to PlatformConfig
     * @return The loaded configuration.
     * @throws IOException          if an i/o error occurs
     * @throws CertificateException if the MemebershipService is invalid
     */
    public ConfigParams load(URI uri) throws IOException, CertificateException {

        if (uri == null) throw new IllegalArgumentException("URI can not be null");

        Logging.logCheckedFine(LOG, "Loading configuration : ", uri);

        PlatformConfig platformConfig = read(uri);

        name = platformConfig.getName();
        peerid = platformConfig.getPeerID();
        description = platformConfig.getDescription();

        XMLElement<?> param;

        // TCP
        tcpEnabled = platformConfig.isSvcEnabled(PeerGroup.tcpProtoClassID);
        tcpConfig = loadTcpAdv(platformConfig, PeerGroup.tcpProtoClassID);

        multicastEnabled = platformConfig.isSvcEnabled(PeerGroup.multicastProtoClassID);
        multicastConfig = loadMulticastAdv(platformConfig, PeerGroup.multicastProtoClassID);

        // HTTP
        try {
            param = (XMLElement) platformConfig.getServiceParam(PeerGroup.httpProtoClassID);
            httpEnabled = platformConfig.isSvcEnabled(PeerGroup.httpProtoClassID);

            Enumeration httpChilds = param.getChildren(TransportAdvertisement.getAdvertisementType());

            // get the TransportAdv from either TransportAdv
            if (httpChilds.hasMoreElements()) {
                param = (XMLElement) httpChilds.nextElement();
            } else {
                throw new IllegalStateException("Missing HTTP Advertisment");
            }
            // Read-in the adv as it is now.
            httpConfig = (HTTPAdv) AdvertisementFactory.newAdvertisement(param);
        } catch (Exception failure) {
            IOException ioe = new IOException("error processing the HTTP config advertisement");
            ioe.initCause(failure);
            throw ioe;
        }

        // HTTP2
        http2Enabled = platformConfig.isSvcEnabled(PeerGroup.http2ProtoClassID);
        http2Config = loadTcpAdv(platformConfig, PeerGroup.http2ProtoClassID);

//        // ProxyService
//        try {
//            param = (XMLElement) platformConfig.getServiceParam(PeerGroup.proxyClassID);
//            if (param != null && !platformConfig.isSvcEnabled(PeerGroup.proxyClassID)) {
//                mode = mode | PROXY_SERVER;
//            }
//        } catch (Exception failure) {
//            IOException ioe = new IOException("error processing the pse config advertisement");
//            ioe.initCause(failure);
//            throw ioe;
//        }

        // Rendezvous
        try {
            param = (XMLElement) platformConfig.getServiceParam(PeerGroup.rendezvousClassID);
            // backwards compatibility
            param.addAttribute("type", RdvConfigAdv.getAdvertisementType());
            rdvConfig = (RdvConfigAdv) AdvertisementFactory.newAdvertisement(param);
            if (rdvConfig.getConfiguration() == RendezVousConfiguration.AD_HOC) {
                mode = mode | RDV_AD_HOC;
            } else if (rdvConfig.getConfiguration() == RendezVousConfiguration.EDGE) {
                mode = mode | RDV_CLIENT;
            } else if (rdvConfig.getConfiguration() == RendezVousConfiguration.RENDEZVOUS) {
                mode = mode | RDV_SERVER;
            }
        } catch (Exception failure) {
            IOException ioe = new IOException("error processing the rendezvous config advertisement");
            ioe.initCause(failure);
            throw ioe;
        }

        // Relay
        try {
            param = (XMLElement) platformConfig.getServiceParam(PeerGroup.relayProtoClassID);
            if (param != null && !platformConfig.isSvcEnabled(PeerGroup.relayProtoClassID)) {
                mode = mode | RELAY_OFF;
            }
            // backwards compatibility
                param.addAttribute("type", RelayConfigAdv.getAdvertisementType());
                relayConfig = (RelayConfigAdv) AdvertisementFactory.newAdvertisement(param);
        } catch (Exception failure) {
            IOException ioe = new IOException("error processing the relay config advertisement");
            ioe.initCause(failure);
            throw ioe;
        }

        // PSE
        param = (XMLElement) platformConfig.getServiceParam(PeerGroup.membershipClassID);
        if (param != null) {

            Advertisement adv = null;
            try {
                adv = AdvertisementFactory.newAdvertisement(param);
            } catch (NoSuchElementException notAnAdv) {
                CertificateException cnfe = new CertificateException("No membership advertisement found");
                cnfe.initCause(notAnAdv);
            } catch (IllegalArgumentException invalidAdv) {
                CertificateException cnfe = new CertificateException("Invalid membership advertisement");
                cnfe.initCause(invalidAdv);
            }

            if (adv instanceof PSEConfigAdv) {
                pseConf = (PSEConfigAdv) adv;
                cert = pseConf.getCertificateChain();
            } else {
                throw new CertificateException("Error processing the Membership config advertisement. Unexpected membership advertisement "
                        + adv.getAdvertisementType());
            }
        }

        // Infra Group
        infraPeerGroupConfig = (PeerGroupConfigAdv) platformConfig.getSvcConfigAdvertisement(PeerGroup.peerGroupClassID);
        if (null == infraPeerGroupConfig) {
            infraPeerGroupConfig = createInfraConfigAdv();
            try {
                URI configPropsURI = storeHome.resolve("config.properties");
                InputStream configPropsIS = configPropsURI.toURL().openStream();
                ResourceBundle rsrcs = new PropertyResourceBundle(configPropsIS);
                configPropsIS.close();

                NetGroupTunables tunables = new NetGroupTunables(rsrcs, new NetGroupTunables());

                infraPeerGroupConfig.setPeerGroupID(tunables.id);
                infraPeerGroupConfig.setName(tunables.name);
                infraPeerGroupConfig.setDesc(tunables.desc);
            } catch (IOException ignored) {
                //ignored
            } catch (MissingResourceException ignored) {
                //ignored
            }
        }
        return platformConfig;
    }

    private TCPAdv loadTcpAdv(PlatformConfig platformConfig, ModuleClassID moduleClassID) {
    	XMLElement<?> param = (XMLElement<?>) platformConfig.getServiceParam(moduleClassID);
        Enumeration<?> tcpChilds = param.getChildren(TransportAdvertisement.getAdvertisementType());

        // get the TransportAdv from either TransportAdv or tcpConfig
        if (tcpChilds.hasMoreElements()) {
            param = (XMLElement<?>) tcpChilds.nextElement();
        } else {
            throw new IllegalStateException("Missing TCP Advertisement");
        }

        return (TCPAdv) AdvertisementFactory.newAdvertisement(param);
	}

    private MulticastAdv loadMulticastAdv(PlatformConfig platformConfig, ModuleClassID moduleClassID) {
        XMLElement<?> param2 = (XMLElement<?>) platformConfig.getServiceParam(moduleClassID);
        Enumeration<?> tcpChilds2 = param2.getChildren(TransportAdvertisement.getAdvertisementType());

        // get the TransportAdv from either TransportAdv or multicastConfig
        if (tcpChilds2.hasMoreElements()) {
            param2 = (XMLElement<?>) tcpChilds2.nextElement();
        } else {
            throw new IllegalStateException("Missing Multicast Advertisment");
        }

        return (MulticastAdv) AdvertisementFactory.newAdvertisement(param2);
    }

    /**
     * Persists a PlatformConfig advertisement under getStoreHome()+"/PlaformConfig"
     * <p/>
     * Home may be overridden by a call to setHome()
     *
     * @throws IOException If there is a failure saving the PlatformConfig.
     * @see #load
     */
    public void save() throws IOException {

        httpEnabled = (httpConfig.isClientEnabled() || httpConfig.isServerEnabled());
        tcpEnabled = (tcpConfig.isClientEnabled() || tcpConfig.isServerEnabled());
        http2Enabled = (http2Config.isClientEnabled() || http2Config.isServerEnabled());

        ConfigParams advertisement = getPlatformConfig();
        OutputStream out = null;

        try {
            if ("file".equalsIgnoreCase(storeHome.getScheme())) {
                File saveDir = new File(storeHome);
                saveDir.mkdirs();

                // Sadly we can't use URL.openConnection() to create the
                // OutputStream for file:// URLs. bogus.
                out = new FileOutputStream(new File(saveDir, "PlatformConfig"));
            } else {
                out = storeHome.resolve("PlatformConfig").toURL().openConnection().getOutputStream();
            }

            XMLDocument aDoc = (XMLDocument) advertisement.getDocument(MimeMediaType.XMLUTF8);
            OutputStreamWriter os = new OutputStreamWriter(out, "UTF-8");
            aDoc.sendToWriter(os);
            os.flush();
        } finally {
            if (null != out) {
                out.close();
            }
        }
    }

    /**
     * Returns a XMLDocument representation of an Advertisement
     *
     * @param enabled whether the param doc is enabled, adds a "isOff"
     *                element if disabled
     * @param adv     the Advertisement to retrieve the param doc from
     * @return the parmDoc value
     */
    protected XMLDocument getParmDoc(boolean enabled, Advertisement adv) {
        XMLDocument parmDoc = (XMLDocument) StructuredDocumentFactory.newStructuredDocument(MimeMediaType.XMLUTF8, "Parm");
        XMLDocument doc = (XMLDocument) adv.getDocument(MimeMediaType.XMLUTF8);

        StructuredDocumentUtils.copyElements(parmDoc, parmDoc, doc);
        if (!enabled) {
            parmDoc.appendChild(parmDoc.createElement("isOff"));
        }
        return parmDoc;
    }

    /**
     * Creates an HTTP transport advertisement
     *
     * @return an HTTP transport advertisement
     */
    protected HTTPAdv createHttpAdv() {
        httpConfig = (HTTPAdv) AdvertisementFactory.newAdvertisement(HTTPAdv.getAdvertisementType());
        httpConfig.setProtocol("http");
        httpConfig.setPort(9700);
        httpConfig.setClientEnabled((mode & HTTP_CLIENT) == HTTP_CLIENT);
        httpConfig.setServerEnabled((mode & HTTP_SERVER) == HTTP_SERVER);
        return httpConfig;
    }

    /**
     * Creates Personal Security Environment Config Advertisement
     * <p/>The configuration advertisement can include an optional seed certificate
     * chain and encrypted private key. If this seed information is present the PSE
     * Membership Service will require an initial authentication to unlock the
     * encrypted private key before creating the PSE keystore. The newly created
     * PSE keystore will be "seeded" with the certificate chain and the private key.
     *
     * @param principal principal
     * @param password  the password used to sign the private key of the root certificate
     * @return PSEConfigAdv an PSE config advertisement
     * @see net.jxta.impl.protocol.PSEConfigAdv
     */
    protected PSEConfigAdv createPSEAdv(String principal, String password) {
        pseConf = (PSEConfigAdv) AdvertisementFactory.newAdvertisement(PSEConfigAdv.getAdvertisementType());
        if (principal != null && password != null) {
            IssuerInfo info = PSEUtils.genCert(principal, null);

            pseConf.setCertificate(info.cert);
            pseConf.setPrivateKey(info.subjectPkey, password.toCharArray());
        }
        return pseConf;
    }

    /**
     * Creates Personal Security Environment Config Advertisement
     * <p/>The configuration advertisement can include an optional seed certificate
     * chain and encrypted private key. If this seed information is present the PSE
     * Membership Service will require an initial authentication to unlock the
     * encrypted private key before creating the PSE keystore. The newly created
     * PSE keystore will be "seeded" with the certificate chain and the private key.
     *
     * @param cert X509Certificate
     * @return PSEConfigAdv an PSE config advertisement
     * @see net.jxta.impl.protocol.PSEConfigAdv
     */
    protected PSEConfigAdv createPSEAdv(X509Certificate cert) {
        pseConf = (PSEConfigAdv) AdvertisementFactory.newAdvertisement(PSEConfigAdv.getAdvertisementType());
        if (subjectPkey != null && password != null) {
            pseConf.setCertificate(cert);
            pseConf.setPrivateKey(subjectPkey, password.toCharArray());
        }
        return pseConf;
    }

    /**
     * Creates Personal Security Environment Config Advertisement
     * <p/>The configuration advertisement can include an optional seed certificate
     * chain and encrypted private key. If this seed information is present the PSE
     * Membership Service will require an initial authentication to unlock the
     * encrypted private key before creating the PSE keystore. The newly created
     * PSE keystore will be "seeded" with the certificate chain and the private key.
     *
     * @param certificateChain X509Certificate[]
     * @return PSEConfigAdv an PSE config advertisement
     * @see net.jxta.impl.protocol.PSEConfigAdv
     */
    protected PSEConfigAdv createPSEAdv(X509Certificate[] certificateChain) {
        pseConf = (PSEConfigAdv) AdvertisementFactory.newAdvertisement(PSEConfigAdv.getAdvertisementType());
        if (subjectPkey != null && password != null) {
            pseConf.setCertificateChain(certificateChain);
            pseConf.setPrivateKey(subjectPkey, password.toCharArray());
        }
        return pseConf;
    }

    /**
     * Creates a ProxyService configuration advertisement
     *
     * @return ProxyService configuration advertisement
     */
    @Deprecated
    protected XMLDocument createProxyAdv() {
        return (XMLDocument) StructuredDocumentFactory.newStructuredDocument(MimeMediaType.XMLUTF8, "Parm");
    }

    /**
     * Creates a RendezVousService configuration advertisement with default values (EDGE)
     *
     * @return a RdvConfigAdv
     */
    protected RdvConfigAdv createRdvConfigAdv() {
        rdvConfig = (RdvConfigAdv) AdvertisementFactory.newAdvertisement(RdvConfigAdv.getAdvertisementType());
        if (mode == RDV_AD_HOC) {
            rdvConfig.setConfiguration(RendezVousConfiguration.AD_HOC);
        } else if ((mode & RDV_CLIENT) == RDV_CLIENT) {
            rdvConfig.setConfiguration(RendezVousConfiguration.EDGE);
        } else if ((mode & RDV_SERVER) == RDV_SERVER) {
            rdvConfig.setConfiguration(RendezVousConfiguration.RENDEZVOUS);
        }
        // A better alternative is to reference rdv service defaults (currently private)
        // rdvConfig.setMaxClients(200);
        return rdvConfig;
    }

    /**
     * Creates a RelayService configuration advertisement with default values (EDGE)
     *
     * @return a RelayConfigAdv
     */
    protected RelayConfigAdv createRelayConfigAdv() {
        relayConfig = (RelayConfigAdv) AdvertisementFactory.newAdvertisement(RelayConfigAdv.getAdvertisementType());

        // Since 2.6 - We should only use seeds when it comes to relay (see Javadoc)
        // relayConfig.setUseOnlySeeds(false);

        relayConfig.setClientEnabled((mode & RELAY_CLIENT) == RELAY_CLIENT || mode == EDGE_NODE);
        relayConfig.setServerEnabled((mode & RELAY_SERVER) == RELAY_SERVER);
        return relayConfig;
    }

    /**
     * Creates an TCP transport advertisement with the platform default values.
     * multicast on, 224.0.1.85:1234, with a max packet size of 16K
     *
     * @return a TCP transport advertisement
     */
    protected TCPAdv createTcpAdv() {
        tcpConfig = (TCPAdv) AdvertisementFactory.newAdvertisement(TCPAdv.getAdvertisementType());
        tcpConfig.setProtocol("tcp");
        tcpConfig.setInterfaceAddress(null);
        tcpConfig.setPort(9701);
        //tcpConfig.setStartPort(9701);
        //tcpConfig.setEndPort(9799);
        tcpConfig.setServer(null);
        tcpConfig.setClientEnabled((mode & TCP_CLIENT) == TCP_CLIENT);
        tcpConfig.setServerEnabled((mode & TCP_SERVER) == TCP_SERVER);
        return tcpConfig;
    }

    /**
     * Creates an multicast transport advertisement with the platform default values.
     * Multicast on, 224.0.1.85:1234, with a max packet size of 16K.
     *
     * @return a TCP transport advertisement
     */
    protected MulticastAdv createMulticastAdv() {
        multicastConfig = (MulticastAdv) AdvertisementFactory.newAdvertisement(MulticastAdv.getAdvertisementType());
        multicastConfig.setProtocol("tcp");
        multicastConfig.setMulticastAddr("224.0.1.85");
        multicastConfig.setMulticastPort(1234);
        multicastConfig.setMulticastSize(16384);
        multicastConfig.setMulticastState((mode & IP_MULTICAST) == IP_MULTICAST);
        return multicastConfig;
    }

    protected TCPAdv createHttp2Adv() {
    	http2Config = (TCPAdv) AdvertisementFactory.newAdvertisement(TCPAdv.getAdvertisementType());
    	http2Config.setProtocol("http2");
    	http2Config.setInterfaceAddress(null);
    	http2Config.setPort(8080);
        http2Config.setStartPort(8080);
        http2Config.setEndPort(8089);
        http2Config.setServer(null);
        http2Config.setClientEnabled((mode & HTTP2_CLIENT) == TCP_CLIENT);
        http2Config.setServerEnabled((mode & HTTP2_SERVER) == TCP_SERVER);

        return http2Config;
    }

    protected PeerGroupConfigAdv createInfraConfigAdv() {
        infraPeerGroupConfig = (PeerGroupConfigAdv) AdvertisementFactory.newAdvertisement(
                PeerGroupConfigAdv.getAdvertisementType());

        NetGroupTunables tunables = new NetGroupTunables(ResourceBundle.getBundle("net.jxta.impl.config"), new NetGroupTunables());

        infraPeerGroupConfig.setPeerGroupID(tunables.id);
        infraPeerGroupConfig.setName(tunables.name);
        infraPeerGroupConfig.setDesc(tunables.desc);

        return infraPeerGroupConfig;
    }

    /**
     * Returns a PlatformConfig which represents a platform configuration.
     * <p/>Fine tuning is achieved through accessing each configured advertisement
     * and achieved through accessing each configured advertisement and modifying
     * each object directly.
     *
     * @return the PeerPlatformConfig Advertisement
     */
    public ConfigParams getPlatformConfig() {
        PlatformConfig advertisement = (PlatformConfig) AdvertisementFactory.newAdvertisement(
                PlatformConfig.getAdvertisementType());

        advertisement.setName(name);
        advertisement.setDescription(description);

        if (tcpConfig != null) {
            boolean enabled = tcpEnabled && (tcpConfig.isServerEnabled() || tcpConfig.isClientEnabled());
            advertisement.putServiceParam(PeerGroup.tcpProtoClassID, getParmDoc(enabled, tcpConfig));
        }

        if (multicastConfig != null) {
            boolean enabled = multicastConfig.getMulticastState();
            advertisement.putServiceParam(PeerGroup.multicastProtoClassID, getParmDoc(enabled, multicastConfig));
        }

        if (httpConfig != null) {
            boolean enabled = httpEnabled && (httpConfig.isServerEnabled() || httpConfig.isClientEnabled());
            advertisement.putServiceParam(PeerGroup.httpProtoClassID, getParmDoc(enabled, httpConfig));
        }

        if (http2Config != null) {
        	boolean enabled = http2Enabled && (http2Config.isServerEnabled() || http2Config.isClientEnabled());
        	advertisement.putServiceParam(PeerGroup.http2ProtoClassID, getParmDoc(enabled, http2Config));
        }

        if (relayConfig != null) {
            boolean isOff = ((mode & RELAY_OFF) == RELAY_OFF) || (relayConfig.isServerEnabled() && relayConfig.isClientEnabled());
            XMLDocument relayDoc = (XMLDocument) relayConfig.getDocument(MimeMediaType.XMLUTF8);

            if (isOff) {
                relayDoc.appendChild(relayDoc.createElement("isOff"));
            }
            advertisement.putServiceParam(PeerGroup.relayProtoClassID, relayDoc);
        }

        if (rdvConfig != null) {
            XMLDocument rdvDoc = (XMLDocument) rdvConfig.getDocument(MimeMediaType.XMLUTF8);
            advertisement.putServiceParam(PeerGroup.rendezvousClassID, rdvDoc);
        }
        
        if (principal == null) {
            principal = System.getProperty("impl.membership.pse.authentication.principal", "JxtaCN");
        }
        if (password == null) {
            password = System.getProperty("impl.membership.pse.authentication.password", "the!one!password");
        }

        if (cert != null) {
            pseConf = createPSEAdv(cert);
        } else {
            pseConf = createPSEAdv(principal, password);
            cert = pseConf.getCertificateChain();
        }

        if (pseConf != null) {
            if (keyStoreLocation != null) {
                if (keyStoreLocation.isAbsolute()) {
                    pseConf.setKeyStoreLocation(keyStoreLocation);
                } else {
                    Logging.logCheckedWarning(LOG, "Keystore location set, but is not absolute: ", keyStoreLocation);
                }
            }
            XMLDocument pseDoc = (XMLDocument) pseConf.getDocument(MimeMediaType.XMLUTF8);
            advertisement.putServiceParam(PeerGroup.membershipClassID, pseDoc);
        }
        
        if (authenticationType == null) {
            authenticationType = System.getProperty("impl.membership.pse.authentication.type", "StringAuthentication");
        }
        StdPeerGroup.setPSEMembershipServiceKeystoreInfoFactory(new StdPeerGroup.DefaultPSEMembershipServiceKeystoreInfoFactory(authenticationType, password));
        
        if (peerid == null) {
            peerid = IDFactory.newPeerID(PeerGroupID.worldPeerGroupID, cert[0].getPublicKey().getEncoded());
        }
        advertisement.setPeerID(peerid);

//        if (proxyConfig != null && ((mode & PROXY_SERVER) == PROXY_SERVER)) {
//            advertisement.putServiceParam(PeerGroup.proxyClassID, proxyConfig);
//        }

        if ((null != infraPeerGroupConfig) && (null != infraPeerGroupConfig.getPeerGroupID())
                && (ID.nullID != infraPeerGroupConfig.getPeerGroupID())
                && (PeerGroupID.defaultNetPeerGroupID != infraPeerGroupConfig.getPeerGroupID())) {
            advertisement.setSvcConfigAdvertisement(PeerGroup.peerGroupClassID, infraPeerGroupConfig);
        }
        return advertisement;
    }

    /**
     * @param location The location of the platform config.
     * @return The platformConfig
     * @throws IOException Thrown for failures reading the PlatformConfig.
     */
    private PlatformConfig read(URI location) throws IOException {
        URL url;

        try {
            url = location.toURL();
        } catch (MalformedURLException mue) {
            IllegalArgumentException failure = new IllegalArgumentException("Failed to convert URI to URL");
            failure.initCause(mue);
            throw failure;
        }

        InputStream input = url.openStream();
        try {
            XMLDocument document = (XMLDocument) StructuredDocumentFactory.newStructuredDocument(MimeMediaType.XMLUTF8, input);
            PlatformConfig platformConfig = (PlatformConfig) AdvertisementFactory.newAdvertisement(document);
            return platformConfig;
        } finally {
            input.close();
        }
    }

    /**
     * Indicates whether Http is enabled
     *
     * @return true if Http is enabled, else returns false
     * @see #setHttpEnabled
     */
    public boolean isHttpEnabled() {
        return this.httpEnabled;
    }

    /**
     * Retrieves the Http incoming status
     *
     * @return true if Http incomming status is enabled, else returns false
     * @see #setHttpIncoming
     */
    public boolean getHttpIncomingStatus() {
        return httpConfig.getServerEnabled();
    }

    /**
     * Retrieves the Http outgoing status
     *
     * @return true if Http outgoing status is enabled, else returns false
     * @see #setHttpOutgoing
     */
    public boolean getHttpOutgoingStatus() {
        return httpConfig.getClientEnabled();
    }

    /**
     * Retrieves the Http port
     *
     * @return the current Http port
     * @see #setHttpPort
     */
    public int getHttpPort() {
        return httpConfig.getPort();
    }

    /**
     * Retrieves the current infrastructure ID
     *
     * @return the current infrastructure ID
     * @see #setInfrastructureID
     */
    public ID getInfrastructureID() {
        return infraPeerGroupConfig.getPeerGroupID();
    }

    /**
     * Retrieves the current multicast address
     *
     * @return the current multicast address
     * @see #setMulticastAddress
     */
    public String getMulticastAddress() {
        return multicastConfig.getMulticastAddr();
    }

    /**
     * Retrieves the current multicast port
     *
     * @return the current mutlicast port
     * @see #setMulticastPort
     */
    public int getMulticastPort() {
        return multicastConfig.getMulticastPort();
    }

    /**
     * Gets the group multicast thread pool size
     *
     * @return multicast thread pool size
     */
    public int getMulticastPoolSize() {
        return multicastConfig.getMulticastPoolSize();
    }

    /**
     * Indicates whether tcp is enabled
     *
     * @return true if tcp is enabled, else returns false
     * @see #setTcpEnabled
     */
    public boolean isTcpEnabled() {
        return this.tcpEnabled;
    }

    /**
     * Retrieves the current tcp end port
     *
     * @return the current tcp port
     * @see #setTcpEndPort
     */
    public int getTcpEndport() {
        return tcpConfig.getEndPort();
    }

    /**
     * Retrieves the Tcp incoming status
     *
     * @return true if tcp incoming is enabled, else returns false
     * @see #setTcpIncoming
     */
    public boolean getTcpIncomingStatus() {
        return tcpConfig.getServerEnabled();
    }

    /**
     * Retrieves the Tcp outgoing status
     *
     * @return true if tcp outcoming is enabled, else returns false
     * @see #setTcpOutgoing
     */
    public boolean getTcpOutgoingStatus() {
        return tcpConfig.getClientEnabled();
    }

    /**
     * Retrieves the Tcp interface address
     *
     * @return the current tcp interface address
     * @see #setTcpInterfaceAddress
     */
    public String getTcpInterfaceAddress() {
        return tcpConfig.getInterfaceAddress();
    }

    /**
     * Retrieves the current Tcp port
     *
     * @return the current tcp port
     * @see #setTcpPort
     */
    public int getTcpPort() {
        return tcpConfig.getPort();
    }

    /**
     * Retrieves the current Tcp public address
     *
     * @return the current tcp public address
     * @see #setTcpPublicAddress
     */
    public String getTcpPublicAddress() {
        return tcpConfig.getServer();
    }

    /**
     * Indicates whether the current Tcp public address is exclusive
     *
     * @return true if the current tcp public address is exclusive, else returns false
     * @see #setTcpPublicAddress
     */
    public boolean isTcpPublicAddressExclusive() {
        return tcpConfig.getPublicAddressOnly();
    }

    /**
     * Retrieves the current Tcp start port
     *
     * @return the current tcp start port
     * @see #setTcpStartPort
     */
    public int getTcpStartPort() {
        return tcpConfig.getStartPort();
    }

    /**
     * Retrieves the multicast use status
     *
     * @return true if multicast is enabled, else returns false
     * @see #setUseMulticast
     */
    public boolean getMulticastStatus() {
        return multicastConfig.getMulticastState();
    }

    /**
     * Retrieves the use relay seeds only status
     *
     * @return true if only relay seeds are used, else returns false
     * @see #setUseOnlyRelaySeeds
     */
    public boolean getUseOnlyRelaySeedsStatus() {
        return relayConfig.getUseOnlySeeds();
    }

    /**
     * Retrieves the use rendezvous seeds only status
     *
     * @return true if only rendezvous seeds are used, else returns false
     * @see #setUseOnlyRendezvousSeeds
     */
    public boolean getUseOnlyRendezvousSeedsStatus() {
        return rdvConfig.getUseOnlySeeds();
    }

    /**
     * Retrieves the RendezVousService maximum number of simultaneous rendezvous clients
     *
     * @return the RendezVousService maximum number of simultaneous rendezvous clients
     * @see #setRendezvousMaxClients
     */
    public int getRendezvousMaxClients() {
        return rdvConfig.getMaxClients();
    }

    /**
     * Retrieves the RelayVousService maximum number of simultaneous relay clients
     *
     * @return the RelayService maximum number of simultaneous relay clients
     * @see #setRelayMaxClients
     */
    public int getRelayMaxClients() {
        return relayConfig.getMaxClients();
    }

    /**
     * Retrieves the rendezvous seedings
     *
     * @return the array of rendezvous seeding URL
     * @see #addRdvSeedingURI
     */
    public URI[] getRdvSeedingURIs() {
        return rdvConfig.getSeedingURIs();
    }

    /**
     * Retrieves the rendezvous seeds
     *
     * @return the array of rendezvous seeds URL
     * @see #addRdvSeedURI
     */
    public URI[] getRdvSeedURIs() {
        return rdvConfig.getSeedRendezvous();
    }

    /**
     * Retrieves the relay seeds
     *
     * @return the array of relay seeds URL
     * @see #addRelaySeedURI
     */
    public URI[] getRelaySeedURIs() {
        return relayConfig.getSeedRelayURIs();
    }

    /**
     * Retrieves the relay seeds
     *
     * @return the array of rendezvous seed URL
     * @see #addRelaySeedingURI
     */
    public URI[] getRelaySeedingURIs() {
        return relayConfig.getSeedingURIs();
    }

    /**
     * Holds the construction tunables for the Net Peer Group. This consists of
     * the peer group id, the peer group name and the peer group description.
     */
    static class NetGroupTunables {

        final ID id;
        final String name;
        final XMLElement desc;

        /**
         * Constructor for loading the default Net Peer Group construction
         * tunables.
         */
        NetGroupTunables() {
            id = PeerGroupID.defaultNetPeerGroupID;
            name = "NetPeerGroup";
            desc = (XMLElement) StructuredDocumentFactory.newStructuredDocument(MimeMediaType.XMLUTF8, "desc", "default Net Peer Group");
        }

        /**
         * Constructor for loading the default Net Peer Group construction
         * tunables.
         *
         * @param pgid   the PeerGroupID
         * @param pgname the group name
         * @param pgdesc the group description
         */
        NetGroupTunables(ID pgid, String pgname, XMLElement pgdesc) {
            id = pgid;
            name = pgname;
            desc = pgdesc;
        }

        /**
         * Constructor for loading the Net Peer Group construction
         * tunables from the provided resource bundle.
         *
         * @param rsrcs    The resource bundle from which resources will be loaded.
         * @param defaults default values
         */
        NetGroupTunables(ResourceBundle rsrcs, NetGroupTunables defaults) {
            ID idTmp;
            String nameTmp;
            XMLElement descTmp;

            try {
                String idTmpStr = rsrcs.getString("NetPeerGroupID").trim();

                if (idTmpStr.startsWith(ID.URNNamespace + ":")) {
                    idTmpStr = idTmpStr.substring(5);
                }
                idTmp = IDFactory.fromURI(new URI(ID.URIEncodingName + ":" + ID.URNNamespace + ":" + idTmpStr));
                nameTmp = rsrcs.getString("NetPeerGroupName").trim();
                descTmp = (XMLElement) StructuredDocumentFactory.newStructuredDocument(MimeMediaType.XMLUTF8, "desc", rsrcs.getString("NetPeerGroupDesc").trim());

            } catch (Exception failed) {

                if (null != defaults) {

                    Logging.logCheckedFine(LOG, "NetPeerGroup tunables not defined or could not be loaded. Using defaults.\n\n", failed);

                    idTmp = defaults.id;
                    nameTmp = defaults.name;
                    descTmp = defaults.desc;

                } else {

                    Logging.logCheckedSevere(LOG, "NetPeerGroup tunables not defined or could not be loaded.\n", failed);
                    throw new IllegalStateException("NetPeerGroup tunables not defined or could not be loaded.");

                }
            }

            id = idTmp;
            name = nameTmp;
            desc = descTmp;
        }
    }
}
