The Source for Java Technology Collaboration


Table of Contents

What is a Well Known ID?

Normally JXTA uses randomly generated IDs for peers, groups, pipes, services and codats. Randomly generated IDs are the best choice for most resouces. A well known ID is an ID who's value is not random, it's value is something which can be calculated or determined by every peer.

What are Well Known IDs normally used for?

Well Known IDs are normally used for resources which are shared by all peers within a group. Examples include common group services, propagate pipes that every peer listens upon and sometimes for the IDs of peers themselves.

Are there alternatives to Well Known IDs?

Yes, you can generate a random ID and then embedd it into your application. By including the ID within the application then every peer will refer to the same resource.

Are there problems with Well Known IDs?

Yes, the biggest problem is the chance for collisions. When choosing Well Known IDs or a scheme for calculating IDs it is very important to ensure that the IDs for different resources have different values. If two resources have the same ID then applications and JXTA will be unable to refer to them properly.

The UUID implementation, for example, will simply copy the first 16 bytes of the byte[] into the ID. The BinaryID? which has an address space of 255 bytes which is large, but can have collisions if yu are not careful.

When this interface was first introduced (creating an ID with a byte arra) there was a lot of naive use of it for constructing IDs based on very non-unique source data such as text strings. (and unfortunately some of this is in the JXTA core). As a result the canonical space of these IDs is very small. (less than 28 bits in the case of the peerview advertising pipe id for example). You can also use the BinaryID? which has an address space of 255 bytes.

Avoiding ID collisions

It is very important to make sure that the data provided to this ID constructor be well chosen. For generating ids based on most input data sources this means that you should generate a cryptographic hash (SHA1, MD5, etc) of the "well known" data and pass that as the input to the byte[] constructor varient rather than the data itself.

With planning, collisions can be exactly the same as with the auto generated random ID. I use a hash which results in low collisions. If you are worried about collisions, you can also use BinaryID? which is a larger ID. Combined with a large hash, you get very low collision probability.

Note that each peer group is a new address space. So a pipe with the same minor ID is different and will not collide in another group.

That said, you do need planning. If you let a user pick their own data for the hash, you get a probable collision because for the most part, people think alike. You also get a Homer-space problem with people picking obvious stuff like 'myAddress' (Doh!). You should also not depend upon names which can collide easily (yes, Mr Smith, I mean you).

But what's good? Choose things that already do not collide. ISBN numbers, phone numbers, email addresses, complete postal addresses, etc. Think about the world around you and find places where the collision is avoided for you. If you are brave you can use passports, drivers license, and social security numbers.

But if you are still afraid of collisions, use a central authority server/database or LDAP. You can still operate in a P2P? world even if you use a server for the first 20 milliseconds to get a unique ID from a database. In fact, many applications in the commercial world need a server to handle the commerce for registering a P2P? application. At that time you can get your well-known ID.

You can also use a single address to apply to many peers. If you specify the peer's ID when connecting to a pipe, you only connect to that peer even when there are other peers waiting on that pipe ID. Note: PeerID is different in each PeerGroup you are a member of.

Playing it Safe

Don't assume an ID is collision proof. Accidents can happen and hash algorythums do not guarentee against collisions, they just make them improbable. Even with highly random ID's there is a chance. So, if it can happen, you need to detect it. The first message sent over a pipe should be some piece of data that identifies the target(in the case of bidirectional). If you combine this strategy with PKI and wrap the data in an encrypted envelope, you get authentication too.

A few links for examples and other information

Here are some links talking about Well-known-ID.

WellKnownIDs (this wiki)

If you already know about a P2P service, is that bad?

Power point on JXTA by Daniel Brookshier including well-known-ID

The Socket API in JXTA 2.0 by Daniel Brookshier

Calculating a Well Known ID

The best approach to calculating a Well Known ID is to use some form of hash function upon the resource who's ID is being calculated. SHA1 or MD5 hashes are long enough and random enough to generate good IDs. There is also a utility class in platform. Here is an example:

    DigestTool digestTool = new DigestTool();
    PeerGroupBinaryID peerGroupBinaryID = digestTool.createPeerGroupID(netPeerGroup.getPeerGroupID(),"hello world application", "chat");

Here is a useful class that can be used to create standard UUID with a hash. It also includes a way to create a pipe ID.

/*
 * com.cluck.jxta.socketChat.MD5ID.java
 * This class is used to create MD5-based UUID
 * Created on November 15, 2002, 5:37 PM
 */

package com.cluck.util;
import java.io.*;

import java.security.*;

import net.jxta.impl.id.UUID.*;
import net.jxta.id.*;
/**
 *
 * @author  Daniel Brookshier
 */
public class MD5ID {
    private  static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(MD5ID.class.getName());
    public static final String functionSeperator = "~";
    
    /**
     * Create a PipeID based on the BinaryID type with a digest of the clearTextID and function.
     *
     * @param peerGroupID Parent peer group ID.
     * @param clearTextID String used as the significant part of the address
     * @param function String used to diferentiate different clearTextID addresses (can be null).
     * @return PipeBinaryID with the digest hash of the string: clearTextID+"~"+function.
     */
    public static final net.jxta.pipe.PipeID createPipeID(net.jxta.peergroup.PeerGroupID peerGroupID,String clearTextID, String function){
        LOG.info("Creatig pipe ID = peerGroupID:"+peerGroupID+", clearText:'"+clearTextID+"' , function:'"+function+"'");
        byte[] digest = generateHash(clearTextID, function);
        return (net.jxta.pipe.PipeID)net.jxta.id.IDFactory.newPipeID(peerGroupID, digest );
    }
   
    /**
     * Create a PeerGroupID based on the BinaryID type with a digest of the clearTextID and function.
     *
     * @param peerGroupID Parent peer group ID.
     * @param clearTextID String used as the significant part of the address
     * @param function String used to diferentiate different clearTextID addresses (can be null).
     * @return PeerGroupBinaryID with the digest hash of the string: clearTextID+"~"+function.
     */
    public static final net.jxta.peergroup.PeerGroupID createPeerGroupID(net.jxta.peergroup.PeerGroupID parentPeerGroupID,String clearTextID, String function){
        LOG.info("Creating peer group ID = peerGroupID:"+parentPeerGroupID+", clearText:'"+clearTextID+"' , function:'"+function+"'");
        byte[] digest = generateHash(clearTextID, function);
        //net.jxta.impl.id.UUID.PeerGroupID pg = (net.jxta.impl.id.UUID.PeerGroupID)parentPeerGroupID;
        System.out.println("parentPeerGroupID:"+parentPeerGroupID.getClass().getName());
        net.jxta.peergroup.PeerGroupID peerGroupID = IDFactory.newPeerGroupID( parentPeerGroupID, digest );
        //net.jxta.peergroup.PeerGroupID peerGroupID = new net.jxta.impl.id.UUID.PeerGroupID((net.jxta.impl.id.UUID.PeerGroupID)parentPeerGroupID,digest);
        return peerGroupID;
    }
    public static final net.jxta.peergroup.PeerGroupID createInftrastructurePeerGroupID(String clearTextID, String function){
        LOG.info("Creating peer group ID =  clearText:'"+clearTextID+"' , function:'"+function+"'");
        byte[] digest = generateHash(clearTextID, function);
        net.jxta.peergroup.PeerGroupID peerGroupID = IDFactory.newPeerGroupID(  digest );
        return peerGroupID;
    }
    /**
     * Generates an SHA-1 digest hash of the string: clearTextID+"-"+function or: clearTextID if function was blank.<p>
     *
     * Note that the SHA-1 used only creates a 20 byte hash.<p>
     *
     * @param clearTextID A string that is to be hashed. This can be any string used for hashing or hiding data.
     * @param function A function related to the clearTextID string. This is used to create a hash associated with clearTextID so that it is a uique code.
     *
     * @return array of bytes containing the hash of the string: clearTextID+"-"+function or clearTextID if function was blank. Can return null if SHA-1 does not exist on platform.
     */
    public static final byte[] generateHash(String clearTextID, String function) {
        String id;
        
        if (function == null) {
            id = clearTextID;
        } else {
            id = clearTextID + functionSeperator + function;
        }
        byte[] buffer = id.getBytes();
        
        MessageDigest algorithm = null;
        
        try {
            algorithm = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            LOG.error("Cannot load selected Digest Hash implementation",e);
            return null;
        }
        
        
        // Generate the digest.
        algorithm.reset();
        algorithm.update(buffer);
        
        try{
            byte[] digest1 = algorithm.digest();
            return digest1;
        }catch(Exception de){
            LOG.error("Failed to creat a digest.",de);
            return null;
        }
    }
}

Topic WellKnownIDs . { Edit | Ref-By | Printable | Diffs r3 < r2 < r1 | More }
 XML java.net RSS

Revision r3 - 26 Jul 2005 - 02:32:00 - DanielBrookshier
Parents: WebHome