GordianByteArrayInteger.java

/*
 * GordianKnot: Security Suite
 * Copyright 2012-2026. Tony Washer
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.  You may obtain a copy
 * of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package io.github.tonywasher.joceanus.gordianknot.impl.core.base;

import org.bouncycastle.util.Arrays;

/**
 * Simple class that handles a byte array as an integer housing an integer of max value 2
 * <sup>8n</sup> where n is the length of the byte array.
 */
public final class GordianByteArrayInteger {
    /**
     * The default counter length.
     */
    private static final int COUNTER_LEN = Integer.BYTES;

    /**
     * The counter length.
     */
    private final int theLength;

    /**
     * The buffer.
     */
    private final byte[] theBuffer;

    /**
     * Constructor.
     */
    public GordianByteArrayInteger() {
        /* use default length */
        this(COUNTER_LEN);
    }

    /**
     * Constructor.
     *
     * @param pLen the length of the counter
     */
    public GordianByteArrayInteger(final int pLen) {
        /* initialise the buffer */
        theBuffer = new byte[pLen];
        theLength = pLen;
        reset();
    }

    /**
     * Constructor.
     *
     * @param pSource the source byte array
     */
    public GordianByteArrayInteger(final byte[] pSource) {
        /* initialise the buffer */
        theLength = pSource.length;
        theBuffer = new byte[theLength];
        System.arraycopy(pSource, 0, theBuffer, 0, theLength);
    }

    /**
     * get buffer pointer.
     *
     * @return the buffer
     */
    public byte[] getBuffer() {
        return theBuffer;
    }

    /**
     * Reset counter to zero.
     */
    public void reset() {
        Arrays.fill(theBuffer, (byte) 0);
    }

    /**
     * Compare counter limit.
     *
     * @param pLimit the limit to check against
     * @return true/false the limit has been reached
     */
    public boolean compareLimit(final long pLimit) {
        /* Check that we are long length */
        if (theLength != Long.BYTES) {
            return false;
        }

        /* Calculate existing value */
        long myVal = 0;
        for (int i = 0; i < theLength; i++) {
            myVal <<= GordianDataConverter.NYBBLE_SHIFT;
            myVal += theBuffer[i]
                    & GordianDataConverter.BYTE_MASK;
        }

        /* Determine whether we have reached the limit */
        return myVal >= pLimit;
    }

    /**
     * Iterate.
     *
     * @return the buffer
     */
    public byte[] iterate() {
        /* Loop through the bytes */
        for (int i = theLength - 1; i >= 0; i--) {
            /* Increment the element */
            byte myByte = theBuffer[i];
            theBuffer[i] = ++myByte;
            if (myByte != 0) {
                break;
            }
        }

        /* Return the buffer */
        return theBuffer;
    }

    /**
     * Add a byte array as a counter.
     *
     * @param pAdjust the array to add.
     * @return the buffer
     */
    public byte[] addTo(final byte[] pAdjust) {
        /* Access length of adjusting array */
        final int myLength = Math.min(pAdjust.length, theLength);

        /* Loop through the adjustment bytes */
        boolean doCarry = false;
        int myOffset = 1;
        while (myOffset <= myLength) {
            /* Calculate sum at offset */
            int myNext = (theBuffer[theLength
                    - myOffset] & GordianDataConverter.BYTE_MASK)
                    + (pAdjust[myLength
                    - myOffset] & GordianDataConverter.BYTE_MASK);
            if (doCarry) {
                myNext++;
            }

            /* Store result and adjust offset */
            theBuffer[theLength
                    - myOffset] = (byte) myNext;
            myOffset++;

            /* Determine the carry */
            doCarry = (myNext & ~GordianDataConverter.BYTE_MASK) != 0;
        }

        /* Adjust remaining bytes for carry */
        if (doCarry) {
            while (myOffset <= theLength) {
                /* Increment the element */
                byte myByte = theBuffer[theLength
                        - myOffset];
                theBuffer[theLength
                        - myOffset] = ++myByte;
                if (myByte != 0) {
                    break;
                }

                /* Adjust offset */
                myOffset++;
            }
        }

        /* Return the buffer */
        return theBuffer;
    }
}