/* * 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.impl.security.crypto; import java.security.NoSuchAlgorithmException; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.ccnx.ccn.impl.support.Log; /** * Implementation of a Merkle hash tree. * * Representation based on Knuth, Vol 1, section 2.3.4.5. We represent * trees as a special sublcass of extended binary * trees, where empty subtrees are only present in one end * of the tree. * * Tree nodes are numbered starting with 1, which is the * root. * * Tree nodes are stored in an array, with node i stored at index * i-1 into the array. * * Incomplete binary trees are represented as multi-level extended * binary trees -- lower-numbered leaves are represented in the * upper half of the tree, in a layer one closer to the root than * leaves in the complete subtree. * * Total number of nodes in the tree = 2n + 1, where n is the number of leaves. * * Taken in terms of node indices (where root == 1), the parent * of node k is node floor(k/2), and the children of node k are * nodes 2k and 2k+1. Leaves are numbered from node n+1 through * 2n+1, where n is the number of leaves. * * The sibling index of node k is (k xor 1). * * Should we want to get fancy, we could have t-ary trees; the * construction above works for tree with internal nodes (non-leaves) * {1,2,...,n}. * * The parent of node k is the node floor((k+t-2)/t) = ceil((k-1)/t). * The children of node k are: * t(k-1)+2, t(k-1)+3,..., tk+1 * * In the methods below, we refer to nodes as having a "nodeIndex" -- their * 1-based index into the node array as described above. Leaf nodes also have * a "leafIndex" -- their index into the set of n leaves. Convenience * methods are provided to convert between the two. * * Store node digests internally as DEROctetStrings for more efficient * encoding. */ public class MerkleTree { /** * Node index of 1 (array index of 0). */ protected static final int ROOT_NODE = 1; protected DEROctetString [] _tree; protected int _numLeaves; protected String _digestAlgorithm; /** * The OID prefix we use to represent Merkle trees. Derived from PARC-s sub-arc of Xerox's OID. */ protected static final String MERKLE_OID_PREFIX = "1.2.840.113550.11.1.2"; /** * Build a MerkleTree. This initializes the tree with content, builds the leaf * and intermediate digests, and derives the root digest. * @param digestAlgorithm the digest algorithm to use for computing leaf and * interior node digests of this tree * @param contentBlocks the segmented leaf content to be hashed into this * Merkle hash tree. One block per leaf. * @param isDigest are the content blocks raw content (false), or are they already digested * with digestAlgorithm? (default algorithm: CCNDigestHelper#DEFAULT_DIGEST_ALGORITHM) * @param blockCount the number of those blocks to include (e.g. we may not * have filled our contentBlocks buffers prior to building the tree). Must be * at least 2. * @param baseBlockIndex the offset into the contentBlocks array at which to start. * @param lastBlockLength the number of bytes of the last block to use * @throws NoSuchAlgorithmException */ public MerkleTree(String digestAlgorithm, byte contentBlocks[][], boolean isDigest, int blockCount, int baseBlockIndex, int lastBlockLength) throws NoSuchAlgorithmException { this(digestAlgorithm, blockCount); initializeTree(contentBlocks, isDigest, baseBlockIndex, lastBlockLength); } /** * Segment content and build a MerkleTree. This initializes the tree with content, builds the leaf * and intermediate digests, and derives the root digest. Uses CCNDigestHelper#DEFAULT_DIGEST_ALGORITHM. * @param content the content to segment into leaves and hash into this * Merkle hash tree. One blockWidth of content per leaf, except for the last leaf which may * be shorter. * @param offset offset into content at which to start processing data. * @param length number of bytes of content to process * @param blockWidth the length of leaf blocks to create */ public MerkleTree(byte [] content, int offset, int length, int blockWidth) { this(CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM, blockCount(length, blockWidth)); try { initializeTree(content, offset, length, blockWidth); } catch (NoSuchAlgorithmException e) { // DKS --big configuration problem Log.warning("Fatal Error: cannot find default algorithm " + CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM); throw new RuntimeException("Error: can't find default algorithm " + CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM + "! " + e.toString()); } } /** * Segment content and build a MerkleTree. This initializes the tree with content, builds the leaf * and intermediate digests, and derives the root digest. * @param digestAlgorithm the digest algorithm to use for computing leaf and * interior node digests of this tree * @param content the content to segment into leaves and hash into this * Merkle hash tree. One blockWidth of content per leaf, except for the last leaf which may * be shorter. * @param offset offset into content at which to start processing data. * @param length number of bytes of content to process * @param blockWidth the length of leaf blocks to create */ public MerkleTree(String digestAlgorithm, byte [] content, int offset, int length, int blockWidth) throws NoSuchAlgorithmException { this(digestAlgorithm, blockCount(length, blockWidth)); initializeTree(content, offset, length, blockWidth); } /** * Build a MerkleTree. This initializes the tree with content, builds the leaf * and intermediate digests, and derives the root digest. Uses CCNDigestHelper#DEFAULT_DIGEST_ALGORITHM. * @param contentBlocks the segmented leaf content to be hashed into this * Merkle hash tree. One block per leaf. * @param isDigest are the content blocks raw content (false), or are they already digested * with digestAlgorithm? (default algorithm: CCNDigestHelper#DEFAULT_DIGEST_ALGORITHM) * @param blockCount the number of those blocks to include (e.g. we may not * have filled our contentBlocks buffers prior to building the tree). Must be * at least 2. * @param baseBlockIndex the offset into the contentBlocks array at which to start. * @param lastBlockLength the amount of the last block to use * @throws NoSuchAlgorithmException */ public MerkleTree(byte contentBlocks[][], boolean isDigest, int blockCount, int baseBlockIndex, int lastBlockLength) { this(CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM, blockCount); try { initializeTree(contentBlocks, isDigest, baseBlockIndex, lastBlockLength); } catch (NoSuchAlgorithmException e) { // DKS --big configuration problem Log.warning("Fatal Error: cannot find default algorithm " + CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM); throw new RuntimeException("Error: can't find default algorithm " + CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM + "! " + e.toString()); } } /** * Subclass constructor. * @param digestAlgorithm digest algorithm to use. If null, use CCNDigestHelper#DEFAULT_DIGEST_ALGORITHM. * @param numLeaves the number of leaf nodes to reserve space for */ protected MerkleTree(String digestAlgorithm, int numLeaves) { if (numLeaves < 2) { throw new IllegalArgumentException("MerkleTrees must have 2 or more nodes!"); } _digestAlgorithm = (null == digestAlgorithm) ? CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM : digestAlgorithm; _numLeaves = numLeaves; _tree = new DEROctetString[nodeCount()]; // Let calling constructor handle building the tree. } /** * Method called by constructors to fill leaf nodes with digests and compute intermediate * node values up the tree. Does its work by calling computeLeafValues(byte [][], boolean, int, int) * and computeNodeValues(). * Separate this out to allow subclasses to initialize members before * building tree. * @throws NoSuchAlgorithmException if the digestAlgorithm specified for this tree is unknown */ protected void initializeTree(byte contentBlocks[][], boolean isDigest, int baseBlockIndex, int lastBlockLength) throws NoSuchAlgorithmException { if ((baseBlockIndex < 0) || (contentBlocks.length-baseBlockIndex < numLeaves())) throw new IllegalArgumentException("MerkleTree: cannot build tree from more blocks than given! Have " + (contentBlocks.length-baseBlockIndex) + " blocks, asked to use: " + (numLeaves())); computeLeafValues(contentBlocks, isDigest, baseBlockIndex, lastBlockLength); computeNodeValues(); } /** * Method called by constructors to fill leaf nodes with digests and compute intermediate * node values up the tree. Does its work by calling computeLeafValues(byte [], int, int, int) * and computeNodeValues(). * Separate this out to allow subclasses to initialize members before * building tree. * @throws NoSuchAlgorithmException if the digestAlgorithm specified for this tree is unknown */ protected void initializeTree(byte [] content, int offset, int length, int blockWidth) throws NoSuchAlgorithmException { if ((offset < 0) || (length > content.length) || (blockCount(length, blockWidth) < numLeaves())) throw new IllegalArgumentException("MerkleTree: cannot build tree from more blocks than given! Have " + blockCount(length, blockWidth) + " blocks, asked to use: " + (numLeaves())); computeLeafValues(content, offset, length, blockWidth); computeNodeValues(); } /** * Returns the digest algorithm used by this tree. * @return the digest algorithm used */ public String digestAlgorithm() { return _digestAlgorithm; } /** * Find the index of the parent of this node. * @param nodeIndex is a (1-based) node index whose parent we want to find. * @return Returns 0 if this node has no parent (is the root), otherwise * the parent's index */ public static int parent(int nodeIndex) { return nodeIndex/2; } /** * Find the index of the left child of a given node. * @param nodeIndex the (1-based) index of the node whose child we want to find * @return the index of the left child, or size() if no left child. */ public int leftChild(int nodeIndex) { return 2*nodeIndex; } /** * Find the index of the right child of a given node. * @param nodeIndex the (1-based) index of the node whose child we want to find * @return the index of the right child, or size() if no left child. */ public int rightChild(int nodeIndex) { return 2*nodeIndex + 1; } /** * Find the index of this node's sibling. * Everything always has a sibling, in this formulation of * (not-necessarily-complete binary trees). For root, returns 0. * @param nodeIndex the (1-based) index of the node whose sibling we want to find * @return the (1-based) index of the sibling, or 0 for if nodeIndex is the root. */ public static int sibling(int nodeIndex) { return nodeIndex^1; // Java has xor! who knew? } /** * Check internal node index (not translated to leaves) to see if it * is a left or right child. Internal nodes for a layer always start * with an even index, as 1 is the root and the only layer with one * member. Every other layer has an even number of nodes (except for * possibly a dangling child at the end). So, left nodes have even * indices, and right nodes have odd ones. * @param nodeIndex node to check whether it is a right child * @return true if it is a right child, false if a left child */ public static boolean isRight(int nodeIndex) { return (0 != (nodeIndex % 2)); } /** * Check internal node index (not translated to leaves) to see if it * is a left or right child. Internal nodes for a layer always start * with an even index, as 1 is the root and the only layer with one * member. Every other layer has an even number of nodes (except for * possibly a dangling child at the end). So, left nodes have even * indices, and right nodes have odd ones. * @param nodeIndex node to check whether it is a left child * @return true if it is a left child, false if a right child */ public static boolean isLeft(int nodeIndex) { return (0 == (nodeIndex % 2)); } /** * Return the root digest * @return the root digest */ public byte [] root() { if ((null == _tree) || (_tree.length == 0)) return new byte[0]; return get(ROOT_NODE); } /** * Get the DEROctetString wrapped digest of the root node. * @return a DEROctetString object containing the root node digest. */ public DEROctetString derRoot() { return derGet(ROOT_NODE); } /** * Get the size of the tree, in nodes. (This is the number of nodes, * not the number of leaves.) * @return the tree size. */ public int size() { return _tree.length; } /** * Returns the digest at the specified node. * @param nodeIndex 1-based node index * @return the digest for this node */ public byte [] get(int nodeIndex) { DEROctetString dv = derGet(nodeIndex); if (null == dv) return null; return dv.getOctets(); } /** * Returns the digest at the specified node as a DEROctetString * @param nodeIndex 1-based node index * @return the digest for this node */ public DEROctetString derGet(int nodeIndex) { if ((nodeIndex < ROOT_NODE) || (nodeIndex > size())) return null; return _tree[nodeIndex-1]; } /** * Get the number of leaves in the tree. * @return returns the number of leaves */ public int numLeaves() { return _numLeaves; } /** * Calculate the number of nodes in a tree with a given number of leaves. * @param numLeaves the number of leaves * @return the number of nodes in the tree */ public static int nodeCount(int numLeaves) { // How many entries do we need? 2*numLeaves + 1 return 2*numLeaves-1; } /** * Calculates the number of nodes in this tree * @return the number of nodes */ public int nodeCount() { return nodeCount(numLeaves()); } /** * Returns the node index of the first leaf. * The node index of the first leaf is either size()-numleaves(), or * nodeIndex = numLeaves. * @return the first leaf's node index */ public int firstLeaf() { return numLeaves(); } /** * Get the node index of a given leaf * @param leafIndex the index of a leaf * @return its node index */ public int leafNodeIndex(int leafIndex) { return firstLeaf() + leafIndex; } /** * Retrieve the digest of a given leaf node. Returns null if there is * no leaf leafIndex. * @param leafIndex leaf index, starting at 0 for the first leaf. * @return its digest */ public byte [] leaf(int leafIndex) { return get(leafNodeIndex(leafIndex)); } /** * Generate a MerklePath for a given leaf, to use in verifying that * leaf. * * There are a variety of traversal algorithms for * computing/reading Merkle hash trees. * * We need to represent the leaves so that the user * a) knows what order they come in, and b) also knows * which is the leaf being represented. The cheapest * way to do that is to represent the leaves in order, * and also start out with an indication of whether * this leaf is the left or right of the last pair. * To make this most general and easy to use, we * will represent this path as * * MerklePath ::= SEQUENCE { * nodeIndex INTEGER, * nodes NodeList * } * * NodeList ::= SEQUENCE OF OCTET STRING * * the nodeIndex here is the index of the leaf node in * the tree as a whole (not just among the leaves), and * the nodes list contains neither the digest of the * leaf itself nor the root of the tree. * * We could probably save a few bytes by not encoding this * as DER, and simply packing in the bytes to represent this * data -- this encoding offers a fair amount of ease of parsing * and clarity, at the cost of probably 5 + 2*pathLength bytes of overhead, * or 20 bytes in typical paths. At some point this may * seem too much, and we will move to a more compact encoding. * * @param leafNum the leaf index of the leaf * @return the MerklePath for verifying that leaf * @see MerklePath */ public MerklePath path(int leafNum) { // Start at the leaf, pushing siblings. We know we always have // a complete path to the leaf. int leafNode = leafNodeIndex(leafNum); // We want to push nodes of the path onto the path structure // in reverse order. We'd then like to turn them into bare // arrays for efficiency. Java's stacks, though, turn them // into arrays in the wrong order. So, make an array. With // the extended binary tree, all paths are complete for their // region of the tree. DEROctetString [] resultStack = new DEROctetString[maxPathLength(leafNode)]; // Start at the leaf, pushing siblings. int node = leafNode; int index = resultStack.length-1; while (node != ROOT_NODE) { int siblingIdx = sibling(node); // returns null if siblingIdx is too large, or if // there is no child at that index (empty subtree) DEROctetString sibling = derGet(siblingIdx); if (null != sibling) { resultStack[index--] = sibling; } node = parent(node); } return new MerklePath(leafNode, resultStack); } /** * What is the maximum path length to a node with this node index, * including its sibling but not including the root? * @param nodeIndex the node to find the path length for * @return the maximum path length */ public static int maxPathLength(int nodeIndex) { int baseLog = (int)Math.floor(MerkleTree.log2(nodeIndex)); return baseLog; } /** * What is the maximum depth of a Merkle tree with * a given number of leaves. If the tree isn't balanced, * many nodes may have shorter paths than maxDepth. * @param numLeaves the number of leaves in the tree. * @return the maximum depth of the tree */ public static int maxDepth(int numLeaves) { if (numLeaves == 0) return 0; if (numLeaves == 1) return 1; int pathLength = (int)Math.ceil(log2(numLeaves)); //Library.info("numLeaves: " + numLeaves + " log2(nl+1) " + log2(numLeaves+1)); return pathLength; } /** * Get the maximum depth of this MerkleTree. * @return the depth */ public int maxDepth() { return maxDepth(numLeaves()); } /** * Compute the raw digest of the leaf content blocks, and format them appropriately. * @param contentBlocks the leaf content, one leaf per array * @param isDigest have these been digested already, or do we need to digest * them using computeBlockDigest(int, byte [][], int, int)? * @param baseBlockIndex first block in the array to use * @param lastBlockLength number of bytes of the last block to use; N/A if isDigest is true * @throws NoSuchAlgorithmException if digestAlgorithm is unknown */ protected void computeLeafValues(byte contentBlocks[][], boolean isDigest, int baseBlockIndex, int lastBlockLength) throws NoSuchAlgorithmException { // Hash the leaves for (int i=0; i < numLeaves(); ++i) { _tree[leafNodeIndex(i)-1] = new DEROctetString( (isDigest ? contentBlocks[i+baseBlockIndex] : computeBlockDigest(i, contentBlocks, baseBlockIndex, lastBlockLength))); } } /** * Compute the raw digest of the leaf content blocks, and format them appropriately. * uses computeBlockDigest(int, byte[], int, int) to compute the leaf digest. * @param content the content to segment into leaves and hash into this * Merkle hash tree. One blockWidth of content per leaf, except for the last leaf which may * be shorter. * @param offset offset into content at which to start processing data. * @param length number of bytes of content to process * @param blockWidth the length of leaf blocks to create * @throws NoSuchAlgorithmException if digestAlgorithm is unknown */ protected void computeLeafValues(byte [] content, int offset, int length, int blockWidth) throws NoSuchAlgorithmException { // Hash the leaves for (int i=0; i < numLeaves(); ++i) { _tree[leafNodeIndex(i)-1] = new DEROctetString( (computeBlockDigest(i, content, offset + (blockWidth*i), ((i < numLeaves()-1) ? blockWidth : (length - (blockWidth*i)))))); } } /** * Compute the intermediate node values by digesting the concatenation of the * left and right children (or the left child alone if there is no right child). * @throws NoSuchAlgorithmException if digestAlgorithm is unknown */ protected void computeNodeValues() throws NoSuchAlgorithmException { // Climb the tree int firstNode = firstLeaf()-1; for (int i=firstNode; i >= ROOT_NODE; --i) { byte [] nodeDigest = CCNDigestHelper.digest(digestAlgorithm(), get(leftChild(i)), get(rightChild(i))); _tree[i-1] = new DEROctetString(nodeDigest); } } /** * Function for validating paths. Given a digest, it returns what node in * the tree has that digest. If no node has that digest, returns 0. * If argument is null, returns -1. Slow. * @param node the node digest to validate * @return the nodeIndex of the node with that digest */ public int getNodeIndex(DEROctetString node) { if (null == node) return -1; for (int i=1; i <= size(); ++i) { if (node.equals(derGet(i))) return i; } return 0; } /** * Get the root node as an encoded PKCS#1 DigestInfo. * @return the encoded DigestInfo */ public byte[] getRootAsEncodedDigest() { // Take root and wrap it up as an encoded DigestInfo return CCNDigestHelper.digestEncoder( digestAlgorithm(), root()); } /** * Compute the digest of a leaf node. * Separate this out so that it can be overridden. * @param leafIndex The index of the leaf we are computing the digest of. * @param contentBlocks The array of content blocks containing the leaf content. * @param baseBlockIndex The first content block in the array containing leaf content (if rolling buffers). * numLeaves() blocks contain leaf content, so the last block used is blockOffset+numLeaves(). * @param lastBlockLength the number of bytes of the last block to use, can be smaller than * the number available * @return the digest for this leaf * @throws NoSuchAlgorithmException */ protected byte [] computeBlockDigest(int leafIndex, byte contentBlocks[][], int baseBlockIndex, int lastBlockLength) throws NoSuchAlgorithmException { if ((leafIndex + baseBlockIndex) > contentBlocks.length) throw new IllegalArgumentException("Cannot ask for a leaf beyond the number of available blocks!"); // Are we on the last block? if ((leafIndex + baseBlockIndex) == (baseBlockIndex + numLeaves() - 1)) computeBlockDigest(leafIndex, contentBlocks[leafIndex+baseBlockIndex], 0, lastBlockLength); return computeBlockDigest(_digestAlgorithm, contentBlocks[leafIndex+baseBlockIndex]); } /** * Compute the digest of a leaf node. * Separate this out so that it can be overridden. * @param leafIndex The index of the leaf we are computing the digest of. * @param content the content to segment into leaves and hash into this * Merkle hash tree. * @param offset offset into content at which this leaf starts * @param length number of bytes of content in this leaf * @return the digest for this leaf * @throws NoSuchAlgorithmException if digestAlgorithm is unknown */ protected byte [] computeBlockDigest(int leafIndex, byte [] content, int offset, int length) throws NoSuchAlgorithmException { return CCNDigestHelper.digest(_digestAlgorithm, content, offset, length); } /** * Compute the digest of a leaf node. * @param digestAlgorithm the digest algorithm to use * @param content the content of this leaf * @return the digest for this leaf * @throws NoSuchAlgorithmException if digestAlgorithm is unknown */ public static byte [] computeBlockDigest(String digestAlgorithm, byte [] content) throws NoSuchAlgorithmException { return CCNDigestHelper.digest(digestAlgorithm, content); } /** * Compute the digest of a leaf node. * @param digestAlgorithm the digest algorithm to use * @param content the content to segment into leaves and hash into this * Merkle hash tree. * @param offset offset into content at which this leaf starts * @param length number of bytes of content in this leaf * @return the digest for this leaf * @throws NoSuchAlgorithmException if digestAlgorithm is unknown */ public static byte [] computeBlockDigest(String digestAlgorithm, byte [] content, int offset, int length) throws NoSuchAlgorithmException { return CCNDigestHelper.digest(digestAlgorithm, content, offset, length); } /** * Compute the digest of a block using CCNDigestHelper#DEFAULT_DIGEST_ALGORITHM. * DKS TODO - check -- was being by MerklePath to compute digest for root without * properly recovering OID from encoded path. * @param block block to digest * @return block digest */ public static byte [] computeBlockDigest(byte [] block) { try { return computeBlockDigest(CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM, block); } catch (NoSuchAlgorithmException e) { // DKS --big configuration problem Log.warning("Fatal Error: cannot find default algorithm " + CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM); throw new RuntimeException("Error: can't find default algorithm " + CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM + "! " + e.toString()); } } /** * Compute the digest for an intermediate node. If this is a last left child (right is null), * simply hash left alone. * @throws NoSuchAlgorithmException */ public static byte [] computeNodeDigest(String algorithm, byte [] left, byte [] right) throws NoSuchAlgorithmException { return CCNDigestHelper.digest(algorithm, left, right); } /** * Compute the digest for an intermediate node with two children. * @param left left child * @param right right child * @return parent digest */ public static byte [] computeNodeDigest(byte [] left, byte [] right) { try { return computeNodeDigest(CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM, left, right); } catch (NoSuchAlgorithmException e) { // DKS --big configuration problem Log.warning("Fatal Error: cannot find default algorithm " + CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM); throw new RuntimeException("Error: can't find default algorithm " + CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM + "! " + e.toString()); } } /** * Does this algorithm identifier indicate a Merkle tree? * @param algorithmId the algorithm identifier * @return true if its a merkle tree, false otherwise */ public static boolean isMerkleTree(AlgorithmIdentifier algorithmId) { // Use a hack -- all MHT OIDs use same prefix. String strAlg = algorithmId.toString(); if (strAlg.startsWith(MERKLE_OID_PREFIX)) return true; return false; } /** * Helper method * @param arg * @return log base 2 of arg */ public static double log2(int arg) { return Math.log(arg)/Math.log(2); } /** * The number of blocks of blockWidth (bytes) necessary to hold length (bytes) * @param length the buffer length * @param blockWidth the segment with * @return the number of blocks */ public static int blockCount(int length, int blockWidth) { if (0 == length) return 0; return (length + blockWidth - 1) / blockWidth; // return (int)Math.ceil((1.0*length)/blockWidth); } }