/* * Part of the CCNx Java Library. * * Copyright (C) 2008, 2009 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.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SignatureException; import org.ccnx.ccn.CCNHandle; import org.ccnx.ccn.impl.CCNFlowControl; import org.ccnx.ccn.impl.CCNSegmenter; import org.ccnx.ccn.impl.CCNFlowControl.Shape; import org.ccnx.ccn.impl.security.crypto.ContentKeys; import org.ccnx.ccn.impl.support.Log; import org.ccnx.ccn.profiles.VersioningProfile; import org.ccnx.ccn.protocol.ContentName; import org.ccnx.ccn.protocol.Interest; import org.ccnx.ccn.protocol.KeyLocator; import org.ccnx.ccn.protocol.MalformedContentNameStringException; import org.ccnx.ccn.protocol.PublisherPublicKeyDigest; import org.ccnx.ccn.protocol.SignedInfo; import org.ccnx.ccn.protocol.SignedInfo.ContentType; /** * Simplest interface to putting data into CCN. Puts buffers of data, optionally * fragmenting them if need be. Useful for writing small test programs, however * more complex clients will usually prefer the higher-level * interfaces offered by CCNOutputStream and its subclasses, or * CCNNetworkObject and its subclasses. */ public class CCNWriter { protected CCNSegmenter _segmenter; /** * Construct a writer that will write content into a certain namespace. Names specified * in calls to put should be descendants of this namespace. * @param namespace The parent namespace this writer will write to as a file path-style string version * of a name (for example /org/ccnx/test). * @param handle The ccn context it will use to write, if null one will be created with CCNHandle#open(). * @throws MalformedContentNameStringException If namespace cannot be parsed. * @throws IOException If network initialization fails. */ public CCNWriter(String namespace, CCNHandle handle) throws MalformedContentNameStringException, IOException { this(ContentName.fromNative(namespace), handle); } /** * Construct a writer that will write content into a certain namespace. Names specified * in calls to put should be descendants of this namespace. * @param namespace The parent namespace this writer will write to. * @param handle The ccn context it will use to write, if null one will be created with CCNHandle#open(). * @throws MalformedContentNameStringException If namespace cannot be parsed. * @throws IOException If network initialization fails. */ public CCNWriter(ContentName namespace, CCNHandle handle) throws IOException { _segmenter = new CCNSegmenter(getFlowController(namespace, handle)); } /** * Construct a writer that will decide later what namespace is should write into * @param handle The ccn context it will use to write, if null one will be created with CCNHandle#open(). * @throws IOException If network initialization fails. */ public CCNWriter(CCNHandle handle) throws IOException { this((ContentName)null, handle); } /** * Create our flow controller. Allow subclass override. * @param namespace * @param handle * @return * @throws IOException */ protected CCNFlowControl getFlowController(ContentName namespace, CCNHandle handle) throws IOException { if (null != namespace) { return new CCNFlowControl(namespace, handle); } return new CCNFlowControl(handle); } /** * Low-level constructor used by implementation. * @param flowControl Output buffer. */ protected CCNWriter(CCNFlowControl flowControl) { _segmenter = new CCNSegmenter(flowControl); } /** * Publish a piece of named content signed by our default identity. * @param name name for content. * @param content content to publish; will be fragmented if necessary. * @throws SignatureException if there is a problem signing. * @throws IOException if there is a problem writing data. */ public ContentName put(String name, String content) throws SignatureException, MalformedContentNameStringException, IOException { return put(ContentName.fromURI(name), content.getBytes(), null, null, null, null); } /** * Publish a piece of named content signed by our default identity. * @param name name for content. * @param content content to publish; will be fragmented if necessary. * @throws SignatureException if there is a problem signing. * @throws IOException if there is a problem writing data. */ public ContentName put(ContentName name, String content) throws SignatureException, MalformedContentNameStringException, IOException { return put(name, content.getBytes(), null, null, null, null); } /** * Publish a piece of named content signed by our default identity. * @param name name for content. * @param content content to publish; will be fragmented if necessary. * @throws SignatureException if there is a problem signing. * @throws IOException if there is a problem writing data. */ public ContentName put(ContentName name, byte[] content) throws SignatureException, IOException { return put(name, content, null, null, null, null); } /** * Publish a piece of named content signed by our default identity. * @param name name for content. * @param content content to publish; will be fragmented if necessary. * @param keys the keys with which to encrypt the content (if non-null) * @throws SignatureException if there is a problem signing. * @throws IOException if there is a problem writing data. */ public ContentName put(ContentName name, byte[] content, ContentKeys keys) throws SignatureException, IOException { return put(name, content, null, null, null, keys); } /** * Publish a piece of named content signed by our default identity in * response to an already-received Interest. The first block of Data * will be written immediately, if name matches this Interest; otherwise * both Data and Interest will be held pending later matches. * @param name name for content. * @param content content to publish; will be fragmented if necessary. * @param outstandingInterest an Interest, usually received by the handleInterests * method of a CCNFilterListener. Only one responder should write data * in response to a given Interest. The Interest should ideally have been * received on the same CCNHandle used by this CCNWriter to write data. * @throws SignatureException if there is a problem signing. * @throws IOException if there is a problem writing data. */ public ContentName put(ContentName name, byte[] content, Interest outstandingInterest) throws SignatureException, IOException { return put(name, content, null, null, null, null, null, outstandingInterest); } /** * Publish a piece of named content signed by a particular identity. * @param name name for content. * @param content content to publish; will be fragmented if necessary. * @param publisher selects one of our identities to publish under * @throws SignatureException if there is a problem signing. * @throws IOException if there is a problem writing data. */ public ContentName put(ContentName name, byte[] content, PublisherPublicKeyDigest publisher) throws SignatureException, IOException { return put(name, content, null, publisher, null, null); } /** * Publish a piece of named content signed by our default identity. * @param name name for content. * @param content content to publish; will be fragmented if necessary. * @param freshnessSeconds how long the content should be considered valid in the cache. * @throws SignatureException if there is a problem signing. * @throws IOException if there is a problem writing data. */ public ContentName put(ContentName name, String content, Integer freshnessSeconds) throws SignatureException, MalformedContentNameStringException, IOException { return put(name, content.getBytes(), null, null, freshnessSeconds, null); } /** * Publish a piece of named content signed by a particular identity. * @param name name for content. * @param content content to publish; will be fragmented if necessary. * @param type type to specify for content. If null, DATA will be used. (see ContentType). * @param publisher selects one of our identities to publish under * @throws SignatureException if there is a problem signing. * @throws IOException if there is a problem writing data. */ public ContentName put(ContentName name, byte[] content, SignedInfo.ContentType type, PublisherPublicKeyDigest publisher, ContentKeys keys) throws SignatureException, IOException { return put(name, content, null, publisher, null, keys); } /** * Publish a piece of named content signed by a particular identity. * @param name name for content. * @param content content to publish; will be fragmented if necessary * @param type type to specify for content. If null, DATA will be used. (see ContentType). * @param publisher selects one of our identities to publish under * @param freshnessSeconds how long the content should be considered valid in the cache. * @throws SignatureException if there is a problem signing. * @throws IOException if there is a problem writing data. */ public ContentName put(ContentName name, byte[] content, SignedInfo.ContentType type, PublisherPublicKeyDigest publisher, Integer freshnessSeconds, ContentKeys keys) throws SignatureException, IOException { return put(name, content, type, publisher, null, freshnessSeconds, keys, null); } /** * Publish a piece of named content signed by a particular identity. * @param name name for content. * @param content content to publish; will be fragmented if necessary * @param type type to specify for content. If null, DATA will be used. (see ContentType). * @param publisher selects one of our identities to publish under * @param freshnessSeconds how long the content should be considered valid in the cache. * @param outstandingInterest an interest this data is being written in response to. If the * name matches the Interest, the first Data segment of the content will be written immediately. * Otherwise both Interest and Data will be cached. * @throws SignatureException if there is a problem signing. * @throws IOException if there is a problem writing data. */ public ContentName put(ContentName name, byte[] content, SignedInfo.ContentType type, PublisherPublicKeyDigest publisher, KeyLocator locator, Integer freshnessSeconds, ContentKeys keys, Interest outstandingInterest) throws SignatureException, IOException { try { addOutstandingInterest(outstandingInterest); _segmenter.getFlowControl().addNameSpace(name); _segmenter.getFlowControl().startWrite(name, Shape.STREAM); // Streams take care of this for the non-gone case. _segmenter.put(name, content, 0, ((null == content) ? 0 : content.length), true, type, freshnessSeconds, locator, publisher, keys); _segmenter.getFlowControl().beforeClose(); _segmenter.getFlowControl().afterClose(); return name; } catch (InvalidKeyException e) { Log.info(Log.FAC_IO, "InvalidKeyException using key for publisher " + publisher + "."); throw new SignatureException(e); } catch (SignatureException e) { Log.info(Log.FAC_IO, "SignatureException using key for publisher " + publisher + "."); throw e; } catch (NoSuchAlgorithmException e) { Log.info(Log.FAC_IO, "NoSuchAlgorithmException using key for publisher " + publisher + "."); throw new SignatureException(e); } catch (InvalidAlgorithmParameterException e) { throw new IOException("Cannot encrypt content -- bad algorithm parameter!: " + e.getMessage()); } } /** * Publishes a piece of content as a new version of a given name. * @param name The (unversioned) name to publish under. * @param content The content to publish, which will be segmented if necessary. * @throws SignatureException if cannot sign * @throws InvalidKeyException if cannot sign with specified key. * @throws NoSuchAlgorithmException if algorithm specified does not exist. * @throws IOException if cannot write data successfully. * @throws InvalidAlgorithmParameterException if there is a problem with the cryptographic parameters. */ public ContentName newVersion(ContentName name, byte[] content) throws SignatureException, IOException, InvalidKeyException, NoSuchAlgorithmException, InvalidAlgorithmParameterException { return newVersion(name, content, null, null); } /** * Publishes a piece of content as a new version of a given name. * @param name The (unversioned) name to publish under. * @param content The content to publish, which will be segmented if necessary. * @param publisher Specifies what key the content should be signed by. * @throws SignatureException if cannot sign * @throws InvalidKeyException if cannot sign with specified key. * @throws NoSuchAlgorithmException if algorithm specified does not exist. * @throws IOException if cannot write data successfully. * @throws InvalidAlgorithmParameterException if there is a problem with the cryptographic parameters. */ public ContentName newVersion( ContentName name, byte[] content, PublisherPublicKeyDigest publisher, ContentKeys keys) throws SignatureException, IOException, InvalidKeyException, NoSuchAlgorithmException, InvalidAlgorithmParameterException { return newVersion(name, content, null, null, publisher, keys); } /** * Publishes a piece of content as a new version of a given name. * @param name The (unversioned) name to publish under. * @param content The content to publish, which will be segmented if necessary. * @param type The type to publish the content as. * @param locator The key locator to used to help consumers find the key used to sign. * @param publisher Specifies what key the content should be signed by. * @throws SignatureException if cannot sign * @throws InvalidKeyException if cannot sign with specified key. * @throws NoSuchAlgorithmException if algorithm specified does not exist. * @throws IOException if cannot write data successfully. * @throws InvalidAlgorithmParameterException if there is a problem with the cryptographic parameters. */ public ContentName newVersion( ContentName name, byte [] content, ContentType type, KeyLocator locator, PublisherPublicKeyDigest publisher, ContentKeys keys) throws SignatureException, InvalidKeyException, NoSuchAlgorithmException, IOException, InvalidAlgorithmParameterException { // Construct new name // <name>/<VERSION_MARKER><version_number> ContentName versionedName = VersioningProfile.addVersion(name); // put result; segmenter will fill in defaults return put(versionedName, content, type, publisher, locator, null, keys, null); } /** * Method for writers used by CCNFilterListeners to output a block * in response to an Interest callback. * We've received an Interest prior to setting up this writer. Use * a method to push this Interest, rather than passing it in in the * constructor to make sure we have completed initializing the writer, * and to limit the number of constructor types. (Similarly, we don't * want to have to repeat each put() in versions that either do or don't * take an Interest argument, or add potentially confusing Interest arguments * to some/all of the put() methods that should usually be null. So * start with this as the simplest option.) * If the Interest doesn't match this writer's content, * no initial block will be output; the writer will wait for matching Interests prior * to writing its blocks. The Interest will be cached in case future * content written to this CCNWriter does match it. * * @param outstandingInterest An interest received prior to constructing * this writer, ideally on the same CCNHandle that the stream is using * for output. Only one block should be put() in response * to this Interest; it is up to the caller to make sure that is the case. */ public void addOutstandingInterest(Interest outstandingInterest) { _segmenter.getFlowControl().handleInterest(outstandingInterest); } /** * @return internal flow buffer. */ protected CCNFlowControl getFlowControl() { return _segmenter.getFlowControl(); } /** * Turn off flow control. * Warning - calling this risks packet drops. It should only * be used for tests or other special circumstances in which * you "know what you are doing". */ public void disableFlowControl() { getFlowControl().disable(); } /** * Close this writer, ensuring all buffers are clear. * @throws IOException If readers do not empty the buffer. */ public void close() throws IOException { _segmenter.getFlowControl().beforeClose(); _segmenter.getFlowControl().afterClose(); } /** * Set the default timeout for this writer. * Default is 10 seconds * @param timeout in msec. */ public void setTimeout(int timeout) { getFlowControl().setTimeout(timeout); } }