/* * Part of the CCNx Java Library. * * Copyright (C) 2008-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.protocol; import static org.ccnx.ccn.protocol.Component.NONCE; import java.io.IOException; import java.util.Arrays; import java.util.logging.Level; import org.ccnx.ccn.TrustManager; import org.ccnx.ccn.impl.encoding.CCNProtocolDTags; import org.ccnx.ccn.impl.encoding.GenericXMLEncodable; import org.ccnx.ccn.impl.encoding.XMLDecoder; import org.ccnx.ccn.impl.encoding.XMLEncodable; import org.ccnx.ccn.impl.encoding.XMLEncoder; import org.ccnx.ccn.impl.support.DataUtils; import org.ccnx.ccn.impl.support.Log; import org.ccnx.ccn.io.content.ContentDecodingException; import org.ccnx.ccn.io.content.ContentEncodingException; /** * Represents a CCN Interest packet, and performs all the allowed specializations * of queries recognized and supported by them. * cf. ContentObject * * Implements Comparable to make it easy to store in a Set and avoid duplicates. */ public class Interest extends GenericXMLEncodable implements XMLEncodable, Comparable<Interest>, Cloneable, ContentNameProvider { // Used to remove spurious *'s public static final String RECURSIVE_POSTFIX = "*"; // ChildSelector values public static final int CHILD_SELECTOR_LEFT = 0; public static final int CHILD_SELECTOR_RIGHT = 1; /** * AnswerOriginKind values * These are bitmapped. Default is 3. 2 is not allowed */ public static final int ANSWER_CONTENT_STORE = 1; public static final int ANSWER_GENERATED = 2; public static final int ANSWER_STALE = 4; // Stale answer OK public static final int MARK_STALE = 16; // Must have Scope 0. Michael calls this a "hack" public static final int DEFAULT_ANSWER_ORIGIN_KIND = ANSWER_CONTENT_STORE | ANSWER_GENERATED; protected ContentName _name; protected Integer _maxSuffixComponents; protected Integer _minSuffixComponents; // DKS TODO can we really support a PublisherID here, or just a PublisherPublicKeyDigest? protected PublisherID _publisher; protected Exclude _exclude; protected Integer _childSelector; protected Integer _answerOriginKind = null; protected Integer _scope; protected byte[] _interestLifetime = null; // For now we don't have the ability to set an interest lifetime protected byte[] _nonce; public long userTime; /** * TODO: DKS figure out how to handle encoding faster, * and how to handle shorter version of names without * copying, particularly without 1.6 array ops. * @param name ContentName of Interest * @param publisher PublisherID of Interest or null */ public Interest(ContentName name, PublisherID publisher) { _name = name; _publisher = publisher; } /** * @param name ContentName of Interest * @param publisher PublisherPublicKeyDigest or null */ public Interest(ContentName name, PublisherPublicKeyDigest publisher) { this(name, (null != publisher) ? new PublisherID(publisher) : (PublisherID)null); } /** * Creates Interest with null publisher ID * @param name */ public Interest(ContentName name) { this(name, (PublisherID)null); } public Interest(String name) throws MalformedContentNameStringException { this(ContentName.fromURI(name), (PublisherID)null); } public Interest() {} // for use by decoders public ContentName name() { return _name; } public void name(ContentName name) { _name = name; } public Integer maxSuffixComponents() { return _maxSuffixComponents; } public void maxSuffixComponents(Integer maxSuffixComponents) { _maxSuffixComponents = maxSuffixComponents; } public Integer minSuffixComponents() { return _minSuffixComponents; } public void minSuffixComponents(Integer minSuffixComponents) { _minSuffixComponents = minSuffixComponents; } public PublisherID publisherID() { return _publisher; } public void publisherID(PublisherID publisherID) { _publisher = publisherID; } public Exclude exclude() { return _exclude; } public void exclude(Exclude exclude) { _exclude = exclude; } public Integer childSelector() { return _childSelector;} public void childSelector(int childSelector) { _childSelector = childSelector; } public byte[] interestLifetime() { return _interestLifetime;} public void interestLifetime(byte[] interestLifetime) { _interestLifetime = interestLifetime; } public Integer answerOriginKind() { if (null == _answerOriginKind) { return DEFAULT_ANSWER_ORIGIN_KIND; } return _answerOriginKind; } public void answerOriginKind(int answerOriginKind) { if (DEFAULT_ANSWER_ORIGIN_KIND == answerOriginKind) { _answerOriginKind = null; } else { _answerOriginKind = answerOriginKind; } } public Integer scope() { return _scope; } public void scope(int scope) { _scope = scope; } /** * XXX - This isn't user settable and is only useful for ccnd internal functionality. * Do we ever need to return it? * @return */ public byte[] nonce() { return _nonce; } /** * Determine whether a piece of content matches the Interest * @param test * @return true if the test data packet matches the Interest */ public boolean matches(ContentObject test) { return matches(test, (null != test.signedInfo()) ? test.signedInfo().getPublisherKeyID() : null); } /** * Determine whether a piece of content's name *without* digest component matches this Interest. * * This doesn't match if the digest is specified in the Interest. * @see Interest#matches(ContentObject, PublisherPublicKeyDigest) * * @param name - Name of a content object missing it's implied digest component * @param resultPublisherKeyID * @return true if the content/publisherPublicKeyDigest matches the Interest */ public boolean matches(ContentName name, PublisherPublicKeyDigest resultPublisherKeyID) { if (null == name() || null == name) return false; // null name() should not happen, null arg can // to get interest that matches everything, should // use / (ROOT) if (isPrefixOf(name)) { return internalMatch(name, false, resultPublisherKeyID); } return false; } /** * Determine whether a piece of content matches this Interest. * Note: this computes the digest for the ContentObject, to know the full name. This is * computationally expensive. * @see Interest#matches(ContentName, PublisherPublicKeyDigest) * TODO: compute digests once when ContentObjects are received into the machine, and pass them * around with the ContentObjects. * * @param co - ContentObject * @param resultPublisherKeyID * @return true if the content & publisherID match the Interest */ public boolean matches(ContentObject co, PublisherPublicKeyDigest resultPublisherKeyID) { if (null == name() || null == co) return false; // null name() should not happen, null arg can // to get interest that matches everything, should // use / (ROOT) boolean digest = co.name().count()+1 == name().count(); if (co.name().count() == name().count() && (exclude() != null && !exclude().empty())) { //the interest does not have a digest in the name, but it does have at least one excluded digest = true; } ContentName name = digest ? co.fullName() : co.name(); if (isPrefixOf(name)) { return internalMatch(name, digest, resultPublisherKeyID); } return false; } // TODO We need to beef this up to deal with the more complex interest specs. private boolean internalMatch(ContentName name, boolean digestIncluded, PublisherPublicKeyDigest resultPublisherKeyID) { if (null != maxSuffixComponents() || null != minSuffixComponents()) { // we know our specified name is a prefix of the result. // the number of additional components must be this value int nameCount = name.count(); int lengthDiff = nameCount + (digestIncluded?0:1) - name().count(); if (null != maxSuffixComponents() && lengthDiff > maxSuffixComponents()) { //Log.fine("Interest match failed: " + lengthDiff + " more than the " + maxSuffixComponents() + " components between expected " + // name() + " and tested " + name); if(Log.isLoggable(Log.FAC_ENCODING, Level.FINE)) Log.fine(Log.FAC_ENCODING, "Interest match failed: {0} more than the {1} components between expected {2} and tested {3}",lengthDiff, maxSuffixComponents(), name(), name); return false; } if (null != minSuffixComponents() && lengthDiff < minSuffixComponents()) { //Log.fine("Interest match failed: " + lengthDiff + " less than the " + minSuffixComponents() + " components between expected " + // name() + " and tested " + name); if(Log.isLoggable(Log.FAC_ENCODING, Level.FINE)) Log.fine(Log.FAC_ENCODING, "Interest match failed: {0} less than the {1} components between expected {2} and tested {3}",lengthDiff, minSuffixComponents(), name(), name); return false; } } if (null != exclude()) { if (exclude().match(name.component(name().count()))) { if (Log.isLoggable(Log.FAC_ENCODING, Level.FINEST)) Log.finest(Log.FAC_ENCODING, "Interest match failed. {0} has been excluded", name); return false; } } if (null != publisherID()) { if (null == resultPublisherKeyID) { if (Log.isLoggable(Log.FAC_ENCODING, Level.FINEST)) Log.finest(Log.FAC_ENCODING, "Interest match failed, target {0} doesn't specify a publisherID and we require a particular one.", name); return false; } // Should this be more general? // TODO DKS handle issuer if (Log.isLoggable(Log.FAC_ENCODING, Level.FINEST)) Log.finest(Log.FAC_ENCODING, "Interest match handed off to trust manager for name: {0}", name); return TrustManager.getTrustManager().matchesRole(publisherID(), resultPublisherKeyID); } if (Log.isLoggable(Log.FAC_ENCODING, Level.FINEST)) Log.finest(Log.FAC_ENCODING, "Interest match succeeded to name: {0}", name); return true; } /** * Return data a specified number of levels below us in the hierarchy, with * order preference of leftmost. * @param name name prefix for interest * @param level number of levels below us we want content. Includes the ephemeral * digest component in the count. * @param publisher who should have signed content (can be null) */ public static Interest lower(ContentName name, int level, PublisherPublicKeyDigest publisher) { Interest interest = new Interest(name, publisher); interest.maxSuffixComponents(level); interest.minSuffixComponents(level); return interest; } /** * Construct an Interest that will give you the next content after the argument * name's first prefixCount components * @param name * @param prefixCount may be null * @param publisher may be null * @return new Interest */ public static Interest next(ContentName name, Integer prefixCount, PublisherPublicKeyDigest publisher) { return next(name, null, prefixCount, null, null, publisher); } /** * Construct an Interest that will give you the next content after the argument * names's first prefixCount components excluding the components specified in the omissions * @param name * @param omissions components to exclude - may be null * @param prefixCount may be null * @param publisher may be null * @return */ public static Interest next(ContentName name,Exclude exclude, Integer prefixCount, Integer maxSuffixComponents, Integer minSuffixComponents, PublisherPublicKeyDigest publisher) { return nextOrLast(name, exclude, new Integer(CHILD_SELECTOR_LEFT), prefixCount, maxSuffixComponents, minSuffixComponents, publisher); } /** * Regardless of whether we are looking for the next or the last Content * we always want to exclude everything before the first component at the * prefix level. * * @param name * @param exclude contains elements to exclude * @param order corresponds to ChildSelector values * @param prefixCount may be null * @param publisher may be null * @return the Interest */ private static Interest nextOrLast(ContentName name, Exclude exclude, Integer order, Integer prefixCount, Integer maxSuffixComponents, Integer minSuffixComponents, PublisherPublicKeyDigest publisher ) { if (null != prefixCount) { if (prefixCount > name.count()) throw new IllegalArgumentException("Invalid prefixCount > components: " + prefixCount); } else prefixCount = name.count() - 1; if (prefixCount < name.count()) { byte [] component = name.component(prefixCount); name = name.cut(prefixCount); if (exclude == null) { exclude = Exclude.uptoFactory(component); } else exclude.excludeUpto(component); } return constructInterest(name, exclude, order, maxSuffixComponents, minSuffixComponents, publisher); } /** * Construct an Interest that will give you the last content after the argument * name's first prefixCount components * @param name * @param prefixCount may be null * @param publisher may be null * @return new Interest */ public static Interest last(ContentName name, Integer prefixCount, PublisherPublicKeyDigest publisher) { return last(name, null, prefixCount, null, null, publisher); } /** * Construct an Interest that will give you the last content after the argument * name excluding the components specified in the Exclude * @param name * @param exclude contains components to exclude - may be null * @param prefixCount may be null * @param publisher may be null * @return the Interest */ public static Interest last(ContentName name, Exclude exclude, Integer prefixCount, Integer maxSuffixComponents, Integer minSuffixComponents, PublisherPublicKeyDigest publisher) { return nextOrLast(name, exclude, new Integer(CHILD_SELECTOR_RIGHT), prefixCount, maxSuffixComponents, minSuffixComponents, publisher); } /** * Construct an Interest that will exclude the values in omissions and require maxSuffixComponents and * minSuffixComponents as specific * @param name * @param omissions components to exclude * @param publisherID * @param maxSuffixComponents * @param minSuffixComponents * @return the Interest */ public static Interest exclude(ContentName name, Exclude exclude, Integer maxSuffixComponents, Integer minSuffixComponents, PublisherPublicKeyDigest publisherID) { return constructInterest(name, exclude, null, maxSuffixComponents, minSuffixComponents, publisherID); } /** * Construct an Interest with specified values set * @param name * @param filter may be null * @param childSelector may be null * @param publisherID may be null * @param maxSuffixComponents may be null * @param minSuffixComponents may be null * @return the Interest */ public static Interest constructInterest(ContentName name, Exclude filter, Integer childSelector, Integer maxSuffixComponents, Integer minSuffixComponents, PublisherPublicKeyDigest publisher) { PublisherID pubID = null; if (publisher!=null) pubID = new PublisherID(publisher); Interest interest = new Interest(name); if (null != childSelector) interest.childSelector(childSelector); if (null != filter) interest.exclude(filter); if (null != pubID) interest.publisherID(pubID); if (null != maxSuffixComponents) interest.maxSuffixComponents(maxSuffixComponents); if (null != minSuffixComponents) interest.minSuffixComponents(minSuffixComponents); return interest; } /** * Currently used as an Interest name component to disambiguate multiple requests for the * same content. * * @return the nonce in component form * @deprecated use {@link Component#NONCE} instead. */ @Deprecated public static byte[] generateNonce() { return NONCE.getComponent(); } /** * Determine if this Interest's name is a prefix of the specified name * @param name * @return true if our name is a prefix of the specified name */ public boolean isPrefixOf(ContentName name) { int count = name().count(); if (null != maxSuffixComponents() && 0 == maxSuffixComponents()) { // This Interest is trying to match a complete content name with digest explicitly included // so we must drop the last component for the prefix test against a name that is // designed to be direct from ContentObject and so does not include digest explicitly //count--; } return name().isPrefixOf(name, count); } /** * Determine if this Interest's name is a prefix of the first "count" components of the input name * @param name * @param count * @return true if our name is a prefix of the specified name's first "count" components */ public boolean isPrefixOf(ContentName name, int count) { return name().isPrefixOf(name, count); } /** * Determine if this Interest's name is a prefix of the specified ContentObject's name * @param other * @return true if our name is a prefix of the specified ContentObject's name */ public boolean isPrefixOf(ContentObject other) { return name().isPrefixOf(other, name().count()); } /** * Thought about encoding and decoding as flat -- no wrapping * declaration. But then couldn't use these solo. */ public void decode(XMLDecoder decoder) throws ContentDecodingException { decoder.readStartElement(getElementLabel()); _name = new ContentName(); _name.decode(decoder); if (decoder.peekStartElement(CCNProtocolDTags.MinSuffixComponents)) { _minSuffixComponents = decoder.readIntegerElement(CCNProtocolDTags.MinSuffixComponents); } if (decoder.peekStartElement(CCNProtocolDTags.MaxSuffixComponents)) { _maxSuffixComponents = decoder.readIntegerElement(CCNProtocolDTags.MaxSuffixComponents); } if (PublisherID.peek(decoder)) { _publisher = new PublisherID(); _publisher.decode(decoder); } if (decoder.peekStartElement(CCNProtocolDTags.Exclude)) { _exclude = new Exclude(); _exclude.decode(decoder); } if (decoder.peekStartElement(CCNProtocolDTags.ChildSelector)) { _childSelector = decoder.readIntegerElement(CCNProtocolDTags.ChildSelector); } if (decoder.peekStartElement(CCNProtocolDTags.AnswerOriginKind)) { // call setter to handle defaulting _answerOriginKind = decoder.readIntegerElement(CCNProtocolDTags.AnswerOriginKind); } if (decoder.peekStartElement(CCNProtocolDTags.Scope)) { _scope = decoder.readIntegerElement(CCNProtocolDTags.Scope); } if (decoder.peekStartElement(CCNProtocolDTags.InterestLifetime)) { _interestLifetime = decoder.readBinaryElement(CCNProtocolDTags.InterestLifetime); } if (decoder.peekStartElement(CCNProtocolDTags.Nonce)) { _nonce = decoder.readBinaryElement(CCNProtocolDTags.Nonce); } decoder.readEndElement(); } public void encode(XMLEncoder encoder) throws ContentEncodingException { if (!validate()) { throw new ContentEncodingException("Cannot encode " + this.getClass().getName() + ": field values missing."); } encoder.writeStartElement(getElementLabel()); name().encode(encoder); if (null != minSuffixComponents()) encoder.writeElement(CCNProtocolDTags.MinSuffixComponents, minSuffixComponents()); if (null != maxSuffixComponents()) encoder.writeElement(CCNProtocolDTags.MaxSuffixComponents, maxSuffixComponents()); if (null != publisherID()) publisherID().encode(encoder); if (null != exclude()) exclude().encode(encoder); if (null != childSelector()) encoder.writeElement(CCNProtocolDTags.ChildSelector, childSelector()); if (DEFAULT_ANSWER_ORIGIN_KIND != answerOriginKind()) encoder.writeElement(CCNProtocolDTags.AnswerOriginKind, answerOriginKind()); if (null != scope()) encoder.writeElement(CCNProtocolDTags.Scope, scope()); if (null != nonce()) encoder.writeElement(CCNProtocolDTags.Nonce, nonce()); encoder.writeEndElement(); } @Override public long getElementLabel() { return CCNProtocolDTags.Interest; } @Override public boolean validate() { // DKS -- do we do recursive validation? // null authenticator ok return (null != name()); } public int compareTo(Interest o) { int result = DataUtils.compare(name(), o.name()); if (result != 0) return result; result = DataUtils.compare(maxSuffixComponents(), o.maxSuffixComponents()); if (result != 0) return result; result = DataUtils.compare(minSuffixComponents(), o.minSuffixComponents()); if (result != 0) return result; result = DataUtils.compare(publisherID(), o.publisherID()); if (result != 0) return result; result = DataUtils.compare(exclude(), o.exclude()); if (result != 0) return result; result = DataUtils.compare(childSelector(), o.childSelector()); if (result != 0) return result; result = DataUtils.compare(answerOriginKind(), o.answerOriginKind()); if (result != 0) return result; result = DataUtils.compare(scope(), o.scope()); if (result != 0) return result; return result; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((_maxSuffixComponents == null) ? 0 : _maxSuffixComponents .hashCode()); result = prime * result + ((_minSuffixComponents == null) ? 0 : _minSuffixComponents .hashCode()); result = prime * result + ((_answerOriginKind == null) ? 0 : _answerOriginKind .hashCode()); result = prime * result + ((_exclude == null) ? 0 : _exclude.hashCode()); result = prime * result + ((_name == null) ? 0 : _name.hashCode()); result = prime * result + ((_childSelector == null) ? 0 : _childSelector.hashCode()); result = prime * result + ((_publisher == null) ? 0 : _publisher.hashCode()); result = prime * result + ((_scope == null) ? 0 : _scope.hashCode()); result = prime * result + Arrays.hashCode(_interestLifetime); result = prime * result + Arrays.hashCode(_nonce); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Interest other = (Interest) obj; if (_maxSuffixComponents == null) { if (other._maxSuffixComponents != null) return false; } else if (!_maxSuffixComponents.equals(other._maxSuffixComponents)) return false; if (_minSuffixComponents == null) { if (other._minSuffixComponents != null) return false; } else if (!_minSuffixComponents.equals(other._minSuffixComponents)) return false; if (_answerOriginKind == null) { if (other._answerOriginKind != null) return false; } else if (!_answerOriginKind.equals(other._answerOriginKind)) return false; if (_exclude == null) { if (other._exclude != null) return false; } else if (!_exclude.equals(other._exclude)) return false; if (_name == null) { if (other._name != null) return false; } else if (!_name.equals(other._name)) return false; if (_childSelector == null) { if (other._childSelector != null) return false; } else if (!_childSelector.equals(other._childSelector)) return false; if (_publisher == null) { if (other._publisher != null) return false; } else if (!_publisher.equals(other._publisher)) return false; if (_scope == null) { if (other._scope != null) return false; } else if (!_scope.equals(other._scope)) return false; return true; } public String toString() { StringBuffer sb = new StringBuffer(_name.toString()); sb.append(": "); if (null != _maxSuffixComponents) sb.append(" maxsc:" + _maxSuffixComponents); if (null != _minSuffixComponents) sb.append(" minsc:" + _minSuffixComponents); if (null != _publisher) sb.append(" p:" + _publisher); if (null != _exclude) sb.append(" ex("+_exclude+")"); return sb.toString(); } public Interest clone() { Interest clone = new Interest(name()); if (null != _maxSuffixComponents) clone.maxSuffixComponents(maxSuffixComponents()); if (null != _minSuffixComponents) clone.minSuffixComponents(minSuffixComponents()); if (null != _publisher) clone.publisherID(publisherID()); if (null != _exclude) clone.exclude(exclude()); if (null != _childSelector) clone.childSelector(childSelector()); if (null != _answerOriginKind) clone.answerOriginKind(answerOriginKind()); if (null != _interestLifetime) clone.interestLifetime(interestLifetime()); if (null != _scope) clone.scope(scope()); return clone; } @SuppressWarnings("serial") public static class NoResponseException extends IOException { protected Interest interest; public NoResponseException(Interest i) { super(i.toString()); interest = i; } public Interest getInterest() { return interest; } } public ContentName getContentName() { return _name; } }