/* * Part of the CCNx Java Library. * * Copyright (C) 2008, 2009, 2011 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.content; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import org.ccnx.ccn.CCNHandle; import org.ccnx.ccn.impl.CCNFlowControl; import org.ccnx.ccn.impl.CCNFlowControl.SaveType; import org.ccnx.ccn.impl.encoding.CCNProtocolDTags; import org.ccnx.ccn.impl.encoding.GenericXMLEncodable; import org.ccnx.ccn.impl.encoding.XMLDecoder; import org.ccnx.ccn.impl.encoding.XMLEncodable; import org.ccnx.ccn.impl.encoding.XMLEncoder; import org.ccnx.ccn.impl.support.Log; import org.ccnx.ccn.io.ErrorStateException; import org.ccnx.ccn.profiles.SegmentationProfile; import org.ccnx.ccn.protocol.ContentName; import org.ccnx.ccn.protocol.ContentObject; import org.ccnx.ccn.protocol.KeyLocator; import org.ccnx.ccn.protocol.PublisherPublicKeyDigest; /** * A Header is a set of metadata describing a particular CCN stream; basically it provides * summary file-level information about that set of content. It is usually stored and * read by CCNFileOutputStream and CCNFileInputStream and their subclasses, rather than * being created directly by clients. * * A number of the segmentation-related definitions currently found in Header will * eventually move to the SegmentationProfile. */ public class Header extends GenericXMLEncodable implements XMLEncodable { /** * A CCNNetworkObject wrapper around Header, used for easily saving and retrieving * versioned Headers to CCN. A typical pattern for using network objects to save * objects that happen to be encodable or serializable is to incorporate such a static * member wrapper class subclassing CCNEncodableObject, CCNSerializableObject, or * CCNNetworkObject itself inside the main class definition. */ public static class HeaderObject extends CCNEncodableObject<Header> { public HeaderObject(ContentName name, Header data, SaveType saveType, CCNHandle handle) throws IOException { super(Header.class, true, name, data, saveType, handle); } public HeaderObject(ContentName name, Header data, SaveType saveType, PublisherPublicKeyDigest publisher, KeyLocator keyLocator, CCNHandle handle) throws IOException { super(Header.class, true, name, data, saveType, publisher, keyLocator, handle); } public HeaderObject(ContentName name, Header data, PublisherPublicKeyDigest publisher, KeyLocator keyLocator, CCNFlowControl flowControl) throws ContentDecodingException, IOException { super(Header.class, true, name, data, publisher, keyLocator, flowControl); } public HeaderObject(ContentName name, CCNHandle handle) throws ContentDecodingException, IOException { super(Header.class, true, name, (PublisherPublicKeyDigest)null, handle); } public HeaderObject(ContentName name, PublisherPublicKeyDigest publisher, CCNHandle handle) throws ContentDecodingException, IOException { super(Header.class, true, name, publisher, handle); } public HeaderObject(ContentObject firstBlock, CCNHandle handle) throws ContentDecodingException, IOException { super(Header.class, true, firstBlock, handle); } public long start() throws ContentGoneException, ContentNotReadyException, ErrorStateException { Header h = header(); return h.start(); } public long count() throws ContentGoneException, ContentNotReadyException, ErrorStateException { Header h = header(); return h.count(); } public int blockSize() throws ContentGoneException, ContentNotReadyException, ErrorStateException { Header h = header(); return h.blockSize(); } public long length() throws ContentGoneException, ContentNotReadyException, ErrorStateException { Header h = header(); return h.length(); } public byte [] rootDigest() throws ContentGoneException, ContentNotReadyException, ErrorStateException { Header h = header(); return h.rootDigest(); } public byte [] contentDigest() throws ContentGoneException, ContentNotReadyException, ErrorStateException { Header h = header(); return h.contentDigest(); } public SegmentationType type() throws ContentGoneException, ContentNotReadyException, ErrorStateException { Header h = header(); return h.type(); } public String typeName() throws ContentNotReadyException, ContentGoneException, ErrorStateException { Header h = header(); return h.typeName(); } public int[] positionToSegmentLocation(long position) throws ContentNotReadyException, ContentGoneException, ErrorStateException { Header h = header(); return h.positionToSegmentLocation(position); } public long segmentLocationToPosition(long block, int offset) throws ContentNotReadyException, ContentGoneException, ErrorStateException { Header h = header(); return h.segmentLocationToPosition(block, offset); } public int segmentCount() throws ContentNotReadyException, ContentGoneException, ErrorStateException { Header h = header(); return h.segmentCount(); } public int segmentRemainder() throws ContentNotReadyException, ContentGoneException, ErrorStateException { Header h = header(); return h.segmentRemainder(); } public Header header() throws ContentNotReadyException, ContentGoneException, ErrorStateException { if (null == data()) return null; return data(); } } public enum SegmentationType {SIMPLE_BLOCK}; protected static final HashMap<SegmentationType, String> SegmentationTypeNames = new HashMap<SegmentationType, String>(); protected static final HashMap<String, SegmentationType> SegmentationNameTypes = new HashMap<String, SegmentationType>(); static { SegmentationTypeNames.put(SegmentationType.SIMPLE_BLOCK, "SIMPLE_BLOCK"); SegmentationNameTypes.put("SIMPLE_BLOCK", SegmentationType.SIMPLE_BLOCK); } /** * Specific to simple block fragmentation. */ protected long _start; // starting block number ( >= 0) protected long _count; // number of blocks in sequence (>= 0) protected long _blockSize; // size in bytes(?) of a block (> 0) protected long _length; // total length in bytes (? same unit as _blockSize) to account for partial last block (>= 0) /** * Generic. * */ protected SegmentationType _type; protected byte [] _contentDigest; protected byte [] _rootDigest; // root of the Merkle tree /** * Basic constructor for content sequence headers. * @param start The starting byte offset for this file. * @param count The number of blocks. * @param blockSize The size of blocks (in bytes). * @param length The total length of the stream. * @param contentDigest For convenience, the digest of the unsegmented content. * @param rootDigest The root digest of the bulk signature tree for the content (Merkle Hash Tree). * This turns out to be less useful than you'd think as there are typically multiple * MHT's per file, and is likely to be removed. */ public Header(long start, long count, int blockSize, long length, byte [] contentDigest, byte [] rootDigest) { _start = start; _count = count; _blockSize = blockSize; _length = length; _contentDigest = contentDigest; _rootDigest = rootDigest; _type = SegmentationType.SIMPLE_BLOCK; } /** * Basic constructor for content sequences * @param length The total length of the stream. * @param contentDigest For convenience, the digest of the unsegmented content. * @param rootDigest The root digest of the bulk signature tree for the content (Merkle Hash Tree). * This turns out to be less useful than you'd think as there are typically multiple * MHT's per file, and is likely to be removed. * @param blockSize The size of blocks (in bytes). */ public Header(long length, byte [] contentDigest, byte [] rootDigest, int blockSize) { this(SegmentationProfile.baseSegment(), (length + blockSize - 1) / blockSize, blockSize, length, contentDigest, rootDigest); } /** * For decoders */ public Header() {} public long start() { return _start; } public long count() { return _count; } public int blockSize() { return (int)_blockSize; } public long length() { return _length; } public byte [] rootDigest() { return _rootDigest; } public byte [] contentDigest() { return _contentDigest; } public SegmentationType type() { return _type; } public void type(SegmentationType type) { this._type = type; } public String typeName() { return typeToName(type()); } @Override public void decode(XMLDecoder decoder) throws ContentDecodingException { decoder.readStartElement(getElementLabel()); _start = decoder.readLongElement(CCNProtocolDTags.Start); _count = decoder.readLongElement(CCNProtocolDTags.Count); _blockSize = decoder.readLongElement(CCNProtocolDTags.BlockSize); _length = decoder.readLongElement(CCNProtocolDTags.Length); _contentDigest = decoder.readBinaryElement(CCNProtocolDTags.ContentDigest); if (null == _contentDigest) { throw new ContentDecodingException("Cannot parse content digest."); } if (decoder.peekStartElement(CCNProtocolDTags.RootDigest)) { _rootDigest = decoder.readBinaryElement(CCNProtocolDTags.RootDigest); if (null == _rootDigest) { throw new ContentDecodingException("Cannot parse root digest."); } } decoder.readEndElement(); // Right now, we're just setting this field to default, and it's not encoded _type = SegmentationType.SIMPLE_BLOCK; } @Override public void encode(XMLEncoder encoder) throws ContentEncodingException { if (!validate()) { throw new ContentEncodingException("Cannot encode " + this.getClass().getName() + ": field values missing."); } encoder.writeStartElement(getElementLabel()); encoder.writeElement(CCNProtocolDTags.Start, _start); encoder.writeElement(CCNProtocolDTags.Count, _count); encoder.writeElement(CCNProtocolDTags.BlockSize, _blockSize); encoder.writeElement(CCNProtocolDTags.Length, _length); encoder.writeElement(CCNProtocolDTags.ContentDigest, contentDigest()); if (null != rootDigest()) encoder.writeElement(CCNProtocolDTags.RootDigest, rootDigest()); encoder.writeEndElement(); // DKS -- currently not putting _type on the wire, not sure why it's here... } @Override public long getElementLabel() { return CCNProtocolDTags.Header; } @Override public boolean validate() { if (_start < 0 || _count < 0 || _length < 0) return false; if (_blockSize <= 0) return false; return true; } public static String typeToName(SegmentationType type) { return SegmentationTypeNames.get(type); } public static SegmentationType nameToType(String name) { return SegmentationNameTypes.get(name); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + (int) (_blockSize ^ (_blockSize >>> 32)); result = prime * result + Arrays.hashCode(_contentDigest); result = prime * result + (int) (_count ^ (_count >>> 32)); result = prime * result + (int) (_length ^ (_length >>> 32)); result = prime * result + Arrays.hashCode(_rootDigest); result = prime * result + (int) (_start ^ (_start >>> 32)); result = prime * result + ((_type == null) ? 0 : _type.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Header other = (Header) obj; if (_blockSize != other._blockSize) return false; if (!Arrays.equals(_contentDigest, other._contentDigest)) return false; if (_count != other._count) return false; if (_length != other._length) return false; if (!Arrays.equals(_rootDigest, other._rootDigest)) return false; if (_start != other._start) return false; if (_type == null) { if (other._type != null) return false; } else if (!_type.equals(other._type)) return false; return true; } public int[] positionToSegmentLocation(long position) { int [] blockLocation = new int[2]; Log.info("Header: " + this); Log.info("position: " + position + " blockSize " + blockSize() + " position/blockSize " + position/blockSize() + " start: " + start()); blockLocation[0] = (int)(position / blockSize()); blockLocation[1] = (int)(position % blockSize()); return blockLocation; } public long segmentLocationToPosition(long block, int offset) { if (offset > blockSize()) { block += offset / blockSize(); offset = offset % blockSize(); } if (block >= segmentCount()) { return length(); } return block * blockSize() + offset; } public int segmentCount() { return (int) (length() + blockSize() - 1) / blockSize(); } /** * Length of last block. * @return */ public int segmentRemainder() { int remainder = (int)(length() % blockSize()); if (remainder == 0) return blockSize(); return remainder; } }