GordianCipherInputStream.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.stream;

import io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianKeySpec;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianKeyedCipher;
import io.github.tonywasher.joceanus.gordianknot.impl.core.cipher.GordianCoreCipher;
import org.bouncycastle.util.Arrays;

import java.io.InputStream;

/**
 * Input stream Cipher implementation.
 *
 * @param <T> the key type
 */
class GordianCipherInputStream<T extends GordianKeySpec>
        extends GordianInputStream {
    /**
     * Constructor.
     *
     * @param pCipher the decryption cipher
     * @param pInput  the underlying input stream
     */
    GordianCipherInputStream(final GordianKeyedCipher<T> pCipher,
                             final InputStream pInput) {
        super(pInput);
        setProcessedBuffer(new GordianCipherBuffer<T>(pCipher));
    }

    /**
     * Buffer to hold the processed data prior to returning it to the caller.
     *
     * @param <T> the key type
     */
    private static final class GordianCipherBuffer<T extends GordianKeySpec>
            extends GordianProcessedBuffer {
        /**
         * The cipher.
         */
        private final GordianCoreCipher<T> theCipher;

        /**
         * The buffer.
         */
        private byte[] theBuffer = new byte[BUFSIZE];

        /**
         * Constructor.
         *
         * @param pCipher the decryption cipher
         */
        GordianCipherBuffer(final GordianKeyedCipher<T> pCipher) {
            theCipher = (GordianCoreCipher<T>) pCipher;
        }

        /**
         * Check buffer length.
         *
         * @param pLength the length of the data to use
         * @return the number of bytes to process
         */
        private int checkBufferLength(final int pLength) {
            /* Check how long a buffer we need to handle the data */
            final int iNumBytes = theCipher.getOutputLength(pLength);

            /* Extend the buffer if required */
            if (iNumBytes > theBuffer.length) {
                Arrays.fill(theBuffer, (byte) 0);
                theBuffer = new byte[iNumBytes];
            }

            /* return number of bytes to process */
            return iNumBytes;
        }

        @Override
        public int processBytes(final byte[] pBuffer,
                                final int pLength) throws GordianException {
            /* Initialise variables */
            int iNumBytes;
            final int iLength = pLength;

            /* If we have EOF from the input stream */
            if (iLength == -1) {
                /* If we have already seen EOF */
                if (hasEOFbeenSeen()) {
                    /* signal true EOF */
                    return -1;
                }

                /* Record EOF */
                setEOFSeen();

                /* Make sure the buffer is large enough */
                iNumBytes = checkBufferLength(0);

                /* Finish the decryption if there is work to do */
                if (iNumBytes > 0) {
                    iNumBytes = theCipher.finish(theBuffer, 0);
                }

                /* else we have bytes to process */
            } else {
                /* Make sure the buffer is large enough */
                checkBufferLength(iLength);

                /* Decrypt the data */
                iNumBytes = theCipher.update(pBuffer, 0, pLength, theBuffer, 0);
            }

            /* Set up buffer variables */
            setBuffer(theBuffer, iNumBytes);
            return iNumBytes;
        }
    }
}