/* * 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.io; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.logging.Level; import org.ccnx.ccn.CCNHandle; import org.ccnx.ccn.ContentVerifier; import org.ccnx.ccn.config.ConfigurationException; import org.ccnx.ccn.impl.security.crypto.CCNDigestHelper; import org.ccnx.ccn.impl.support.DataUtils; import org.ccnx.ccn.impl.support.Log; import org.ccnx.ccn.profiles.SegmentationProfile; import org.ccnx.ccn.profiles.VersioningProfile; import org.ccnx.ccn.profiles.nameenum.EnumeratedNameList; import org.ccnx.ccn.protocol.ContentName; import org.ccnx.ccn.protocol.ContentObject; import org.ccnx.ccn.protocol.Interest; import org.ccnx.ccn.protocol.PublisherPublicKeyDigest; import org.ccnx.ccn.protocol.SignedInfo.ContentType; /** * Miscellaneous helper functions to read data. Most clients will * prefer the higher-level interfaces offered by CCNInputStream and * its subclasses, or CCNNetworkObject and its subclasses. */ public class CCNReader { protected CCNHandle _handle; public CCNReader(CCNHandle handle) throws ConfigurationException, IOException { _handle = handle; if (null == _handle) _handle = CCNHandle.open(); } /** * Gets a ContentObject matching this name and publisher * @param name desired name or prefix for data * @param publisher desired publisher or null for any publisher * @param timeout milliseconds to wait for data * @return data matching the name and publisher or null * @throws IOException */ public ContentObject get(ContentName name, PublisherPublicKeyDigest publisher, long timeout) throws IOException { return _handle.get(name, publisher, timeout); } /** * Gets a ContentObject matching this interest, CURRENTLY UNVERIFIED. * @param interest interest for desired object * @param timeout milliseconds to wait for data * @return data matching the interest or null * @throws IOException */ public ContentObject get(Interest interest, long timeout) throws IOException { return _handle.get(interest, timeout); } /** * Helper method to retrieve a set of segmented content blocks and rebuild them * into a single buffer. Equivalent to CCNWriter's put. Does not do anything about * versioning. */ public byte [] getData(ContentName name, PublisherPublicKeyDigest publisher, int timeout) throws IOException { CCNInputStream inputStream = new CCNInputStream(name, publisher, _handle); inputStream.setTimeout(timeout); byte [] data = DataUtils.getBytesFromStream(inputStream); return data; } /** * Helper method to retrieve a set of segmented content blocks and rebuild them * into a single buffer. Equivalent to CCNWriter's put. Does not do anything about * versioning. */ public byte [] getVersionedData(ContentName name, PublisherPublicKeyDigest publisher, int timeout) throws IOException { CCNInputStream inputStream = new CCNVersionedInputStream(name, publisher, _handle); inputStream.setTimeout(timeout); byte [] data = DataUtils.getBytesFromStream(inputStream); return data; } /** * Return data the specified number of levels below us in the * hierarchy, with order preference of leftmost. * * @param handle handle to use for requests * @param name of content to get * @param level number of levels below name in the hierarchy content should sit * @param publisher the desired publisher of this content, or null for any publisher. * @param timeout timeout for retrieval * @return matching content, if found * @throws IOException */ public ContentObject getLower(ContentName name, int level, PublisherPublicKeyDigest publisher, long timeout) throws IOException { return _handle.get(Interest.lower(name, level, publisher), timeout); } /** * Enumerate matches below query name in the hierarchy, looking * at raw content. For a higher-level enumeration protocol see the * name enumeration protocol. * Note this method is also quite slow because it has to timeout requests at every * search level * @param query an Interest defining the highest level of the query * @param timeout - milliseconds to wait for each individual get of data, default is 5 seconds * @return a list of the content objects matching this query * @throws IOException */ public ArrayList<ContentObject> enumerate(Interest query, long timeout) throws IOException { ArrayList<ContentObject> result = new ArrayList<ContentObject>(); // This won't work without a correct order preference int count = query.name().count(); while (true) { ContentObject co = null; co = _handle.get(query, timeout); if (co == null) break; Log.info(Log.FAC_IO, "enumerate: retrieved " + co.fullName() + " on query: " + query.name()); result.add(co); for (int i = co.name().count() - 1; i > count; i--) { result.addAll(enumerate(new Interest(co.name().cut(i)), timeout)); } query = Interest.next(co.name(), count, null); } Log.info(Log.FAC_IO, "enumerate: retrieved " + result.size() + " objects."); return result; } /** * API to determine whether a piece of content exists in the repository. Currently uses * name enumeration. Will change to alternative protocol whenever repository supports one. * * Deprecated - instead use RepositoryControl.localRepoSync which will sync data if its not in the * repository which is what we want to do if this returns false. * * @param availableContent * @param timeout * @param handle * @return * @throws IOException */ @Deprecated public static ContentObject isContentInRepository(ContentObject availableContent, long timeout, CCNHandle handle) throws IOException { if (timeout == 0) { // don't check (don't do < 0, -1 is NO_TIMEOUT) return null; } // Enumerate the name of the object we got, and see if it is actually in the repository. // If the first segment is in the repository, assume whole thing is there (mostly this will be used // for keys, which only have one segment, typically.) EnumeratedNameList enl = new EnumeratedNameList(availableContent.name(), handle); // TODO don't wait for all children, just wait for this one for a maximum time of timeout. enl.waitForChildren(timeout); // have to time out, may be nothing there. enl.stopEnumerating(); if (enl.hasChild(availableContent.digest())) { // it's in there... return availableContent; } // Not in there, could be all sorts of places it was off. Don't worry about those for now. Log.info(Log.FAC_IO, "Repository does not contain expected child of {0}, has {1} children at that point", availableContent.name(), enl.childCount()); return null; } /** * API to determine whether a piece of content exists in the repository. Currently uses * name enumeration. Will change to alternative protocol whenever repository supports one. * * Deprecated - instead use RepositoryControl.localRepoSync which will sync data if its not in the * repository which is what we want to do if this returns false. * * @param contentName * @param desiredType * @param desiredContentDigest * @param verifier * @param timeout * @param handle * @return * @throws IOException */ @Deprecated public static ContentObject isContentInRepository(ContentName contentName, ContentType desiredType, byte [] desiredContentDigest, PublisherPublicKeyDigest desiredPublisher, ContentVerifier verifier, long timeout, CCNHandle handle) throws IOException { // Originally wrote this just for keys, but it is actually general; make it // so for now. Replace by better repo protocols when available. // Repo content is assumed to have a version, contentName may not in which case it // refers to the latest version available. (Note: should we check to see what the // latest version is in the caches before we see what version is in the repo? // Probably best to do that, rather than to then enum the repo and just see what // the latest version there is. // HACK - want to use repo confirmation protocol to make sure data makes it to a repo // even if it doesn't come from us. Problem is, we may have already written it, and don't // want to write a brand new version of identical data. If we try to publish it under // the same (unversioned) name, the repository may get some of the data from the ccnd // cache, which will cause us to think it hasn't been written. So for the moment, we use the // name enumeration protocol to determine whether this key has been written to a repository // already. We can't ask the repo (currently) what the content is of the thing it has written; // we need to see if the digest matches. If we merely wanted to see if the repository has // the exact object we have in our server, it'd be easy. But we might have published it // a week ago, and so the current version in the server might be signed later... // There are several options: contentName has a version, in which case the repo has to have that // version, or contentName doesn't have a version, in which case the repo ought to have something // with a version. // See if we can get the content, or its latest version, somewhere. This is a little sketchy -- // doesn't handle keys that are not versioned. TODO should we handle those -- retry in isContentAvailalble // should do it. ContentObject latestAvailableContent = isVersionedContentAvailable(contentName, desiredType, desiredContentDigest, desiredPublisher, verifier, timeout, handle); if (null == latestAvailableContent) { return null; // if it's not available, it's not in a repo } return CCNReader.isContentInRepository(latestAvailableContent, timeout, handle); } /** * Checks to see if the named content or its latest version is available on the network; if it * is, returns its first segment as a ContentObject. Actually should dereference links, as * actual reads are done via input streams. Could remove our separate pull if we * plumb content verifiers up through streams. * @param contentName * @param desiredType * @param desiredContentDigest * @param desiredPublisher * @param verifier * @param timeout * @return * @throws IOException */ public static ContentObject isVersionedContentAvailable(ContentName contentName, ContentType desiredType, byte [] desiredContentDigest, PublisherPublicKeyDigest desiredPublisher, ContentVerifier verifier, long timeout, CCNHandle handle) throws IOException { if (timeout == 0) { // don't check (don't do < 0, -1 is NO_TIMEOUT) return null; } // TODO -- add type checking. Cheezy way to add type checking to input streams; put it in // the verifier and allows verifiers to nest. // Null if no terminal version byte [] contentNameVersionComponent = VersioningProfile.cutTerminalVersion(contentName).second(); ContentObject retrievedObject = null; // If we don't have a version, we need to find the latest version out there, and then see if // that is in the repository, rather than seeing what the latest version in a repo is. if (null == contentNameVersionComponent) { // If this isn't the one we want -- i.e. wrong content; no easy way to go // back and check to see if there are more. retrievedObject = VersioningProfile.getFirstBlockOfLatestVersion(contentName, null, desiredPublisher, timeout, ((null != verifier) ? verifier : handle.keyManager().getDefaultVerifier()), handle); } else { retrievedObject = SegmentationProfile.getSegment(contentName, null, desiredPublisher, timeout, ((null != verifier) ? verifier : handle.keyManager().getDefaultVerifier()), handle); } if (null == retrievedObject) { // Couldn't find one at all. That means it's definitely not in a repository (or a cache, // or a key server). Done. Log.info(Log.FAC_IO, "isContentAvailable: no content available corresponding to {0}", contentName); return null; } else { if (Log.isLoggable(Log.FAC_IO, Level.FINER)) Log.finer(Log.FAC_IO, "isContentAvailable: found content {0} matching name {1}", retrievedObject.name(), contentName); // Found one. Does it contain what we want, if we know what that is? if (null != desiredContentDigest) { CCNInputStream inputStream = new CCNInputStream(retrievedObject, null, handle); byte [] streamContent = CCNDigestHelper.digest(inputStream); if (!Arrays.equals(streamContent, desiredContentDigest)) { Log.info(Log.FAC_IO, "Retrieved content {0} matching name {1}, but that stream's content is {2}, not expected {3}.", retrievedObject.name(), contentName, DataUtils.printBytes(streamContent), DataUtils.printBytes(desiredContentDigest)); return null; } } return retrievedObject; } } /** * Is there anything available below this name that we can find in the available time? * This really just wraps get, but puts a place for us to hang verification and other * checks, and makes intent clear. * @param contentPrefix Prefix content must start with. * @param requiredPublisher Publisher that must have signed content, null for any. * @param timeout How long to wait for content. If 0, returns immediately. * @param handle */ public static ContentObject isAnyContentAvailable(ContentName contentPrefix, PublisherPublicKeyDigest requiredPublisher, long timeout, CCNHandle handle) throws IOException { if (timeout == 0) { // don't check (don't do < 0, -1 is NO_TIMEOUT) return null; } ContentObject object = handle.get(contentPrefix, requiredPublisher, timeout); // put in default verification check? return object; } }