GordianCoreDigestSubSpec.java
/*
* GordianKnot: Security Suite
* Copyright 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.spec.digest;
import io.github.tonywasher.joceanus.gordianknot.api.base.GordianLength;
import io.github.tonywasher.joceanus.gordianknot.api.digest.spec.GordianDigestSubSpec;
import io.github.tonywasher.joceanus.gordianknot.api.digest.spec.GordianDigestSubSpec.GordianDigestState;
import io.github.tonywasher.joceanus.gordianknot.api.digest.spec.GordianDigestType;
import java.util.Collection;
import java.util.EnumMap;
import java.util.Map;
/**
* Digest SubSpec.
*/
public interface GordianCoreDigestSubSpec {
/**
* Obtain the subSpec.
*
* @return the subSpec
*/
GordianDigestSubSpec getSubSpec();
/**
* Obtain the possible subSpecTypes for the digestType.
*
* @param pType the digestType
* @return the subSpec types
*/
static GordianDigestSubSpec[] getPossibleSubSpecsForType(final GordianDigestType pType) {
return switch (pType) {
case SHA2, BLAKE2, HARAKA ->
new GordianDigestState[]{GordianDigestState.STATE256, GordianDigestState.STATE512};
case SKEIN ->
new GordianDigestState[]{GordianDigestState.STATE256, GordianDigestState.STATE512, GordianDigestState.STATE1024};
case SHAKE, KANGAROO -> new GordianDigestState[]{GordianDigestState.STATE128, GordianDigestState.STATE256};
default -> new GordianDigestState[]{null};
};
}
/**
* Obtain the subSpec for the type and length.
*
* @param pType the digestType
* @param pLength the length
* @return the subSpec
*/
static GordianDigestSubSpec getDefaultSubSpecForTypeAndLength(final GordianDigestType pType,
final GordianLength pLength) {
return switch (pType) {
case SHA2 -> pLength == GordianLength.LEN_224 || pLength == GordianLength.LEN_256
? GordianDigestState.STATE256
: GordianDigestState.STATE512;
case SKEIN -> switch (pLength) {
case LEN_1024 -> GordianDigestState.STATE1024;
case LEN_512, LEN_384 -> GordianDigestState.STATE512;
default -> GordianDigestState.STATE256;
};
case SHAKE, KANGAROO -> pLength == GordianLength.LEN_256
? GordianDigestState.STATE128
: GordianDigestState.STATE256;
case BLAKE2 -> pLength == GordianLength.LEN_128 || pLength == GordianLength.LEN_224
? GordianDigestState.STATE256
: GordianDigestState.STATE512;
case HARAKA -> GordianDigestState.STATE256;
default -> null;
};
}
/**
* State subSpecification.
*/
final class GordianCoreDigestState
implements GordianCoreDigestSubSpec {
/**
* The digestStateMap.
*/
private static final Map<GordianDigestState, GordianCoreDigestState> STATEMAP = newStateMap();
/**
* The State.
*/
private final GordianDigestState theState;
/**
* The length.
*/
private final GordianLength theLength;
/**
* Constructor.
*
* @param pState the state
*/
private GordianCoreDigestState(final GordianDigestState pState) {
theState = pState;
theLength = lengthForDigestState();
}
@Override
public GordianDigestSubSpec getSubSpec() {
return getState();
}
/**
* Obtain the state.
*
* @return the state
*/
public GordianDigestState getState() {
return theState;
}
/**
* Obtain length for state.
*
* @return the length
*/
public GordianLength getLength() {
return theLength;
}
@Override
public String toString() {
return theLength.toString();
}
/**
* Is this state a hybrid for sha2 length?
*
* @param pLength the length
* @return true/false
*/
public boolean isSha2Hybrid(final GordianLength pLength) {
return theState == GordianDigestState.STATE512
&& (GordianLength.LEN_224.equals(pLength)
|| GordianLength.LEN_256.equals(pLength));
}
/**
* Obtain the length for an explicit Xof variant.
*
* @param pType the digestType
* @return the length
*/
GordianLength lengthForXofType(final GordianCoreDigestType pType) {
return switch (pType.getType()) {
case SKEIN -> switch (theState) {
case STATE256, STATE512, STATE1024 -> theLength;
default -> null;
};
case BLAKE2 -> switch (theState) {
case STATE256, STATE512 -> theLength;
default -> null;
};
default -> null;
};
}
/**
* is length available for this type and length?
*
* @param pType the digestType
* @param pLength the length
* @return true/false
*/
boolean validForTypeAndLength(final GordianCoreDigestType pType,
final GordianLength pLength) {
return switch (pType.getType()) {
case SHA2 -> validForSha2Length(pLength);
case SHAKE, KANGAROO -> validForSHAKELength(pLength);
case SKEIN -> validForSkeinLength(pLength);
case BLAKE2 -> validForBlake2Length(pLength);
case HARAKA -> validForHarakaLength(pLength);
default -> false;
};
}
/**
* Is this state valid for the skeinLength?
*
* @param pLength the length
* @return true/false
*/
private boolean validForSha2Length(final GordianLength pLength) {
return switch (theState) {
case STATE512 -> switch (pLength) {
case LEN_224, LEN_256, LEN_384, LEN_512 -> true;
default -> false;
};
case STATE256 -> switch (pLength) {
case LEN_224, LEN_256 -> true;
default -> false;
};
default -> false;
};
}
/**
* Is this state valid for the skeinLength?
*
* @param pLength the length
* @return true/false
*/
private boolean validForSHAKELength(final GordianLength pLength) {
return switch (theState) {
case STATE256 -> pLength == GordianLength.LEN_512;
case STATE128 -> pLength == GordianLength.LEN_256;
default -> false;
};
}
/**
* Is this state valid for the skeinLength?
*
* @param pLength the length
* @return true/false
*/
private boolean validForSkeinLength(final GordianLength pLength) {
return switch (theState) {
case STATE1024 -> switch (pLength) {
case LEN_384, LEN_512, LEN_1024 -> true;
default -> false;
};
case STATE512 -> switch (pLength) {
case LEN_128, LEN_160, LEN_224, LEN_256, LEN_384, LEN_512 -> true;
default -> false;
};
case STATE256 -> switch (pLength) {
case LEN_128, LEN_160, LEN_224, LEN_256 -> true;
default -> false;
};
default -> false;
};
}
/**
* Is this state valid for the blake2Length.
*
* @param pLength the length
* @return true/false
*/
private boolean validForBlake2Length(final GordianLength pLength) {
return switch (theState) {
case STATE512 -> switch (pLength) {
case LEN_160, LEN_256, LEN_384, LEN_512 -> true;
default -> false;
};
case STATE256 -> switch (pLength) {
case LEN_128, LEN_160, LEN_224, LEN_256 -> true;
default -> false;
};
default -> false;
};
}
/**
* Is this state valid for the harakaLength.
*
* @param pLength the length
* @return true/false
*/
private boolean validForHarakaLength(final GordianLength pLength) {
return switch (theState) {
case STATE512, STATE256 -> pLength == GordianLength.LEN_256;
default -> false;
};
}
/**
* Is this the Blake2b algorithm?
*
* @return true/false
*/
public boolean isBlake2bState() {
return GordianDigestState.STATE512.equals(theState);
}
/**
* Obtain the blake2Algorithm name for State.
*
* @param pXofMode is this a Xof variant
* @return the algorithm name
*/
public String getBlake2Algorithm(final boolean pXofMode) {
return (pXofMode ? "X" : "")
+ (isBlake2bState() ? "b" : "s");
}
/**
* Obtain the kangarooAlgorithm name for State.
*
* @return the algorithmName
*/
String getKangarooAlgorithm() {
return theState == GordianDigestState.STATE256
? GordianDigestResource.DIGEST_MARSUPILAMI.getValue()
: GordianDigestResource.DIGEST_KANGAROO.getValue();
}
/**
* Obtain length for state.
*
* @return the length
*/
public GordianLength lengthForDigestState() {
return switch (theState) {
case STATE128 -> GordianLength.LEN_128;
case STATE256 -> GordianLength.LEN_256;
case STATE512 -> GordianLength.LEN_512;
case STATE1024 -> GordianLength.LEN_1024;
default -> throw new IllegalArgumentException();
};
}
@Override
public boolean equals(final Object pThat) {
/* Handle the trivial cases */
if (this == pThat) {
return true;
}
if (pThat == null) {
return false;
}
/* Check subFields */
return pThat instanceof GordianCoreDigestState myThat
&& theState == myThat.getState();
}
@Override
public int hashCode() {
return theState.hashCode();
}
/**
* Obtain the core state.
*
* @param pState the base state
* @return the core state
*/
public static GordianCoreDigestState mapCoreState(final Object pState) {
return pState instanceof GordianDigestState myState ? STATEMAP.get(myState) : null;
}
/**
* Build the state map.
*
* @return the state map
*/
private static Map<GordianDigestState, GordianCoreDigestState> newStateMap() {
final Map<GordianDigestState, GordianCoreDigestState> myMap = new EnumMap<>(GordianDigestState.class);
for (GordianDigestState myState : GordianDigestState.values()) {
myMap.put(myState, new GordianCoreDigestState(myState));
}
return myMap;
}
/**
* Obtain the values.
*
* @return the values
*/
public static Collection<GordianCoreDigestState> values() {
return STATEMAP.values();
}
}
}