/*
* 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.repo;
import static org.ccnx.ccn.profiles.CommandMarker.COMMAND_MARKER_REPO_CHECKED_START_WRITE;
import static org.ccnx.ccn.profiles.SegmentationProfile.getSegmentNumberNameComponent;
import static org.ccnx.ccn.protocol.Component.NONCE;
import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Level;
import org.ccnx.ccn.CCNHandle;
import org.ccnx.ccn.config.SystemConfiguration;
import org.ccnx.ccn.impl.repo.RepositoryInfo;
import org.ccnx.ccn.impl.repo.RepositoryInfo.RepoInfoType;
import org.ccnx.ccn.impl.support.Log;
import org.ccnx.ccn.io.CCNAbstractInputStream;
import org.ccnx.ccn.io.content.CCNNetworkObject;
import org.ccnx.ccn.io.content.ContentDecodingException;
import org.ccnx.ccn.io.content.Link.LinkObject;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.ContentObject;
import org.ccnx.ccn.protocol.Interest;
import org.ccnx.ccn.protocol.SignedInfo.ContentType;
/**
* Provides support for controlling repository functions beyond
* the basic operations for writing data from an application
* into a repository supported by classes in the org.ccnx.ccn.io
* package.
* @author jthornto
*
*/
public class RepositoryControl {
/**
* Temporary cache of blocks we have synced, to avoid double-syncing blocks.
*/
protected static Set<ContentName> syncedObjects = Collections.synchronizedSet(new HashSet<ContentName>());
/**
* Request that a local repository preserve a copy
* of the exact contents of a given stream opened for reading
* with a given handle, and all links that were dereferenced
* to read the stream.
*
* A local repository is one connected
* directly to the same ccnd as the given handle; such a repository
* is likely to be special in the sense that it may be available to local
* applications even when there is no connectivity beyond the local
* machine. An application reading certain content that it did not originate
* may have reason to need that content to be available reliably as connectivity changes
* in the future. Since the application is not the source of the content, it need
* only ask the local repository to be interested in it rather than creating an
* output stream to write it. This method serves that purpose.
*
* This method is experimental and the way of addressing this problem
* is likely to change in the future.
*
* This method may fail (IOException) if the local repository is not already
* configured to support holding the data in question. For example, the
* repository policy might not admit the namespace in question
* and this method does not override such overall policy.
*
* There may be more than one local repository but this method does not
* presently distinguish one if that is the case. Any one local repository
* that is available may be used, and at most one should be expected to respond to
* the request. This method should verify that a confirmation is from an acceptable
* local repository.
*
* If the repository already holds the content it may confirm immediately, otherwise the repository
* will begin to retrieve and store the content but there is no guarantee that this is complete
* upon return from this method. The return value indicates whether data is already confirmed.
*
* @param handle the handle
* @param stream The stream for which the content should be preserved
* @return boolean true iff confirmation received from repository
* @throws IOException if no repository responds or another communication error occurs
*/
public static boolean localRepoSync(CCNHandle handle, CCNAbstractInputStream stream) throws IOException {
boolean result;
byte[] digest = stream.getFirstDigest(); // This forces reading if not done already
ContentName name = stream.getBaseName();
Long segment = stream.firstSegmentNumber();
if (null == segment) {
throw new IOException("LocalRepoSync: Can't read stream: " + name);
}
Log.info("RepositoryControl.localRepoSync called for name {0}", name);
// Request preserving the dereferenced content of the stream first
result = internalRepoSync(handle, name, segment, digest, stream.getFirstSegment().fullName());
// Now also deal with each of the links dereferenced to get to the ultimate content
LinkObject link = stream.getDereferencedLink();
while (null != link) {
// Request preserving current link: note that all of these links have
// been dereferenced already to get to the content, and so have been read
digest = link.getFirstDigest();
name = link.getVersionedName(); // we need versioned name; link basename may or may not be
segment = link.firstSegmentNumber();
if (Log.isLoggable(Level.INFO)) {
Log.info("localRepoSync synchronizing link: {0}", link);
}
if (!internalRepoSync(handle, name, segment, digest, link.getFirstSegment().fullName())) {
result = false;
}
link = link.getDereferencedLink();
}
return result;
}
/**
* Method to do the same as above but using CCNNetworkObjects instead of streams
* @param handle
* @param obj
* @param syncSigner
* @return
* @throws IOException
*/
public static boolean localRepoSync(CCNHandle handle, CCNNetworkObject<?> obj) throws IOException {
boolean result;
byte[] digest = obj.getFirstDigest();
ContentName name = obj.getVersionedName();
if (Log.isLoggable(Level.INFO)) {
Log.info("RepositoryControl.localRepoSync called for net obj name {0}", name);
}
Long segment = obj.firstSegmentNumber(); // This forces reading if not done already
if (null == segment) {
throw new IOException("LocalRepoSync: Can't read object: " + name);
}
// Request preserving the dereferenced content of the stream first
result = internalRepoSync(handle, name, segment, digest, obj.getFirstSegment().fullName());
// Now also deal with each of the links dereferenced to get to the ultimate content
LinkObject link = obj.getDereferencedLink();
while (null != link) {
// Request preserving current link: note that all of these links have
// been dereferenced already to get to the content, and so have been read
digest = link.getFirstDigest();
name = link.getVersionedName(); // we need versioned name; link basename may or may not be
segment = link.firstSegmentNumber();
if (!internalRepoSync(handle, name, segment, digest, link.getFirstSegment().fullName())) {
result = false;
}
link = link.getDereferencedLink();
}
return result;
}
/*
* Internal method to generate request for local repository to preserve content stream
* @param handle the CCNHandle to use
* @param baseName The name of the content up to but not including segment number
* @param startingSegmentNumber Initial segment number of the stream
* @param firstDigest Digest of the first segment
*/
static boolean internalRepoSync(CCNHandle handle, ContentName baseName, Long startingSegmentNumber,
byte[] firstDigest, ContentName fullName) throws IOException {
// UNNECESSARY OVERHEAD: shouldn't have to re-generate full name here, so hand it in.
// probably better way than sending in both name and parts.
if (syncedObjects.contains(fullName)) {
if (Log.isLoggable(Log.FAC_IO, Level.INFO)) {
Log.info(Log.FAC_IO, "Sync: skipping already-synced object {0}", fullName);
}
return true;
}
RepositoryInfo repoInfo = doLocalCheckedWrite(baseName, startingSegmentNumber, firstDigest,
handle);
if (repoInfo.getType() == RepoInfoType.DATA) {
// This type from checked write is confirmation that content already held
// TODO improve result handling. Currently we get true if repo has content already,
// false if error or repo is storing content but didn't have it already. We don't care
// whether repo had it already, all we care is whether it is already or will be synced --
// want to separate errors, repo non-response from "repo will take care of it" responses.
// At this point, a repo has responded and will deal with our data. Don't need to
// sync it again. I don't understand all the issues above, but until they are resolved,
// we don't want to declare the object "already synced" until we are actually going to
// return true here.
syncedObjects.add(fullName);
return true;
}
return false;
}
public static RepositoryInfo doLocalCheckedWrite(ContentName baseName,
Long startingSegmentNumber, byte[] firstDigest, CCNHandle handle)
throws IOException, ContentDecodingException {
// INCORRECT: the protocol is using a nonce.
// We do not use a nonce in this protocol, because a cached confirmation is satisfactory,
// assuming verification of the repository that published it.
ContentName repoCommandName = new ContentName(
baseName,
COMMAND_MARKER_REPO_CHECKED_START_WRITE,
NONCE,
getSegmentNumberNameComponent(startingSegmentNumber),
firstDigest
);
Interest interest = new Interest(repoCommandName);
interest.scope(1); // local repositories only
if (Log.isLoggable(Log.FAC_IO, Level.INFO)) {
Log.info(Log.FAC_IO, "Checked write to repository: {0}", baseName);
}
// Send out Interest
ContentObject co = handle.get(interest, SystemConfiguration.FC_TIMEOUT);
if (null == co) {
throw new Interest.NoResponseException(interest);
}
// TODO verify object as published by local repository rather than just signed by anybody
if (!handle.defaultVerifier().verify(co)) {
// TODO need to bypass unacceptable data to see if something good is out there
throw new IOException("verify failed on " + co.fullName());
}
if (co.signedInfo().getType() != ContentType.DATA)
throw new IOException("Invalid repository response for checked write, type " + co.signedInfo().getType());
RepositoryInfo repoInfo = new RepositoryInfo();
repoInfo.decode(co.content());
return repoInfo;
}
}