protected synchronized int engineDoFinal()

in src/com/amazon/corretto/crypto/provider/AesGcmSpi.java [518:719]


    protected synchronized int engineDoFinal(byte[] bytes, int offset, int length, byte[] output, int outputOffset)
        throws ShortBufferException, IllegalBlockSizeException, BadPaddingException
    {
        try {
            if (bytes == null) bytes = EMPTY_ARRAY;

            boolean overlaps = Utils.arraysOverlap(
                    bytes, offset, output, outputOffset, Math.max(length , engineGetOutputSize(length))
            );

            if (opMode != NATIVE_MODE_ENCRYPT && opMode != NATIVE_MODE_DECRYPT) {
                throw new IllegalStateException("Cipher not initialized");
            }

            if (outputOffset < 0) {
                throw new ArrayIndexOutOfBoundsException("Negative output offset");
            }

            checkOutputBuffer(length, output, outputOffset);
            checkArrayLimits(bytes, offset, length);

            int resultLength = 0;

            if (overlaps) {
                // The input and output potentially overlap. We'll need to make sure we copy the input somewhere safe before
                // proceeding too much further.

                // Since we need to take care of this on engineUpdate as well, we can just delegate to engineUpdate, which
                // will make sure to copy the buffer - on encrypt this is an explicit check, while on decrypt engineUpdate
                // unconditionally copies to a temporary buffer.

                resultLength = engineUpdate(bytes, offset, length, output, outputOffset);
                outputOffset += resultLength;

                // We processed all of the input in engineUpdate. So there's no longer an overlap to deal with.
                length = 0;
            }

            if (opMode == NATIVE_MODE_DECRYPT) {
                // We use this temporary buffer both for our input ciphertext and our output (possibly unauthenticated) plaintext.
                // We'll copy it to the actual output location iff decryption completes successfully.
                engineUpdate(bytes, offset, length); // Decrypt mode never generates output for updates
                final byte[] tempBuffer = decryptInputBuf.getDataBuffer();
                final int tempBufferLength = decryptInputBuf.size();

                if (tempBufferLength < tagLength) {
                    throw new AEADBadTagException("Input too short - need tag");
                }

                keyUsageCount++;
                final int outLen;
                if (context != null) {
                    // We already have a context, so let's reuse it.
                    outLen = context.use(ptr -> {
                        return oneShotDecrypt(
                                ptr,
                                null,
                                tempBuffer,
                                0,
                                tempBufferLength,

                                tempBuffer,
                                0,

                                tagLength,
                                key,
                                iv,

                                // The cost of calling decryptAADBuf.getDataBuffer() when its buffer is empty is significant for 16-byte
                                // decrypt operations (approximately a 7% performance hit). To avoid this, we reuse the same empty array
                                // instead in this common-case path.
                                decryptAADBuf.size() != 0 ? decryptAADBuf.getDataBuffer() : EMPTY_ARRAY,
                                decryptAADBuf.size()
                        );
                    });
                } else {
                    // We don't have an existing context, however we might want to save one
                    final long[] ptrOut = keyUsageCount > KEY_REUSE_THRESHOLD ? new long[1] : null;
                    outLen = oneShotDecrypt(
                            0,
                            ptrOut,
                            tempBuffer,
                            0,
                            tempBufferLength,

                            tempBuffer,
                            0,

                            tagLength,
                            key,
                            iv,

                            // The cost of calling decryptAADBuf.getDataBuffer() when its buffer is empty is significant for 16-byte
                            // decrypt operations (approximately a 7% performance hit). To avoid this, we reuse the same empty array
                            // instead in this common-case path.
                            decryptAADBuf.size() != 0 ? decryptAADBuf.getDataBuffer() : EMPTY_ARRAY,
                            decryptAADBuf.size()
                    );

                    if (ptrOut != null) {
                        context = new NativeContext(ptrOut[0]);
                    }
                }
                // Decryption completed successfully. Copy it to the output location
                System.arraycopy(tempBuffer, 0, output, outputOffset, outLen);
                return outLen;
            } // End of Decrypt mode

            // Start of Encrypt mode
            checkNeedReset();

            this.needReset = true;
            final byte[] finalBytes = bytes;
            final int finalOffset = offset;
            final int finalOutputOffset = outputOffset;
            final int finalLength = length;
            if (!contextInitialized) {
                // Context has not been initialized, meaning the user called doFinal immediately after init(). In this case
                // we make a single native call to perform the encryption operation in one go.

                keyUsageCount++;
                if (context != null) {
                    // Our key, but not our IV has been initialized
                    return context.use(ptr -> {
                        return oneShotEncrypt(
                                ptr,
                                null,
                                finalBytes,
                                finalOffset,
                                finalLength,
                                output,
                                finalOutputOffset,
                                tagLength,
                                key,
                                iv
                        );
                    });
                } else {
                    // We don't have an existing context, however we might want to save one
                    final long[] ptrOut = keyUsageCount > KEY_REUSE_THRESHOLD ? new long[1] : null;
                    final int outLen = oneShotEncrypt(
                            0,
                            ptrOut,
                            finalBytes,
                            finalOffset,
                            finalLength,
                            output,
                            finalOutputOffset,
                            tagLength,
                            key,
                            iv
                    );
                    if (ptrOut != null) {
                        context = new NativeContext(ptrOut[0]);
                    }
                    return outLen;
                }
            } else {
                // We need to make sure to add resultLength here; engineUpdate in encrypt mode produces incremental
                // output (unlike in decrypt mode) and so we need to carry forward whatever amount of data it produced
                // in our return value.

                keyUsageCount++;

                final int finalOutputLen;

                final byte[] inBytes = bytes;
                final int inOffset = offset;
                final int inLength = length;
                final int outOffset = outputOffset;
                if (keyUsageCount > KEY_REUSE_THRESHOLD) {
                    finalOutputLen = context.use(ptr ->
                            encryptDoFinal(
                            ptr,
                            false, // releaseContext
                            inBytes,
                            inOffset,
                            inLength,
                            output,
                            outOffset,
                            tagLength
                    ));
                } else {
                    finalOutputLen =
                        encryptDoFinal(
                                context.take(),
                                true, // releaseContext
                                bytes,
                                offset,
                                length,
                                output,
                                outputOffset,
                                tagLength
                        );
                    context = null;
                }
                return resultLength + finalOutputLen;
            }
        } finally {
            stateReset();
        }
    }