GordianMacSpec.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.api.mac;

import io.github.tonywasher.joceanus.gordianknot.api.base.GordianKeySpec;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianLength;
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.GordianDigestState;
import io.github.tonywasher.joceanus.gordianknot.api.digest.GordianDigestType;

import java.util.Objects;

/**
 * Mac Specification.
 */
public final class GordianMacSpec
        implements GordianKeySpec {
    /**
     * The Separator.
     */
    static final String SEP = "-";

    /**
     * The Mac Type.
     */
    private final GordianMacType theMacType;

    /**
     * The KeyLength.
     */
    private final GordianLength theKeyLength;

    /**
     * The SubSpec.
     */
    private final Object theSubSpec;

    /**
     * The Validity.
     */
    private final boolean isValid;

    /**
     * The String name.
     */
    private String theName;

    /**
     * hMac/skeinMac Constructor.
     *
     * @param pMacType    the macType
     * @param pKeyLength  the keyLength
     * @param pDigestSpec the digestSpec
     */
    public GordianMacSpec(final GordianMacType pMacType,
                          final GordianLength pKeyLength,
                          final GordianDigestSpec pDigestSpec) {
        /* Store parameters */
        theMacType = pMacType;
        theKeyLength = pKeyLength;
        theSubSpec = pDigestSpec;
        isValid = checkValidity();
    }

    /**
     * gMac/Poly1305 Constructor.
     *
     * @param pMacType the macType
     * @param pKeySpec the keySpec
     */
    public GordianMacSpec(final GordianMacType pMacType,
                          final GordianSymKeySpec pKeySpec) {
        /* Store macType */
        theMacType = pMacType;

        /* Special handling for Poly1305 */
        theKeyLength = GordianMacType.POLY1305 == pMacType
                ? GordianLength.LEN_256
                : pKeySpec.getKeyLength();
        theSubSpec = pKeySpec;
        isValid = checkValidity();
    }

    /**
     * zucMac Constructor.
     *
     * @param pMacType   the macType
     * @param pKeyLength the keyLength
     * @param pLength    the length
     */
    public GordianMacSpec(final GordianMacType pMacType,
                          final GordianLength pKeyLength,
                          final GordianLength pLength) {
        /* Store parameters */
        theMacType = pMacType;
        theKeyLength = pKeyLength;
        theSubSpec = pLength;
        isValid = checkValidity();
    }

    /**
     * sipHash Constructor.
     *
     * @param pMacType the macType
     * @param pSpec    the SipHashSpec
     */
    public GordianMacSpec(final GordianMacType pMacType,
                          final GordianSipHashSpec pSpec) {
        /* Store parameters */
        theMacType = pMacType;
        theKeyLength = GordianLength.LEN_128;
        theSubSpec = pSpec;
        isValid = checkValidity();
    }

    /**
     * vmpcMac/raw poly1305Mac Constructor.
     *
     * @param pKeyLength the keyLength
     * @param pMacType   the macType
     */
    public GordianMacSpec(final GordianMacType pMacType,
                          final GordianLength pKeyLength) {
        theMacType = pMacType;
        theKeyLength = pKeyLength;
        theSubSpec = null;
        isValid = checkValidity();
    }

    /**
     * Obtain Mac Type.
     *
     * @return the MacType
     */
    public GordianMacType getMacType() {
        return theMacType;
    }

    @Override
    public GordianLength getKeyLength() {
        return theKeyLength;
    }

    /**
     * Obtain SubSpec.
     *
     * @return the SubSpec
     */
    public Object getSubSpec() {
        return theSubSpec;
    }

    /**
     * Is the macSpec valid?
     *
     * @return true/false.
     */
    public boolean isValid() {
        return isValid;
    }

    /**
     * Obtain DigestSpec.
     *
     * @return the DigestSpec
     */
    public GordianDigestSpec getDigestSpec() {
        return theSubSpec instanceof GordianDigestSpec mySpec
                ? mySpec
                : null;
    }

    /**
     * Obtain DigestState.
     *
     * @return the State
     */
    private GordianDigestState getDigestState() {
        return theSubSpec instanceof GordianDigestSpec mySpec
                ? mySpec.getDigestState()
                : null;
    }

    /**
     * Obtain DigestLength.
     *
     * @return the Length
     */
    private GordianLength getDigestLength() {
        return theSubSpec instanceof GordianDigestSpec mySpec
                ? mySpec.getDigestLength()
                : null;
    }

    /**
     * Obtain SymKeySpec.
     *
     * @return the SymKeySpec
     */
    public GordianSymKeySpec getSymKeySpec() {
        return theSubSpec instanceof GordianSymKeySpec mySpec
                ? mySpec
                : null;
    }

    /**
     * Obtain SymKeyType.
     *
     * @return the Type
     */
    private GordianSymKeyType getSymKeyType() {
        return theSubSpec instanceof GordianSymKeySpec mySpec
                ? mySpec.getSymKeyType()
                : null;
    }

    /**
     * Obtain SymKeyBlockLength.
     *
     * @return the BlockLength
     */
    private GordianLength getSymKeyBlockLength() {
        return theSubSpec instanceof GordianSymKeySpec mySpec
                ? mySpec.getBlockLength()
                : null;
    }

    /**
     * Obtain SymKeyBlockLength.
     *
     * @return the BlockLength
     */
    private int getSymKeyBlockByteLength() {
        return theSubSpec instanceof GordianSymKeySpec mySpec
                ? Objects.requireNonNull(mySpec.getBlockLength()).getByteLength()
                : 0;
    }

    /**
     * Obtain SymKeyHalfBlockLength.
     *
     * @return the HalfBlockLength
     */
    private GordianLength getSymKeyHalfBlockLength() {
        return theSubSpec instanceof GordianSymKeySpec mySpec
                ? mySpec.getHalfBlockLength()
                : null;
    }

    /**
     * Obtain SipHashSpec.
     *
     * @return the Spec
     */
    public GordianSipHashSpec getSipHashSpec() {
        return theSubSpec instanceof GordianSipHashSpec mySpec
                ? mySpec
                : null;
    }

    /**
     * Obtain MacLength.
     *
     * @return the Length
     */
    public GordianLength getMacLength() {
        switch (theMacType) {
            case HMAC:
            case BLAKE2:
            case BLAKE3:
            case SKEIN:
            case KUPYNA:
            case KMAC:
                return getDigestLength();
            case GMAC:
            case POLY1305:
                return GordianLength.LEN_128;
            case CMAC:
            case KALYNA:
                return getSymKeyBlockLength();
            case CBCMAC:
            case CFBMAC:
                return getSymKeyHalfBlockLength();
            case ZUC:
                return (GordianLength) theSubSpec;
            case VMPC:
                return GordianLength.LEN_160;
            case GOST:
                return GordianLength.LEN_32;
            case SIPHASH:
                return ((GordianSipHashSpec) theSubSpec).getOutLength();
            default:
                return GordianLength.LEN_64;
        }
    }

    /**
     * Obtain the IV length.
     *
     * @return the IV Length
     */
    public int getIVLen() {
        switch (theMacType) {
            case VMPC:
            case SKEIN:
                return GordianLength.LEN_128.getByteLength();
            case POLY1305:
                return theSubSpec == null
                        ? 0
                        : GordianLength.LEN_128.getByteLength();
            case BLAKE2:
                return Objects.requireNonNull(getDigestState()).isBlake2bState()
                        ? GordianLength.LEN_128.getByteLength()
                        : GordianLength.LEN_64.getByteLength();
            case GMAC:
                return GordianLength.LEN_96.getByteLength();
            case CBCMAC:
            case CFBMAC:
                return getSymKeyBlockByteLength();
            case GOST:
                return GordianLength.LEN_64.getByteLength();
            case ZUC:
                return GordianLength.LEN_128 == theKeyLength
                        ? GordianLength.LEN_128.getByteLength()
                        : GordianLength.LEN_200.getByteLength();
            default:
                return 0;
        }
    }

    /**
     * Check spec validity.
     *
     * @return valid true/false
     */
    private boolean checkValidity() {
        /* Make sure that macType and keyLength are non-null */
        if (theMacType == null || theKeyLength == null) {
            return false;
        }

        /* Switch on MacType */
        switch (theMacType) {
            case HMAC:
                return checkDigestValidity(null);
            case KUPYNA:
                return checkDigestValidity(GordianDigestType.KUPYNA);
            case SKEIN:
                return checkDigestValidity(GordianDigestType.SKEIN);
            case BLAKE2:
                return checkBlake2Validity();
            case BLAKE3:
                return checkDigestValidity(GordianDigestType.BLAKE3);
            case KALYNA:
                return checkSymKeyValidity(GordianSymKeyType.KALYNA);
            case KMAC:
                return checkKMACValidity();
            case CMAC:
            case GMAC:
            case CBCMAC:
            case CFBMAC:
                return checkSymKeyValidity(null);
            case POLY1305:
                return checkPoly1305Validity();
            case ZUC:
                return checkZucValidity();
            case SIPHASH:
                return theSubSpec instanceof GordianSipHashSpec
                        && theKeyLength == GordianLength.LEN_128;
            case GOST:
                return theSubSpec == null
                        && theKeyLength == GordianLength.LEN_256;
            case VMPC:
                return theSubSpec == null;
            default:
                return false;
        }
    }

    /**
     * Check digest validity.
     *
     * @param pDigestType required digestType (or null)
     * @return valid true/false
     */
    private boolean checkDigestValidity(final GordianDigestType pDigestType) {
        /* Check that the digestSpec is valid */
        if (!(theSubSpec instanceof GordianDigestSpec)
                || !((GordianDigestSpec) theSubSpec).isValid()) {
            return false;
        }

        /* Check for digestType restrictions */
        final GordianDigestType myType = ((GordianDigestSpec) theSubSpec).getDigestType();
        return pDigestType == null
                ? myType.supportsLargeData()
                : myType == pDigestType;
    }

    /**
     * Check symKey validity.
     *
     * @param pSymKeyType required symKeyType (or null)
     * @return valid true/false
     */
    private boolean checkSymKeyValidity(final GordianSymKeyType pSymKeyType) {
        /* Check that the symKeySpec is valid */
        if (!(theSubSpec instanceof GordianSymKeySpec)
                || !((GordianSymKeySpec) theSubSpec).isValid()) {
            return false;
        }

        /* Check for symKeyType restrictions */
        return pSymKeyType == null
                || ((GordianSymKeySpec) theSubSpec).getSymKeyType() == pSymKeyType;
    }

    /**
     * Check poly1305 validity.
     *
     * @return valid true/false
     */
    private boolean checkPoly1305Validity() {
        /* Check that the subSpec is reasonable */
        if (theSubSpec != null
                && !checkSymKeyValidity(null)) {
            return false;
        }

        /* Restrict keyLengths */
        final GordianSymKeySpec mySpec = (GordianSymKeySpec) theSubSpec;
        return theKeyLength == GordianLength.LEN_256
                && (mySpec == null
                || mySpec.getKeyLength() == GordianLength.LEN_128);
    }

    /**
     * Check blake validity.
     *
     * @return valid true/false
     */
    private boolean checkBlake2Validity() {
        /* Check that the spec is reasonable */
        if (!checkDigestValidity(GordianDigestType.BLAKE2)) {
            return false;
        }

        /* Check keyLength */
        return checkBlake2KeyLength(theKeyLength, (GordianDigestSpec) theSubSpec);
    }

    /**
     * Check blake2 keyLength validity.
     *
     * @param pKeyLen the keyLength
     * @param pSpec   the digestSpec
     * @return valid true/false
     */
    private static boolean checkBlake2KeyLength(final GordianLength pKeyLen,
                                                final GordianDigestSpec pSpec) {
        /* Key length must be less or equal to the stateLength */
        return pKeyLen.getLength() <= pSpec.getDigestState().getLength().getLength();
    }

    /**
     * Check blake validity.
     *
     * @return valid true/false
     */
    private boolean checkKMACValidity() {
        /* Check that the spec is reasonable */
        if (!checkDigestValidity(GordianDigestType.SHAKE)) {
            return false;
        }

        /* Check keyLength */
        return checkKMACKeyLength(theKeyLength, (GordianDigestSpec) theSubSpec);
    }

    /**
     * Check KMAC keyLength validity.
     *
     * @param pKeyLen the keyLength
     * @param pSpec   the digestSpec
     * @return valid true/false
     */
    private static boolean checkKMACKeyLength(final GordianLength pKeyLen,
                                              final GordianDigestSpec pSpec) {
        /* Key length must be greater or equal to the stateLength */
        return pKeyLen.getLength() >= pSpec.getDigestState().getLength().getLength();
    }

    /**
     * Check zuc validity.
     *
     * @return valid true/false
     */
    private boolean checkZucValidity() {
        switch (theKeyLength) {
            case LEN_128:
                return GordianLength.LEN_32 == theSubSpec;
            case LEN_256:
                return GordianLength.LEN_32 == theSubSpec
                        || GordianLength.LEN_64 == theSubSpec
                        || GordianLength.LEN_128 == theSubSpec;
            default:
                return false;
        }
    }

    /**
     * Is this a Xof Mac?
     *
     * @return true/false
     */
    public boolean isXof() {
        switch (theMacType) {
            case KMAC:
            case BLAKE3:
                return true;
            case BLAKE2:
            case SKEIN:
                return Objects.requireNonNull(getDigestSpec()).isXof();
            default:
                return false;
        }
    }

    @Override
    public String toString() {
        /* If we have not yet loaded the name */
        if (theName == null) {
            /* If the macSpec is invalid */
            if (!isValid) {
                /* Report invalid spec */
                theName = "InvalidMacSpec: " + theMacType + ":" + theSubSpec + ":" + theKeyLength;
                return theName;
            }

            /* Load the name */
            theName = theMacType.toString();
            switch (theMacType) {
                case SIPHASH:
                    theName = theSubSpec.toString();
                    break;
                case POLY1305:
                    theName += theSubSpec == null ? "" : SEP + getSymKeyType();
                    break;
                case GMAC:
                case CMAC:
                case CFBMAC:
                case CBCMAC:
                    theName += SEP + theSubSpec.toString();
                    break;
                case KALYNA:
                    theName += getSymKeyBlockLength() + SEP + theKeyLength;
                    break;
                case KUPYNA:
                    theName += SEP + getDigestLength() + SEP + theKeyLength;
                    break;
                case KMAC:
                    theName += getDigestState() + SEP + theKeyLength;
                    break;
                case SKEIN:
                    final boolean isSkeinXof = Objects.requireNonNull(getDigestSpec()).isXofMode();
                    theName = GordianDigestType.SKEIN
                            + (isSkeinXof ? "X" : "")
                            + "Mac"
                            + SEP + getDigestState()
                            + (isSkeinXof ? "" : SEP + getDigestLength())
                            + SEP + theKeyLength;
                    break;
                case HMAC:
                case ZUC:
                    theName += theSubSpec.toString() + SEP + theKeyLength;
                    break;
                case BLAKE2:
                    final boolean isBlakeXof = Objects.requireNonNull(getDigestSpec()).isXofMode();
                    theName = GordianDigestType.BLAKE2
                            + Objects.requireNonNull(getDigestState())
                            .getBlake2Algorithm(isBlakeXof)
                            + "Mac" + (isBlakeXof ? "" : SEP + getDigestLength())
                            + SEP + theKeyLength;
                    break;
                case BLAKE3:
                    theName += SEP + getDigestLength();
                    break;
                case VMPC:
                    theName += theKeyLength;
                    break;
                case GOST:
                default:
                    break;
            }
        }

        /* return the name */
        return theName;
    }

    @Override
    public boolean equals(final Object pThat) {
        /* Handle the trivial cases */
        if (this == pThat) {
            return true;
        }
        if (pThat == null) {
            return false;
        }

        /* Check MacType, keyLength and subSpec */
        return pThat instanceof GordianMacSpec myThat
                && theMacType == myThat.getMacType()
                && theKeyLength == myThat.getKeyLength()
                && Objects.equals(theSubSpec, myThat.getSubSpec());
    }

    @Override
    public int hashCode() {
        return Objects.hash(theMacType, theKeyLength, theSubSpec);
    }
}