/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2015 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * 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 org.wildfly.security.sasl.util;

import static java.util.Arrays.asList;
import static java.util.Collections.emptySet;
import static java.util.Collections.singleton;
import static java.util.Collections.unmodifiableSet;

import java.security.Principal;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.function.Predicate;

import org.wildfly.security.credential.BearerTokenCredential;
import org.wildfly.security.credential.Credential;
import org.wildfly.security.credential.GSSKerberosCredential;
import org.wildfly.security.credential.PasswordCredential;
import org.wildfly.security.credential.X509CertificateChainPublicCredential;
import org.wildfly.security.evidence.AlgorithmEvidence;
import org.wildfly.security.evidence.BearerTokenEvidence;
import org.wildfly.security.evidence.Evidence;
import org.wildfly.security.evidence.PasswordGuessEvidence;
import org.wildfly.security.password.OneWayPassword;
import org.wildfly.security.password.Password;
import org.wildfly.security.password.TwoWayPassword;
import org.wildfly.security.password.interfaces.ClearPassword;
import org.wildfly.security.password.interfaces.DigestPassword;
import org.wildfly.security.password.interfaces.OneTimePassword;
import org.wildfly.security.password.interfaces.ScramDigestPassword;
import org.wildfly.security.credential.X509CertificateChainPrivateCredential;

/**
 * A collection of predicates and other information which can be used to filter SASL mechanisms.
 *
 * @see FilterMechanismSaslClientFactory
 * @see FilterMechanismSaslServerFactory
 * @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
 */
public final class SaslMechanismInformation {

    /**
     * The class providing Sasl Mechanism Names.
     */
    public static final class Names {
        public static final String CRAM_MD5 = "CRAM-MD5";
        public static final String DIGEST_MD5 = "DIGEST-MD5";
        public static final String DIGEST_SHA = "DIGEST-SHA";
        public static final String DIGEST_SHA_256 = "DIGEST-SHA-256";
        public static final String DIGEST_SHA_384 = "DIGEST-SHA-384";
        public static final String DIGEST_SHA_512 = "DIGEST-SHA-512";
        public static final String DIGEST_SHA_512_256 = "DIGEST-SHA-512-256";
        public static final String SCRAM_SHA_1 = "SCRAM-SHA-1";
        public static final String SCRAM_SHA_1_PLUS = "SCRAM-SHA-1-PLUS";
        public static final String SCRAM_SHA_256 = "SCRAM-SHA-256";
        public static final String SCRAM_SHA_256_PLUS = "SCRAM-SHA-256-PLUS";
        public static final String SCRAM_SHA_384 = "SCRAM-SHA-384";
        public static final String SCRAM_SHA_384_PLUS = "SCRAM-SHA-384-PLUS";
        public static final String SCRAM_SHA_512 = "SCRAM-SHA-512";
        public static final String SCRAM_SHA_512_PLUS = "SCRAM-SHA-512-PLUS";
        public static final String IEC_ISO_9798_M_DSA_SHA1 = "9798-M-DSA-SHA1";
        public static final String IEC_ISO_9798_M_ECDSA_SHA1 = "9798-M-ECDSA-SHA1";
        public static final String IEC_ISO_9798_M_RSA_SHA1_ENC = "9798-M-RSA-SHA1-ENC";
        public static final String IEC_ISO_9798_U_DSA_SHA1 = "9798-U-DSA-SHA1";
        public static final String IEC_ISO_9798_U_ECDSA_SHA1 = "9798-U-ECDSA-SHA1";
        public static final String IEC_ISO_9798_U_RSA_SHA1_ENC = "9798-U-RSA-SHA1-ENC";
        public static final String ANONYMOUS = "ANONYMOUS";
        public static final String EAP_AES128 = "EAP-AES128";
        public static final String EAP_AES128_PLUS = "EAP-AES128-PLUS";
        public static final String EXTERNAL = "EXTERNAL";
        public static final String JBOSS_LOCAL_USER = "JBOSS-LOCAL-USER";
        public static final String OAUTH_10_A = "OAUTH10A";
        public static final String OAUTHBEARER = "OAUTHBEARER";
        public static final String OPENID20 = "OPENID20";
        public static final String OTP = "OTP";
        public static final String SAML20 = "SAML20";
        public static final String SECURID = "SECURID";
        public static final String PLAIN = "PLAIN";
        public static final String GS2_KRB5 = "GS2-KRB5";
        public static final String GS2_KRB5_PLUS = "GS2-KRB5-PLUS";
        public static final String GSSAPI = "GSSAPI";

        private Names() {}
    }

    private static final Set<String> MD5_MECHS = nSet(
        Names.CRAM_MD5,
        Names.DIGEST_MD5
    );

    private static final Set<String> SHA_MECHS = nSet(
        Names.DIGEST_SHA,
        Names.SCRAM_SHA_1,
        Names.SCRAM_SHA_1_PLUS
    );

    private static final Set<String> SHA_256_MECHS = nSet(
        Names.DIGEST_SHA_256,
        Names.SCRAM_SHA_256,
        Names.SCRAM_SHA_256_PLUS
    );

    private static final Set<String> SHA_384_MECHS = nSet(
        Names.DIGEST_SHA_384,
        Names.SCRAM_SHA_384,
        Names.SCRAM_SHA_384_PLUS
    );

    private static final Set<String> SHA_512_MECHS = nSet(
        Names.DIGEST_SHA_512,
        Names.SCRAM_SHA_512,
        Names.SCRAM_SHA_512_PLUS
    );

    private static final Set<String> SHA_512_256_MECHS = nSet(
            Names.DIGEST_SHA_512_256
    );

    private static final Set<String> MUTUAL_MECHS = nSet(
        Names.IEC_ISO_9798_M_DSA_SHA1,
        Names.IEC_ISO_9798_M_ECDSA_SHA1,
        Names.IEC_ISO_9798_M_RSA_SHA1_ENC
    );

    private static final Set<String> RECOMMENDED_MECHS = nSet(
        Names.IEC_ISO_9798_M_DSA_SHA1,
        Names.IEC_ISO_9798_M_ECDSA_SHA1,
        Names.IEC_ISO_9798_M_RSA_SHA1_ENC,
        Names.IEC_ISO_9798_U_DSA_SHA1,
        Names.IEC_ISO_9798_U_ECDSA_SHA1,
        Names.IEC_ISO_9798_U_RSA_SHA1_ENC,
        Names.ANONYMOUS,
        Names.EAP_AES128,
        Names.EAP_AES128_PLUS,
        Names.EXTERNAL,
        Names.OAUTH_10_A,
        Names.OAUTHBEARER,
        Names.OPENID20,
        Names.OTP,
        Names.SAML20,
        Names.SECURID
    );

    private static final Set<String> KNOWN_MECHS = nSet(
            Names.CRAM_MD5,
            Names.DIGEST_MD5,
            Names.DIGEST_SHA,
            Names.DIGEST_SHA_256,
            Names.DIGEST_SHA_384,
            Names.DIGEST_SHA_512,
            Names.DIGEST_SHA_512_256,
            Names.SCRAM_SHA_1,
            Names.SCRAM_SHA_1_PLUS,
            Names.SCRAM_SHA_256,
            Names.SCRAM_SHA_256_PLUS,
            Names.SCRAM_SHA_384,
            Names.SCRAM_SHA_384_PLUS,
            Names.SCRAM_SHA_512,
            Names.SCRAM_SHA_512_PLUS,
            Names.IEC_ISO_9798_M_DSA_SHA1,
            Names.IEC_ISO_9798_M_ECDSA_SHA1,
            Names.IEC_ISO_9798_M_RSA_SHA1_ENC,
            Names.IEC_ISO_9798_U_DSA_SHA1,
            Names.IEC_ISO_9798_U_ECDSA_SHA1,
            Names.IEC_ISO_9798_U_RSA_SHA1_ENC,
            Names.ANONYMOUS,
            Names.EAP_AES128,
            Names.EAP_AES128_PLUS,
            Names.EXTERNAL,
            Names.JBOSS_LOCAL_USER,
            Names.OAUTH_10_A,
            Names.OAUTHBEARER,
            Names.OPENID20,
            Names.OTP,
            Names.SAML20,
            Names.SECURID,
            Names.PLAIN,
            Names.GS2_KRB5,
            Names.GS2_KRB5_PLUS,
            Names.GSSAPI
    );

    /**
     * A predicate which is true when the mechanism uses MD5.
     */
    public static final Predicate<String> HASH_MD5 = MD5_MECHS::contains;

    /**
     * A predicate which is true when the mechanism uses SHA.
     */
    public static final Predicate<String> HASH_SHA = SHA_MECHS::contains;

    /**
     * A predicate which is true when the mechanism uses SHA-256.
     */
    public static final Predicate<String> HASH_SHA_256 = SHA_256_MECHS::contains;

    /**
     * A predicate which is true when the mechanism uses SHA-384.
     */
    public static final Predicate<String> HASH_SHA_384 = SHA_384_MECHS::contains;

    /**
     * A predicate which is true when the mechanism uses SHA-512.
     */
    public static final Predicate<String> HASH_SHA_512 = SHA_512_MECHS::contains;

    /**
     * A predicate which is true when the mechanism uses SHA-512/256.
     */
    public static final Predicate<String> HASH_SHA_512_256 = SHA_512_256_MECHS::contains;

    /**
     * A predicate which is true when a GS2-family mechanism is being used.
     */
    public static final Predicate<String> GS2 = name -> name.startsWith("GS2-");

    /**
     * A predicate which is true when a SCRAM-family mechanism is being used.
     */
    public static final Predicate<String> SCRAM = name -> name.startsWith("SCRAM-");

    /**
     * A predicate which is true when a DIGEST-family mechanism is being used.
     */
    public static final Predicate<String> DIGEST = name -> name.startsWith("DIGEST-");

    /**
     * A predicate which is true when a IEC/ISO-9798-family mechanism is being used.
     */
    public static final Predicate<String> IEC_ISO_9798 = name -> name.startsWith("9798-");

    /**
     * A predicate which is true when an EAP-family mechanism is being used.
     */
    public static final Predicate<String> EAP = name -> name.startsWith("EAP-");

    /**
     * A predicate which is true when the mechanism supports mutual authentication.
     */
    public static final Predicate<String> MUTUAL = ((Predicate<String>) MUTUAL_MECHS::contains).or(SCRAM).or(GS2);

    /**
     * A predicate which is true when a mechanism which uses channel binding is being used.
     */
    public static final Predicate<String> BINDING = name -> name.endsWith("-PLUS");

    /**
     * A predicate which is true when the name being tested is a recommended mechanism as of the time of this release.
     */
    public static final Predicate<String> RECOMMENDED = ((Predicate<String>) RECOMMENDED_MECHS::contains).or(GS2).or(SCRAM).and(HASH_MD5.negate());

    // credential type sets

    static final Set<Class<? extends Password>> JUST_ONE_WAY = singleton(OneTimePassword.class);
    static final Set<Class<? extends Password>> JUST_TWO_WAY = singleton(TwoWayPassword.class);
    static final Set<Class<? extends Password>> ONE_WAY_AND_TWO_WAY = nSet(OneWayPassword.class, TwoWayPassword.class);
    static final Set<Class<? extends Password>> DIGEST_AND_TWO_WAY = nSet(DigestPassword.class, TwoWayPassword.class);
    static final Set<Class<? extends Password>> SCRAM_AND_TWO_WAY = nSet(ScramDigestPassword.class, TwoWayPassword.class);

    static final Set<Class<? extends Credential>> JUST_X509 = singleton(X509CertificateChainPrivateCredential.class);
    static final Set<Class<? extends Credential>> X_509_PUBLIC_OR_PRIVATE = nSet(X509CertificateChainPublicCredential.class, X509CertificateChainPrivateCredential.class);
    static final Set<Class<? extends Credential>> JUST_PASSWORD = singleton(PasswordCredential.class);
    static final Set<Class<? extends Credential>> JUST_GSS = singleton(GSSKerberosCredential.class);
    static final Set<Class<? extends Credential>> JUST_BEARER_TOKEN = singleton(BearerTokenCredential.class);

    static final Set<Class<? extends Evidence>> JUST_PASSWORD_EVIDENCE = singleton(PasswordGuessEvidence.class);

    static final Set<Class<? extends Evidence>> JUST_BEARER_TOKEN_EVIDENCE = singleton(BearerTokenEvidence.class);

    // algorithm name sets

    static final Set<String> DIGEST_MD5_AND_PLAIN = nSet(DigestPassword.ALGORITHM_DIGEST_MD5, ClearPassword.ALGORITHM_CLEAR);
    static final Set<String> DIGEST_SHA_AND_PLAIN = nSet(DigestPassword.ALGORITHM_DIGEST_SHA, ClearPassword.ALGORITHM_CLEAR);
    static final Set<String> DIGEST_SHA_256_AND_PLAIN = nSet(DigestPassword.ALGORITHM_DIGEST_SHA_256, ClearPassword.ALGORITHM_CLEAR);
    static final Set<String> DIGEST_SHA_384_AND_PLAIN = nSet(DigestPassword.ALGORITHM_DIGEST_SHA_384, ClearPassword.ALGORITHM_CLEAR);
    static final Set<String> DIGEST_SHA_512_AND_PLAIN = nSet(DigestPassword.ALGORITHM_DIGEST_SHA_512, ClearPassword.ALGORITHM_CLEAR);
    static final Set<String> DIGEST_SHA_512_256_AND_PLAIN = nSet(DigestPassword.ALGORITHM_DIGEST_SHA_512_256, ClearPassword.ALGORITHM_CLEAR);
    static final Set<String> SCRAM_SHA_1_AND_PLAIN = nSet(ScramDigestPassword.ALGORITHM_SCRAM_SHA_1, ClearPassword.ALGORITHM_CLEAR);
    static final Set<String> SCRAM_SHA_256_AND_PLAIN = nSet(ScramDigestPassword.ALGORITHM_SCRAM_SHA_256, ClearPassword.ALGORITHM_CLEAR);
    static final Set<String> SCRAM_SHA_384_AND_PLAIN = nSet(ScramDigestPassword.ALGORITHM_SCRAM_SHA_384, ClearPassword.ALGORITHM_CLEAR);
    static final Set<String> SCRAM_SHA_512_AND_PLAIN = nSet(ScramDigestPassword.ALGORITHM_SCRAM_SHA_512, ClearPassword.ALGORITHM_CLEAR);
    static final Set<String> OTP_ALGORITHMS = nSet(OneTimePassword.ALGORITHM_OTP_MD5, OneTimePassword.ALGORITHM_OTP_SHA1,
            OneTimePassword.ALGORITHM_OTP_SHA_256, OneTimePassword.ALGORITHM_OTP_SHA_384,
            OneTimePassword.ALGORITHM_OTP_SHA_512);
    static final Set<String> JUST_PLAIN = singleton(ClearPassword.ALGORITHM_CLEAR);
    static final Set<String> JUST_DSA = singleton("DSA");
    static final Set<String> JUST_EC = singleton("EC");
    static final Set<String> JUST_RSA = singleton("RSA");
    static final Set<String> ALL_ALGORITHMS = singleton("*");

    /**
     * Get the supported credential types for the given SASL client mechanism.  If an empty set is returned, then no
     * credentials are used by the mechanism or the mechanism is not known.
     *
     * @param mechName the mechanism name
     * @return the set of allowed client credentials
     */
    public static Set<Class<? extends Credential>> getSupportedClientCredentialTypes(String mechName) {
        switch (mechName) {
            case Names.EXTERNAL:
            case Names.ANONYMOUS: {
                return emptySet();
            }
            case Names.PLAIN:
            case Names.OTP:
            case Names.CRAM_MD5:
            case Names.DIGEST_MD5:
            case Names.DIGEST_SHA:
            case Names.DIGEST_SHA_256:
            case Names.DIGEST_SHA_384:
            case Names.DIGEST_SHA_512:
            case Names.DIGEST_SHA_512_256:
            case Names.SCRAM_SHA_1:
            case Names.SCRAM_SHA_1_PLUS:
            case Names.SCRAM_SHA_256:
            case Names.SCRAM_SHA_256_PLUS:
            case Names.SCRAM_SHA_384:
            case Names.SCRAM_SHA_384_PLUS:
            case Names.SCRAM_SHA_512:
            case Names.SCRAM_SHA_512_PLUS: {
                return JUST_PASSWORD;
            }
            case Names.IEC_ISO_9798_M_DSA_SHA1:
            case Names.IEC_ISO_9798_U_DSA_SHA1:
            case Names.IEC_ISO_9798_M_ECDSA_SHA1:
            case Names.IEC_ISO_9798_U_ECDSA_SHA1:
            case Names.IEC_ISO_9798_M_RSA_SHA1_ENC:
            case Names.IEC_ISO_9798_U_RSA_SHA1_ENC: {
                return X_509_PUBLIC_OR_PRIVATE;
            }
            case Names.OAUTHBEARER: {
                return JUST_BEARER_TOKEN;
            }
            case Names.GSSAPI: {
                return JUST_GSS;
            }
            default: {
                if (GS2.test(mechName)) {
                    return JUST_GSS;
                }
                // unknown
                return emptySet();
            }
        }
    }

    /**
     * Get the supported password types for the given SASL client mechanism.  If an empty set is returned, then no
     * passwords are used by the mechanism or nothing is known about the mechanism.
     *
     * @param mechName the mechanism name
     * @return the set of allowed client password types
     */
    public static Set<Class<? extends Password>> getSupportedClientPasswordTypes(String mechName) {
        switch (mechName) {
            case Names.EXTERNAL:
            case Names.ANONYMOUS: {
                return emptySet();
            }
            case Names.PLAIN:
            case Names.OTP:
            case Names.CRAM_MD5: {
                return JUST_TWO_WAY;
            }
            case Names.DIGEST_MD5:
            case Names.DIGEST_SHA:
            case Names.DIGEST_SHA_256:
            case Names.DIGEST_SHA_384:
            case Names.DIGEST_SHA_512:
            case Names.DIGEST_SHA_512_256: {
                return DIGEST_AND_TWO_WAY;
            }
            case Names.SCRAM_SHA_1:
            case Names.SCRAM_SHA_1_PLUS:
            case Names.SCRAM_SHA_256:
            case Names.SCRAM_SHA_256_PLUS:
            case Names.SCRAM_SHA_384:
            case Names.SCRAM_SHA_384_PLUS:
            case Names.SCRAM_SHA_512:
            case Names.SCRAM_SHA_512_PLUS: {
                return SCRAM_AND_TWO_WAY;
            }
            case Names.IEC_ISO_9798_M_DSA_SHA1:
            case Names.IEC_ISO_9798_U_DSA_SHA1:
            case Names.IEC_ISO_9798_M_ECDSA_SHA1:
            case Names.IEC_ISO_9798_U_ECDSA_SHA1:
            case Names.IEC_ISO_9798_M_RSA_SHA1_ENC:
            case Names.IEC_ISO_9798_U_RSA_SHA1_ENC: {
                return emptySet();
            }
            default: {
                // unknown
                return emptySet();
            }
        }
    }

    /**
     * Get the supported credential types for the given SASL server mechanism.  If an empty set is returned, then no
     * credentials are used by the mechanism or the mechanism is unknown.
     *
     * @param mechName the mechanism name
     * @return the set of allowed server credential types
     */
    public static Set<Class<? extends Credential>> getSupportedServerCredentialTypes(String mechName) {
        switch (mechName) {
            case Names.EXTERNAL:
            case Names.ANONYMOUS: {
                return emptySet();
            }
            case Names.PLAIN:
            case Names.OTP:
            case Names.CRAM_MD5:
            case Names.DIGEST_MD5:
            case Names.DIGEST_SHA:
            case Names.DIGEST_SHA_256:
            case Names.DIGEST_SHA_384:
            case Names.DIGEST_SHA_512:
            case Names.DIGEST_SHA_512_256:
            case Names.SCRAM_SHA_1:
            case Names.SCRAM_SHA_1_PLUS:
            case Names.SCRAM_SHA_256:
            case Names.SCRAM_SHA_256_PLUS:
            case Names.SCRAM_SHA_384:
            case Names.SCRAM_SHA_384_PLUS:
            case Names.SCRAM_SHA_512:
            case Names.SCRAM_SHA_512_PLUS: {
                return JUST_PASSWORD;
            }
            case Names.IEC_ISO_9798_M_DSA_SHA1:
            case Names.IEC_ISO_9798_U_DSA_SHA1:
            case Names.IEC_ISO_9798_M_ECDSA_SHA1:
            case Names.IEC_ISO_9798_U_ECDSA_SHA1:
            case Names.IEC_ISO_9798_M_RSA_SHA1_ENC:
            case Names.IEC_ISO_9798_U_RSA_SHA1_ENC: {
                // TODO: look into verification process
                return JUST_X509;
            }
            case Names.GSSAPI: {
                return JUST_GSS;
            }
            default: {
                if (GS2.test(mechName)) {
                    return JUST_GSS;
                }
                // unknown
                return emptySet();
            }
        }
    }

    /**
     * Get the supported password types for the given SASL server mechanism.  If an empty set is returned, then no
     * passwords are used by the mechanism or nothing is known about the mechanism
     *
     * @param mechName the mechanism name
     * @return the set of allowed server password types
     */
    public static Set<Class<? extends Password>> getSupportedServerPasswordTypes(String mechName) {
        switch (mechName) {
            case Names.EXTERNAL:
            case Names.ANONYMOUS: {
                return emptySet();
            }
            case Names.PLAIN: {
                return ONE_WAY_AND_TWO_WAY;
            }
            case Names.OTP: {
                return JUST_ONE_WAY;
            }
            case Names.CRAM_MD5: {
                return JUST_TWO_WAY;
            }
            case Names.DIGEST_MD5:
            case Names.DIGEST_SHA:
            case Names.DIGEST_SHA_256:
            case Names.DIGEST_SHA_384:
            case Names.DIGEST_SHA_512: {
                return DIGEST_AND_TWO_WAY;
            }
            case Names.SCRAM_SHA_1:
            case Names.SCRAM_SHA_1_PLUS:
            case Names.SCRAM_SHA_256:
            case Names.SCRAM_SHA_256_PLUS:
            case Names.SCRAM_SHA_384:
            case Names.SCRAM_SHA_384_PLUS:
            case Names.SCRAM_SHA_512:
            case Names.SCRAM_SHA_512_PLUS: {
                return SCRAM_AND_TWO_WAY;
            }
            case Names.IEC_ISO_9798_M_DSA_SHA1:
            case Names.IEC_ISO_9798_U_DSA_SHA1:
            case Names.IEC_ISO_9798_M_ECDSA_SHA1:
            case Names.IEC_ISO_9798_U_ECDSA_SHA1:
            case Names.IEC_ISO_9798_M_RSA_SHA1_ENC:
            case Names.IEC_ISO_9798_U_RSA_SHA1_ENC: {
                return emptySet();
            }
            default: {
                // unknown
                return emptySet();
            }
        }
    }

    /**
     * Get the supported algorithm names for a SASL client mechanism and credential type.  If the mechanism or
     * credential type is not recognized, or if the given credential type does not use algorithms for the
     * given mechanism name, an empty set is returned.  If all algorithms are supported, a set containing the special
     * string {@code "*"} is returned.
     *
     * @param mechName the SASL mechanism name
     * @param credentialType the proposed credential type
     *
     * @return the set of algorithms, or an empty set if all algorithms have unknown support
     */
    public static Set<String> getSupportedClientCredentialAlgorithms(String mechName, Class<? extends Credential> credentialType) {
        switch (mechName) {
            case Names.CRAM_MD5:
            case Names.PLAIN: {
                return credentialType.isAssignableFrom(PasswordCredential.class) ? JUST_PLAIN : emptySet();
            }
            case Names.DIGEST_MD5: {
                return credentialType.isAssignableFrom(PasswordCredential.class) ? DIGEST_MD5_AND_PLAIN : emptySet();
            }
            case Names.DIGEST_SHA: {
                return credentialType.isAssignableFrom(PasswordCredential.class) ? DIGEST_SHA_AND_PLAIN : emptySet();
            }
            case Names.DIGEST_SHA_256: {
                return credentialType.isAssignableFrom(PasswordCredential.class) ? DIGEST_SHA_256_AND_PLAIN : emptySet();
            }
            case Names.DIGEST_SHA_384: {
                return credentialType.isAssignableFrom(PasswordCredential.class) ? DIGEST_SHA_384_AND_PLAIN : emptySet();
            }
            case Names.DIGEST_SHA_512: {
                return credentialType.isAssignableFrom(PasswordCredential.class) ? DIGEST_SHA_512_AND_PLAIN : emptySet();
            }
            case Names.DIGEST_SHA_512_256: {
                return credentialType.isAssignableFrom(PasswordCredential.class) ? DIGEST_SHA_512_256_AND_PLAIN : emptySet();
            }
            case Names.SCRAM_SHA_1:
            case Names.SCRAM_SHA_1_PLUS: {
                return credentialType.isAssignableFrom(PasswordCredential.class) ? SCRAM_SHA_1_AND_PLAIN : emptySet();
            }
            case Names.SCRAM_SHA_256:
            case Names.SCRAM_SHA_256_PLUS: {
                return credentialType.isAssignableFrom(PasswordCredential.class) ? SCRAM_SHA_256_AND_PLAIN : emptySet();
            }
            case Names.SCRAM_SHA_384:
            case Names.SCRAM_SHA_384_PLUS: {
                return credentialType.isAssignableFrom(PasswordCredential.class) ? SCRAM_SHA_384_AND_PLAIN : emptySet();
            }
            case Names.SCRAM_SHA_512:
            case Names.SCRAM_SHA_512_PLUS: {
                return credentialType.isAssignableFrom(PasswordCredential.class) ? SCRAM_SHA_512_AND_PLAIN : emptySet();
            }
            case Names.IEC_ISO_9798_M_DSA_SHA1:
            case Names.IEC_ISO_9798_U_DSA_SHA1: {
                return credentialType.isAssignableFrom(X509CertificateChainPrivateCredential.class) ? JUST_DSA : emptySet();
            }
            case Names.IEC_ISO_9798_M_ECDSA_SHA1:
            case Names.IEC_ISO_9798_U_ECDSA_SHA1: {
                // todo: double-check
                return credentialType.isAssignableFrom(X509CertificateChainPrivateCredential.class) ? JUST_EC : emptySet();
            }
            case Names.IEC_ISO_9798_M_RSA_SHA1_ENC:
            case Names.IEC_ISO_9798_U_RSA_SHA1_ENC: {
                return credentialType.isAssignableFrom(X509CertificateChainPrivateCredential.class) ? JUST_RSA : emptySet();
            }
            default:
                return emptySet();
        }
    }

    /**
     * Get the supported algorithm names for a SASL server mechanism and credential type.  If the mechanism or
     * credential type is not recognized, or if the given credential type does not use algorithms for the
     * given mechanism name, an empty set is returned.  If all algorithms are supported, a set containing the special
     * string {@code "*"} is returned.
     *
     * @param mechName the SASL mechanism name
     * @param credentialType the proposed credential type
     *
     * @return the set of algorithms, or an empty set if all algorithms have equal or unknown support
     */
    public static Set<String> getSupportedServerCredentialAlgorithms(String mechName, Class<? extends Credential> credentialType) {
        switch (mechName) {
            case Names.PLAIN: {
                return ALL_ALGORITHMS;
            }
            case Names.DIGEST_MD5: {
                return credentialType.isAssignableFrom(PasswordCredential.class) ? DIGEST_MD5_AND_PLAIN : emptySet();
            }
            case Names.DIGEST_SHA: {
                return credentialType.isAssignableFrom(PasswordCredential.class) ? DIGEST_SHA_AND_PLAIN : emptySet();
            }
            case Names.DIGEST_SHA_256: {
                return credentialType.isAssignableFrom(PasswordCredential.class) ? DIGEST_SHA_256_AND_PLAIN : emptySet();
            }
            case Names.DIGEST_SHA_384: {
                return credentialType.isAssignableFrom(PasswordCredential.class) ? DIGEST_SHA_384_AND_PLAIN : emptySet();
            }
            case Names.DIGEST_SHA_512: {
                return credentialType.isAssignableFrom(PasswordCredential.class) ? DIGEST_SHA_512_AND_PLAIN : emptySet();
            }
            case Names.DIGEST_SHA_512_256: {
                return credentialType.isAssignableFrom(PasswordCredential.class) ? DIGEST_SHA_512_256_AND_PLAIN : emptySet();
            }
            case Names.SCRAM_SHA_1:
            case Names.SCRAM_SHA_1_PLUS: {
                return credentialType.isAssignableFrom(PasswordCredential.class) ? SCRAM_SHA_1_AND_PLAIN : emptySet();
            }
            case Names.SCRAM_SHA_256:
            case Names.SCRAM_SHA_256_PLUS: {
                return credentialType.isAssignableFrom(PasswordCredential.class) ? SCRAM_SHA_256_AND_PLAIN : emptySet();
            }
            case Names.SCRAM_SHA_384:
            case Names.SCRAM_SHA_384_PLUS: {
                return credentialType.isAssignableFrom(PasswordCredential.class) ? SCRAM_SHA_384_AND_PLAIN : emptySet();
            }
            case Names.SCRAM_SHA_512:
            case Names.SCRAM_SHA_512_PLUS: {
                return credentialType.isAssignableFrom(PasswordCredential.class) ? SCRAM_SHA_512_AND_PLAIN : emptySet();
            }
            case Names.OTP: {
                return credentialType.isAssignableFrom(PasswordCredential.class) ? OTP_ALGORITHMS : emptySet();
            }
            case Names.IEC_ISO_9798_M_DSA_SHA1:
            case Names.IEC_ISO_9798_U_DSA_SHA1:
            case Names.IEC_ISO_9798_M_ECDSA_SHA1:
            case Names.IEC_ISO_9798_U_ECDSA_SHA1:
            case Names.IEC_ISO_9798_M_RSA_SHA1_ENC:
            case Names.IEC_ISO_9798_U_RSA_SHA1_ENC: {
                // TODO: look into verification process
                return emptySet();
            }
            default:
                return emptySet();
        }
    }

    /**
     * Get the supported evidence types for the given SASL server mechanism.  If an empty set is returned, then no
     * evidence is used by the mechanism.
     *
     * @param mechName the mechanism name
     * @return the set of allowed server credential types
     */
    public static Set<Class<? extends Evidence>> getSupportedServerEvidenceTypes(final String mechName) {
        switch (mechName) {
            case Names.OTP:
            case Names.CRAM_MD5:
            case Names.PLAIN:
            case Names.DIGEST_MD5:
            case Names.DIGEST_SHA:
            case Names.DIGEST_SHA_256:
            case Names.DIGEST_SHA_384:
            case Names.DIGEST_SHA_512:
            case Names.DIGEST_SHA_512_256:
            case Names.SCRAM_SHA_1:
            case Names.SCRAM_SHA_1_PLUS:
            case Names.SCRAM_SHA_256:
            case Names.SCRAM_SHA_256_PLUS:
            case Names.SCRAM_SHA_384:
            case Names.SCRAM_SHA_384_PLUS:
            case Names.SCRAM_SHA_512:
            case Names.SCRAM_SHA_512_PLUS: {
                return JUST_PASSWORD_EVIDENCE;
            }
            case Names.IEC_ISO_9798_M_DSA_SHA1:
            case Names.IEC_ISO_9798_U_DSA_SHA1:
            case Names.IEC_ISO_9798_M_ECDSA_SHA1:
            case Names.IEC_ISO_9798_U_ECDSA_SHA1:
            case Names.IEC_ISO_9798_M_RSA_SHA1_ENC:
            case Names.IEC_ISO_9798_U_RSA_SHA1_ENC: {
                // TODO: look into verification process
                return emptySet();
            }
            case Names.OAUTHBEARER: {
                return JUST_BEARER_TOKEN_EVIDENCE;
            }
            default:
                return emptySet();
        }
    }

    /**
     * Get the supported algorithm names for a SASL server mechanism and evidence type.  If the mechanism or
     * evidence type is not recognized, or if the given evidence type does not have an algorithm restriction for the
     * given mechanism name, an empty set is returned.
     *
     * @param mechName the SASL mechanism name
     * @param evidenceType the proposed evidence type
     *
     * @return the set of algorithms, or an empty set if all algorithms have equal or unknown support
     */
    public static Set<String> getSupportedServerEvidenceAlgorithms(final String mechName, final Class<? extends AlgorithmEvidence> evidenceType) {
        switch (mechName) {
            case Names.IEC_ISO_9798_M_DSA_SHA1:
            case Names.IEC_ISO_9798_U_DSA_SHA1:
            case Names.IEC_ISO_9798_M_ECDSA_SHA1:
            case Names.IEC_ISO_9798_U_ECDSA_SHA1:
            case Names.IEC_ISO_9798_M_RSA_SHA1_ENC:
            case Names.IEC_ISO_9798_U_RSA_SHA1_ENC: {
                // TODO: look into verification process
                return emptySet();
            }
            default:
                return emptySet();
        }
    }

    /**
     * Determine whether a mechanism needs server-side credentials in order to authenticate.  This may include credential
     * verification or acquisition, or both.
     *
     * @param mechName the mechanism name
     * @return {@code true} if the mechanism uses credentials, {@code false} otherwise
     */
    public static boolean needsServerCredentials(final String mechName) {
        switch (mechName) {
            case Names.ANONYMOUS:
            case Names.EXTERNAL:
            case Names.JBOSS_LOCAL_USER:
            case Names.GSSAPI:
            case Names.GS2_KRB5:
            case Names.GS2_KRB5_PLUS: {
                return false;
            }
            default: {
                return true;
            }
        }
    }

    /**
     * Determine whether the given mechanism name is known to not use any sort of {@link Principal} for authentication.
     *
     * @param mechName the mechanism name (must not be {@code null})
     * @return {@code true} if the mechanism does not use a principal, {@code false} if it does or it is not known
     */
    public static boolean doesNotUsePrincipal(final String mechName) {
        switch (mechName) {
            case Names.ANONYMOUS:
            case Names.EXTERNAL:
            case Names.GSSAPI:
            case Names.OAUTHBEARER: {
                return true;
            }
            default: {
                return false;
            }
        }
    }

    /**
     * Determine whether a mechanism does not need the client to present credentials.
     *
     * @param mechName the mechanism name
     * @return {@code true} if the mechanism does not require client credentials, {@code false} if it it does or it is not known
     */
    public static boolean doesNotRequireClientCredentials(final String mechName) {
        switch (mechName) {
            case Names.JBOSS_LOCAL_USER:
            case Names.ANONYMOUS:
            case Names.GSSAPI:
            case Names.EXTERNAL: {
                return true;
            }
            default: {
                return false;
            }
        }
    }

    /**
     * Determine whether a mechanism is known by WildFly Elytron.
     *
     * If the mechanism is not known the other methods in this class can not be relied upon.
     *
     * @param mechName the mechanism name
     * @return {@code true} if the mechanism is known to WildFly Elytron, {@code false} if it is not known
     */
    public static boolean isKnownMechanism(final String mechName) {
        return KNOWN_MECHS.contains(mechName);
    }

    @SafeVarargs
    private static <T> Set<T> nSet(T... values) {
        return unmodifiableSet(new LinkedHashSet<>(asList(values)));
    }
}
