/*
* Part of the CCNx Java Library.
*
* Copyright (C) 2010-2012 Palo Alto Research Center, Inc.
*
* This library is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 2.1
* as published by the Free Software Foundation.
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details. You should have received
* a copy of the GNU Lesser General Public License along with this library;
* if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
* Fifth Floor, Boston, MA 02110-1301 USA.
*/
package org.ccnx.ccn.profiles.context;
import static org.ccnx.ccn.profiles.security.KeyProfile.KEY_NAME;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.util.ArrayList;
import java.util.logging.Level;
import org.ccnx.ccn.CCNHandle;
import org.ccnx.ccn.ContentVerifier;
import org.ccnx.ccn.KeyManager;
import org.ccnx.ccn.impl.support.Log;
import org.ccnx.ccn.io.content.PublicKeyObject;
import org.ccnx.ccn.profiles.CCNProfile;
import org.ccnx.ccn.profiles.CommandMarker;
import org.ccnx.ccn.profiles.security.KeyProfile;
import org.ccnx.ccn.protocol.Component;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.ContentObject;
import org.ccnx.ccn.protocol.Exclude;
import org.ccnx.ccn.protocol.Interest;
import org.ccnx.ccn.protocol.PublisherPublicKeyDigest;
import org.ccnx.ccn.protocol.ContentObject.SimpleVerifier;
import org.ccnx.ccn.protocol.SignedInfo.ContentType;
/**
* The ServiceDiscovery protocol aids in finding data about local (same-machine)
* and nearby (network neighborhood) services. We start by retrieving service keys, using:
* /%C1.M.S.localhost/%C1.M.SRV/<servicename>/KEY/
* and getting as an answer an encoded, self-signed key published under the
* KeyProfile.keyName() of that service's key under that prefix.
*/
public class ServiceDiscoveryProfile implements CCNProfile {
public static final String STRING_LOCALHOST = "localhost";
public static final CommandMarker LOCALHOST_SCOPE =
CommandMarker.commandMarker(CommandMarker.COMMAND_MARKER_SCOPE, STRING_LOCALHOST);
public static final CommandMarker SERVICE_NAME_COMPONENT_MARKER =
CommandMarker.commandMarker(CommandMarker.MARKER_NAMESPACE, "SRV");
public static final int SCOPE_COMPONENT = 0;
public static final int SERVICE_MARKER_COMPONENT = 1;
public static final int SERVICE_NAME_COMPONENT = 3;
// Where should these go?
public static final String CCND_SERVICE_NAME = "ccnd";
public static final String REPOSITORY_SERVICE_NAME = "repository";
public static final ContentName localhostScopeName() {
// could make a static variable if we expect this to get called.
return new ContentName(LOCALHOST_SCOPE);
}
public static ContentName localServiceName(String service) {
return new ContentName(LOCALHOST_SCOPE, SERVICE_NAME_COMPONENT_MARKER, service);
}
public static String getLocalServiceName(ContentName nameWithServicePrefix) {
// /%C1.M.S.localhost/%C1.M.SRV/<servicename>
if (nameWithServicePrefix.count() < 3) {
if (Log.isLoggable(Log.FAC_KEYS, Level.FINER)) {
Log.finer(Log.FAC_KEYS, "Cannot get local service name, {0} does not have enough components.",
nameWithServicePrefix);
}
}
if (!ServiceDiscoveryProfile.LOCALHOST_SCOPE.isMarker(nameWithServicePrefix.component(SCOPE_COMPONENT))) {
if (Log.isLoggable(Log.FAC_KEYS, Level.FINER)) {
Log.finer(Log.FAC_KEYS, "Cannot get local service name, {0} does not begin with local service prefix {1}.",
nameWithServicePrefix, ServiceDiscoveryProfile.LOCALHOST_SCOPE);
}
return null;
}
if (!SERVICE_NAME_COMPONENT_MARKER.isMarker(
nameWithServicePrefix.component(SERVICE_MARKER_COMPONENT))) {
if (Log.isLoggable(Log.FAC_KEYS, Level.FINER)) {
Log.finer(Log.FAC_KEYS, "Cannot get local service name, {0} does not contain a service name component {1}.",
nameWithServicePrefix,
Component.printURI(nameWithServicePrefix.component(SERVICE_MARKER_COMPONENT)));
}
return null;
}
byte [] serviceNameComponent =
nameWithServicePrefix.component(SERVICE_NAME_COMPONENT);
return Component.printNative(serviceNameComponent);
}
/**
* We want to get a list of local implementors of this service and their keys; until the
* timeout. We use excludes to get all of them within the timeout. We get whole keys because
* they're usually a single object, so there is no reason not to load them. Rather than decoding
* them all, though, we just hand back the CO to the caller to decide what to do.
*
* Start by taking a timeout to use as the inter-response interval; if we haven't heard anything
* in that long, we stop.
* @param service
* @param serviceKey
* @param keyManager
* @throws IOException
*/
public static ArrayList<ContentObject> getLocalServiceKeys(String service, long timeout, CCNHandle handle) throws IOException {
ContentName serviceKeyName = new ContentName(localServiceName(service), KEY_NAME);
// Construct an interest in anything below this that has the right form -- this prefix, a component
// for the key id, and then a version and segments. Might be more expensive to apply the filters than
// to throw things away and go around again...
Interest theInterest = Interest.lower(serviceKeyName, 4, null);
theInterest.answerOriginKind(0); // bypass the cache
ArrayList<ContentObject> results = new ArrayList<ContentObject>();
ContentObject theResult = null;
int keyidComponent = serviceKeyName.count();
ArrayList<byte[]> excludeList = new ArrayList<byte[]>();
// We need a verifier that checks the match between publisher and public key.
ContentVerifier verifier = new SimpleVerifier(null, handle.keyManager());
do {
if (excludeList.size() > 0) {
//we have explicit excludes, add them to this interest
byte [][] e = new byte[excludeList.size()][];
excludeList.toArray(e);
if (null != theInterest.exclude()) {
theInterest.exclude().add(e);
} else {
Exclude theExclude = new Exclude(e);
theInterest.exclude(theExclude);
}
}
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO)) {
Log.info(Log.FAC_KEYS, "getLocalServiceKeys, interest: {0}", theInterest);
}
theResult = handle.get(theInterest, timeout);
if (null != theResult) {
// Verify theResult (should go into handle.get)
// Check to see if theResult matches criteria
if (verifier.verify(theResult) && (ContentType.KEY == theResult.signedInfo().getType())) {
// it's a key, remember it, and see if we can find any others.
results.add(theResult);
excludeList.add(theResult.name().component(keyidComponent));
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO)) {
Log.info(Log.FAC_KEYS, "Got key for service {0}: {1}", service, theResult.name());
}
} else {
// we don't want to exclude other things with this next component, but
// do want to exclude this one; need digest exclude
}
}
} while (null != theResult);
return results;
}
/**
* Query for local service keys when we believe there is only a single instance of a
* service. Returns as soon as it gets a first response, or on timeout.
* @param service
* @param timeout
* @param handle
* @return
* @throws IOException
*/
public static PublicKeyObject getLocalServiceKey(String service, long timeout, CCNHandle handle) throws IOException {
ContentName serviceKeyName = new ContentName(localServiceName(service), KEY_NAME);
// Construct an interest in anything below this that has the right form -- this prefix, a component
// for the key id, and then a version and segments. Might be more expensive to apply the filters than
// to throw things away and go around again...
Interest theInterest = Interest.lower(serviceKeyName, 4, null);
theInterest.answerOriginKind(0); // bypass the cache
ContentObject theResult = null;
int keyidComponent = serviceKeyName.count();
ArrayList<byte[]> excludeList = new ArrayList<byte[]>();
// We need a verifier that checks the match between publisher and public key.
ContentVerifier verifier = new SimpleVerifier(null, handle.keyManager());
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO)) {
Log.info(Log.FAC_KEYS, "getLocalServiceKey, interest: {0}", theInterest);
}
long stopTime = System.currentTimeMillis() + timeout;
do {
if (excludeList.size() > 0) {
//we have explicit excludes, add them to this interest
byte [][] e = new byte[excludeList.size()][];
excludeList.toArray(e);
if (null != theInterest.exclude()) {
theInterest.exclude().add(e);
} else {
Exclude theExclude = new Exclude(e);
theInterest.exclude(theExclude);
}
}
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO)) {
Log.info(Log.FAC_KEYS, "getLocalServiceKeys, interest: {0}", theInterest);
}
theResult = handle.get(theInterest, timeout);
if (null != theResult) {
// Verify theResult (should go into handle.get)
// Check to see if theResult matches criteria
if (verifier.verify(theResult) && (ContentType.KEY == theResult.signedInfo().getType())) {
// it's a key, remember it, and see if we can find any others.
if (Log.isLoggable(Log.FAC_KEYS, Level.INFO)) {
Log.info(Log.FAC_KEYS, "Got key for service {0}: {1}", service, theResult.name());
}
return new PublicKeyObject(theResult, handle);
} else {
// we don't want to exclude other things with this next component, but
// do want to exclude this one; need digest exclude
// for now, exclude the keyid, which is wrong TODO FIX
excludeList.add(theResult.name().component(keyidComponent));
}
}
} while (System.currentTimeMillis() < stopTime);
return null;
}
public static void publishLocalServiceKey(String service, PublisherPublicKeyDigest serviceKey, KeyManager keyManager) throws InvalidKeyException, IOException {
if (null == serviceKey) {
serviceKey = keyManager.getDefaultKeyID();
}
ContentName serviceKeyPrefix = new ContentName(localServiceName(service), KEY_NAME);
ContentName serviceKeyName =
KeyProfile.keyName(serviceKeyPrefix, serviceKey);
// Need a way to override any stored key locator. Don't remember this key locator.
keyManager.publishSelfSignedKey(serviceKeyName, serviceKey, false);
// Register a filter for the interest we are likely to actually get.
keyManager.respondToKeyRequests(serviceKeyPrefix);
}
}