/*
* 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.impl;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
import java.util.ArrayList;
import java.util.logging.Level;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import org.ccnx.ccn.CCNHandle;
import org.ccnx.ccn.config.ConfigurationException;
import org.ccnx.ccn.config.SystemConfiguration;
import org.ccnx.ccn.impl.security.crypto.CCNAggregatedSigner;
import org.ccnx.ccn.impl.security.crypto.CCNMerkleTree;
import org.ccnx.ccn.impl.security.crypto.CCNMerkleTreeSigner;
import org.ccnx.ccn.impl.security.crypto.ContentKeys;
import org.ccnx.ccn.impl.security.crypto.UnbufferedCipherInputStream;
import org.ccnx.ccn.impl.support.Log;
import org.ccnx.ccn.io.content.ContentEncodingException;
import org.ccnx.ccn.profiles.SegmentationProfile;
import org.ccnx.ccn.profiles.SegmentationProfile.SegmentNumberType;
import org.ccnx.ccn.protocol.CCNTime;
import org.ccnx.ccn.protocol.ContentName;
import org.ccnx.ccn.protocol.ContentObject;
import org.ccnx.ccn.protocol.KeyLocator;
import org.ccnx.ccn.protocol.PublisherPublicKeyDigest;
import org.ccnx.ccn.protocol.Signature;
import org.ccnx.ccn.protocol.SignedInfo;
import org.ccnx.ccn.protocol.SignedInfo.ContentType;
/**
* Combines segmentation, signing and encryption. This is used
* to prepare data for writing out to ccnd. The intent is to provide a user-friendly,
* efficient, minimum-copy interface, with some support for extensibility.
*
* <ul>
* <li>
* Segmentation is the division of a large piece of content into multiple
* smaller content objects. A segment component is appended to the content
* name to distinguish different segments.
* @see org.ccnx.ccn.profiles.SegmentationProfile
*
* This class currently supports a range of quite complex
* segmentation options. At this point, only a subset of these are supported
* by the higher level org.ccnx.ccn.io interfaces.
*
* Contiguous blocks (fixed or variable size), or sparse blocks, e.g. at various
* time offsets. Configurations set the numbering scheme. The two interfaces
* are either contiguous writes, or (for the sparse case), writes of individual
* segments specified by offset (can be multi-buffered, as may be multiple KB).
*
* Simplest way to handle might be to expect contiguous blocks (either
* increments or byte count) and remember what we last wrote, so next
* call gets next value. Clients writing sparse blocks (only byte count
* or scaled byte count makes sense for this) can override by setting
* counter on a call.</li>
*
* <li>Signing Control -- per ContentObject signing with a choice of signature algorithm,
* or amortized signing. The default is Merkle Hash Tree based amortization, later
* there will be other options.
* @see org.ccnx.ccn.impl.security.crypto.CCNMerkleTreeSigner</li>
*
* <li>Stock low-level encryption. --
* Given a key K, an IV, and a chosen encryption algorithm segment content so as
* to meet a desired net data length with potential block expansion, and encrypt.
*
* For this, we use the standard Java encryption mechanisms, augmented by
* alternative providers (e.g. BouncyCastle for AES-CTR). We just need
* a Cipher, a SecretKeySpec, and an IvParameterSpec holding the relevant
* key data.
*
* For block ciphers, we require a certain amount of extra space in the
* blocks to accommodate padding (a minimum of 1 bytes for PKCS5 padding,
* for example).
* DKS TODO -- deal with the padding and length expansion
* For the moment, until we deal with padding we use only AES-CTR.</li>
* </ul>
*
* Overall this class attempts to minimize copying of data. Data must be copied into final
* ContentObjects returned by the signing operations. On the way, it may need
* to pass through a block encrypter, which may perform local copies. Higher-level
* constructs, such as streams, may buffer it above.
*/
public class CCNSegmenter {
/**
* Number of content objects we keep around prior to signing and outputting to the flow controller
* Note that the flow controller also uses this value to determine its default high water mark.
*/
public static final int HOLD_COUNT = 128;
public static final long LAST_SEGMENT = Long.valueOf(-1);
protected int _blockSize = SegmentationProfile.DEFAULT_BLOCKSIZE;
protected int _blockIncrement = SegmentationProfile.DEFAULT_INCREMENT;
protected int _byteScale = SegmentationProfile.DEFAULT_SCALE;
protected SegmentNumberType _sequenceType = SegmentNumberType.SEGMENT_FIXED_INCREMENT;
protected ArrayList<ContentObject> _blocks = new ArrayList<ContentObject>(HOLD_COUNT + 1);
protected CCNHandle _handle;
/**
* Eventually may not contain this; callers may access it exogenously.
*/
protected CCNFlowControl _flowControl;
/**
* Handle multi-block amortized signing. If null, default to single-block signing.
*/
protected CCNAggregatedSigner _bulkSigner;
/**
* The first segment, useful for obtaining starting segment number and digest to characterize
* set of segmented content.
*/
protected ContentObject _firstSegment = null;
/**
* Create a segmenter with default (Merkle hash tree) bulk signing
* behavior, making a new handle for it to use.
* @throws ConfigurationException if there is a problem creating the handle
* @throws IOException if there is a problem creating the handle
*/
public CCNSegmenter() throws ConfigurationException, IOException {
this(CCNHandle.open());
}
/**
* Create a segmenter with default (Merkle hash tree) bulk signing
* behavior.
* @param handle the handle to use, will open a new one if null
* @throws IOException if there is a problem creating the handle
*/
public CCNSegmenter(CCNHandle handle) throws IOException {
this(new CCNFlowControl(handle));
}
/**
* Create a segmenter with default (Merkle hash tree) bulk signing
* behavior.
* @param flowControl the specified flow controller to use
*/
public CCNSegmenter(CCNFlowControl flowControl) {
this(flowControl, null);
}
/**
* Create a segmenter, specifying the signing behavior to use.
* @param flowControl the specified flow controller to use
* @param signer the bulk signer to use. If null, will use default Merkle hash tree behavior.
*/
public CCNSegmenter(CCNFlowControl flowControl, CCNAggregatedSigner signer) {
if ((null == flowControl) || (null == flowControl.getHandle())) {
// Tries to get a library or make a flow control, yell if we fail.
throw new IllegalArgumentException("CCNSegmenter: must provide a valid library or flow controller.");
}
_flowControl = flowControl;
_handle = _flowControl.getHandle();
if (null == signer) {
_bulkSigner = new CCNMerkleTreeSigner();
} else {
_bulkSigner = signer; // if null, default to merkle tree
}
_blockSize = SystemConfiguration.BLOCK_SIZE;
}
/**
* Factory method to create a standard segmenter that generates blocks of fixed length in bytes.
* @param blockSize number of bytes to put in each block (the last block will have an odd number of
* bytes)
* @param flowControl the flow controller to use
* @return the new segmenter
*/
public static CCNSegmenter getBlockSegmenter(int blockSize, CCNFlowControl flowControl) {
CCNSegmenter segmenter = new CCNSegmenter(flowControl);
segmenter.useFixedIncrementSequenceNumbers(1);
segmenter.setBlockSize(blockSize);
return segmenter;
}
/**
* Factory method to create a standard segmenter that generates blocks of variable length in bytes,
* whose segment numbers are scaled by a fixed increment. This could be used, for example to generate
* segments that had variable number of bytes in each, and naming them by scaling the byte
* offset by a scale useful to the application - e.g. to end up with millisecond offsets
* for a video or audio stream, etc.
* NOTE: the reader infrastructure currently expects incrementing segment numbers; a special
* stream class is necessary to read data generated this way. That would not be difficult to
* write.
* @param scale multiplier to apply to the byte count before recording it as a sequence number
* @param flowControl the flow controller to use
* @return the new segmenter
*/
public static CCNSegmenter getScaledByteCountSegmenter(int scale, CCNFlowControl flowControl) {
CCNSegmenter segmenter = new CCNSegmenter(flowControl);
segmenter.useScaledByteCountSequenceNumbers(scale);
return segmenter;
}
/**
* Factory method to create a standard segmenter that generates blocks of variable length in bytes,
* whose segment numbers are scaled by a fixed increment. This could be used, for example to generate
* segments that had variable number of bytes in each, and naming them by byte offset.
* NOTE: This is used by CCNBlockInputStream and CCNBlockOutputStream; the other stream classes
* will not read data generated this way.
* @param flowControl the flow controller to use
* @return the new segmenter
*/
public static CCNSegmenter getByteCountSegmenter(CCNFlowControl flowControl) {
return getScaledByteCountSegmenter(1, flowControl);
}
public CCNHandle getLibrary() { return _handle; }
public CCNFlowControl getFlowControl() { return _flowControl; }
/**
* Return the first segment.
* @return The first segment or null if no segments generated yet
*/
public ContentObject getFirstSegment() {
return _firstSegment;
}
/**
* Sets the segmentation block size to use
* @param blockSize block size in bytes
*/
public synchronized void setBlockSize(int blockSize) {
_blockSize = blockSize;
}
/**
* Gets the current block size
* @return block size in bytes
*/
public synchronized int getBlockSize() {
return _blockSize;
}
public void useByteCountSequenceNumbers() {
setSequenceType(SegmentNumberType.SEGMENT_BYTE_COUNT);
setByteScale(1);
}
public void useFixedIncrementSequenceNumbers(int increment) {
setSequenceType(SegmentNumberType.SEGMENT_FIXED_INCREMENT);
setBlockIncrement(increment);
}
public void useScaledByteCountSequenceNumbers(int scale) {
setSequenceType(SegmentNumberType.SEGMENT_BYTE_COUNT);
setByteScale(scale);
}
public void setSequenceType(SegmentNumberType seqType) { _sequenceType = seqType; }
/**
* Sets the increment between block numbers.
* @param blockIncrement
*/
public void setBlockIncrement(int blockIncrement) { _blockIncrement = blockIncrement; }
/**
* Gets the increment between block numbers.
* @return
*/
public int getBlockIncrement() { return _blockIncrement; }
public void setByteScale(int byteScale) { _byteScale = byteScale; }
public int getByteScale() { return _byteScale; }
/**
* Puts a complete data item, segmenting it if necessary. The
* assumption of this method is that this single call puts
* all the blocks of the item; if multiple calls to the
* segmenter will be required to output an item, use other
* methods to manage segment identifiers.
*
* If the data is small enough this doesn't fragment. Otherwise, does.
* If multi-fragment, uses the naming profile and specified
* bulk signer (default: Merkle Hash Tree) to generate names and signatures.
* @param keys the keys to use for encrypting this segment, or null if unencrypted. The
* specific Key/IV used for this segment will be obtained by calling keys.getSegmentEncryptionCipher().
* @return ContentObject of the data that was put (in the case
* of fragmented data, the first fragment is returned). This way the
* caller can then easily link to the data if
* they need to, or put again with a different name.
* @throws InvalidAlgorithmParameterException
**/
public long put(
ContentName name, byte [] content, int offset, int length,
boolean lastSegments,
SignedInfo.ContentType type,
Integer freshnessSeconds,
KeyLocator locator,
PublisherPublicKeyDigest publisher,
ContentKeys keys)
throws InvalidKeyException, SignatureException, NoSuchAlgorithmException, IOException, InvalidAlgorithmParameterException {
if (null == content) {
throw new IOException("Content cannot be null!");
}
if (null == publisher) {
publisher = _handle.keyManager().getDefaultKeyID();
}
if (null == locator)
locator = _handle.keyManager().getKeyLocator(publisher);
if (null == type) {
type = ContentType.DATA;
}
// Remove existing segmentation markers on end of name, at point right
// before put. If do it sooner, have to re-do it just to be sure.
if (SegmentationProfile.isSegment(name)) {
Log.info("Asked to store fragments under fragment name: " + name + ". Stripping fragment information");
}
// DKS TODO -- take encryption overhead into account
// DKS TODO -- hook up last segment
if (outputLength(length, keys) >= getBlockSize()) {
return fragmentedPut(name, content, offset, length, (lastSegments ? CCNSegmenter.LAST_SEGMENT : null),
type, freshnessSeconds, locator, publisher, keys);
} else {
try {
// We should only get here on a single-fragment object, where the lastBlocks
// argument is false (omitted).
return putFragment(name, SegmentationProfile.baseSegment(),
content, offset, length, type,
null, freshnessSeconds,
Long.valueOf(SegmentationProfile.baseSegment()),
locator, publisher, keys);
} catch (IOException e) {
Log.warning("This should not happen: put failed with an IOException.");
Log.warningStackTrace(e);
throw e;
}
}
}
/**
* Segments content, builds segment names and ContentObjects, signs
* them, and writes them to the flow controller to go out to the network.
* Low-level segmentation interface. Assume arguments have been cleaned
* prior to arrival -- name is not already segmented, type is set, etc.
*
* Starts segmentation at segment SegmentationProfile().baseSegment().
* @param name name prefix to use for the segments
* @param content content buffer containing content to put
* @param offset offset into buffer at which to start reading content to put
* @param length number of bytes of buffer to put
* @param finalSegmentIndex the expected segment number of the last segment of this stream,
* null to omit, Long(-1) to set as the last segment of this put, whatever
* its number turns out to be
* @param type the type for the content
* @param freshnessSeconds the number of seconds this content should be considered fresh, or null
* to leave unset
* @param locator the key locator to use
* @param publisher the publisher to use
* @param keys the keys to use for encrypting this segment, or null if unencrypted. The
* specific Key/IV used for this segment will be obtained by calling keys.getSegmentEncryptionCipher().
* @return returns the segment identifier for the next segment to be written, if any.
* If the caller doesn't want to override this, they can hand this number back
* to a subsequent call to fragmentedPut.
* @throws InvalidKeyException
* @throws SignatureException
* @throws NoSuchAlgorithmException
* @throws IOException
* @throws InvalidAlgorithmParameterException
*/
public long fragmentedPut(
ContentName name,
byte [] content, int offset, int length,
Long finalSegmentIndex,
SignedInfo.ContentType type,
Integer freshnessSeconds,
KeyLocator locator,
PublisherPublicKeyDigest publisher,
ContentKeys keys)
throws InvalidKeyException, SignatureException, NoSuchAlgorithmException,
IOException, InvalidAlgorithmParameterException {
return fragmentedPut(name, SegmentationProfile.baseSegment(),
content, offset, length, getBlockSize(), type,
null, freshnessSeconds, finalSegmentIndex, locator, publisher,
keys);
}
/**
* Segments content, builds segment names and ContentObjects, signs
* them, and writes them to the flow controller to go out to the network.
* NOTE - ControlFlow.addNameSpace must be done before calling this
*
* @param name name prefix to use for the segments
* @param baseSegmentNumber the segment number to start this batch with
* @param content content buffer containing content to put
* @param offset offset into buffer at which to start reading content to put
* @param length number of bytes of buffer to put
* @param blockWidth the block size to use
* @param type the type for the content
* @param timestamp the timestamp for the content
* @param freshnessSeconds the number of seconds this content should be considered fresh, or null
* to leave unset
* @param finalSegmentIndex the expected segment number of the last segment of this stream,
* null to omit, Long(-1) to set as the last segment of this put, whatever
* its number turns out to be
* @param locator the key locator to use
* @param publisher the publisher to use
* @param keys the keys to use for encrypting this segment, or null if unencrypted. The
* specific Key/IV used for this segment will be obtained by calling keys.getSegmentEncryptionCipher().
* @return returns the segment identifier for the next segment to be written, if any.
* If the caller doesn't want to override this, they can hand this number back
* to a subsequent call to fragmentedPut.
* @throws InvalidKeyException
* @throws SignatureException
* @throws NoSuchAlgorithmException
* @throws IOException
* @throws InvalidAlgorithmParameterException
* @see fragmentedPut(ContentName, byte[], int, int, Long, ContentType, Integer, KeyLocator, PublisherPublicKeyDigest)
* Starts segmentation at segment SegmentationProfile().baseSegment().
*/
public long fragmentedPut(
ContentName name, long baseSegmentNumber,
byte [] content, int offset, int length, int blockWidth,
ContentType type,
CCNTime timestamp,
Integer freshnessSeconds, Long finalSegmentIndex,
KeyLocator locator,
PublisherPublicKeyDigest publisher,
ContentKeys keys) throws InvalidKeyException,
SignatureException, IOException,
InvalidAlgorithmParameterException, NoSuchAlgorithmException {
// This will call into CCNBase after picking appropriate credentials
// take content, blocksize (static), divide content into array of
// content blocks, call hash fn for each block, call fn to build merkle
// hash tree. Build header, for each block, get authinfo for block,
// (with hash tree, block identifier, timestamp -- SQLDateTime)
// insert header using mid-level insert, low-level insert for actual blocks.
if (length == 0)
return baseSegmentNumber;
if (null == publisher) {
publisher = getFlowControl().getHandle().keyManager().getDefaultKeyID();
}
Key signingKey = getFlowControl().getHandle().keyManager().getSigningKey(publisher);
if (null == locator)
locator = getFlowControl().getHandle().keyManager().getKeyLocator(publisher);
ContentName rootName = SegmentationProfile.segmentRoot(name);
if (null == type) {
type = ContentType.DATA;
}
byte [] finalBlockID = null;
if (null != finalSegmentIndex) {
if (finalSegmentIndex.longValue() == CCNSegmenter.LAST_SEGMENT) {
// compute final segment number
// compute final segment number; which might be this one if blockCount == 1
int blockCount = CCNMerkleTree.blockCount(length, blockWidth);
finalBlockID = SegmentationProfile.getSegmentNumberNameComponent(
lastSegmentIndex(baseSegmentNumber, (blockCount-1)*blockWidth,
blockCount));
} else {
finalBlockID = SegmentationProfile.getSegmentNumberNameComponent(finalSegmentIndex);
}
}
long nextSegmentIndex =
buildBlocks(rootName, baseSegmentNumber,
new SignedInfo(publisher, timestamp, type, locator, freshnessSeconds, finalBlockID),
content, offset, length, blockWidth, keys, signingKey, null != finalSegmentIndex);
if (_blocks.size() >= HOLD_COUNT || null != finalSegmentIndex) {
outputCurrentBlocks(signingKey);
}
return nextSegmentIndex;
}
public long fragmentedPut(
ContentName name, long baseSegmentNumber,
byte contentBlocks[][], int blockCount,
int firstBlockIndex, int lastBlockLength,
ContentType type,
CCNTime timestamp,
Integer freshnessSeconds, Long finalSegmentIndex,
KeyLocator locator,
PublisherPublicKeyDigest publisher,
ContentKeys keys) throws InvalidKeyException, SignatureException,
NoSuchAlgorithmException, IOException, InvalidAlgorithmParameterException {
return fragmentedPut(name, baseSegmentNumber, contentBlocks, blockCount, firstBlockIndex, lastBlockLength,
type, timestamp, freshnessSeconds, finalSegmentIndex, locator, publisher, keys, false);
}
/**
* Takes pre-segmented content, builds segment names and ContentObjects, signs
* them, and writes them to the flow controller to go out to the network.
* @param name name prefix to use for the segments
* @param baseSegmentNumber the segment number to start this batch with
* @param contentBlocks content buffers containing content to put, one buffer per ContentObject
* @param blockCount the number of these content buffers to write
* @param firstBlockIndex the index into the content buffer array to start writing blocks
* @param lastBlockLength the number of bytes of the last block to be written to use -- this
* allows a fixed set of byte [] to be used to buffer content for segmentation, and still cope
* with variable-length last blocks
* @param type the type for the content
* @param timestamp the timestamp for the content
* @param freshnessSeconds the number of seconds this content should be considered fresh, or null
* to leave unset
* @param finalSegmentIndex the expected segment number of the last segment of this stream,
* null to omit, Long(-1) to set as the last segment of this put, whatever
* its number turns out to be
* @param locator the key locator to use
* @param publisher the publisher to use
* @param keys the keys to use for encrypting this segment, or null if unencrypted. The
* specific Key/IV used for this segment will be obtained by calling keys.getSegmentEncryptionCipher().
* @param flushNow Used for user level flush requests to flush the data to the flow controller early.
* @return returns the segment identifier for the next segment to be written, if any.
* If the caller doesn't want to override this, they can hand this number back
* to a subsequent call to fragmentedPut.
* @throws InvalidKeyException
* @throws SignatureException
* @throws NoSuchAlgorithmException
* @throws IOException
* @throws InvalidAlgorithmParameterException
* @see fragmentedPut(ContentName, byte[], int, int, Long, ContentType, Integer, KeyLocator, PublisherPublicKeyDigest)
* Starts segmentation at segment SegmentationProfile().baseSegment().
*/
public long fragmentedPut(
ContentName name, long baseSegmentNumber,
byte contentBlocks[][], int blockCount,
int firstBlockIndex, int lastBlockLength,
ContentType type,
CCNTime timestamp,
Integer freshnessSeconds, Long finalSegmentIndex,
KeyLocator locator,
PublisherPublicKeyDigest publisher,
ContentKeys keys,
boolean flushNow) throws InvalidKeyException, SignatureException,
NoSuchAlgorithmException, IOException, InvalidAlgorithmParameterException {
if (!flushNow && blockCount == 0)
return baseSegmentNumber;
if (null == publisher) {
publisher = getFlowControl().getHandle().keyManager().getDefaultKeyID();
}
Key signingKey = getFlowControl().getHandle().keyManager().getSigningKey(publisher);
if (null == locator)
locator = getFlowControl().getHandle().keyManager().getKeyLocator(publisher);
ContentName rootName = SegmentationProfile.segmentRoot(name);
if (null == type) {
type = ContentType.DATA;
}
byte [] finalBlockID = null;
if (null != finalSegmentIndex) {
if (finalSegmentIndex.longValue() == CCNSegmenter.LAST_SEGMENT) {
long length = 0;
for (int j = firstBlockIndex; j < firstBlockIndex + blockCount - 1; j++) {
length += contentBlocks[j].length;
}
// don't include last block length; want intervening byte count before the last block
// compute final segment number
finalBlockID = SegmentationProfile.getSegmentNumberNameComponent(
lastSegmentIndex(baseSegmentNumber, length, blockCount));
} else {
finalBlockID = SegmentationProfile.getSegmentNumberNameComponent(finalSegmentIndex);
}
}
long nextIndex = baseSegmentNumber;
for (int i = firstBlockIndex; i < firstBlockIndex + blockCount; i++) {
nextIndex = newBlock(rootName, nextIndex,
new SignedInfo(publisher, timestamp, type, locator, freshnessSeconds, finalBlockID),
contentBlocks[i], 0, (i < firstBlockIndex + blockCount - 1)
? contentBlocks[i].length : lastBlockLength, keys);
if (_blocks.size() >= HOLD_COUNT) {
outputCurrentBlocks(signingKey);
}
}
if (flushNow || null != finalSegmentIndex) {
outputCurrentBlocks(signingKey);
}
return nextIndex;
}
/**
* Sign and output all outstanding blocks to the flow controller. This is done when the number of
* blocks reaches HOLD_COUNT (see above) or we are doing a final flush of a file.
*
* There are 2 cases:
* 1) we're flushing a single block and can put it out with a straight signature (includes
* 0-length file case)
* 2) we're flushing more than one block, and need to use a bulk signer.
*
* All code is capable of handling any mix of these types of blocks but internal mixing should not
* happen anymore unless we decide to add a higher level capability to allow an immediate flush all
* the way to the flow controller. Normally we would see groups of bulk signatures followed by a
* straight signature block in rare cases where only a single block is left over for the flush
* after a bulk signing pass.
*
* @param signingKey
* @param finalFlush sign and dump everything if true
* @throws InvalidKeyException
* @throws SignatureException
* @throws NoSuchAlgorithmException
* @throws IOException
*/
protected void outputCurrentBlocks(Key signingKey) throws InvalidKeyException, SignatureException, NoSuchAlgorithmException, IOException {
if (_blocks.size() == 0)
return;
if (_blocks.size() == 1) {
ContentObject co = _blocks.get(0);
co.sign(signingKey);
if( Log.isLoggable(Level.FINER))
Log.finer("CCNSegmenter: putting " + co.name() + " (timestamp: " + co.signedInfo().getTimestamp() + ", length: " + co.contentLength() + ")");
_flowControl.put(co);
} else {
// Digest of complete contents
// If we're going to unique-ify the block names
// (or just in general) we need to incorporate the names
// and signedInfos in the MerkleTree blocks.
// For now, this generates the root signature too, so can
// ask for the signature for each block.
ContentObject[] blocks = new ContentObject[_blocks.size()];
_blocks.toArray(blocks);
if (Log.isLoggable(Log.FAC_IO, Level.INFO))
Log.info(Log.FAC_IO, "flush: putting merkle tree to the network, name starts with " + blocks[0].name() + "; "
+ _blocks.size() + " blocks");
_bulkSigner.signBlocks(blocks, signingKey);
getFlowControl().put(blocks);
}
_blocks.clear();
}
/**
* Puts a single block of content of arbitrary length using a segment naming convention. The only
* current use of this is to allow a Segmenter.put of less than a blocksize.
* I'm not quite sure why that needs to use this and it would be nice to get rid of this since its
* mostly superfluous and duplicating other code at this point but for now I'll leave it in.
*
* @param name name prefix to use for the object, without the segment number
* @param segmentNumber the segment number to use for this object
* @param content content buffer containing content to put
* @param offset offset into buffer at which to start reading content to put
* @param length number of bytes of buffer to put
* @param type the type for the content
* @param timestamp the timestamp for the content
* @param freshnessSeconds the number of seconds this content should be considered fresh, or null
* to leave unset
* @param finalSegmentIndex the expected segment number of the last segment of this stream,
* null to omit, Long(-1) to set as the last segment of this put, whatever
* its number turns out to be
* @param locator the key locator to use
* @param publisher the publisher to use
* @param keys the keys to use for encrypting this segment, or null if unencrypted. The
* specific Key/IV used for this segment will be obtained by calling keys.getSegmentEncryptionCipher().
* @return returns the segment identifier for the next segment to be written, if any.
* If the caller doesn't want to override this, they can hand this number back
* to a subsequent call to fragmentedPut.
* @throws InvalidKeyException
* @throws SignatureException
* @throws NoSuchAlgorithmException
* @throws IOException
* @throws InvalidAlgorithmParameterException
*/
public long putFragment(
ContentName name, long segmentNumber,
byte [] content, int offset, int length,
ContentType type,
CCNTime timestamp,
Integer freshnessSeconds, Long finalSegmentIndex,
KeyLocator locator,
PublisherPublicKeyDigest publisher,
ContentKeys keys) throws InvalidKeyException, SignatureException,
NoSuchAlgorithmException, IOException, InvalidAlgorithmParameterException {
if (null == publisher) {
publisher = _handle.keyManager().getDefaultKeyID();
}
Key signingKey = _handle.keyManager().getSigningKey(publisher);
if (null == locator)
locator = _handle.keyManager().getKeyLocator(publisher);
if (null == type) {
type = ContentType.DATA;
}
ContentName rootName = SegmentationProfile.segmentRoot(name);
_flowControl.addNameSpace(rootName);
byte [] finalBlockID = ((null == finalSegmentIndex) ? null :
((finalSegmentIndex.longValue() == LAST_SEGMENT) ?
SegmentationProfile.getSegmentNumberNameComponent(segmentNumber) :
SegmentationProfile.getSegmentNumberNameComponent(finalSegmentIndex)));
SignedInfo signedInfo = new SignedInfo(publisher, timestamp, type, locator,freshnessSeconds, finalBlockID);
segmentNumber = newBlock(rootName, segmentNumber,
signedInfo, content, offset, length, keys);
if (_blocks.size() >= HOLD_COUNT + 1 || null != finalSegmentIndex)
outputCurrentBlocks(signingKey);
return segmentNumber;
}
/**
* Helper method to build ContentObjects for segments out of a contiguous buffer.
* @param rootName
* @param baseSegmentNumber
* @param signedInfo
* @param content
* @param offset
* @param length
* @param blockWidth
* @param keys the keys to use for encrypting this segment, or null if unencrypted. The
* specific Key/IV used for this segment will be obtained by calling keys.getSegmentEncryptionCipher().
* @param signingKey
* @return
* @throws InvalidKeyException
* @throws InvalidAlgorithmParameterException
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws SignatureException
*/
protected long buildBlocks(ContentName rootName,
long baseSegmentNumber, SignedInfo signedInfo,
byte[] content, int offset, int length, int blockWidth,
ContentKeys keys, Key signingKey, boolean finalFlush)
throws InvalidKeyException, InvalidAlgorithmParameterException, IOException, SignatureException, NoSuchAlgorithmException {
int blockCount = CCNMerkleTree.blockCount(length, blockWidth);
long nextSegmentIndex = baseSegmentNumber;
for (int i=0; i < blockCount; ++i) {
InputStream dataStream = new ByteArrayInputStream(content, offset, length);
if (null != keys) {
// DKS TODO -- move to streaming version to cut down copies. Here using input
// streams, eventually push down with this at the end of an output stream.
// Make a separate cipher, so this segmenter can be used by multiple callers at once.
Cipher thisCipher = keys.getSegmentEncryptionCipher(rootName, signedInfo.getPublisherKeyID(), nextSegmentIndex);
if (Log.isLoggable(Level.FINEST))
Log.finest("Created new encryption cipher "+thisCipher);
// Override content type to mark encryption.
// Note: we don't require that writers use our facilities for encryption, so
// content previously encrypted may not be marked as type ENCR. So on the decryption
// side we don't require that encrypted data be marked ENCR -- if you give us a
// decryption key, we'll try to decrypt it.
signedInfo.setType(ContentType.ENCR);
dataStream = new UnbufferedCipherInputStream(dataStream, thisCipher);
}
ContentObject co =
new ContentObject(
SegmentationProfile.segmentName(rootName, nextSegmentIndex),
signedInfo,
dataStream, blockWidth);
_blocks.add(co);
if (null == _firstSegment) {
_firstSegment = co;
}
nextSegmentIndex = nextSegmentIndex(nextSegmentIndex,
co.contentLength());
offset += blockWidth;
length -= blockWidth;
if (_blocks.size() >= HOLD_COUNT + 1 || finalFlush) {
outputCurrentBlocks(signingKey);
}
}
return nextSegmentIndex;
}
/**
* Create a ContentObject, encrypt it if requested, and add it to the list of ContentObjects
* awaiting signing and output to the flow controller. Also creates the segmented name for the CO.
*
* @param rootName
* @param segmentNumber
* @param signedInfo
* @param contentBlock
* @param offset
* @param blockLength
* @param keys
* @return next segment number to use
* @throws InvalidKeyException
* @throws InvalidAlgorithmParameterException
* @throws ContentEncodingException
*/
protected long newBlock(ContentName rootName,
long segmentNumber, SignedInfo signedInfo,
byte contentBlock[], int offset, int blockLength,
ContentKeys keys) throws InvalidKeyException, InvalidAlgorithmParameterException, ContentEncodingException {
int length = blockLength;
if (null != keys) {
try {
// Make a separate cipher, so this segmenter can be used by multiple callers at once.
Cipher thisCipher = keys.getSegmentEncryptionCipher(rootName, signedInfo.getPublisherKeyID(), segmentNumber);
// TODO -- incurs an extra copy
contentBlock = thisCipher.doFinal(contentBlock, offset, blockLength);
length = contentBlock.length;
offset = 0;
// Override content type to mark encryption.
// Note: we don't require that writers use our facilities for encryption, so
// content previously encrypted may not be marked as type ENCR. So on the decryption
// side we don't require that encrypted data be marked ENCR -- if you give us a
// decryption key, we'll try to decrypt it.
signedInfo.setType(ContentType.ENCR);
} catch (IllegalBlockSizeException e) {
Log.warning("Unexpected IllegalBlockSizeException for an algorithm we have already used!");
throw new InvalidKeyException("Unexpected IllegalBlockSizeException for an algorithm we have already used!", e);
} catch (BadPaddingException e) {
Log.warning("Unexpected BadPaddingException for an algorithm we have already used!");
throw new InvalidAlgorithmParameterException("Unexpected BadPaddingException for an algorithm we have already used!", e);
}
}
ContentObject co =
new ContentObject(
SegmentationProfile.segmentName(rootName, segmentNumber),
signedInfo,contentBlock, offset, length,(Signature)null);
_blocks.add(co);
if (null == _firstSegment) {
_firstSegment = co;
}
int contentLength = co.contentLength();
long nextSegment = nextSegmentIndex(segmentNumber, contentLength);
return nextSegment;
}
/**
* Increment segment number according to the numbering profile in force.
* @param lastSegmentNumber the last segment number we emitted
* @param lastSegmentLength the length of the last segment we emitted
* @return
*/
public long nextSegmentIndex(long lastSegmentNumber, long lastSegmentLength) {
if (SegmentNumberType.SEGMENT_FIXED_INCREMENT == _sequenceType) {
return lastSegmentNumber + getBlockIncrement();
} else if (SegmentNumberType.SEGMENT_BYTE_COUNT == _sequenceType) {
return lastSegmentNumber + (getByteScale() * lastSegmentLength);
} else {
Log.warning("Unknown segmentation type: " + _sequenceType);
return lastSegmentNumber + 1;
}
}
/**
* Compute the index of the last block of a set of segments, according to the
* numbering profile.
* @param currentSegmentNumber
* @param bytesIntervening
* @param blocksRemaining
* @return
*/
public Long lastSegmentIndex(long currentSegmentNumber, long bytesIntervening, int blocksRemaining) {
if (SegmentNumberType.SEGMENT_FIXED_INCREMENT == _sequenceType) {
return currentSegmentNumber + (getBlockIncrement() * (blocksRemaining - 1));
} else if (SegmentNumberType.SEGMENT_BYTE_COUNT == _sequenceType) {
// don't want this, want number of bytes prior to last block
return currentSegmentNumber + (getByteScale() * bytesIntervening);
} else {
Log.warning("Unknown segmentation type: " + _sequenceType);
return currentSegmentNumber + (blocksRemaining - 1);
}
}
/**
* Set the timeout on the contained flow controller.
* @param timeout
*/
public void setTimeout(int timeout) {
getFlowControl().setTimeout(timeout);
}
/**
* How many content bytes will it take to represent content of length
* length, including any padding incurred by encryption?
* @param inputLength
* @return the output length
*/
public long outputLength(int inputLength, ContentKeys keys) {
if (null == keys) {
return inputLength;
} else {
return keys.getCipher().getOutputSize(inputLength);
}
}
}