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() - 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 }