/* * Part of the CCNx Java Library. * * Copyright (C) 2008, 2009, 2013 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.io.content; import java.io.IOException; import java.util.EnumSet; import org.ccnx.ccn.CCNHandle; import org.ccnx.ccn.impl.CCNFlowControl; import org.ccnx.ccn.impl.CCNFlowControl.SaveType; 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.io.ErrorStateException; import org.ccnx.ccn.io.CCNAbstractInputStream.FlagTypes; import org.ccnx.ccn.profiles.SegmentationProfile; import org.ccnx.ccn.profiles.VersioningProfile; import org.ccnx.ccn.protocol.ContentName; import org.ccnx.ccn.protocol.ContentObject; import org.ccnx.ccn.protocol.Interest; import org.ccnx.ccn.protocol.KeyLocator; import org.ccnx.ccn.protocol.PublisherPublicKeyDigest; import org.ccnx.ccn.protocol.SignedInfo.ContentType; /** * Represents a secure, authenticatable link from one part of the CCN namespace to another. * * CCN links are very flexible and can be used to represent a wide variety of application-level * structures. A link can point to a specific content object (an individual block of content), * the collection of "segments" making up a specific version of a stream or document, an aggregated * "document" object consisting of multiple versions and their associated metadata, or to an arbitrary * point in the name tree -- essentially saying "treat the children of this node as if they * were my children". * * CCN links have authentication information associated with them, and can be made highly secure -- * by specifying who should have published (signed) the target of a given link, one can say effectively * "what I mean by name N is whatever Tom means by name N'". The authentication information * associated with a Link is called a LinkAuthenticator; its form and capabilities are still * evolving, but it will at least have the ability to offer indirection -- "trust anyone whose * key is signed by key K to have signed a valid target for this link". * * Links also play an important role in making up Collections, the CCN notion of a container full * of objects or names. */ public class Link extends GenericXMLEncodable implements XMLEncodable, Cloneable { /** * A CCNNetworkObject wrapper around Link, used for easily saving and retrieving * versioned Links to CCN. A typical pattern for using network objects to save * objects that happen to be encodable or serializable is to incorporate such a static * member wrapper class subclassing CCNEncodableObject, CCNSerializableObject, or * CCNNetworkObject itself inside the main class definition. */ public static class LinkObject extends CCNEncodableObject<Link> { public LinkObject(ContentName name, Link data, SaveType saveType, CCNHandle handle) throws IOException { super(Link.class, true, name, data, saveType, handle); } public LinkObject(ContentName name, Link data, SaveType saveType, PublisherPublicKeyDigest publisher, KeyLocator keyLocator, CCNHandle handle) throws IOException { super(Link.class, true, name, data, saveType, publisher, keyLocator, handle); } public LinkObject(ContentName name, CCNHandle handle) throws ContentDecodingException, IOException { super(Link.class, true, name, (PublisherPublicKeyDigest)null, handle); } public LinkObject(ContentName name, PublisherPublicKeyDigest publisher, CCNHandle handle) throws ContentDecodingException, IOException { super(Link.class, true, name, publisher, handle); } public LinkObject(ContentObject firstBlock, CCNHandle handle) throws ContentDecodingException, IOException { super(Link.class, true, firstBlock, handle); } public LinkObject(ContentName name, PublisherPublicKeyDigest publisher, CCNFlowControl flowControl) throws ContentDecodingException, IOException { super(Link.class, true, name, publisher, flowControl); } public LinkObject(ContentObject firstBlock, CCNFlowControl flowControl) throws ContentDecodingException, IOException { super(Link.class, true, firstBlock, flowControl); } public LinkObject(ContentName name, Link data, PublisherPublicKeyDigest publisher, KeyLocator keyLocator, CCNFlowControl flowControl) throws IOException { super(Link.class, true, name, data, publisher, keyLocator, flowControl); } public LinkObject(CCNEncodableObject<? extends Link> other) { super(Link.class, other); } /** * Subclasses that need to write an object of a particular type can override. * @return Content type to use. */ @Override public ContentType contentType() { return ContentType.LINK; } public ContentName getTargetName() throws ContentGoneException, ContentNotReadyException, ErrorStateException { Link lr = link(); if (null == lr) return null; return lr.targetName(); } public LinkAuthenticator getTargetAuthenticator() throws ContentNotReadyException, ContentGoneException, ErrorStateException { Link lr = link(); if (null == lr) return null; return lr.targetAuthenticator(); } public Link link() throws ContentNotReadyException, ContentGoneException, ErrorStateException { if (null == data()) return null; return data(); } public ContentObject dereference(long timeout) throws IOException { if (null == data()) return null; return link().dereference(timeout, _handle); } /** * Modify the properties of the input streams we read to read links themselves, * rather than dereferencing them and causing an infinite loop; must modify * in constructor to handle passed in content objects.. */ @Override protected EnumSet<FlagTypes> getInputStreamFlags() { return EnumSet.of(FlagTypes.DONT_DEREFERENCE); } } protected ContentName _targetName; protected String _targetLabel; protected LinkAuthenticator _targetAuthenticator = null; public Link(ContentName targetName, String targetLabel, LinkAuthenticator targetAuthenticator) { _targetName = targetName; if ((null != targetLabel) && (targetLabel.length() == 0)) targetLabel = null; _targetLabel = targetLabel; _targetAuthenticator = targetAuthenticator; } public Link(ContentName targetName, LinkAuthenticator targetAuthenticator) { _targetName = targetName; _targetAuthenticator = targetAuthenticator; } public Link(ContentName targetName) { this(targetName, null); } /** * Decoding constructor. */ public Link() {} public Link(Link other) { _targetName = other.targetName(); _targetLabel = other.targetLabel(); _targetAuthenticator = other.targetAuthenticator(); } public ContentName targetName() { return _targetName; } public String targetLabel() { return _targetLabel; } public LinkAuthenticator targetAuthenticator() { return _targetAuthenticator; } public void setTargetLabel(String label) { _targetLabel = label; } public void setTargetName(ContentName name) { _targetName = name; } public void setTargetAuthenticator(LinkAuthenticator authenticator) { _targetAuthenticator = authenticator; } /** * A stab at a dereference() method. Dereferencing is not well-defined in this * general setting -- we don't know what we'll find down below this name. A link may * link to anything in the tree, including an intermediate node, or a name qualified * down to the digest, and we need a way of distinguishing these things (TODO). Usually * you'll read the target of the link using a method that knows something about what * kind of data to find there. This is a brute-force method that hands you back a block * underneath the link target name that meets the authentication criteria; at minimum * it should pull an exact match if the link fully specifies digests and so on (TODO -- TBD), * and otherwise it'll probably assume that what is below here is either a version and * segments (get latest version) or that this is versioned and it wants segments. * * @param timeout How long to try for, in milliseconds. * @param handle Handle to use. Should not be null. * @return Returns a child object. Verifies that it meets the requirement of the link, * and that it is signed by who it claims. Could allow caller to pass in verifier * to verify higher-level trust and go look for another block on failure. * @throws IOException */ public ContentObject dereference(long timeout, CCNHandle handle) throws IOException { // getLatestVersion will return the latest version of an unversioned name, or the // latest version after a given version. So if given a specific version, get that one. // TODO -- verify, use non-default verifier. if (VersioningProfile.hasTerminalVersion(targetName())) { return handle.get(targetName(), (null != targetAuthenticator()) ? targetAuthenticator().publisher() : null, timeout); } // Don't know if we are referencing a particular object, so don't look for segments. PublisherPublicKeyDigest desiredPublisher = (null != targetAuthenticator()) ? targetAuthenticator().publisher() : null; ContentObject result = VersioningProfile.getLatestVersion(targetName(), desiredPublisher, timeout, new ContentObject.SimpleVerifier(desiredPublisher, handle.keyManager()), handle); if (null != result) { return result; } // Alright, last shot -- resolve link to unversioned data. Interest unversionedInterest = SegmentationProfile.anySegmentInterest(targetName(), (null != targetAuthenticator()) ? targetAuthenticator().publisher() : null); result = handle.get(unversionedInterest, timeout); if ((null != result) && !SegmentationProfile.isSegment(result.name())) { return null; } return result; } @Override public void decode(XMLDecoder decoder) throws ContentDecodingException { decoder.readStartElement(getElementLabel()); _targetName = new ContentName(); _targetName.decode(decoder); if (decoder.peekStartElement(CCNProtocolDTags.Label)) { _targetLabel = decoder.readUTF8Element(CCNProtocolDTags.Label); } if (decoder.peekStartElement(CCNProtocolDTags.LinkAuthenticator)) { _targetAuthenticator = new LinkAuthenticator(); _targetAuthenticator.decode(decoder); } decoder.readEndElement(); } @Override public void encode(XMLEncoder encoder) throws ContentEncodingException { if (!validate()) throw new ContentEncodingException("Link failed to validate!"); encoder.writeStartElement(getElementLabel()); _targetName.encode(encoder); if (null != targetLabel()) { encoder.writeElement(CCNProtocolDTags.Label, targetLabel()); } if (null != _targetAuthenticator) _targetAuthenticator.encode(encoder); encoder.writeEndElement(); } @Override public long getElementLabel() { return CCNProtocolDTags.Link; } @Override public boolean validate() { return (null != targetName()); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((_targetAuthenticator == null) ? 0 : _targetAuthenticator .hashCode()); result = prime * result + ((_targetLabel == null) ? 0 : _targetLabel.hashCode()); result = prime * result + ((_targetName == null) ? 0 : _targetName.hashCode()); return result; } /** * Return true if this link matches target on all fields where * target is non-null. * @param linkToMatch The specification of the values we want. * @return */ public boolean approximates(Link linkToMatch) { if (null != _targetName) { if (null == linkToMatch._targetName) return false; if (!linkToMatch._targetName.equals(_targetName)) { if (VersioningProfile.hasTerminalVersion(linkToMatch._targetName) && !VersioningProfile.hasTerminalVersion(_targetName)) { if (!_targetName.isPrefixOf(linkToMatch._targetName)) { return false; } } else { return false; } } } if (null != _targetLabel) { if (null == linkToMatch._targetLabel) return false; if (!linkToMatch._targetLabel.equals(_targetLabel)) return false; } if (null != _targetAuthenticator) { if (null == linkToMatch._targetAuthenticator) return false; return _targetAuthenticator.approximates(linkToMatch._targetAuthenticator); } return true; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Link other = (Link) obj; if (_targetAuthenticator == null) { if (other._targetAuthenticator != null) return false; } else if (!_targetAuthenticator.equals(other._targetAuthenticator)) return false; if (_targetLabel == null) { if (other._targetLabel != null) return false; } else if (!_targetLabel.equals(other._targetLabel)) return false; if (_targetName == null) { if (other._targetName != null) return false; } else if (!_targetName.equals(other._targetName)) return false; return true; } @Override public Link clone() throws CloneNotSupportedException { Link l = (Link)super.clone(); l._targetName = _targetName; l._targetLabel = _targetLabel; l._targetAuthenticator = _targetAuthenticator; return l; } @Override public String toString() { return "Link [targetName=" + targetName() + ", targetLabel=" + targetLabel() + ", targetAuthenticator=" + targetAuthenticator() + "]"; } }