View Javadoc
1   /*
2    * GordianKnot: Security Suite
3    * Copyright 2012-2026. Tony Washer
4    *
5    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6    * use this file except in compliance with the License.  You may obtain a copy
7    * of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
14   * License for the specific language governing permissions and limitations under
15   * the License.
16   */
17  package io.github.tonywasher.joceanus.gordianknot.impl.ext.digests;
18  
19  import io.github.tonywasher.joceanus.gordianknot.impl.ext.digests.GordianSkeinBase.Configuration;
20  import io.github.tonywasher.joceanus.gordianknot.impl.ext.params.GordianSkeinParameters;
21  import io.github.tonywasher.joceanus.gordianknot.impl.ext.params.GordianSkeinParameters.GordianSkeinParametersBuilder;
22  import org.bouncycastle.crypto.DataLengthException;
23  import org.bouncycastle.crypto.Digest;
24  import org.bouncycastle.crypto.OutputLengthException;
25  import org.bouncycastle.util.Arrays;
26  
27  import java.util.Enumeration;
28  import java.util.NoSuchElementException;
29  
30  /**
31   * Skein Tree Hash.
32   * <p>This implementation will support all elements of SkeinXParameters except for Maximum OutputLength.
33   * This is restricted to the output length of the underlying digest.
34   * <p>Tree configuration is required and defaults to unlimited fanOut, maxDepth of 2 and 4K leafLength.
35   * <p>The implementation consumes a large message as a standard digest and builds the lowest level of the tree on the fly.
36   * On the {@link #doFinal(byte[], int)} call, the remainder of the tree is calculated and the topmost tree node is returned as the hash.
37   * This tree is retained until an explicit {@link #reset()} is called. Further update calls are disabled while the tree is retained.
38   * <p>While the tree is retained, each leaf may be explicitly replaced via {@link #updateLeaf(int, byte[], int, int)}, leading to a recalculation
39   * of the tree node which may be obtained via {@link #obtainResult(byte[], int)}.
40   * <p>The last leaf at the bottom level of the tree can be any length from 1 to leafLen. It may be replaced by data that is between 1 to leafLen using the
41   * {@link #updateLeaf(int, byte[], int, int)} method where the new length does not have to be the same as the old length.
42   * Other leaves must be replaced by data of length leafLen.
43   * <p>The number of leaves cannot be increased/decreased once the tree has been built. If the length of data is changed, a new tree should be built.
44   * <p>TODO
45   * <ul>
46   * <li>Replacing a leaf should not automatically trigger a recalculation of the tree. Instead the updated leaves should be left in an update list.
47   * This update list will be processed only when the final result is requested, allowing several leaves to be replaced before the tree is recalculated.
48   * <li>A duplicate leaf in the update list will replace the earlier leaf.
49   * <li>On recalculation the updateList will be validated. The tree can be extended if all additional leaves are present in the update list and the previously last
50   * leaf was either leafLen long or is now present as a full leaf in the update list. Only the last leaf may be shorter than the leafLen.
51   * <li>A new call should be provided to truncate the tree at a total length. This must be shorter than the current length and will form part of the update list
52   * to be processed when calculating the result.
53   * </ul>
54   */
55  public class GordianSkeinTree
56          implements Digest {
57      /**
58       * The maximum Byte.
59       */
60      private static final int MAXBYTE = 255;
61  
62      /**
63       * The base for the treeConfig.
64       */
65      private static final int CONFIGBASE = 16;
66  
67      /**
68       * The underlying Skein instance.
69       */
70      private final GordianSkeinBase theDigest;
71  
72      /**
73       * The TreeStore.
74       */
75      private final GordianSkeinTreeStore theStore;
76  
77      /**
78       * Hash buffer.
79       */
80      private final byte[] theHash;
81  
82      /**
83       * The single byte buffer.
84       */
85      private final byte[] singleByte = new byte[1];
86  
87      /**
88       * The leafLength.
89       */
90      private int theLeafLen;
91  
92      /**
93       * The shift.
94       */
95      private int theShift;
96  
97      /**
98       * The leaf index.
99       */
100     private int theLeafIdx;
101 
102     /**
103      * The data processed on current leaf.
104      */
105     private int theProcessed;
106 
107     /**
108      * Constructor.
109      *
110      * @param pDigest the underlying digest.
111      */
112     public GordianSkeinTree(final GordianSkeinBase pDigest) {
113         /* Store parameters and initialise store */
114         theDigest = pDigest;
115         theStore = new GordianSkeinTreeStore(pDigest);
116         theHash = new byte[theDigest.getOutputSize()];
117 
118         /* Initialise to default values */
119         final GordianSkeinParametersBuilder myBuilder = new GordianSkeinParametersBuilder();
120         myBuilder.setTreeConfig(1, MAXBYTE, 1);
121         init(myBuilder.build());
122     }
123 
124     @Override
125     public String getAlgorithmName() {
126         return "SkeinTree-" + (theDigest.getOutputSize() * Byte.SIZE);
127     }
128 
129     @Override
130     public int getDigestSize() {
131         return theDigest.getOutputSize();
132     }
133 
134     @Override
135     public void update(final byte pIn) {
136         singleByte[0] = pIn;
137         update(singleByte, 0, 1);
138     }
139 
140     @Override
141     public void update(final byte[] pIn,
142                        final int pInOff,
143                        final int pLen) {
144         processData(pIn, pInOff, pLen);
145     }
146 
147     @Override
148     public int doFinal(final byte[] pOut,
149                        final int pOutOffset) {
150         /* Finalise the leaf and process the result */
151         theDigest.calculateNode(theHash, 0);
152         theStore.addElement(Arrays.clone(theHash));
153         return theStore.calculateTree(pOut, pOutOffset);
154     }
155 
156     @Override
157     public void reset() {
158         theLeafIdx = 0;
159         theProcessed = 0;
160         theDigest.initTreeNode(1, theLeafIdx, theShift);
161         theStore.reset();
162     }
163 
164     /**
165      * Obtain the tree result.
166      *
167      * @param pOut       the output buffer
168      * @param pOutOffset the offset into the output buffer
169      * @return the number of bytes returned
170      */
171     public int obtainResult(final byte[] pOut,
172                             final int pOutOffset) {
173         /* Access result from store */
174         return theStore.obtainResult(pOut, pOutOffset);
175     }
176 
177     /**
178      * Obtain the leaf length.
179      *
180      * @return the leafLength.
181      */
182     public int getLeafLen() {
183         return theDigest.getOutputSize() << theLeafLen;
184     }
185 
186     /**
187      * Process data.
188      *
189      * @param pIn       the input buffer
190      * @param pInOffSet the starting offset in the input buffer
191      * @param pLen      the length of data to process
192      */
193     private void processData(final byte[] pIn,
194                              final int pInOffSet,
195                              final int pLen) {
196         /* Cannot process further data once tree is built */
197         if (theStore.treeBuilt()) {
198             throw new IllegalStateException("Tree has been built");
199         }
200 
201         /* Determine space in current block */
202         final int blkSize = getLeafLen();
203         final int mySpace = blkSize - theProcessed;
204 
205         /* If all data can be processed by the current leaf */
206         if (mySpace >= pLen) {
207             /* Update and return */
208             theDigest.update(pIn, pInOffSet, pLen);
209             theProcessed += pLen;
210             return;
211         }
212 
213         /* Process as much as possible into current leaf */
214         if (mySpace > 0) {
215             theDigest.update(pIn, pInOffSet, mySpace);
216             theProcessed += mySpace;
217         }
218 
219         /* Loop while we have data remaining */
220         int myProcessed = mySpace;
221         while (myProcessed < pLen) {
222             /* If the current leaf is full */
223             if (theProcessed == blkSize) {
224                 /* Finalise the leaf and process the result */
225                 theDigest.calculateNode(theHash, 0);
226                 theStore.addElement(theHash);
227                 theDigest.initTreeNode(1, ++theLeafIdx, theShift);
228                 theProcessed = 0;
229             }
230 
231             /* Process next block */
232             final int myDataLen = Math.min(pLen - myProcessed, blkSize);
233             theDigest.update(pIn, pInOffSet + myProcessed, myDataLen);
234             theProcessed += myDataLen;
235             myProcessed += myDataLen;
236         }
237     }
238 
239     /**
240      * Initialise.
241      *
242      * @param pParams the parameters.
243      */
244     public void init(final GordianSkeinParameters pParams) {
245         /* Reject a bad leaf length length */
246         final int myLeafLen = pParams.getTreeLeafLen();
247         if (myLeafLen < 1 || myLeafLen > MAXBYTE) {
248             throw new IllegalArgumentException("Invalid leaf length");
249         }
250 
251         /* Reject a bad fanOut */
252         final short myFanOut = pParams.getTreeFanOut();
253         if (myFanOut < 1 || myFanOut > MAXBYTE) {
254             throw new IllegalArgumentException("Invalid fanOut");
255         }
256 
257         /* Reject a bad maxDepth */
258         final short myMaxDepth = pParams.getTreeMaxDepth();
259         if (myMaxDepth < 2 || myMaxDepth > MAXBYTE) {
260             throw new IllegalArgumentException("Invalid maxDepth");
261         }
262 
263         /* Record the values */
264         theLeafLen = myLeafLen;
265         theShift = highestBitSet(theDigest.getOutputSize()) + theLeafLen - 1;
266 
267         /* Declare the configuration */
268         declareConfig(myFanOut, myMaxDepth);
269 
270         /* Pass selective parameters to the underlying hash */
271         theDigest.init(pParams);
272 
273         /* Reset everything */
274         reset();
275     }
276 
277     /**
278      * Declare extended configuration.
279      *
280      * @param pFanOut   the fanOut
281      * @param pMaxDepth the max depth
282      */
283     private void declareConfig(final int pFanOut,
284                                final int pMaxDepth) {
285         /* Declare the configuration */
286         final long myLen = theDigest.getOutputSize() * 8L;
287         final Configuration myConfig = new TreeConfiguration(myLen, theLeafLen, pFanOut, pMaxDepth);
288         theDigest.setConfiguration(myConfig);
289 
290         /* Update the store */
291         theStore.declareConfig(pFanOut, pMaxDepth);
292     }
293 
294     /**
295      * Update leaf.
296      *
297      * @param pIndex    the index of the leaf
298      * @param pInput    the input buffer
299      * @param pInOffSet the starting offset the the input buffer
300      */
301     public void updateLeaf(final int pIndex,
302                            final byte[] pInput,
303                            final int pInOffSet) {
304         /* Full leafLen */
305         updateLeaf(pIndex, pInput, pInOffSet, getLeafLen());
306     }
307 
308     /**
309      * Update leaf.
310      *
311      * @param pIndex    the index of the leaf
312      * @param pInput    the input buffer
313      * @param pInOffSet the starting offset the the input buffer
314      * @param pLen      the length of data
315      */
316     public void updateLeaf(final int pIndex,
317                            final byte[] pInput,
318                            final int pInOffSet,
319                            final int pLen) {
320         /* Check index validity */
321         final boolean bLast = theStore.checkLeafIndex(pIndex);
322 
323         /* Validate the leaf length */
324         final int myLeafLen = getLeafLen();
325         if (pLen < 0 || pLen > myLeafLen) {
326             throw new DataLengthException("Invalid length");
327         }
328 
329         /* Any leaf that is not the last must be leafLen in length */
330         if (!bLast && pLen != myLeafLen) {
331             throw new DataLengthException("All but the last leaf must have byteLength " + myLeafLen);
332         }
333 
334         /* Make sure that the buffer is valid */
335         if (pLen + pInOffSet > pInput.length) {
336             throw new DataLengthException("Invalid input buffer");
337         }
338 
339         /* Initialise the treeNode */
340         theDigest.initTreeNode(1, pIndex, theShift);
341 
342         /* Recalculate the digest */
343         theDigest.update(pInput, pInOffSet, pLen);
344         theDigest.calculateNode(theHash, 0);
345 
346         /* Replace the hash */
347         theStore.replaceElement(pIndex, theHash);
348     }
349 
350     /**
351      * Calculate highestBitSet.
352      *
353      * @param pValue the value to examine
354      * @return the index of the highest but set
355      */
356     private static int highestBitSet(final int pValue) {
357         int highestBit = 0;
358         int myValue = pValue;
359         while (myValue != 0) {
360             highestBit++;
361             myValue = myValue >>> 1;
362         }
363         return highestBit;
364     }
365 
366     /**
367      * The Skein tree.
368      */
369     private static class GordianSkeinTreeStore {
370         /**
371          * The underlying Skein instance.
372          */
373         private final GordianSkeinBase theDigest;
374 
375         /**
376          * The Hash Result.
377          */
378         private final byte[] theResult;
379 
380         /**
381          * The Array of Hashes.
382          */
383         private final SimpleVector theHashes;
384 
385         /**
386          * The fanOut.
387          */
388         private short theFanOut;
389 
390         /**
391          * The maxDepth.
392          */
393         private short theMaxDepth;
394 
395         /**
396          * The shift.
397          */
398         private int theShift;
399 
400         /**
401          * Has the tree been built?.
402          */
403         private boolean treeBuilt;
404 
405         /**
406          * Constructor.
407          *
408          * @param pDigest the underlying digest.
409          */
410         GordianSkeinTreeStore(final GordianSkeinBase pDigest) {
411             /* Store details */
412             theDigest = pDigest;
413             theResult = new byte[theDigest.getOutputSize()];
414             theHashes = new SimpleVector();
415         }
416 
417         /**
418          * Declare extended configuration.
419          *
420          * @param pFanOut   the fanOut
421          * @param pMaxDepth the max depth
422          */
423         void declareConfig(final int pFanOut,
424                            final int pMaxDepth) {
425             theFanOut = (short) pFanOut;
426             theMaxDepth = (short) pMaxDepth;
427             theShift = highestBitSet(theDigest.getOutputSize()) + theFanOut - 1;
428         }
429 
430         /**
431          * Has the tree been built?
432          *
433          * @return true/false
434          */
435         boolean treeBuilt() {
436             return treeBuilt;
437         }
438 
439         /**
440          * Reset the store.
441          */
442         void reset() {
443             theHashes.clear();
444             treeBuilt = false;
445         }
446 
447         /**
448          * Add intermediate node.
449          *
450          * @param pHash the intermediate hash
451          */
452         void addElement(final byte[] pHash) {
453             /* Access the base level */
454             if (theHashes.isEmpty()) {
455                 theHashes.addElement(new SimpleVector());
456             }
457             final SimpleVector myLevel = (SimpleVector) theHashes.firstElement();
458 
459             /* Add the element to the vector */
460             myLevel.addElement(Arrays.clone(pHash));
461         }
462 
463         /**
464          * Obtain the tree result.
465          *
466          * @param pOut       the output buffer
467          * @param pOutOffset the offset into the output buffer
468          * @return the number of bytes returned
469          */
470         int obtainResult(final byte[] pOut,
471                          final int pOutOffset) {
472             /* Check parameters */
473             if (pOut.length < pOutOffset + theResult.length) {
474                 throw new OutputLengthException("Insufficient output buffer");
475             }
476             if (!treeBuilt) {
477                 throw new IllegalStateException("tree has not been built");
478             }
479 
480             /* Access the final level */
481             final SimpleVector myLevel = (SimpleVector) theHashes.lastElement();
482             final byte[] myState = (byte[]) myLevel.firstElement();
483 
484             /* Process the output */
485             theDigest.restoreForOutput(myState);
486             theDigest.output(0, pOut, pOutOffset, theResult.length);
487 
488             /* Return the length */
489             return theResult.length;
490         }
491 
492         /**
493          * Calculate tree result.
494          *
495          * @param pOut       the output buffer
496          * @param pOutOffset the offset into the output buffer
497          * @return the number of bytes returned
498          */
499         int calculateTree(final byte[] pOut,
500                           final int pOutOffset) {
501             /* Check parameters */
502             if (pOut.length < pOutOffset + theResult.length) {
503                 throw new OutputLengthException("Insufficient output buffer");
504             }
505             if (treeBuilt) {
506                 throw new IllegalStateException("tree already built");
507             }
508 
509             /* Access the only level */
510             SimpleVector myLevel = (SimpleVector) theHashes.lastElement();
511 
512             /* While we have elements that must be reduced */
513             while (myLevel.size() > 1) {
514                 /* Calculate the next set of hashes */
515                 myLevel = calculateNextLevel(myLevel);
516                 theHashes.addElement(myLevel);
517             }
518 
519             /* Note that the tree has been built */
520             treeBuilt = true;
521 
522             /* Return the final hash */
523             return obtainResult(pOut, pOutOffset);
524         }
525 
526         /**
527          * Calculate next level.
528          *
529          * @param pInput the current set of hashes
530          * @return the next level
531          */
532         private SimpleVector calculateNextLevel(final SimpleVector pInput) {
533             /* Set the depth of the tree */
534             final int myCurDepth = theHashes.size() + 1;
535             theDigest.initTreeNode(myCurDepth, 0, theShift);
536 
537             /* Create the new level */
538             final SimpleVector myResults = new SimpleVector();
539 
540             /* Determine the number of nodes to combine */
541             final int myFanOut = 1 << theFanOut;
542 
543             /* Determine whether we are calculating the root node */
544             final boolean lastStage = pInput.size() <= myFanOut
545                     || myCurDepth == theMaxDepth;
546 
547             /* Loop through all the elements */
548             int myCount = 0;
549             int myOffSet = 0;
550             final Enumeration<?> myEnumeration = pInput.elements();
551             while (myEnumeration.hasMoreElements()) {
552                 /* If we need to move to the next node  */
553                 if (!lastStage && myCount == myFanOut) {
554                     /* Calculate node and add to level */
555                     theDigest.calculateNode(theResult, 0);
556                     myResults.addElement(Arrays.clone(theResult));
557 
558                     /* Switch to next node */
559                     theDigest.initTreeNode(myCurDepth, ++myOffSet, theShift);
560                     myCount = 0;
561                 }
562 
563                 /* Fold hash into current node */
564                 final byte[] myHash = (byte[]) myEnumeration.nextElement();
565                 theDigest.update(myHash, 0, myHash.length);
566                 myCount++;
567             }
568 
569             /* Calculate final node at this level */
570             theDigest.calculateNode(theResult, 0);
571             myResults.addElement(Arrays.clone(theResult));
572 
573             /* Return the results */
574             return myResults;
575         }
576 
577         /**
578          * Check the leaf index.
579          *
580          * @param pIndex the index of the element
581          * @return is this the last element in the tree? true/false
582          */
583         boolean checkLeafIndex(final int pIndex) {
584             /* Cannot replace leaf if not built */
585             if (!treeBuilt) {
586                 throw new IllegalStateException("Tree has not been built");
587             }
588 
589             /* Check that the index is valid */
590             final SimpleVector myLevel = (SimpleVector) theHashes.firstElement();
591             if (pIndex < 0 || pIndex >= myLevel.size()) {
592                 throw new IllegalArgumentException("Invalid index");
593             }
594 
595             /* Return whether this is the last index */
596             return pIndex == myLevel.size() - 1;
597         }
598 
599         /**
600          * Replace the hash for a leaf node.
601          *
602          * @param pIndex the index of the element
603          * @param pHash  the new hashValue
604          */
605         void replaceElement(final int pIndex,
606                             final byte[] pHash) {
607             /* Check that the index is correct */
608             final SimpleVector myLevel = (SimpleVector) theHashes.firstElement();
609             if (pIndex < 0 || pIndex >= myLevel.size()) {
610                 throw new IllegalArgumentException("Invalid index");
611             }
612 
613             /* Replace the element */
614             myLevel.setElementAt(Arrays.clone(pHash), pIndex);
615 
616             /* Loop through the levels */
617             int myIndex = pIndex;
618             for (int i = 2; i <= theHashes.size(); i++) {
619                 /* Recalculate the parent node */
620                 myIndex = recalculateParent(i, myIndex);
621             }
622         }
623 
624         /**
625          * Recalculate Node.
626          *
627          * @param pLevel the tree level of the node
628          * @param pIndex of the node
629          * @return the parent index
630          */
631         private int recalculateParent(final int pLevel,
632                                       final int pIndex) {
633             /* Make sure that the level is reasonable */
634             if (pLevel < 2 || pLevel > theHashes.size()) {
635                 throw new IllegalArgumentException("Invalid level");
636             }
637 
638             /* Access the Vector for the parent and input levels */
639             final SimpleVector myInput = (SimpleVector) theHashes.elementAt(pLevel - 2);
640             final SimpleVector myLevel = (SimpleVector) theHashes.elementAt(pLevel - 1);
641 
642             /* Determine the number of nodes to combine */
643             final int myFanOut = 1 << theFanOut;
644 
645             /* Determine whether we are calculating the root node */
646             final boolean lastStage = myInput.size() <= myFanOut
647                     || pLevel == theMaxDepth;
648 
649             /* Calculate bounds */
650             final int myParentIndex = lastStage
651                     ? 0
652                     : pIndex / myFanOut;
653             final int myIndex = myParentIndex * myFanOut;
654             final int myNumHashes = myInput.size();
655             final int myMaxHash = lastStage ? myNumHashes : Math.min(myFanOut, myNumHashes - myIndex);
656 
657             /* Initialise the digest */
658             theDigest.initTreeNode(pLevel, myParentIndex, theShift);
659 
660             /* Loop through the input hashes */
661             for (int i = 0; i < myMaxHash; i++) {
662                 /* Fold into new hash */
663                 final byte[] myHash = (byte[]) myInput.elementAt(i + myIndex);
664                 theDigest.update(myHash, 0, myHash.length);
665             }
666 
667             /* Calculate new digest and replace it */
668             theDigest.calculateNode(theResult, 0);
669             myLevel.setElementAt(Arrays.clone(theResult), myParentIndex);
670             return myParentIndex;
671         }
672     }
673 
674     /**
675      * Simple Vector class.
676      * <p>This is a cut down version of the java Vector class to avoid use of synchronised.
677      */
678     private static class SimpleVector {
679         /**
680          * The initial capacity.
681          */
682         private static final int INITCAPACITY = 8;
683 
684         /**
685          * The array buffer holding elements.
686          */
687         private Object[] elementData;
688 
689         /**
690          * The number of valid components in this {@code SimpleVector} object.
691          */
692         private int elementCount;
693 
694         /**
695          * Constructor.
696          */
697         SimpleVector() {
698             elementData = new Object[INITCAPACITY];
699         }
700 
701         /**
702          * Returns the number of components in this vector.
703          *
704          * @return the vector size
705          */
706         int size() {
707             return elementCount;
708         }
709 
710         /**
711          * Tests if this vector has no components.
712          *
713          * @return true/false
714          */
715         boolean isEmpty() {
716             return elementCount == 0;
717         }
718 
719         /**
720          * Returns the first component of the vector.
721          *
722          * @return the first component of the vector
723          * @throws NoSuchElementException if this vector is empty
724          */
725         Object firstElement() {
726             if (elementCount == 0) {
727                 throw new NoSuchElementException();
728             }
729             return elementData[0];
730         }
731 
732         /**
733          * Returns the last component of the vector.
734          *
735          * @return the last component of the vector, i.e., the component at index
736          * <code>size()&nbsp;-&nbsp;1</code>.
737          * @throws NoSuchElementException if this vector is empty
738          */
739         Object lastElement() {
740             if (elementCount == 0) {
741                 throw new NoSuchElementException();
742             }
743             return elementData[elementCount - 1];
744         }
745 
746         /**
747          * Returns the component at the specified index.
748          *
749          * @param index an index into this vector
750          * @return the component at the specified index
751          * @throws ArrayIndexOutOfBoundsException if the index is out of range
752          *                                        ({@code index < 0 || index >= size()})
753          */
754         Object elementAt(final int index) {
755             if (index >= elementCount) {
756                 throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
757             }
758 
759             return elementData[index];
760         }
761 
762         /**
763          * Sets the component at the specified {@code index} of this
764          * vector to be the specified object. The previous component at that
765          * position is discarded.
766          *
767          * <p>The index must be a value greater than or equal to {@code 0}
768          * and less than the current size of the vector.
769          *
770          * @param obj   what the component is to be set to
771          * @param index the specified index
772          * @throws ArrayIndexOutOfBoundsException if the index is out of range
773          *                                        ({@code index < 0 || index >= size()})
774          */
775         void setElementAt(final Object obj,
776                           final int index) {
777             if (index >= elementCount) {
778                 throw new ArrayIndexOutOfBoundsException(index + " >= " + elementCount);
779             }
780             elementData[index] = obj;
781         }
782 
783         /**
784          * Adds the specified component to the end of this vector,
785          * increasing its size by one. The capacity of this vector is
786          * increased if its size becomes greater than its capacity.
787          *
788          * @param obj the component to be added
789          */
790         void addElement(final Object obj) {
791             if (elementCount == elementData.length) {
792                 final Object[] newData = new Object[elementData.length << 1];
793                 System.arraycopy(elementData, 0, newData, 0, elementCount);
794                 elementData = newData;
795             }
796             elementData[elementCount++] = obj;
797         }
798 
799         /**
800          * Removes all of the elements from this Vector.  The Vector will
801          * be empty after this call returns (unless it throws an exception).
802          */
803         void clear() {
804             for (int i = 0; i < elementCount; i++) {
805                 elementData[i] = null;
806             }
807             elementCount = 0;
808         }
809 
810         /**
811          * Returns an enumeration of the components of this vector.
812          *
813          * @return the enumeration
814          */
815         Enumeration<Object> elements() {
816             return new Enumeration<>() {
817                 private int count;
818 
819                 public boolean hasMoreElements() {
820                     return count < elementCount;
821                 }
822 
823                 public Object nextElement() {
824                     if (count < elementCount) {
825                         return elementData[count++];
826                     }
827                     throw new NoSuchElementException("Vector Enumeration");
828                 }
829             };
830         }
831     }
832 
833     /**
834      * Extended configuration to include Tree details.
835      */
836     private static class TreeConfiguration
837             extends Configuration {
838         TreeConfiguration(final long outputSizeBits,
839                           final int treeLeafLen,
840                           final int treeFanOut,
841                           final int treeMaxDepth) {
842             /* Initialise main part of config */
843             super(outputSizeBits);
844 
845             // 16..18 treeConfig
846             final byte[] bytes = getBytes();
847             bytes[CONFIGBASE] = (byte) treeLeafLen;
848             bytes[CONFIGBASE + 1] = (byte) treeFanOut;
849             bytes[CONFIGBASE + 2] = (byte) treeMaxDepth;
850         }
851     }
852 }