/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 1999-2003 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 *
 */

//  SLPConfig.java
//

/**
 * This class is a singleton - it has the configuration which
 * is the default.  It reads from a configuration file and
 * this overrides the default.  If the config file does not
 * expressly forbid it, the ServiceLocationManager interface
 * allows some of these configuration options to be modified.
 * This configuration is refered to by many points of the
 * implementation. Note that the class itself is abstract,
 * and is extended by two classes, one that allows slpd to
 * run as an SA server only, the other allows it to run
 * as a DA as well.
 *
 * @see com.sun.slp.ServiceLocationManager
 */

package com.sun.slp;

import java.net.*;
import java.util.*;
import java.text.*;
import java.io.*;

/*
 * This class contains all configuration information.  It
 * is hard coded to know the defaults, and will read a config
 * file if it is present.  The config file will override the
 * default values and policy.  If the config file allows it
 * the user may reset some of these values using the
 * ServiceLocationManager interface.
 *
 */
class SLPConfig {

    /**
     * A Java properties file defines `\' as an escape character, which
     * conflicts with the SLP API escape convention. Therefore, we need
     * to subclass properties so we can parse in the file ourselves.
     */

    public static class SLPProperties extends Properties {

	SLPProperties(Properties p) {
	    super(p);

	}

	// Parse the SLP properties file ourselves. Groan! We don't recognize
	//  backslash as an escape.

	public synchronized void load(InputStream in) throws IOException {

	    BufferedReader rd = new BufferedReader(new InputStreamReader(in));

	    while (rd.ready()) {
		String ln = rd.readLine();

		// Throw out anything that begins with '#' or ';'.

		if (ln.startsWith("#") ||
		    ln.startsWith(";") ||
		    ln.length() <= 0) {
		    continue;

		}

		// Parse out equals sign, if any. Note that we trim any
		//  white space preceding or following data strings.   
		//  Although the grammar doesn't allow it, users may 
		//  enter blanks in their configuration files.
		// NOTE:  White space is not allowed in the data of
		//  property tag or values.  If included, according
		//  to RFC 2614, Section 2.1 these MUST be escaped, 
		//  ie. space would be represented with '\20'.  
		//  Therefore, it is *completely* safe to perform 
		//  these trim()s.  They will catch errors resulting 
		//  from sloppy data entry in slp.conf files and 
		//  never corrupt or alter correctly formatted 
		//  properties.

		SLPTokenizer tk = new SLPTokenizer(ln, "=");

		if (!tk.hasMoreTokens()) {// empty line...
		    continue;

		}

		String prop = tk.nextToken().trim();

		if (prop.trim().length() <= 0) {// line has just spaces...
		    continue;

		}

		if (!tk.hasMoreTokens()) {// line has no definition...
		    continue;

		}

		// Register the property.
		String def = tk.nextToken().trim();
		this.setProperty(prop, def);
	    }
	}

    }

    protected SLPConfig() {

	// Create a temporary, default log to report any errors during
	//  configuration.
	log = new StderrLog();

	// Initialize properties. Properties on command line override config
	//  file properties, and both override defaults.

	Properties sysProps = (Properties)(System.getProperties().clone());

	// Load Defalts.

	try {
	    Class.forName("com.sun.slp.Defaults");

	} catch (ClassNotFoundException ex) {

	    Assert.printMessageAndDie(this,
				      "no_class",
				      new Object[] {"com.sun.slp.Defaults"});
	}

	// System properties now contain Defaults
	Properties defaultProps = System.getProperties();

	// Load config file.

	SLPProperties slpProps = new SLPProperties(new Properties());
	try {
	    InputStream fis = getConfigURLStream();
	    if (fis != null) {
		slpProps.load(fis);
		System.setProperties(slpProps);
	    }

	} catch (IOException ex) {
	    writeLog("unparsable_config_file",
		     new Object[] {ex.getMessage()});
	}

	// Add config properties to Defaults, overwritting any pre-existing
	//  entries
	defaultProps.putAll(slpProps);

	// Now add in system props, overwritting any pre-existing entries
	defaultProps.putAll(sysProps);

	System.setProperties(defaultProps);


	// Initialize useScopes property. This is read-only after the file
	//  has been loaded.

	configuredScopes = initializeScopes("net.slp.useScopes");
	saConfiguredScopes = (Vector)configuredScopes.clone();

	// Add default scope to scopes for SA.

	if (saConfiguredScopes.size() <= 0) {
	    saConfiguredScopes.addElement(Defaults.DEFAULT_SCOPE);

	}

	// Initialize SA scopes. This uses a Sun specific property for
	//  scopes only used by the SA and adds in the DA scopes.

	saOnlyScopes = initializeScopes(DATable.SA_ONLY_SCOPES_PROP);

	// Initialized preconfigured DAs.

	preconfiguredDAs = initializePreconfiguredDAs();

	// Initialize broadcast flag.

	broadcastOnly = Boolean.getBoolean("net.slp.isBroadcastOnly");

	// Initialize logging. Default is stderr, first check for alternate.

	String failed = null;

	try {
	    String loggerClassName =
		System.getProperty("sun.net.slp.loggerClass");
	    if (loggerClassName != null) {
		Class loggerClass = Class.forName(loggerClassName);
		// Protect against disastrous pilot error, such as trying
		// to use com.sun.slp.SLPConfig as the log class
		// (causes stack recursion)
		if (Class.forName("java.io.Writer").isAssignableFrom(
							loggerClass)) {
		    Object logger = loggerClass.newInstance();
		    log = (Writer) logger;
		} else {
		    failed = formatMessage(
					   "bad_log_class",
					   new Object[] {
			loggerClass.toString()}) + "\n";
		}
	    }

	} catch (Throwable ex) {
	    log = null;
	    failed = formatMessage(
				   "bad_log",
				   new Object[] {
		ex.toString()}) + "\n";
	}

	// If no alternate log, revert to minimal default
	if (log == null) {
	    log = new StderrLog();

	    // If the alternate log failed, log it through the default log
	    if (failed != null) {
		try {
		    synchronized (log) {
			log.write(failed);
			log.flush();
		    }
		} catch (IOException giveUp) {}
	    }
	}

    }

    private InputStream getConfigURLStream() {

	// Open a URL onto the configuration file.

	String conf = System.getProperty("sun.net.slp.configURL");

	if (conf == null) {
	    conf = Defaults.SOLARIS_CONF;

	}

	InputStream str = null;

	try {

	    URL confURL = new URL(conf);

	    str = confURL.openStream();

	} catch (MalformedURLException ex) {
	    writeLog("url_malformed",
		     new Object[] {conf});

	} catch (IOException ex) {
	    if (conf != Defaults.SOLARIS_CONF) {
		// don't complain if we can't find our own default
		writeLog("unparsable_config_file",
			 new Object[] {ex.getMessage()});
	    }

	}

	return str;
    }

    // ------------------------------------------------------------
    // Property manipulation functions
    //

    private boolean OKBound(int i, int lb, int ub) {
	if (i < lb || i > ub)
	    return false;
	else
	    return true;
    }

    int getIntProperty(String prop, int df, int lb, int ub) {

	int i = Integer.getInteger(prop, df).intValue();

	if (OKBound(i, lb, ub)) {
	    return i;

	} else {
	    writeLog("bad_prop_tag", new Object[] {prop});

	    return df;
	}
    }

    // ------------------------------------------------------------
    // Multicast radius
    //
    private int iMinMCRadius = 1;   // link local scope
    private int iMaxMCRadius = 255; // universal scope

    int getMCRadius() {
	return getIntProperty("net.slp.multicastTTL",
			      Defaults.iMulticastRadius,
			      iMinMCRadius,
			      iMaxMCRadius);
    }

    // ------------------------------------------------------------
    // Heartbeat interval, seconds.
    //
    private final int iMinHeart = 2000;    // 10 minutes
    private final int iMaxHeart = 259200000; // 3 days

    int getAdvertHeartbeatTime() {
	return getIntProperty("net.slp.DAHeartBeat",
			      Defaults.iHeartbeat,
			      iMinHeart,
			      iMaxHeart);
    }

    // ------------------------------------------------------------
    // Active discovery interval, seconds.
    //

    private final int iMinDisc = 300;    // 5 minutes
    private final int iMaxDisc = 10800;  // 3 hours

    int getActiveDiscoveryInterval() {

	// We allow zero in order to turn active discovery off, but
	//  if 5 minutes is the smallest actual time.

	int prop = getIntProperty("net.slp.DAActiveDiscoveryInterval",
				  Defaults.iActiveDiscoveryInterval,
				  0,
				  iMaxDisc);
	if (prop > 0 && prop < iMinDisc) {
	    writeLog("bad_prop_tag",
		     new Object[] {"net.slp.DAActiveDiscoveryInterval"});
	    return iMinDisc;

	}

	return prop;
    }


    // ------------------------------------------------------------
    // Active discovery granularity, seconds.
    //

    private int iMaxDiscGran = iMaxDisc * 2;

    int getActiveDiscoveryGranularity() {
	return getIntProperty("sun.net.slp.DAActiveDiscoveryGranularity",
			      Defaults.iActiveDiscoveryGranularity,
			      0,
			      iMaxDiscGran);
    }

    // ------------------------------------------------------------
    // Bound for random wait, milliseconds.
    //

    private final int iMinWait = 1000;  // 1 sec.
    private final int iMaxWait = 3000;  // 3 sec.

    int getRandomWaitBound() {
	return getIntProperty("net.slp.randomWaitBound",
			      Defaults.iRandomWaitBound,
			      iMinWait,
			      iMaxWait);
    }

    private static Random randomWait = null;

    long getRandomWait() {

	if (randomWait == null) {
	    randomWait = new Random();
	}

	double r = randomWait.nextDouble();
	double max = (double)getRandomWaitBound();

	return (long)(max * r);
    }

    // ------------------------------------------------------------
    // TCP timeout, milliseconds.
    //
    final static private int iMinTimeout = 100;
    final static private int iMaxTimeout = 360000;

    int getTCPTimeout() {
	return getIntProperty("sun.net.slp.TCPTimeout",
			      Defaults.iTCPTimeout,
			      iMinTimeout,
			      iMaxTimeout);
    }

    // ------------------------------------------------------------
    //  Path MTU
    //
    private final int iMinMTU = 128; // used for some ppp connections
    private final int iMaxMTU = 8192; // used on some LANs

    int getMTU() {
	return getIntProperty("net.slp.MTU",
			      Defaults.iMTU,
			      iMinMTU,
			      iMaxMTU);
    }


    // ------------------------------------------------------------
    // Serialized registrations.
    //

    String getSerializedRegURL() {

	return System.getProperty("net.slp.serializedRegURL", null);

    }

    // ------------------------------------------------------------
    // Are we running as a DA or SA server?
    //

    protected static boolean isSA = false;

    boolean isDA() {
	return false;
    }

    boolean isSA() {
	return isSA;
    }

    // ------------------------------------------------------------
    // DA and SA attributes
    //

    Vector getDAAttributes() {
	return getAttributes("net.slp.DAAttributes",
			     Defaults.defaultDAAttributes,
			     true);
    }

    Vector getSAAttributes() {
	return getAttributes("net.slp.SAAttributes",
			     Defaults.defaultSAAttributes,
			     false);
    }

    private Vector getAttributes(String prop,
				 Vector defaults,
				 boolean daAttrs) {
	String attrList =
	    System.getProperty(prop);

	if (attrList == null || attrList.length() <= 0) {
	    return (Vector)defaults.clone();

	}

	try {
	    Vector sAttrs =
		SrvLocHeader.parseCommaSeparatedListIn(attrList, false);

	    Vector attrs = new Vector();
	    int i, n = sAttrs.size();

	    // Create attribute objects.

	    for (i = 0; i < n; i++) {
		String attrExp = (String)sAttrs.elementAt(i);
		ServiceLocationAttribute attr =
		    new ServiceLocationAttribute(attrExp, false);

		// If this is the min-refresh-interval, then check the value.

		if (daAttrs &&
		    attr.getId().equals(
				Defaults.MIN_REFRESH_INTERVAL_ATTR_ID)) {
		    Vector values = attr.getValues();
		    boolean errorp = true;
	
		    if (values != null && values.size() == 1) {
			Object val = values.elementAt(0);

			if (val instanceof Integer) {
			    int ival = ((Integer)val).intValue();

			    if (ival >= 0 &&
				ival <= ServiceURL.LIFETIME_MAXIMUM) {
				errorp = false;

			    }
			}
		    }

		    // Throw exception if it didn't work.

		    if (errorp) {
			throw new ServiceLocationException(
				ServiceLocationException.PARSE_ERROR,
				"syntax_error_prop",
				new Object[] {prop, attrs});

		    }
		}

		// Add attribute to vector.

		attrs.addElement(attr);

	    }

	    return attrs;

	} catch (Exception ex) {

	    writeLog("syntax_error_prop",
		     new Object[] {prop, attrList});
	    return (Vector)defaults.clone();

	}
    }

    // -------------------------------------------------------------
    // Do we support V1?
    //

    boolean isV1Supported() {
	return false;
    }

    // -------------------------------------------------------------
    // Queue length for server socket.
    //

    int getServerSocketQueueLength() {
	return getIntProperty("sun.net.slp.serverSocketQueueLength",
			      Defaults.iSocketQueueLength,
			      0,
			      Integer.MAX_VALUE);
    }

    // ------------------------------------------------------------
    // Testing options
    //


    boolean traceAll() {// not official!
	return Boolean.getBoolean("sun.net.slp.traceALL");
    }

    boolean regTest() {
	if (Boolean.getBoolean("sun.net.slp.traceALL") ||
	    Boolean.getBoolean("net.slp.traceReg"))
	    return true;
	else
	    return false;
    }

    boolean traceMsg() {
	if (Boolean.getBoolean("sun.net.slp.traceALL") ||
	    Boolean.getBoolean("net.slp.traceMsg"))
	    return true;
	else
	    return false;
    }

    boolean traceDrop() {
	if (Boolean.getBoolean("sun.net.slp.traceALL") ||
	    Boolean.getBoolean("net.slp.traceDrop"))
	    return true;
	else
	    return false;
    }

    boolean traceDATraffic() {
	if (Boolean.getBoolean("sun.net.slp.traceALL") ||
	    Boolean.getBoolean("net.slp.traceDATraffic"))
	    return true;
	else
	    return false;
    }

    // cannot use Boolean.getBoolean as the default is 'true'
    // using that mechanism, absense would be considered 'false'

    boolean passiveDADetection() {

	String sPassive =
	    System.getProperty("net.slp.passiveDADetection", "true");
	if (sPassive.equalsIgnoreCase("true"))
	    return true;
	else
	    return false;

    }

    // Initialized when the SLPConfig object is created to avoid changing
    //  during the program.
    private boolean broadcastOnly = false;

    boolean isBroadcastOnly() {
	return broadcastOnly;
    }


    // ------------------------------------------------------------
    // Multicast/broadcast socket mangement.
    //
    DatagramSocket broadSocket = null;   // cached broadcast socket.


    // Reopen the multicast/broadcast socket bound to the
    //  interface. If groups is not null, then join all
    //  the groups. Otherwise, this is send only.

    DatagramSocket
	refreshMulticastSocketOnInterface(InetAddress interfac,
					  Vector groups) {

	try {

	    // Reopen it.

	    DatagramSocket dss =
		getMulticastSocketOnInterface(interfac,
					      (groups == null ? true:false));

	    if ((groups != null) && (dss instanceof MulticastSocket)) {
		int i, n = groups.size();
		MulticastSocket mss = (MulticastSocket)dss;

		for (i = 0; i < n; i++) {
		    InetAddress maddr = (InetAddress)groups.elementAt(i);

		    mss.joinGroup(maddr);

		}
	    }

	    return dss;

	} catch (Exception ex) {

	    // Any exception in error recovery causes program to die.

	    Assert.slpassert(false,
			  "cast_socket_failure",
			  new Object[] {ex, ex.getMessage()});

	}

	return null;
    }

    // Open a multicast/broadcast socket on the interface. Note that if
    //  the socket is broadcast, the network interface is not specified in the
    //  creation message. Is it bound to all interfaces? The isSend parameter
    //  specifies whether the socket is for send only.

    DatagramSocket
	getMulticastSocketOnInterface(InetAddress interfac, boolean isSend)
	throws ServiceLocationException {

	DatagramSocket castSocket = null;

	// Substitute broadcast if we are configured for it.

	if (isBroadcastOnly()) {

	    try {

		// If transmit, then simply return a new socket.

		if (isSend) {
		    castSocket = new DatagramSocket();

		} else {

		    // Return cached socket if there.

		    if (broadSocket != null) {
			castSocket = broadSocket;

		    } else {

			// Make a new broadcast socket.

			castSocket =
			    new DatagramSocket(Defaults.iSLPPort,
					       getBroadcastAddress());

		    }

		    // Cache for future reference.

		    broadSocket = castSocket;
		}
	    } catch (SocketException ex) {
		throw 	
		    new ServiceLocationException(
				ServiceLocationException.NETWORK_INIT_FAILED,
				"socket_creation_failure",
				new Object[] {
			getBroadcastAddress(), ex.getMessage()});
	    }

	} else {

	    // Create a multicast socket.

	    MulticastSocket ms;

	    try {

		if (isSend) {
		    ms = new MulticastSocket();

		} else {
		    ms = new MulticastSocket(Defaults.iSLPPort);

		}

	    } catch (IOException ex) {
		throw
		    new ServiceLocationException(
				ServiceLocationException.NETWORK_INIT_FAILED,
				"socket_creation_failure",
				new Object[] {interfac, ex.getMessage()});
	    }


	    try {

		// Set the TTL and the interface on the multicast socket.
		//  Client is responsible for joining group.

		ms.setTimeToLive(getMCRadius());
		ms.setInterface(interfac);

	    } catch (IOException ex) {
		throw
		    new ServiceLocationException(
				ServiceLocationException.NETWORK_INIT_FAILED,
				"socket_initializtion_failure",
				new Object[] {interfac, ex.getMessage()});
	    }

	    castSocket = ms;

	}

	return castSocket;
    }

    // ------------------------------------------------------------
    // Type hint
    //

    // Return a vector of ServiceType objects for the type hint.

    Vector getTypeHint() {
	Vector hint = new Vector();
	String sTypeList = System.getProperty("net.slp.typeHint", "");

	if (sTypeList.length() <= 0) {
	    return hint;

	}

	// Create a vector of ServiceType objects for the type hint.

	try {

	    hint = SrvLocHeader.parseCommaSeparatedListIn(sTypeList, true);

	    int i, n = hint.size();

	    for (i = 0; i < n; i++) {
		String type = (String)hint.elementAt(i);

		hint.setElementAt(new ServiceType(type), i);

	    }
	} catch (ServiceLocationException ex) {

	    writeLog("syntax_error_prop",
		     new Object[] {"net.slp.typeHint", sTypeList});

	    hint.removeAllElements();

	}

	return hint;

    }

    // ------------------------------------------------------------
    // Configured scope handling
    //

    // Vector of configured scopes.

    private Vector configuredScopes = null;

    // Vector of configures scopes for SA.

    private Vector saConfiguredScopes = null;

    // Vector of scopes only in the sa server.

    private Vector saOnlyScopes = null;

    // Return the configured scopes.

    Vector getConfiguredScopes() {
	return (Vector)configuredScopes.clone();
    }

    // Return SA scopes.

    Vector getSAOnlyScopes() {
	return (Vector)saOnlyScopes.clone();

    }

    // Return the configured scopes for the SA.

    Vector getSAConfiguredScopes() {
	return (Vector)saConfiguredScopes.clone();

    }

    // Add scopes discovered during preconfigured DA contact.
    //  These count as configured scopes.

    void addPreconfiguredDAScopes(Vector scopes) {

	int i, n = scopes.size();

	for (i = 0; i < n; i++) {
	    Object scope = scopes.elementAt(i);

	    if (!configuredScopes.contains(scope)) {
		configuredScopes.addElement(scope);

	    }

	    // There better be none extra here for the SA server/DA.

	    if (isSA() || isDA()) {
		Assert.slpassert(saConfiguredScopes.contains(scope),
			      "sa_new_scope",
			      new Object[] {scope, saConfiguredScopes});

	    }
	}
    }

    // Initialize the scopes list on property.

    private Vector initializeScopes(String prop) {

	String sScopes = System.getProperty(prop);

	if (sScopes == null || sScopes.length() <= 0) {
	    return new Vector();
	}

	try {

	    Vector vv =
		SrvLocHeader.parseCommaSeparatedListIn(sScopes, true);

	    // Unescape scope strings.

	    SLPHeaderV2.unescapeScopeStrings(vv);

	    // Validate, lower case scope names.

	    DATable.validateScopes(vv, getLocale());

	    if (vv.size() > 0) {
		return vv;
	    }

	} catch (ServiceLocationException ex) {
	    writeLog("syntax_error_prop",
		     new Object[] {
		prop,
		    sScopes});
	

	}

	return new Vector();
    }

    // Vector of preconfigured DAs. Read only after initialized.

    private Vector preconfiguredDAs = null;

    // Return a vector of DA addresses.

    Vector getPreconfiguredDAs() {
	return (Vector)preconfiguredDAs.clone();

    }

    // Initialize preconfigured DA list.

    private Vector initializePreconfiguredDAs() {
	String sDAList = System.getProperty("net.slp.DAAddresses", "");
	Vector ret = new Vector();

	sDAList.trim();

	if (sDAList.length() <= 0) {
	    return ret;

	}

	try {

	    ret = SrvLocHeader.parseCommaSeparatedListIn(sDAList, true);

	} catch (ServiceLocationException ex) {

	    writeLog("syntax_error_prop",
		     new Object[] {"net.slp.DAAddress", sDAList});

	    return ret;

	}

	// Convert to InetAddress objects.

	int i;

	for (i = 0; i < ret.size(); i++) {
	    String da = "";

	    try {
		da = ((String)ret.elementAt(i)).trim();
		InetAddress daAddr = InetAddress.getByName(da);

		ret.setElementAt(daAddr, i);

	    } catch (UnknownHostException ex) {

		writeLog("resolve_failed",
			 new Object[] {da});

		/*
		 *  Must decrement the index 'i' otherwise the next iteration
		 *  around the loop will miss the element immediately after
		 *  the element removed.
		 *
		 *  WARNING: Do not use 'i' again until the loop has
		 *           iterated as it may, after decrementing,
		 *           be negative.
		 */
		ret.removeElementAt(i);
		i--;
		continue;
	    }
	}


	return ret;
    }

    // ------------------------------------------------------------
    // SLPv1 Support Switches
    //

    boolean getSLPv1NotSupported() {// not official!
	return Boolean.getBoolean("sun.net.slp.SLPv1NotSupported");

    }

    boolean getAcceptSLPv1UnscopedRegs() {// not official!

	if (!getSLPv1NotSupported()) {
	    return Boolean.getBoolean("sun.net.slp.acceptSLPv1UnscopedRegs");

	}

	return false;
    }

    // ------------------------------------------------------------
    // Accessor for SLPConfig object
    //

    protected static SLPConfig theSLPConfig = null;

    static SLPConfig getSLPConfig() {

	if (theSLPConfig == null) {
	    theSLPConfig = new SLPConfig();
	}

	return theSLPConfig;

    }

    /**
     * @return Maximum number of messages/objects to return.
     */

    int getMaximumResults()  {
	int i = Integer.getInteger("net.slp.maxResults",
				   Defaults.iMaximumResults).intValue();
	if (i == -1) {
	    i = Integer.MAX_VALUE;

	}

	if (OKBound(i, 1, Integer.MAX_VALUE)) {
	    return i;

	} else {

	    writeLog("bad_prop_tag",
		     new Object[] {
		"net.slp.maxResults"});

	    return Defaults.iMaximumResults;

	}
    }

    /**
     * Convert a language tag into a locale.
     */

    static Locale langTagToLocale(String ltag) {

	// We treat the first part as the ISO 639 language and the
	// second part as the ISO 3166 country tag, even though RFC
	// 1766 doesn't necessarily require that. We should probably
	// use a lookup table here to determine if they are correct.

	StringTokenizer tk = new StringTokenizer(ltag, "-");
	String lang = "";
	String country = "";

	if (tk.hasMoreTokens()) {
	    lang = tk.nextToken();

	    if (tk.hasMoreTokens()) {
		country = tk.nextToken("");
					// country name may have "-" in it...

	    }
	}

	return new Locale(lang, country);
    }

    /**
     * Convert a Locale object into a language tag for output.
     *
     * @param locale The Locale.
     * @return String with the language tag encoded.
     */

    static String localeToLangTag(Locale locale) {

	// Construct the language tag.

	String ltag = locale.getCountry();
	ltag = locale.getLanguage() + (ltag.length() <= 0 ? "" : ("-" + ltag));

	return ltag;

    }

    /**
     * @return the language requests will be made in.
     */
    static Locale  getLocale()    {
	String s = System.getProperty("net.slp.locale");

	if (s != null && s.length() > 0) {
	    return langTagToLocale(s);

	} else {

	    // Return the Java default if the SLP property is not set.

	    return Locale.getDefault();

	}
    }

    /**
     * @return the InetAddress of the broadcast interface.
     */

    static private InetAddress broadcastAddress;

    static InetAddress getBroadcastAddress() {
	if (broadcastAddress == null) {

	    try {
		broadcastAddress =
		    InetAddress.getByName(Defaults.sBroadcast);
	    } catch (UnknownHostException uhe) {

		Assert.slpassert(false,
			      "cast_address_failure",
			      new Object[] {Defaults.sBroadcast});

	    }
	}
	return broadcastAddress;
    }


    /**
     * @return the InetAddress of the multicast group.
     */

    static private InetAddress multicastAddress;

    static InetAddress getMulticastAddress() {
	if (multicastAddress == null) {

	    try {
		multicastAddress =
		    InetAddress.getByName(Defaults.sGeneralSLPMCAddress);
	    } catch (UnknownHostException uhe) {
		Assert.slpassert(false,
			      "cast_address_failure",
			      new Object[] {Defaults.sGeneralSLPMCAddress});

	    }
	}
	return multicastAddress;
    }

    /**
     * @return the interfaces on which SLP should listen and transmit.
     */

    private static Vector interfaces = null;

    Vector getInterfaces() {

	if (interfaces == null) {
	    InetAddress iaLocal = null;

	    // Get local host.

	    try {
		iaLocal =  InetAddress.getLocalHost();

	    }  catch (UnknownHostException ex) {
		Assert.slpassert(false,
			      "resolve_failed",
			      new Object[] {"localhost"});
	    }

	    String mcastI = System.getProperty("net.slp.interfaces");
	    interfaces = new Vector();

	    // Only add local host if nothing else is given.

	    if (mcastI == null || mcastI.length() <= 0) {
		interfaces.addElement(iaLocal);
		return interfaces;

	    }

	    Vector nintr;

	    try {

		nintr = SrvLocHeader.parseCommaSeparatedListIn(mcastI, true);

	    } catch (ServiceLocationException ex) {
		writeLog("syntax_error_prop",
			 new Object[] {
		    "net.slp.multicastInterfaces",
			mcastI});

		// Add local host.

		interfaces.addElement(iaLocal);
	
		return interfaces;

	    }

	    // See if they are really there.

	    int i, n = nintr.size();

	    for (i = 0; i < n; i++) {
		InetAddress ia;
		String host = (String)nintr.elementAt(i);

		try {

		    ia = InetAddress.getByName(host);

		} catch (UnknownHostException ex) {
		    writeLog("unknown_interface",
			     new Object[] {host,
					       "net.slp.multicastInterfaces"});
		    continue;

		}

		if (!interfaces.contains(ia)) {

		    // Add default at beginning.

		    if (ia.equals(iaLocal)) {
			interfaces.insertElementAt(ia, 0);

		    } else {
			interfaces.addElement(ia);

		    }
		}
	    }
	}

	return interfaces;

    }

    /**
     * @return An InetAddress object representing 127.0.0.1
     */
    InetAddress getLoopback() {
	InetAddress iaLoopback = null;

	try {
	    iaLoopback = InetAddress.getByName(Defaults.LOOPBACK_ADDRESS);

	}  catch (UnknownHostException ex) {
	    Assert.slpassert(false,
			  "resolve_failed",
			  new Object[] {"localhost loopback"});
	}

	return iaLoopback;
    }

    /**
     * @return The default interface, which should be the first in the
     *         interfaces vector Vector.
     */

    InetAddress getLocalHost() {
	Vector inter = getInterfaces();
	return (InetAddress)inter.elementAt(0);

    }

    // Return true if the address is one of the local interfaces.

    boolean isLocalHostSource(InetAddress addr) {

	// First check loopback

	if (addr.equals(getLoopback())) {
	    return true;

	}

	return interfaces.contains(addr);

    }

    // -----------------
    // Timeouts
    //

    // Return the maximum wait for multicast convergence.

    final static private int iMultiMin = 1000;  // one second
    final static private int iMultiMax = 60000; // one minute

    int getMulticastMaximumWait() {

	return getIntProperty("net.slp.multicastMaximumWait",
			      Defaults.iMulticastMaxWait,
			      iMultiMin,
			      iMultiMax);
    }

    /*
     * @return Vector of timeouts for multicast convergence.
     */

    int[] getMulticastTimeouts() {
	int[] timeouts = parseTimeouts("net.slp.multicastTimeouts",
			     Defaults.a_iConvergeTimeout);

	timeouts = capTimeouts("net.slp.multicastTimeouts",
			       timeouts,
			       false,
			       0,
			       0);

	return timeouts;
    }

    /**
     * @return Vector of timeouts to try for datagram transmission.
     */

    int[] getDatagramTimeouts() {
	int[] timeouts = parseTimeouts("net.slp.datagramTimeouts",
			     Defaults.a_iDatagramTimeout);

	timeouts = capTimeouts("net.slp.datagramTimeouts",
			       timeouts,
			       true,
			       iMultiMin,
			       iMultiMax);

	return timeouts;
    }

    /**
     * @return Vector of timeouts for DA discovery multicast.
     */

    int[] getDADiscoveryTimeouts() {
	int[] timeouts = parseTimeouts("net.slp.DADiscoveryTimeouts",
			     Defaults.a_iDADiscoveryTimeout);

	timeouts = capTimeouts("net.slp.DADiscoveryTimeouts",
				timeouts,
				false,
				0,
				0);

	return timeouts;
    }

    /**
     *  This method ensures that all the timeouts are within valid ranges.
     *  The sum of all timeouts for the given property name must not
     *  exceed the value returned by <i>getMulticastMaximumWait()</i>. If
     *  the sum of all timeouts does exceed the maximum wait period the
     *  timeouts are averaged out so that the sum equals the maximum wait
     *  period.
     *	<br>
     *  Additional range checking is also performed when <i>rangeCheck</i>
     *  is true. Then the sum of all timeouts must also be between <i>min</i>
     *  and <i>max</i>. If the sum of all timeouts is not within the range
     *  the average is taken from the closest range boundary.
     *
     *  @param property
     *	    Name of timeout property being capped. This is only present for
     *	    reporting purposes and no actual manipulation of the property
     *      is made within this method.
     *  @param timeouts
     *      Array of timeout values.
     *  @param rangeCheck
     *      Indicator of whether additional range checking is required. When
     *      false <i>min</i> and <i>max</i> are ignored.
     *  @param min
     *      Additional range checking lower boundary.
     *  @param max
     *      Additional range checking upper boundary.
     *  @return
     *      Array of capped timeouts. Note this may be the same array as
     *      passed in (<i>timeouts</i>).
     */
    private int[] capTimeouts(String property,
			      int[] timeouts,
			      boolean rangeCheck,
			      int min,
			      int max) {

	int averagedTimeout;
	int totalWait = 0;

	for (int index = 0; index < timeouts.length; index++) {
	    totalWait += timeouts[index];
	}

	if (rangeCheck) {
	    // If sum of timeouts within limits then finished.
	    if (totalWait >= min && totalWait <= max) {
		return timeouts;
	    }

	    // Average out the timeouts so the sum is equal to the closest
	    // range boundary.
	    if (totalWait < min) {
		averagedTimeout = min / timeouts.length;
	    } else {
		averagedTimeout = max / timeouts.length;
	    }

	    writeLog("capped_range_timeout_prop",
		     new Object[] {property,
				   String.valueOf(totalWait),
				   String.valueOf(min),
				   String.valueOf(max),
				   String.valueOf(timeouts.length),
				   String.valueOf(averagedTimeout)});
	} else {
	    // Sum of all timeouts must not exceed this value.
	    int maximumWait = getMulticastMaximumWait();

	    // If sum of timeouts within limits then finished.
	    if (totalWait <= maximumWait) {
		return timeouts;
	    }

	    // Average out the timeouts so the sum is equal to the maximum
	    // timeout.
	    averagedTimeout = maximumWait / timeouts.length;

	    writeLog("capped_timeout_prop",
		     new Object[] {property,
				   String.valueOf(totalWait),
				   String.valueOf(maximumWait),
				   String.valueOf(timeouts.length),
				   String.valueOf(averagedTimeout)});
	}

	for (int index = 0; index < timeouts.length; index++) {
	    timeouts[index] = averagedTimeout;
	}

	return timeouts;
    }

    private int[] parseTimeouts(String property, int[] defaults) {

	String sTimeouts = System.getProperty(property);

	if (sTimeouts == null || sTimeouts.length() <= 0) {
	    return defaults;

	}

	Vector timeouts = null;

	try {
	    timeouts = SrvLocHeader.parseCommaSeparatedListIn(sTimeouts, true);

	} catch (ServiceLocationException ex) {
	    writeLog("syntax_error_prop",
		     new Object[] {property, sTimeouts});
	    return defaults;

	}

	int iCount = 0;
	int[] iTOs = new int[timeouts.size()];

	for (Enumeration en = timeouts.elements(); en.hasMoreElements(); ) {
	    String s1 = (String)en.nextElement();

	    try {
		iTOs[iCount] = Integer.parseInt(s1);

	    }	catch (NumberFormatException nfe) {
		writeLog("syntax_error_prop",
			 new Object[] {property, sTimeouts});
		return defaults;

	    }

	    if (iTOs[iCount] < 0) {
		writeLog("invalid_timeout_prop",
			 new Object[] {property, String.valueOf(iTOs[iCount])});
		return defaults;
	    }

	    iCount++;
	}

	return iTOs;
    }

    // -----------------------------
    // SLP Time Calculation
    //

    /**
     * Returns the number of seconds since 00:00 Universal Coordinated
     * Time, January 1, 1970.
     *
     * Java returns the number of milliseconds, so all the method does is
     * divide by 1000.
     *
     * This implementation still will have a problem when the Java time
     * values wraps, but there isn't much we can do now.
     */
    static long currentSLPTime() {
	return (System.currentTimeMillis() / 1000);
    }

    /* security */

    // Indicates whether security class is available.

    boolean getSecurityEnabled() {
	return securityEnabled;

    }

    private static boolean securityEnabled;

    // Indicates whether the securityEnabled property is true

    boolean getHasSecurity() {
	return securityEnabled &&
	    (new Boolean(System.getProperty("net.slp.securityEnabled",
					    "false")).booleanValue());
    }

    // I18N Support.

    private static final String BASE_BUNDLE_NAME = "com/sun/slp/ClientLib";

    ResourceBundle getMessageBundle(Locale locale) {

	ResourceBundle msgBundle = null;

	// First try the Solaris Java locale area

	try {
	    URL[] urls = new URL[] {new URL("file:/usr/share/lib/locale/")};

	    URLClassLoader ld = new URLClassLoader(urls);

	    msgBundle = ResourceBundle.getBundle(BASE_BUNDLE_NAME, locale, ld);

	    return msgBundle;
	} catch (MalformedURLException e) {	// shouldn't get here
	} catch (MissingResourceException ex) {
	    System.err.println("Missing resource bundle ``"+
			       "/usr/share/lib/locale/" + BASE_BUNDLE_NAME +
			       "'' for locale ``" +
			       locale + "''; trying default...");
	}

	try {
	    msgBundle = ResourceBundle.getBundle(BASE_BUNDLE_NAME, locale);

	} catch (MissingResourceException ex) {  // can't localize this one!

	    // We can't print out to the log, because we may be in the
	    //  process of trying to.

	    System.err.println("Missing resource bundle ``"+
			       BASE_BUNDLE_NAME+
			       "'' for locale ``"+
			       locale+
			       "''");
	    // Hosed if the default locale is missing.

	    if (locale.equals(Defaults.locale)) {

		System.err.println("Exiting...");
		System.exit(1);
	    }

	    // Otherwise, return the default locale.

	    System.err.println("Using SLP default locale ``" +
			       Defaults.locale +
			       "''");

	    msgBundle = getMessageBundle(Defaults.locale);

	}

	return msgBundle;
    }

    String formatMessage(String msgTag, Object[] params) {
	ResourceBundle bundle = getMessageBundle(getLocale());
	return formatMessageInternal(msgTag, params, bundle);

    }

    // MessageFormat is picky about types. Convert the params into strings.

    static void convertToString(Object[] params) {
	int i, n = params.length;

	for (i = 0; i < n; i++) {

	    if (params[i] != null) {
		params[i] = params[i].toString();

	    } else {
		params[i] = "<null>";

	    }
	}
    }

    static String
	formatMessageInternal(String msgTag,
			      Object[] params,
			      ResourceBundle bundle) {
	String pattern = "";

	try {
	    pattern = bundle.getString(msgTag);

	} catch (MissingResourceException ex) {

	    // Attempt to report error. Can't use Assert here because it
	    //  calls back into SLPConfig.
	    String msg = "Can''t find message ``{0}''''.";

	    try {
		pattern = bundle.getString("cant_find_resource");
		msg = MessageFormat.format(pattern, new Object[] {msgTag});
	
	    } catch (MissingResourceException exx) {

	    }

	    System.err.println(msg);
	    System.exit(-1);
	}

	convertToString(params);

	return MessageFormat.format(pattern, params);
    }

    // logging.

    // Protected so slpd can replace it.

    protected Writer log;

    // Synchronized so writes from multiple threads don't get interleaved.

    void writeLog(String msgTag, Object[] params) {

	// MessageFormat is picky about types. Convert the params into strings.

	convertToString(params);

	try {
	    synchronized (log) {
		log.write(formatMessage(msgTag, params));
		log.flush();
	    }
	} catch (IOException ex) {}
    }

    void writeLogLine(String msgTag, Object[] params) {

	try {
	    String pattern = getMessageBundle(getLocale()).getString(msgTag);

	    synchronized (log) {
		log.write(formatMessage(msgTag, params));
		log.write("\n");
		log.flush();
	    }
	} catch (IOException ex) {}

    }

    static String getDateString() {

	DateFormat df = DateFormat.getDateTimeInstance(DateFormat.DEFAULT,
						       DateFormat.DEFAULT,
						       getLocale());
	Calendar calendar = Calendar.getInstance(getLocale());
	return df.format(calendar.getTime());

    }


    // On load, check whether the signature class is available, and turn
    //  security off if not.

    static {

	securityEnabled = true;
	try {
	    Class c = Class.forName("com.sun.slp.AuthBlock");

	} catch (ClassNotFoundException e) {
	    securityEnabled = false;
	}
    }

}
