/*
* Part of the CCNx Java Library.
*
* Copyright (C) 2008, 2009, 2010 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.EnumSet;
import java.util.logging.Level;
import org.ccnx.ccn.CCNHandle;
import org.ccnx.ccn.impl.security.crypto.ContentKeys;
import org.ccnx.ccn.impl.support.Log;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.ContentObject;
import org.ccnx.ccn.protocol.PublisherPublicKeyDigest;
/**
* Perform sequential reads on any segmented CCN content, as if it
* were a standard InputStream.
* This input stream will read from a sequence of blocks, authenticating
* each as it goes, and caching what verification information it can to speed
* up verification of future blocks. All it assumes
* is that the last component of the name is a segment number as described in
* SegmentationProfile.
*
* Read buffer size is independent of segment size; the stream will pull additional
* content fragments dynamically when possible to fill out the requested number
* of bytes.
* @author smetters
*/
public class CCNInputStream extends CCNAbstractInputStream {
/**
* Set up an input stream to read segmented CCN content under a given name. Content is assumed
* to be unencrypted, or keys will be retrieved automatically via another
* process.
* Will use the default handle given by CCNHandle#getHandle().
* This constructor will attempt to retrieve the first block of content.
*
* @param baseName Name to read from. If contains a segment number, will start to read from that
* segment.
* @throws IOException Not currently thrown, will be thrown when constructors retrieve first block.
*/
public CCNInputStream(ContentName baseName) throws IOException {
this(baseName, null);
}
/**
* Set up an input stream to read segmented CCN content under a given name. Content is assumed
* to be unencrypted, or keys will be retrieved automatically via another
* process.
* This constructor will attempt to retrieve the first block of content.
*
* @param baseName Name to read from. If contains a segment number, will start to read from that
* segment.
* @param handle The CCN handle to use for data retrieval. If null, the default handle
* given by CCNHandle#getHandle() will be used.
* @throws IOException Not currently thrown, will be thrown when constructors retrieve first block.
*/
public CCNInputStream(ContentName baseName, CCNHandle handle) throws IOException {
this(baseName, null, null, handle);
}
/**
* Set up an input stream to read segmented CCN content under a given name. Content is assumed
* to be unencrypted, or keys will be retrieved automatically via another
* process.
* This constructor will attempt to retrieve the first block of content.
*
* @param baseName Name to read from. If contains a segment number, will start to read from that
* segment.
* @param publisher The key we require to have signed this content. If null, will accept any publisher
* (subject to higher-level verification).
* @param handle The CCN handle to use for data retrieval. If null, the default handle
* given by CCNHandle#getHandle() will be used.
* @throws IOException Not currently thrown, will be thrown when constructors retrieve first block.
*/
public CCNInputStream(ContentName baseName, PublisherPublicKeyDigest publisher, CCNHandle handle)
throws IOException {
this(baseName, null, publisher, handle);
}
/**
* Set up an input stream to read segmented CCN content under a given name. Content is assumed
* to be unencrypted, or keys will be retrieved automatically via another
* process.
* This constructor will attempt to retrieve the first block of content.
*
* @param baseName Name to read from. If contains a segment number, will start to read from that
* segment.
* @param startingSegmentNumber Alternative specification of starting segment number. If
* null, will be SegmentationProfile#baseSegment().
* @param handle The CCN handle to use for data retrieval. If null, the default handle
* given by CCNHandle#getHandle() will be used.
* @throws IOException Not currently thrown, will be thrown when constructors retrieve first block.
*/
public CCNInputStream(ContentName baseName, Long startingSegmentNumber, CCNHandle handle) throws IOException {
this(baseName, startingSegmentNumber, null, handle);
}
/**
* Set up an input stream to read segmented CCN content under a given name. Content is assumed
* to be unencrypted, or keys will be retrieved automatically via another
* process.
* This constructor will attempt to retrieve the first block of content.
*
* @param baseName Name to read from. If contains a segment number, will start to read from that
* segment.
* @param startingSegmentNumber Alternative specification of starting segment number. If
* null, will be SegmentationProfile#baseSegment().
* @param publisher The key we require to have signed this content. If null, will accept any publisher
* (subject to higher-level verification).
* @param handle The CCN handle to use for data retrieval. If null, the default handle
* given by CCNHandle#getHandle() will be used.
* @throws IOException Not currently thrown, will be thrown when constructors retrieve first block.
*/
public CCNInputStream(ContentName baseName, Long startingSegmentNumber, PublisherPublicKeyDigest publisher,
CCNHandle handle) throws IOException {
super(baseName, startingSegmentNumber, publisher, null, null, handle);
}
/**
* Set up an input stream to read segmented CCN content under a given name.
* This constructor will attempt to retrieve the first block of content.
*
* @param baseName Name to read from. If contains a segment number, will start to read from that
* segment.
* @param startingSegmentNumber Alternative specification of starting segment number. If
* null, will be SegmentationProfile#baseSegment().
* @param publisher The key we require to have signed this content. If null, will accept any publisher
* (subject to higher-level verification).
* @param keys The keys to use to decrypt this content. If null, assumes content unencrypted, or another
* process will be used to retrieve the keys.
* @param handle The CCN handle to use for data retrieval. If null, the default handle
* given by CCNHandle#getHandle() will be used.
* @throws IOException Not currently thrown, will be thrown when constructors retrieve first block.
*/
public CCNInputStream(ContentName baseName, Long startingSegmentNumber, PublisherPublicKeyDigest publisher,
ContentKeys keys, CCNHandle handle) throws IOException {
super(baseName, startingSegmentNumber, publisher, keys, null, handle);
}
/**
* Set up an input stream to read segmented CCN content starting with a given
* ContentObject that has already been retrieved. Content is assumed
* to be unencrypted, or keys will be retrieved automatically via another
* process.
*
* @param startingSegment The first segment to read from. If this is not the
* first segment of the stream, reading will begin from this point.
* We assume that the signature on this segment was verified by our caller.
* @param flags any stream flags that must be set to handle even this first block (otherwise
* they can be set with setFlags prior to read). Can be null.
* @param handle The CCN handle to use for data retrieval. If null, the default handle
* given by CCNHandle#getHandle() will be used.
* @throws IOException If startingSegment's name does not contain a valid segment number
*/
public CCNInputStream(ContentObject startingSegment, EnumSet<FlagTypes> flags, CCNHandle handle) throws IOException {
super(startingSegment, null, flags, handle);
}
/**
* Set up an input stream to read segmented CCN content starting with a given
* ContentObject that has already been retrieved.
* @param startingSegment The first segment to read from. If this is not the
* first segment of the stream, reading will begin from this point.
* We assume that the signature on this segment was verified by our caller.
* @param keys The keys to use to decrypt this content. Null if content unencrypted, or another
* process will be used to retrieve the keys.
* @param flags any stream flags that must be set to handle even this first block (otherwise
* they can be set with setFlags prior to read). Can be null.
* @param handle The CCN handle to use for data retrieval. If null, the default handle
* given by CCNHandle#getHandle() will be used.
* @throws IOException If startingSegment's name does not contain a valid segment number
*/
public CCNInputStream(ContentObject startingSegment, ContentKeys keys, EnumSet<FlagTypes> flags, CCNHandle handle) throws IOException {
super(startingSegment, keys, flags, handle);
}
/**
* Implement sequential reads of data across multiple segments. As we run out of bytes
* on a given segment, the next segment is retrieved and reading continues.
*/
@Override
protected int readInternal(byte [] buf, int offset, int len) throws IOException {
if (_atEOF) {
if (Log.isLoggable(Log.FAC_IO, Level.FINE))
Log.fine(Log.FAC_IO, "At EOF: {0}", ((null == _currentSegment) ? "null" : _currentSegment.name()));
return -1;
}
if (Log.isLoggable(Log.FAC_IO, Level.FINEST))
Log.finest(Log.FAC_IO, getBaseName() + ": reading " + len + " bytes into buffer of length " +
((null != buf) ? buf.length : "null") + " at offset " + offset);
// is this the first block?
if (null == _currentSegment) {
// This will throw an exception if no block found, which is what we want.
setFirstSegment(getFirstSegment());
}
if (Log.isLoggable(Log.FAC_IO, Level.FINEST))
Log.finest(Log.FAC_IO, "reading from block: {0}, length: {1}", _currentSegment.name(),
_currentSegment.contentLength());
// Now we have a block in place. Read from it. If we run out of block before
// we've read len bytes, pull next block.
int lenToRead = len;
int lenRead = 0;
long readCount = 0;
while (lenToRead > 0) {
if (null == _segmentReadStream) {
Log.severe(Log.FAC_IO, "Unexpected null block read stream!");
}
if (null != buf) { // use for skip
if (Log.isLoggable(Log.FAC_IO, Level.FINEST))
Log.finest(Log.FAC_IO, "before block read: content length "+_currentSegment.contentLength()+" position "+ tell() +" available: " + _segmentReadStream.available() + " dst length "+buf.length+" dst index "+offset+" len to read "+lenToRead);
// Read as many bytes as we can
readCount = _segmentReadStream.read(buf, offset, lenToRead);
} else {
readCount = _segmentReadStream.skip(lenToRead);
}
if (readCount <= 0) {
if (Log.isLoggable(Log.FAC_IO, Level.FINE))
Log.fine(Log.FAC_IO, "Tried to read at end of block, go get next block.");
if (!hasNextSegment()) {
if (Log.isLoggable(Log.FAC_IO, Level.FINE))
Log.fine(Log.FAC_IO, "No next block expected, setting _atEOF, returning " + ((lenRead > 0) ? lenRead : -1));
_atEOF = true;
if (lenRead > 0) {
return lenRead;
}
return -1; // no bytes read, at eof
}
ContentObject nextSegment = getNextSegment();
if (null == nextSegment) {
if (Log.isLoggable(Log.FAC_IO, Level.FINE))
Log.fine(Log.FAC_IO, "Next block is null, setting _atEOF, returning " + ((lenRead > 0) ? lenRead : -1));
_atEOF = true;
if (lenRead > 0) {
return lenRead;
}
return -1; // no bytes read, at eof
}
setCurrentSegment(nextSegment);
if (Log.isLoggable(Log.FAC_IO, Level.FINE))
Log.fine(Log.FAC_IO, "now reading from block: " + _currentSegment.name() + " length: " +
_currentSegment.contentLength());
} else {
offset += readCount;
lenToRead -= readCount;
lenRead += readCount;
if (Log.isLoggable(Log.FAC_IO, Level.FINEST))
Log.finest(Log.FAC_IO, " read " + readCount + " bytes for " + lenRead + " total, " + lenToRead + " remaining.");
}
}
return lenRead;
}
}