/*
* Part of the CCNx Java Library.
*
* Copyright (C) 2012, 2013 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 static org.ccnx.ccn.impl.encoding.CCNProtocolDTags.SyncVersion;
import java.util.ArrayList;
import java.util.Arrays;
import org.ccnx.ccn.CCNSync;
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.profiles.SegmentationProfile;
import org.ccnx.ccn.profiles.sync.Sync;
import org.ccnx.ccn.protocol.Component;
import org.ccnx.ccn.protocol.ContentName;
/**
* A SyncNodeComposite object holds the necessary data for a sync tree node
*/
public class SyncNodeComposite extends GenericXMLEncodable implements XMLEncodable, Cloneable {
public enum SyncNodeType {HASH, LEAF, COMPONENT, BINARY};
public static class SyncNodeElement extends GenericXMLEncodable implements XMLEncodable {
public SyncNodeType _type = SyncNodeType.LEAF;
public ContentName _name;
public byte[] _data;
public SyncNodeElement() {}
public SyncNodeElement(ContentName name) {
_name = name;
}
public SyncNodeElement(byte[] data) {
_data = data;
_type = SyncNodeType.HASH;
}
public SyncNodeType getType() {
return _type;
}
public ContentName getName() {
return _name;
}
public byte[] getData() {
return _data;
}
public void decode(XMLDecoder decoder) throws ContentDecodingException {
if (decoder.peekStartElement(CCNProtocolDTags.Name)) {
_name = new ContentName();
_name.decode(decoder);
} else if (decoder.peekStartElement(CCNProtocolDTags.SyncContentHash)) {
_data = decoder.readBinaryElement(CCNProtocolDTags.SyncContentHash);
_type = SyncNodeType.HASH;
} else if (decoder.peekStartElement(CCNProtocolDTags.Component)) {
_data = decoder.readBinaryElement(CCNProtocolDTags.Component);
_type = SyncNodeType.COMPONENT;
} else if (decoder.peekStartElement(CCNProtocolDTags.BinaryValue)) {
_data = decoder.readBinaryElement(CCNProtocolDTags.BinaryValue);
_type = SyncNodeType.BINARY;
} else
throw new ContentDecodingException("Unexpected element in SyncNodeElements");
}
@Override
public void encode(XMLEncoder encoder) throws ContentEncodingException {
if (!validate())
throw new ContentEncodingException("Link failed to validate!");
encoder.writeStartElement(getElementLabel());
switch (_type) {
case LEAF:
_name.encode(encoder);
break;
case HASH:
encoder.writeElement(CCNProtocolDTags.SyncContentHash, _data);
break;
case COMPONENT:
encoder.writeElement(CCNProtocolDTags.Component, _data);
break;
case BINARY:
encoder.writeElement(CCNProtocolDTags.BinaryValue, _data);
break;
default:
break;
}
encoder.writeEndElement();
}
@Override
public long getElementLabel() {
return CCNProtocolDTags.SyncNodeElement;
}
@Override
public boolean validate() {
return true;
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (! (obj instanceof SyncNodeElement)) {
return false;
}
SyncNodeElement other = (SyncNodeElement) obj;
if (_type != other._type)
return false;
if (_type == SyncNodeType.LEAF) {
if (!_name.equals(other._name))
return false;
} else {
if (!Arrays.equals(_data, other._data))
return false;
}
return true;
}
public int hashCode() {
if (_type == SyncNodeType.LEAF)
return _name.hashCode();
return Arrays.hashCode(_data);
}
}
public int _version;
public ArrayList<SyncNodeElement> _refs = new ArrayList<SyncNodeElement>();
public byte[] _longhash = null;
public SyncNodeElement _minName;
public SyncNodeElement _maxName;
public int _kind;
public int _leafCount;
public int _treeDepth;
public int _byteCount = 0;
public boolean _retrievable = true; // Its "retrievable" if we got it from the network
// If we built it ourselves, we may not know how to redo that
public SyncNodeComposite() {}
public SyncNodeComposite(ArrayList<SyncNodeElement> refs, SyncNodeElement minName, SyncNodeElement maxName, int leafCount, int depth) {
_refs = refs;
_minName = minName;
_maxName = maxName;
_leafCount = leafCount;
computeHash();
_version = Sync.SYNC_VERSION;
_treeDepth = depth;
_retrievable= false;
}
public ArrayList<SyncNodeElement> getRefs() {
return _refs;
}
public void setLeafCount(int count) {
_leafCount = count;
}
public SyncNodeElement getElement(int position) {
if (position >= _refs.size())
return null;
return _refs.get(position);
}
public void decode(XMLDecoder decoder) throws ContentDecodingException {
decoder.readStartElement(getElementLabel());
_version = decoder.readIntegerElement(SyncVersion);
if (_version != Sync.SYNC_VERSION)
throw new ContentDecodingException("Sync version mismatch: " + _version);
if (decoder.peekStartElement(CCNProtocolDTags.SyncNodeElements)) {
decoder.readStartElement(CCNProtocolDTags.SyncNodeElements);
while (true) {
try {
SyncNodeElement ref = new SyncNodeElement();
ref.decode(decoder);
_refs.add(ref);
} catch (ContentDecodingException cde) {
break;
}
}
decoder.readEndElement();
}
if (decoder.peekStartElement(CCNProtocolDTags.SyncContentHash)) {
decoder.readStartElement(CCNProtocolDTags.SyncContentHash);
_longhash = decoder.readBlob();
}
_minName = new SyncNodeElement();
_minName.decode(decoder);
_maxName = new SyncNodeElement();
_maxName.decode(decoder);
_kind = decoder.readIntegerElement(CCNProtocolDTags.SyncNodeKind);
_leafCount = decoder.readIntegerElement(CCNProtocolDTags.SyncLeafCount);
_treeDepth = decoder.readIntegerElement(CCNProtocolDTags.SyncTreeDepth);
_byteCount = decoder.readIntegerElement(CCNProtocolDTags.SyncByteCount);
decoder.readEndElement();
}
public void encode(XMLEncoder encoder) throws ContentEncodingException {
// No current need to encode this - will add if needed
}
/**
* Note we are purposely not comparing byte counts - this is too complicated
* to figure out accurately for us and since we won't be transmitting nodes, the
* place where it is important to be accurate are in the other values (actually
* depth isn't really important either but that's easy - I think - to compute)
*/
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (! (obj instanceof SyncNodeComposite)) {
return false;
}
SyncNodeComposite other = (SyncNodeComposite) obj;
if (_version != other._version)
return false;
if (_refs.size() != other._refs.size())
return false;
for (int i = 0; i < _refs.size(); i++) {
if (! _refs.get(i).equals(other._refs.get(i)))
return false;
}
if (! _minName.equals(other._minName))
return false;
if (! _maxName.equals(other._maxName))
return false;
if (!Arrays.equals(_longhash, other._longhash))
return false;
if (_kind != other._kind)
return false;
if (_leafCount != other._leafCount)
return false;
if (_treeDepth != other._treeDepth)
return false;
return true;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + _version;
for (SyncNodeElement sne : _refs) {
result = prime * result + sne.hashCode();
}
result = prime
* result
+ ((_minName == null) ? 0 : _minName.hashCode());
result = prime
* result
+ ((_maxName == null) ? 0 : _maxName.hashCode());
result = prime * result + _kind;
result = prime * result + _leafCount;
result = prime * result + _treeDepth;
return result;
}
public long getElementLabel() {
return CCNProtocolDTags.SyncNode;
}
public boolean retrievable() {
return _retrievable;
}
public boolean validate() {
return _version == Sync.SYNC_VERSION;
}
public SyncNodeElement getMinName() {
return _minName;
}
public SyncNodeElement getMaxName() {
return _maxName;
}
public byte[] getHash() {
return _longhash;
}
public int getLeafCount() {
return _leafCount;
}
public int getDepth() {
return _treeDepth;
}
public static void decodeLogging(SyncNodeComposite node) {
Log.finest(Log.FAC_SYNC, "decode node for {0} depth = {1} refs = {2}", Component.printURI(node._longhash),
node._treeDepth, node.getRefs().size());
Log.finest(Log.FAC_SYNC, "min is {0}, max is {1}, expanded min is {2}, expanded max is {3}",
SegmentationProfile.getSegmentNumber(node._minName.getName().parent()),
SegmentationProfile.getSegmentNumber(node._maxName.getName().parent()),
node._minName.getName(), node._maxName.getName());
}
/**
* The C code handles different sized hashes and digests. Since I currently
* believe that digests are always 32 bytes, I'm not worrying about that for now...
*/
private void computeHash() {
byte[] tmpHash = new byte[CCNSync.SYNC_HASH_MAX_LENGTH];
for (SyncNodeElement sne : _refs) {
switch (sne.getType()) {
case LEAF:
ContentName name = sne.getName();
byte[] nc = name.lastComponent();
if (null != nc) { // Should always be true
accumHash(nc, tmpHash);
}
break;
case HASH:
accumHash(sne.getData(), tmpHash);
break;
default:
break;
}
}
int hashLength = CCNSync.SYNC_HASH_MAX_LENGTH;
for (int i = 0; i < tmpHash.length; i++) {
if (tmpHash[i] == 0)
hashLength--;
else
break;
}
_longhash = new byte[hashLength];
System.arraycopy(tmpHash, CCNSync.SYNC_HASH_MAX_LENGTH - hashLength, _longhash, 0, hashLength);
}
private void accumHash(byte[] toAdd, byte[] hash) {
int c = 0;
int as = hash.length;
int xs = toAdd.length;
// first accum from digest until no more bytes
while (xs > 0 && as > 0) {
xs--;
as--;
int val = c;
val = val + (hash[as] & 255) + (toAdd[xs] & 255);
c = (val >> 8) & 255;
hash[as] = (byte)(val & 255);
}
// Now propagate the carry (if any)
while (c > 0 && as > 0) {
as--;
c += hash[as];
hash[as] = (byte)(c & 255);
c = (c >> 8) & 255;
}
}
}