GordianCoreKnuthObfuscater.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 io.github.tonywasher.joceanus.gordianknot.api.base.GordianException;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianIdSpec;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianLength;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianCipherMode;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianPadding;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamCipherSpec;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamCipherSpecBuilder;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamKeySpec;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamKeySpec.GordianBlakeXofKey;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamKeySpec.GordianChaCha20Key;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamKeySpec.GordianElephantKey;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamKeySpec.GordianISAPKey;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamKeySpec.GordianRomulusKey;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamKeySpec.GordianSalsa20Key;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamKeySpec.GordianSkeinXofKey;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamKeySpec.GordianSparkleKey;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamKeySpec.GordianStreamSubKeyType;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamKeySpec.GordianVMPCKey;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianStreamKeyType;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymCipherSpec;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymKeySpec;
import io.github.tonywasher.joceanus.gordianknot.api.cipher.GordianSymKeyType;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestSpec;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestSubSpec;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestSubSpec.GordianDigestState;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestType;
import io.github.tonywasher.joceanus.gordianknot.api.factory.GordianKnuthObfuscater;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacSpec;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacSpecBuilder;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianMacType;
import io.github.tonywasher.joceanus.gordianknot.api.mac.GordianSipHashSpec;
import io.github.tonywasher.joceanus.gordianknot.impl.core.base.GordianPersonalisation.GordianPersonalId;
import io.github.tonywasher.joceanus.gordianknot.impl.core.exc.GordianDataException;

import java.math.BigInteger;
import java.util.Objects;

/**
 * Knuth Obfuscater.
 */
public class GordianCoreKnuthObfuscater
        implements GordianKnuthObfuscater {
    /**
     * Make sure that the top positive bit is set for the Knuth Prime.
     */
    private static final int VALUE_MASK = 0x40000000;

    /**
     * Knuth Prime.
     */
    private final int thePrime;

    /**
     * Knuth Inverse.
     */
    private final int theInverse;

    /**
     * Knuth Mask.
     */
    private final int theMask;

    /**
     * Constructor.
     *
     * @param pFactory the factory
     * @throws GordianException on error
     */
    public GordianCoreKnuthObfuscater(final GordianBaseFactory pFactory) throws GordianException {
        /* Generate Knuth Prime/Inverse */
        final GordianPersonalisation myPersonal = pFactory.getPersonalisation();
        final BigInteger[] myKnuth = generatePrime(myPersonal.getPersonalisedInteger(GordianPersonalId.KNUTHPRIME));
        thePrime = myKnuth[0].intValue();
        theInverse = myKnuth[1].intValue();
        theMask = myPersonal.getPersonalisedInteger(GordianPersonalId.KNUTHMASK);
    }

    /**
     * Obtain a large integer prime based on the supplied value.
     *
     * @param pBase the base value
     * @return the encoded value
     */
    private static BigInteger[] generatePrime(final int pBase) {
        /* Ensure that the value is positive */
        int myVal = pBase < 0
                ? -pBase
                : pBase;

        /* Ensure that the top positive bit is set */
        myVal |= VALUE_MASK;

        /* Make sure that the value is prime */
        BigInteger myValue = BigInteger.valueOf(myVal);
        if (!myValue.isProbablePrime(Integer.SIZE)) {
            myValue = myValue.nextProbablePrime();
        }

        /* Calculate the inverse */
        final BigInteger myMax = BigInteger.valueOf(1).shiftLeft(Integer.SIZE);
        final BigInteger myInverse = myValue.modInverse(myMax);

        /* Return the pair of values */
        return new BigInteger[]
                {myValue, myInverse};
    }

    /**
     * Encode an integer value via Knuth Multiplication.
     *
     * @param pInput the input
     * @return the encoded value
     */
    public int knuthEncodeInteger(final int pInput) {
        return (int) ((pInput ^ theMask) * (long) thePrime);
    }

    /**
     * Encode an integer value via Knuth Multiplication.
     *
     * @param pInput      the input
     * @param pAdjustment the adjustment
     * @return the encoded value
     */
    public int knuthEncodeInteger(final int pInput,
                                  final int pAdjustment) {
        final int myId = pInput + pAdjustment;
        return knuthEncodeInteger(myId);
    }

    /**
     * Decode a Knuth Encoded integer value.
     *
     * @param pEncoded the encoded value
     * @return the original input
     */
    public int knuthDecodeInteger(final int pEncoded) {
        return theMask ^ (int) (pEncoded * (long) theInverse);
    }

    /**
     * Decode a Knuth Encoded integer value.
     *
     * @param pEncoded    the encoded value
     * @param pAdjustment the adjustment
     * @return the original input
     */
    public int knuthDecodeInteger(final int pEncoded,
                                  final int pAdjustment) {
        final int myId = knuthDecodeInteger(pEncoded);
        return myId - pAdjustment;
    }

    /**
     * Encode a long value via Knuth Multiplication.
     *
     * @param pInput the input
     * @return the encoded value
     */
    public long knuthEncodeLong(final long pInput) {
        final long myHigh = knuthEncodeInteger((int) (pInput >>> Integer.SIZE));
        final int myLow = knuthEncodeInteger((int) pInput);
        return (myHigh << Integer.SIZE) | Integer.toUnsignedLong(myLow);
    }

    /**
     * Encode a long value via Knuth Multiplication.
     *
     * @param pInput      the input
     * @param pAdjustment the adjustment
     * @return the encoded value
     */
    public long knuthEncodeLong(final long pInput,
                                final int pAdjustment) {
        final long myHigh = knuthEncodeInteger((int) (pInput >>> Integer.SIZE), pAdjustment);
        final int myLow = knuthEncodeInteger((int) pInput, pAdjustment);
        return (myHigh << Integer.SIZE) | Integer.toUnsignedLong(myLow);
    }

    /**
     * Decode a Knuth Encoded long value.
     *
     * @param pEncoded the encoded value
     * @return the original input
     */
    public long knuthDecodeLong(final long pEncoded) {
        final long myHigh = knuthDecodeInteger((int) (pEncoded >>> Integer.SIZE));
        final int myLow = knuthDecodeInteger((int) pEncoded);
        return (myHigh << Integer.SIZE) | Integer.toUnsignedLong(myLow);
    }

    /**
     * Decode a Knuth Encoded long value.
     *
     * @param pEncoded    the encoded value
     * @param pAdjustment the adjustment
     * @return the original input
     */
    public long knuthDecodeLong(final long pEncoded,
                                final int pAdjustment) {
        final long myHigh = knuthDecodeInteger((int) (pEncoded >>> Integer.SIZE), pAdjustment);
        final int myLow = knuthDecodeInteger((int) pEncoded, pAdjustment);
        return (myHigh << Integer.SIZE) | Integer.toUnsignedLong(myLow);
    }

    @Override
    public int deriveExternalIdFromType(final GordianIdSpec pType,
                                        final int pAdjustment) throws GordianException {
        return knuthEncodeInteger(deriveEncodedIdFromType(pType), pAdjustment);
    }

    @Override
    public int deriveExternalIdFromType(final GordianIdSpec pType) throws GordianException {
        return knuthEncodeInteger(deriveEncodedIdFromType(pType));
    }

    /**
     * Obtain external Id from Type.
     *
     * @param <T>   the type class
     * @param pType the type
     * @return the externalId
     * @throws GordianException on error
     */
    private static <T extends GordianIdSpec> int deriveEncodedIdFromType(final T pType) throws GordianException {
        if (pType instanceof GordianDigestSpec mySpec) {
            final int myId = deriveEncodedIdFromDigestSpec(mySpec);
            return GordianIdMarker.DIGEST.applyMarker(myId);
        }
        if (pType instanceof GordianSymCipherSpec mySpec) {
            final int myId = deriveEncodedIdFromSymCipherSpec(mySpec);
            return GordianIdMarker.SYMCIPHER.applyMarker(myId);
        }
        if (pType instanceof GordianStreamCipherSpec mySpec) {
            final int myId = deriveEncodedIdFromStreamCipherSpec(mySpec);
            return GordianIdMarker.STREAMCIPHER.applyMarker(myId);
        }
        if (pType instanceof GordianMacSpec mySpec) {
            final int myId = deriveEncodedIdFromMacSpec(mySpec);
            return GordianIdMarker.MACKEY.applyMarker(myId);
        }
        if (pType instanceof GordianSymKeySpec mySpec) {
            final int myId = deriveEncodedIdFromSymKeySpec(mySpec);
            return GordianIdMarker.SYMKEY.applyMarker(myId);
        }
        if (pType instanceof GordianStreamKeySpec mySpec) {
            final int myId = deriveEncodedIdFromStreamKeySpec(mySpec);
            return GordianIdMarker.STREAMKEY.applyMarker(myId);
        }
        throw new GordianDataException("Invalid type: " + pType.getClass().getCanonicalName());
    }

    @Override
    public GordianIdSpec deriveTypeFromExternalId(final int pId,
                                                  final int pAdjustment) throws GordianException {
        return deriveTypeFromEncodedId(knuthDecodeInteger(pId, pAdjustment));
    }

    @Override
    public GordianIdSpec deriveTypeFromExternalId(final int pId) throws GordianException {
        return deriveTypeFromEncodedId(knuthDecodeInteger(pId));
    }

    /**
     * Obtain Type from external Id.
     *
     * @param pId the external id
     * @return the Type
     * @throws GordianException on error
     */
    private static GordianIdSpec deriveTypeFromEncodedId(final int pId) throws GordianException {
        final GordianIdMarker myMarker = GordianIdMarker.determine(pId);
        final int myId = GordianIdMarker.removeMarker(pId);
        switch (myMarker) {
            case DIGEST:
                return deriveDigestSpecFromEncodedId(myId);
            case SYMCIPHER:
                return deriveSymCipherSpecFromEncodedId(myId);
            case STREAMCIPHER:
                return deriveStreamCipherSpecFromEncodedId(myId);
            case MACKEY:
                return deriveMacSpecFromEncodedId(myId);
            case SYMKEY:
                return deriveSymKeySpecFromEncodedId(myId);
            case STREAMKEY:
                return deriveStreamKeySpecFromEncodedId(myId);
            default:
                throw new GordianDataException("Unsupported encoding");
        }
    }

    /**
     * Obtain encoded DigestSpecId.
     *
     * @param pDigestSpec the digestSpec
     * @return the encoded id
     */
    private static int deriveEncodedIdFromDigestSpec(final GordianDigestSpec pDigestSpec) {
        /* Build the encoded id */
        int myCode = deriveEncodedIdFromDigestType(pDigestSpec.getDigestType());
        final GordianDigestState myState = pDigestSpec.getDigestState();
        myCode <<= determineShiftForDigestSubSpec();
        if (myState != null) {
            myCode += deriveEncodedIdFromDigestState(myState);
        }
        myCode <<= determineShiftForEnum(GordianLength.class);
        myCode += deriveEncodedIdFromLength(pDigestSpec.getDigestLength());
        myCode <<= 1;
        myCode += Boolean.TRUE.equals(pDigestSpec.isXofMode()) ? 1 : 0;

        /* return the code */
        return myCode;
    }

    /**
     * Obtain digestSpec from encodedId.
     *
     * @param pEncodedId the encoded id
     * @return the digestSpec
     * @throws GordianException on error
     */
    private static GordianDigestSpec deriveDigestSpecFromEncodedId(final int pEncodedId) throws GordianException {
        /* Isolate id Components */
        final boolean isXof = (pEncodedId & 1) == 1;
        int myCode = pEncodedId >> 1;
        final int myLenCode = myCode & determineMaskForEnum(GordianLength.class);
        myCode = myCode >> determineShiftForEnum(GordianLength.class);
        final int mySubSpecCode = myCode & determineMaskForDigestSubSpec();
        final int myId = myCode >> determineShiftForDigestSubSpec();

        /* Translate components */
        final GordianDigestType myType = deriveDigestTypeFromEncodedId(myId);
        final GordianLength myLength = deriveLengthFromEncodedId(myLenCode);
        GordianDigestSubSpec mySubSpec = null;
        if (mySubSpecCode != 0) {
            mySubSpec = deriveDigestStateFromEncodedId(mySubSpecCode);
        }

        /* Create DigestSpec */
        return new GordianDigestSpec(myType, mySubSpec, myLength, isXof);
    }

    /**
     * Obtain encoded SymKeySpecId.
     *
     * @param pSymKeySpec the symKeySpec
     * @return the encoded id
     */
    private static int deriveEncodedIdFromSymKeySpec(final GordianSymKeySpec pSymKeySpec) {
        /* Build the encoded id */
        int myCode = deriveEncodedIdFromSymKeyType(pSymKeySpec.getSymKeyType());
        myCode <<= determineShiftForEnum(GordianLength.class);
        myCode += deriveEncodedIdFromLength(pSymKeySpec.getBlockLength());
        myCode <<= determineShiftForEnum(GordianLength.class);
        myCode += deriveEncodedIdFromLength(pSymKeySpec.getKeyLength());

        /* return the code */
        return myCode;
    }

    /**
     * Obtain symKeySpec from encodedId.
     *
     * @param pEncodedId the encoded id
     * @return the symKeySpec
     * @throws GordianException on error
     */
    private static GordianSymKeySpec deriveSymKeySpecFromEncodedId(final int pEncodedId) throws GordianException {
        /* Isolate id Components */
        final int myKeyLenCode = pEncodedId & determineMaskForEnum(GordianLength.class);
        final int myCode = pEncodedId >> determineShiftForEnum(GordianLength.class);
        final int myBlkLenCode = myCode & determineMaskForEnum(GordianLength.class);
        final int myId = myCode >> determineShiftForEnum(GordianLength.class);

        /* Translate components */
        final GordianSymKeyType myType = deriveSymKeyTypeFromEncodedId(myId);
        final GordianLength myBlkLength = deriveLengthFromEncodedId(myBlkLenCode);
        final GordianLength myKeyLength = deriveLengthFromEncodedId(myKeyLenCode);

        /* Create SymKeySpec */
        return new GordianSymKeySpec(myType, myBlkLength, myKeyLength);
    }

    /**
     * Obtain encoded symCipherSpecId.
     *
     * @param pCipherSpec the symCipherSpec
     * @return the external id
     */
    private static int deriveEncodedIdFromSymCipherSpec(final GordianSymCipherSpec pCipherSpec) {
        /* Derive the encoded id */
        int myCode = deriveEncodedIdFromSymKeySpec(pCipherSpec.getKeyType());
        myCode <<= determineShiftForEnum(GordianCipherMode.class);
        myCode += deriveEncodedIdFromCipherMode(pCipherSpec.getCipherMode());
        myCode <<= determineShiftForEnum(GordianPadding.class);
        myCode += deriveEncodedIdFromPadding(pCipherSpec.getPadding());

        /* Return the code */
        return myCode;
    }

    /**
     * Obtain cipherSpec from encoded symCipherSpecId.
     *
     * @param pEncodedId the encoded id
     * @return the symCipherSpec
     * @throws GordianException on error
     */
    private static GordianSymCipherSpec deriveSymCipherSpecFromEncodedId(final int pEncodedId) throws GordianException {
        /* Isolate id Components */
        final int myPaddingCode = pEncodedId & determineMaskForEnum(GordianPadding.class);
        final int myCode = pEncodedId >> determineShiftForEnum(GordianPadding.class);
        final int myModeCode = myCode & determineMaskForEnum(GordianCipherMode.class);
        final int myId = myCode >> determineShiftForEnum(GordianCipherMode.class);

        /* Determine KeyType */
        final GordianSymKeySpec mySpec = deriveSymKeySpecFromEncodedId(myId);
        final GordianCipherMode myMode = deriveCipherModeFromEncodedId(myModeCode);
        final GordianPadding myPadding = derivePaddingFromEncodedId(myPaddingCode);

        /* Create the cipherSpec */
        return new GordianSymCipherSpec(mySpec, myMode, myPadding);
    }

    /**
     * Obtain encoded StreamKeySpecId.
     *
     * @param pStreamKeySpec the streamKeySpec
     * @return the encoded id
     */
    private static int deriveEncodedIdFromStreamKeySpec(final GordianStreamKeySpec pStreamKeySpec) {
        /* Build the encoded id */
        int myCode = deriveEncodedIdFromStreamKeyType(pStreamKeySpec.getStreamKeyType());
        myCode <<= determineShiftForEnum(GordianLength.class);
        myCode += deriveEncodedIdFromLength(pStreamKeySpec.getKeyLength());
        myCode <<= determineShiftForStreamKeySubType();
        myCode += deriveEncodedIdFromStreamKeySubType(pStreamKeySpec);

        /* return the code */
        return myCode;
    }

    /**
     * Obtain streamKeySpec from encodedId.
     *
     * @param pEncodedId the encoded id
     * @return the streamKeySpec
     * @throws GordianException on error
     */
    private static GordianStreamKeySpec deriveStreamKeySpecFromEncodedId(final int pEncodedId) throws GordianException {
        /* Isolate id Components */
        final int mySubKeyCode = pEncodedId & determineMaskForStreamKeySubType();
        final int myCode = pEncodedId >> determineShiftForStreamKeySubType();
        final int myKeyLenCode = myCode & determineMaskForEnum(GordianLength.class);
        final int myId = myCode >> determineShiftForEnum(GordianLength.class);

        /* Translate components */
        final GordianStreamKeyType myType = deriveStreamKeyTypeFromEncodedId(myId);
        final GordianLength myKeyLength = deriveLengthFromEncodedId(myKeyLenCode);
        final GordianStreamSubKeyType mySubKeyType = deriveStreamSubKeyTypeFromEncodedId(myType, mySubKeyCode);

        /* Create StreamKeySpec */
        return new GordianStreamKeySpec(myType, myKeyLength, mySubKeyType);
    }

    /**
     * Obtain encoded symCipherSpecId.
     *
     * @param pCipherSpec the symCipherSpec
     * @return the external id
     */
    private static int deriveEncodedIdFromStreamCipherSpec(final GordianStreamCipherSpec pCipherSpec) {
        /* Build the encoded id */
        int myCode = deriveEncodedIdFromStreamKeySpec(pCipherSpec.getKeyType());
        myCode <<= 1;
        myCode += (pCipherSpec.isAEADMode() ? 1 : 0);

        /* Return the encoded id */
        return myCode;
    }

    /**
     * Obtain cipherSpec from encoded symCipherSpecId.
     *
     * @param pEncodedId the encoded id
     * @return the symCipherSpec
     * @throws GordianException on error
     */
    private static GordianStreamCipherSpec deriveStreamCipherSpecFromEncodedId(final int pEncodedId) throws GordianException {
        /* Determine KeySpec */
        final int myAAD = pEncodedId & 1;
        final int myCode = pEncodedId >> 1;
        final GordianStreamKeySpec mySpec = deriveStreamKeySpecFromEncodedId(myCode);

        /* Create the cipherSpec */
        return GordianStreamCipherSpecBuilder.stream(mySpec, myAAD != 0);
    }

    /**
     * Obtain encoded StreamKeySpecId.
     *
     * @param pStreamKeySpec the streamKeySpec
     * @return the encoded id
     */
    private static int deriveEncodedIdFromStreamKeySubType(final GordianStreamKeySpec pStreamKeySpec) {
        /* Switch on keyType */
        switch (pStreamKeySpec.getStreamKeyType()) {
            case CHACHA20:
                return deriveEncodedIdFromEnum((GordianChaCha20Key) pStreamKeySpec.getSubKeyType());
            case SALSA20:
                return deriveEncodedIdFromEnum((GordianSalsa20Key) pStreamKeySpec.getSubKeyType());
            case VMPC:
                return deriveEncodedIdFromEnum((GordianVMPCKey) pStreamKeySpec.getSubKeyType());
            case SKEINXOF:
                return deriveEncodedIdFromEnum((GordianSkeinXofKey) pStreamKeySpec.getSubKeyType());
            case BLAKE2XOF:
                return deriveEncodedIdFromEnum((GordianBlakeXofKey) pStreamKeySpec.getSubKeyType());
            case ELEPHANT:
                return deriveEncodedIdFromEnum((GordianElephantKey) pStreamKeySpec.getSubKeyType());
            case ISAP:
                return deriveEncodedIdFromEnum((GordianISAPKey) pStreamKeySpec.getSubKeyType());
            case ROMULUS:
                return deriveEncodedIdFromEnum((GordianRomulusKey) pStreamKeySpec.getSubKeyType());
            case SPARKLE:
                return deriveEncodedIdFromEnum((GordianSparkleKey) pStreamKeySpec.getSubKeyType());
            default:
                return 0;
        }
    }

    /**
     * Obtain subKeyType from encoded streamSubKeyType.
     *
     * @param pKeyType   the keyType
     * @param pEncodedId the encodedId
     * @return the subKeyType
     * @throws GordianException on error
     */
    private static GordianStreamSubKeyType deriveStreamSubKeyTypeFromEncodedId(final GordianStreamKeyType pKeyType,
                                                                               final int pEncodedId) throws GordianException {
        /* Switch on keyType */
        switch (pKeyType) {
            case CHACHA20:
                return deriveEnumFromEncodedId(pEncodedId, GordianChaCha20Key.class);
            case SALSA20:
                return deriveEnumFromEncodedId(pEncodedId, GordianSalsa20Key.class);
            case VMPC:
                return deriveEnumFromEncodedId(pEncodedId, GordianVMPCKey.class);
            case SKEINXOF:
                return deriveEnumFromEncodedId(pEncodedId, GordianSkeinXofKey.class);
            case BLAKE2XOF:
                return deriveEnumFromEncodedId(pEncodedId, GordianBlakeXofKey.class);
            case ELEPHANT:
                return deriveEnumFromEncodedId(pEncodedId, GordianElephantKey.class);
            case ISAP:
                return deriveEnumFromEncodedId(pEncodedId, GordianISAPKey.class);
            case ROMULUS:
                return deriveEnumFromEncodedId(pEncodedId, GordianRomulusKey.class);
            case SPARKLE:
                return deriveEnumFromEncodedId(pEncodedId, GordianSparkleKey.class);
            default:
                return null;
        }
    }

    /**
     * Obtain mask for DigestSubSpec.
     *
     * @return the mask
     */
    private static int determineMaskForDigestSubSpec() {
        return ~(-1 << determineShiftForDigestSubSpec());
    }

    /**
     * Obtain shift for StreamKeySubType.
     *
     * @return the bit shift
     */
    private static int determineShiftForDigestSubSpec() {
        return determineShiftForEnum(GordianDigestState.class);
    }

    /**
     * Obtain mask for StreamKeySubType.
     *
     * @return the mask
     */
    private static int determineMaskForStreamKeySubType() {
        return ~(-1 << determineShiftForStreamKeySubType());
    }

    /**
     * Obtain shift for StreamKeySubType.
     *
     * @return the bit shift
     */
    private static int determineShiftForStreamKeySubType() {
        int myShift = determineShiftForEnum(GordianVMPCKey.class);
        myShift = Math.max(myShift, determineShiftForEnum(GordianSalsa20Key.class));
        myShift = Math.max(myShift, determineShiftForEnum(GordianChaCha20Key.class));
        myShift = Math.max(myShift, determineShiftForEnum(GordianSkeinXofKey.class));
        myShift = Math.max(myShift, determineShiftForEnum(GordianBlakeXofKey.class));
        myShift = Math.max(myShift, determineShiftForEnum(GordianElephantKey.class));
        myShift = Math.max(myShift, determineShiftForEnum(GordianISAPKey.class));
        myShift = Math.max(myShift, determineShiftForEnum(GordianRomulusKey.class));
        return Math.max(myShift, determineShiftForEnum(GordianSparkleKey.class));
    }

    /**
     * Obtain encoded macSpecId.
     *
     * @param pMacSpec the macSpec
     * @return the external id
     */
    private static int deriveEncodedIdFromMacSpec(final GordianMacSpec pMacSpec) {
        /* Build the encoded macId */
        final GordianMacType myMacType = pMacSpec.getMacType();
        int myCode = deriveEncodedIdFromMacType(myMacType);
        int myShift = determineShiftForEnum(GordianMacType.class);
        myCode += deriveEncodedIdFromLength(pMacSpec.getKeyLength()) << myShift;
        myShift += determineShiftForEnum(GordianLength.class);

        /* Switch on MacType */
        switch (myMacType) {
            case HMAC:
            case SKEIN:
            case BLAKE2:
            case BLAKE3:
            case KUPYNA:
            case KMAC:
                myCode += deriveEncodedIdFromDigestSpec(Objects.requireNonNull(pMacSpec.getDigestSpec())) << myShift;
                break;
            case GMAC:
            case CMAC:
            case KALYNA:
            case CBCMAC:
            case CFBMAC:
                myCode += deriveEncodedIdFromSymKeySpec(Objects.requireNonNull(pMacSpec.getSymKeySpec())) << myShift;
                break;
            case POLY1305:
                if (pMacSpec.getSymKeySpec() != null) {
                    myCode += deriveEncodedIdFromSymKeySpec(pMacSpec.getSymKeySpec()) << myShift;
                }
                break;
            case ZUC:
                myCode += deriveEncodedIdFromLength(pMacSpec.getMacLength()) << myShift;
                break;
            case SIPHASH:
                myCode += deriveEncodedIdFromSipHashSpec(pMacSpec.getSipHashSpec()) << myShift;
                break;
            default:
                break;
        }

        /* Return the code */
        return myCode;
    }

    /**
     * Obtain macSpec from encoded macSpecId.
     *
     * @param pEncodedId the encoded id
     * @return the macSpec
     * @throws GordianException on error
     */
    private static GordianMacSpec deriveMacSpecFromEncodedId(final int pEncodedId) throws GordianException {
        /* Isolate id Components */
        final int myMacId = pEncodedId & determineMaskForEnum(GordianMacType.class);
        final int myCode = pEncodedId >> determineShiftForEnum(GordianMacType.class);
        final int myKeyLenId = myCode & determineMaskForEnum(GordianLength.class);
        final int myId = myCode >> determineShiftForEnum(GordianLength.class);

        /* Determine MacType and keyLength */
        final GordianMacType myMacType = deriveMacTypeFromEncodedId(myMacId);
        final GordianLength myKeyLen = deriveLengthFromEncodedId(myKeyLenId);

        /* Switch on the MacType */
        switch (myMacType) {
            case HMAC:
                return GordianMacSpecBuilder.hMac(deriveDigestSpecFromEncodedId(myId), myKeyLen);
            case GMAC:
            case CMAC:
            case KALYNA:
            case CFBMAC:
            case CBCMAC:
                return new GordianMacSpec(myMacType, deriveSymKeySpecFromEncodedId(myId));
            case POLY1305:
                return myId == 0
                        ? GordianMacSpecBuilder.poly1305Mac()
                        : new GordianMacSpec(myMacType, deriveSymKeySpecFromEncodedId(myId));
            case SKEIN:
                final GordianDigestSpec mySkeinSpec = deriveDigestSpecFromEncodedId(myId);
                return GordianMacSpecBuilder.skeinMac(myKeyLen, mySkeinSpec);
            case BLAKE2:
                final GordianDigestSpec myBlake2Spec = deriveDigestSpecFromEncodedId(myId);
                return GordianMacSpecBuilder.blake2Mac(myKeyLen, myBlake2Spec);
            case BLAKE3:
                final GordianDigestSpec myBlake3Spec = deriveDigestSpecFromEncodedId(myId);
                return GordianMacSpecBuilder.blake3Mac(myBlake3Spec.getDigestLength());
            case KMAC:
                final GordianDigestSpec myKMACSpec = deriveDigestSpecFromEncodedId(myId);
                return GordianMacSpecBuilder.kMac(myKeyLen, myKMACSpec);
            case KUPYNA:
                final GordianDigestSpec myKupynaSpec = deriveDigestSpecFromEncodedId(myId);
                return GordianMacSpecBuilder.kupynaMac(myKeyLen, myKupynaSpec.getDigestLength());
            case ZUC:
                final GordianLength myLength = deriveLengthFromEncodedId(myId);
                return GordianMacSpecBuilder.zucMac(myKeyLen, myLength);
            case SIPHASH:
                return new GordianMacSpec(GordianMacType.SIPHASH, deriveSipHashSpecFromEncodedId(myId));
            default:
                return new GordianMacSpec(myMacType, myKeyLen);
        }
    }

    /**
     * Obtain encoded SipHashId.
     *
     * @param pSpec the sipHashSpec
     * @return the encoded id
     */
    private static int deriveEncodedIdFromSipHashSpec(final GordianSipHashSpec pSpec) {
        return deriveEncodedIdFromEnum(pSpec);
    }

    /**
     * Obtain sipHashSpec from encoded Id.
     *
     * @param pEncodedId the encoded id
     * @return the sipHashSpec
     * @throws GordianException on error
     */
    private static GordianSipHashSpec deriveSipHashSpecFromEncodedId(final int pEncodedId) throws GordianException {
        return deriveEnumFromEncodedId(pEncodedId, GordianSipHashSpec.class);
    }

    /**
     * Obtain encoded DigestId.
     *
     * @param pDigest the digestType
     * @return the encoded id
     */
    private static int deriveEncodedIdFromDigestType(final GordianDigestType pDigest) {
        return deriveEncodedIdFromEnum(pDigest);
    }

    /**
     * Obtain digestType from encoded Id.
     *
     * @param pEncodedId the encoded id
     * @return the digestType
     * @throws GordianException on error
     */
    private static GordianDigestType deriveDigestTypeFromEncodedId(final int pEncodedId) throws GordianException {
        return deriveEnumFromEncodedId(pEncodedId, GordianDigestType.class);
    }

    /**
     * Obtain encoded symKeyId.
     *
     * @param pSymKey the symKeyType
     * @return the encoded id
     */
    private static int deriveEncodedIdFromSymKeyType(final GordianSymKeyType pSymKey) {
        return deriveEncodedIdFromEnum(pSymKey);
    }

    /**
     * Obtain symKeyType from encoded Id.
     *
     * @param pEncodedId the encoded id
     * @return the symKeyType
     * @throws GordianException on error
     */
    private static GordianSymKeyType deriveSymKeyTypeFromEncodedId(final int pEncodedId) throws GordianException {
        return deriveEnumFromEncodedId(pEncodedId, GordianSymKeyType.class);
    }

    /**
     * Obtain encoded streamKeyId.
     *
     * @param pStreamKey the streamKeyType
     * @return the encoded id
     */
    private static int deriveEncodedIdFromStreamKeyType(final GordianStreamKeyType pStreamKey) {
        return deriveEncodedIdFromEnum(pStreamKey);
    }

    /**
     * Obtain streamKeyType from encoded Id.
     *
     * @param pEncodedId the encoded id
     * @return the streamKeyType
     * @throws GordianException on error
     */
    private static GordianStreamKeyType deriveStreamKeyTypeFromEncodedId(final int pEncodedId) throws GordianException {
        return deriveEnumFromEncodedId(pEncodedId, GordianStreamKeyType.class);
    }

    /**
     * Obtain encoded MacId.
     *
     * @param pMac the macType
     * @return the encoded id
     */
    private static int deriveEncodedIdFromMacType(final GordianMacType pMac) {
        return deriveEncodedIdFromEnum(pMac);
    }

    /**
     * Obtain macType from encoded Id.
     *
     * @param pEncodedId the encoded id
     * @return the macType
     * @throws GordianException on error
     */
    private static GordianMacType deriveMacTypeFromEncodedId(final int pEncodedId) throws GordianException {
        return deriveEnumFromEncodedId(pEncodedId, GordianMacType.class);
    }

    /**
     * Obtain encoded Length.
     *
     * @param pLength the length
     * @return the encoded id
     */
    private static int deriveEncodedIdFromLength(final GordianLength pLength) {
        return deriveEncodedIdFromEnum(pLength);
    }

    /**
     * Obtain length from encoded Id.
     *
     * @param pEncodedId the encoded id
     * @return the length
     * @throws GordianException on error
     */
    private static GordianLength deriveLengthFromEncodedId(final int pEncodedId) throws GordianException {
        return deriveEnumFromEncodedId(pEncodedId, GordianLength.class);
    }

    /**
     * Obtain encoded digestState.
     *
     * @param pState the state
     * @return the encoded id
     */
    private static int deriveEncodedIdFromDigestState(final GordianDigestState pState) {
        return deriveEncodedIdFromEnum(pState);
    }

    /**
     * Obtain digestState from encoded Id.
     *
     * @param pEncodedId the encoded id
     * @return the state
     * @throws GordianException on error
     */
    private static GordianDigestState deriveDigestStateFromEncodedId(final int pEncodedId) throws GordianException {
        return deriveEnumFromEncodedId(pEncodedId, GordianDigestState.class);
    }

    /**
     * Obtain encoded CipherMode.
     *
     * @param pMode the cipherMode
     * @return the encoded id
     */
    private static int deriveEncodedIdFromCipherMode(final GordianCipherMode pMode) {
        return deriveEncodedIdFromEnum(pMode);
    }

    /**
     * Obtain cipherMode from encoded Id.
     *
     * @param pEncodedId the encoded id
     * @return the cipherMode
     * @throws GordianException on error
     */
    private static GordianCipherMode deriveCipherModeFromEncodedId(final int pEncodedId) throws GordianException {
        return deriveEnumFromEncodedId(pEncodedId, GordianCipherMode.class);
    }

    /**
     * Obtain encoded Padding.
     *
     * @param pPadding the padding
     * @return the encoded id
     */
    private static int deriveEncodedIdFromPadding(final GordianPadding pPadding) {
        return deriveEncodedIdFromEnum(pPadding);
    }

    /**
     * Obtain padding from encoded Id.
     *
     * @param pEncodedId the encoded id
     * @return the padding
     * @throws GordianException on error
     */
    private static GordianPadding derivePaddingFromEncodedId(final int pEncodedId) throws GordianException {
        return deriveEnumFromEncodedId(pEncodedId, GordianPadding.class);
    }

    /**
     * Obtain encodedId from enum.
     *
     * @param <E>   the Enum type
     * @param pEnum the enum
     * @return the encoded id
     */
    private static <E extends Enum<E>> int deriveEncodedIdFromEnum(final E pEnum) {
        return pEnum.ordinal() + 1;
    }

    /**
     * Obtain enum from encoded id.
     *
     * @param <E>        the enum type
     * @param pEncodedId the encoded id
     * @param pClazz     the Enum class
     * @return the padding
     * @throws GordianException on error
     */
    private static <E extends Enum<E>> E deriveEnumFromEncodedId(final int pEncodedId,
                                                                 final Class<E> pClazz) throws GordianException {
        final int myId = pEncodedId - 1;
        for (final E myEnum : pClazz.getEnumConstants()) {
            if (myEnum.ordinal() == myId) {
                return myEnum;
            }
        }
        throw new GordianDataException("Invalid enumId: " + pEncodedId + " for class: " + pClazz.getSimpleName());
    }

    /**
     * Obtain mask for enum.
     *
     * @param <E>    the Enum type
     * @param pClazz the enum class
     * @return the mask
     */
    private static <E extends Enum<E>> int determineMaskForEnum(final Class<E> pClazz) {
        return ~(-1 << determineShiftForEnum(pClazz));
    }

    /**
     * Obtain shift for enum.
     *
     * @param <E>    the Enum type
     * @param pClazz the enum class
     * @return the bit shift
     */
    private static <E extends Enum<E>> int determineShiftForEnum(final Class<E> pClazz) {
        return Integer.SIZE - Integer.numberOfLeadingZeros(pClazz.getEnumConstants().length);
    }

    /**
     * GordianIdSpec markers.
     */
    private enum GordianIdMarker {
        /**
         * SymKey.
         */
        SYMKEY(1),

        /**
         * StreamKey.
         */
        STREAMKEY(2),

        /**
         * MacKey.
         */
        MACKEY(3),

        /**
         * Digest.
         */
        DIGEST(4),

        /**
         * SymKeyCipher.
         */
        SYMCIPHER(5),

        /**
         * StreamCipher.
         */
        STREAMCIPHER(6);

        /**
         * The marker mask.
         */
        private static final int MASK = 0x70000000;

        /**
         * The marker shift.
         */
        private static final int SHIFT = 28;

        /**
         * The marker.
         */
        private final int theMarker;

        /**
         * Constructor.
         *
         * @param pMarker the marker
         */
        GordianIdMarker(final int pMarker) {
            theMarker = pMarker;
        }

        /**
         * Apply marker.
         *
         * @param pId the encoded id
         * @return the marked and encoded id
         */
        int applyMarker(final int pId) {
            if ((pId & MASK) != 0) {
                throw new IllegalArgumentException();
            }

            return pId | (theMarker << SHIFT);
        }

        /**
         * Remove marker.
         *
         * @param pId the merked encoded id
         * @return the marked and encoded id
         */
        static int removeMarker(final int pId) {
            return pId & ~MASK;
        }

        /**
         * Determine marker.
         *
         * @param pId the merked encoded id
         * @return the marker
         */
        static GordianIdMarker determine(final int pId) {
            final int myMark = (pId & MASK) >> SHIFT;
            for (GordianIdMarker myMarker : values()) {
                if (myMarker.theMarker == myMark) {
                    return myMarker;
                }
            }
            throw new IllegalArgumentException();
        }
    }
}