// 
// Decompiled by Procyon v0.6.0
// 

package io.netty.handler.codec.quic;

import io.netty.util.concurrent.Future;
import java.util.function.BiConsumer;
import io.netty.util.AbstractReferenceCounted;
import java.util.NoSuchElementException;
import java.util.Enumeration;
import javax.net.ssl.SSLSession;
import java.util.Collections;
import java.util.Iterator;
import io.netty.util.internal.SystemPropertyUtil;
import io.netty.util.internal.logging.InternalLoggerFactory;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSessionContext;
import java.util.concurrent.Executor;
import io.netty.handler.ssl.SslHandler;
import io.netty.buffer.ByteBufAllocator;
import java.util.List;
import io.netty.util.ReferenceCounted;
import java.util.function.LongFunction;
import java.io.IOException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.CertificateException;
import io.netty.handler.ssl.SslContext;
import java.security.cert.X509Certificate;
import java.io.File;
import javax.net.ssl.TrustManager;
import javax.net.ssl.KeyManager;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509TrustManager;
import java.util.Arrays;
import java.util.Set;
import java.util.LinkedHashSet;
import io.netty.util.internal.EmptyArrays;
import java.security.KeyStore;
import io.netty.util.internal.ObjectUtil;
import io.netty.handler.ssl.SslContextOption;
import java.util.Map;
import io.netty.util.Mapping;
import javax.net.ssl.KeyManagerFactory;
import org.jetbrains.annotations.Nullable;
import javax.net.ssl.TrustManagerFactory;
import io.netty.handler.ssl.ApplicationProtocolNegotiator;
import io.netty.handler.ssl.ClientAuth;
import io.netty.util.internal.logging.InternalLogger;

final class QuicheQuicSslContext extends QuicSslContext
{
    private static final InternalLogger LOGGER;
    private static final String[] DEFAULT_NAMED_GROUPS;
    private static final String[] NAMED_GROUPS;
    final ClientAuth clientAuth;
    private final boolean server;
    private final ApplicationProtocolNegotiator apn;
    private long sessionCacheSize;
    private long sessionTimeout;
    private final QuicheQuicSslSessionContext sessionCtx;
    private final QuicheQuicSslEngineMap engineMap;
    private final QuicClientSessionCache sessionCache;
    private final BoringSSLSessionTicketCallback sessionTicketCallback;
    final NativeSslContext nativeSslContext;
    
    QuicheQuicSslContext(final boolean server, final long sessionTimeout, final long sessionCacheSize, final ClientAuth clientAuth, @Nullable TrustManagerFactory trustManagerFactory, @Nullable final KeyManagerFactory keyManagerFactory, final String password, @Nullable final Mapping<? super String, ? extends QuicSslContext> mapping, @Nullable final Boolean earlyData, @Nullable final BoringSSLKeylog keylog, final String[] applicationProtocols, final Map.Entry<SslContextOption<?>, Object>... ctxOptions) {
        this.engineMap = new QuicheQuicSslEngineMap();
        this.sessionTicketCallback = new BoringSSLSessionTicketCallback();
        Quic.ensureAvailability();
        this.server = server;
        this.clientAuth = (server ? ObjectUtil.checkNotNull(clientAuth, "clientAuth") : ClientAuth.NONE);
        X509TrustManager trustManager = null;
        Label_0109: {
            if (trustManagerFactory == null) {
                try {
                    trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                    trustManagerFactory.init((KeyStore)null);
                    trustManager = chooseTrustManager(trustManagerFactory);
                    break Label_0109;
                }
                catch (final Exception e) {
                    throw new IllegalStateException(e);
                }
            }
            trustManager = chooseTrustManager(trustManagerFactory);
        }
        X509ExtendedKeyManager keyManager;
        if (keyManagerFactory == null) {
            if (server) {
                throw new IllegalArgumentException("No KeyManagerFactory");
            }
            keyManager = null;
        }
        else {
            keyManager = this.chooseKeyManager(keyManagerFactory);
        }
        String[] groups = QuicheQuicSslContext.NAMED_GROUPS;
        String[] sigalgs = EmptyArrays.EMPTY_STRINGS;
        Map<String, String> serverKeyTypes = null;
        Set<String> clientKeyTypes = null;
        if (ctxOptions != null) {
            for (final Map.Entry<SslContextOption<?>, Object> ctxOpt : ctxOptions) {
                final SslContextOption<?> option = ctxOpt.getKey();
                if (option == BoringSSLContextOption.GROUPS) {
                    final String[] groupsArray = ctxOpt.getValue();
                    final Set<String> groupsSet = new LinkedHashSet<String>(groupsArray.length);
                    for (final String group : groupsArray) {
                        groupsSet.add(GroupsConverter.toBoringSSL(group));
                    }
                    groups = groupsSet.toArray(EmptyArrays.EMPTY_STRINGS);
                }
                else if (option == BoringSSLContextOption.SIGNATURE_ALGORITHMS) {
                    final String[] sigalgsArray = ctxOpt.getValue();
                    final Set<String> sigalgsSet = new LinkedHashSet<String>(sigalgsArray.length);
                    for (final String sigalg : sigalgsArray) {
                        sigalgsSet.add(sigalg);
                    }
                    sigalgs = sigalgsSet.toArray(EmptyArrays.EMPTY_STRINGS);
                }
                else if (option == BoringSSLContextOption.CLIENT_KEY_TYPES) {
                    clientKeyTypes = ctxOpt.getValue();
                }
                else if (option == BoringSSLContextOption.SERVER_KEY_TYPES) {
                    serverKeyTypes = ctxOpt.getValue();
                }
                else {
                    QuicheQuicSslContext.LOGGER.debug("Skipping unsupported " + SslContextOption.class.getSimpleName() + ": " + ctxOpt.getKey());
                }
            }
        }
        BoringSSLPrivateKeyMethod privateKeyMethod;
        if (keyManagerFactory instanceof BoringSSLKeylessManagerFactory) {
            privateKeyMethod = new BoringSSLAsyncPrivateKeyMethodAdapter(this.engineMap, ((BoringSSLKeylessManagerFactory)keyManagerFactory).privateKeyMethod);
        }
        else {
            privateKeyMethod = null;
        }
        this.sessionCache = (server ? null : new QuicClientSessionCache());
        final int verifyMode = server ? boringSSLVerifyModeForServer(this.clientAuth) : BoringSSL.SSL_VERIFY_PEER;
        this.nativeSslContext = new NativeSslContext(BoringSSL.SSLContext_new(server, applicationProtocols, new BoringSSLHandshakeCompleteCallback(this.engineMap), new BoringSSLCertificateCallback(this.engineMap, keyManager, password, serverKeyTypes, clientKeyTypes), new BoringSSLCertificateVerifyCallback(this.engineMap, trustManager), (mapping == null) ? null : new BoringSSLTlsextServernameCallback(this.engineMap, mapping), (keylog == null) ? null : new BoringSSLKeylogCallback(this.engineMap, keylog), server ? null : new BoringSSLSessionCallback(this.engineMap, this.sessionCache), privateKeyMethod, this.sessionTicketCallback, verifyMode, BoringSSL.subjectNames(trustManager.getAcceptedIssuers())));
        boolean success = false;
        try {
            if (groups.length > 0 && BoringSSL.SSLContext_set1_groups_list(this.nativeSslContext.ctx, groups) == 0) {
                String msg = "failed to set curves / groups list: " + Arrays.toString(groups);
                final String lastError = BoringSSL.ERR_last_error();
                if (lastError != null) {
                    msg = msg + ". " + lastError;
                }
                throw new IllegalStateException(msg);
            }
            if (sigalgs.length > 0 && BoringSSL.SSLContext_set1_sigalgs_list(this.nativeSslContext.ctx, sigalgs) == 0) {
                String msg = "failed to set signature algorithm list: " + Arrays.toString(sigalgs);
                final String lastError = BoringSSL.ERR_last_error();
                if (lastError != null) {
                    msg = msg + ". " + lastError;
                }
                throw new IllegalStateException(msg);
            }
            this.apn = new QuicheQuicApplicationProtocolNegotiator(applicationProtocols);
            if (this.sessionCache != null) {
                this.sessionCache.setSessionCacheSize((int)sessionCacheSize);
                this.sessionCache.setSessionTimeout((int)sessionTimeout);
            }
            else {
                BoringSSL.SSLContext_setSessionCacheSize(this.nativeSslContext.address(), sessionCacheSize);
                this.sessionCacheSize = sessionCacheSize;
                BoringSSL.SSLContext_setSessionCacheTimeout(this.nativeSslContext.address(), sessionTimeout);
                this.sessionTimeout = sessionTimeout;
            }
            if (earlyData != null) {
                BoringSSL.SSLContext_set_early_data_enabled(this.nativeSslContext.address(), earlyData);
            }
            this.sessionCtx = new QuicheQuicSslSessionContext(this);
            success = true;
        }
        finally {
            if (!success) {
                this.nativeSslContext.release();
            }
        }
    }
    
    private X509ExtendedKeyManager chooseKeyManager(final KeyManagerFactory keyManagerFactory) {
        for (final KeyManager manager : keyManagerFactory.getKeyManagers()) {
            if (manager instanceof X509ExtendedKeyManager) {
                return (X509ExtendedKeyManager)manager;
            }
        }
        throw new IllegalArgumentException("No X509ExtendedKeyManager included");
    }
    
    private static X509TrustManager chooseTrustManager(final TrustManagerFactory trustManagerFactory) {
        for (final TrustManager manager : trustManagerFactory.getTrustManagers()) {
            if (manager instanceof X509TrustManager) {
                return (X509TrustManager)manager;
            }
        }
        throw new IllegalArgumentException("No X509TrustManager included");
    }
    
    static X509Certificate[] toX509Certificates0(@Nullable final File file) throws CertificateException {
        return SslContext.toX509Certificates(file);
    }
    
    static PrivateKey toPrivateKey0(@Nullable final File keyFile, @Nullable final String keyPassword) throws Exception {
        return SslContext.toPrivateKey(keyFile, keyPassword);
    }
    
    static TrustManagerFactory buildTrustManagerFactory0(final X509Certificate[] certCollection) throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException {
        return SslContext.buildTrustManagerFactory(certCollection, null, null);
    }
    
    private static int boringSSLVerifyModeForServer(final ClientAuth mode) {
        switch (mode) {
            case NONE: {
                return BoringSSL.SSL_VERIFY_NONE;
            }
            case REQUIRE: {
                return BoringSSL.SSL_VERIFY_PEER | BoringSSL.SSL_VERIFY_FAIL_IF_NO_PEER_CERT;
            }
            case OPTIONAL: {
                return BoringSSL.SSL_VERIFY_PEER;
            }
            default: {
                throw new Error("Unexpected mode: " + mode);
            }
        }
    }
    
    @Nullable
    QuicheQuicConnection createConnection(final LongFunction<Long> connectionCreator, final QuicheQuicSslEngine engine) {
        this.nativeSslContext.retain();
        final long ssl = BoringSSL.SSL_new(this.nativeSslContext.address(), this.isServer(), engine.tlsHostName);
        this.engineMap.put(ssl, engine);
        final long connection = connectionCreator.apply(ssl);
        if (connection == -1L) {
            this.engineMap.remove(ssl);
            this.nativeSslContext.release();
            return null;
        }
        return new QuicheQuicConnection(connection, ssl, engine, this.nativeSslContext);
    }
    
    long add(final QuicheQuicSslEngine engine) {
        this.nativeSslContext.retain();
        engine.connection.reattach(this.nativeSslContext);
        this.engineMap.put(engine.connection.ssl, engine);
        return this.nativeSslContext.address();
    }
    
    void remove(final QuicheQuicSslEngine engine) {
        final QuicheQuicSslEngine removed = this.engineMap.remove(engine.connection.ssl);
        assert removed == engine;
        engine.removeSessionFromCacheIfInvalid();
    }
    
    @Nullable
    QuicClientSessionCache getSessionCache() {
        return this.sessionCache;
    }
    
    @Override
    public boolean isClient() {
        return !this.server;
    }
    
    @Override
    public List<String> cipherSuites() {
        return Arrays.asList("TLS_AES_128_GCM_SHA256", "TLS_AES_256_GCM_SHA384");
    }
    
    @Override
    public long sessionCacheSize() {
        if (this.sessionCache != null) {
            return this.sessionCache.getSessionCacheSize();
        }
        synchronized (this) {
            return this.sessionCacheSize;
        }
    }
    
    @Override
    public long sessionTimeout() {
        if (this.sessionCache != null) {
            return this.sessionCache.getSessionTimeout();
        }
        synchronized (this) {
            return this.sessionTimeout;
        }
    }
    
    @Override
    public ApplicationProtocolNegotiator applicationProtocolNegotiator() {
        return this.apn;
    }
    
    @Override
    public QuicSslEngine newEngine(final ByteBufAllocator alloc) {
        return new QuicheQuicSslEngine(this, null, -1);
    }
    
    @Override
    public QuicSslEngine newEngine(final ByteBufAllocator alloc, final String peerHost, final int peerPort) {
        return new QuicheQuicSslEngine(this, peerHost, peerPort);
    }
    
    @Override
    public QuicSslSessionContext sessionContext() {
        return this.sessionCtx;
    }
    
    @Override
    protected SslHandler newHandler(final ByteBufAllocator alloc, final boolean startTls) {
        throw new UnsupportedOperationException();
    }
    
    @Override
    public SslHandler newHandler(final ByteBufAllocator alloc, final Executor delegatedTaskExecutor) {
        throw new UnsupportedOperationException();
    }
    
    @Override
    protected SslHandler newHandler(final ByteBufAllocator alloc, final boolean startTls, final Executor executor) {
        throw new UnsupportedOperationException();
    }
    
    @Override
    protected SslHandler newHandler(final ByteBufAllocator alloc, final String peerHost, final int peerPort, final boolean startTls) {
        throw new UnsupportedOperationException();
    }
    
    @Override
    public SslHandler newHandler(final ByteBufAllocator alloc, final String peerHost, final int peerPort, final Executor delegatedTaskExecutor) {
        throw new UnsupportedOperationException();
    }
    
    @Override
    protected SslHandler newHandler(final ByteBufAllocator alloc, final String peerHost, final int peerPort, final boolean startTls, final Executor delegatedTaskExecutor) {
        throw new UnsupportedOperationException();
    }
    
    @Override
    protected void finalize() throws Throwable {
        try {
            this.nativeSslContext.release();
        }
        finally {
            super.finalize();
        }
    }
    
    void setSessionTimeout(final int seconds) throws IllegalArgumentException {
        if (this.sessionCache != null) {
            this.sessionCache.setSessionTimeout(seconds);
        }
        else {
            BoringSSL.SSLContext_setSessionCacheTimeout(this.nativeSslContext.address(), seconds);
            this.sessionTimeout = seconds;
        }
    }
    
    void setSessionCacheSize(final int size) throws IllegalArgumentException {
        if (this.sessionCache != null) {
            this.sessionCache.setSessionCacheSize(size);
        }
        else {
            BoringSSL.SSLContext_setSessionCacheSize(this.nativeSslContext.address(), size);
            this.sessionCacheSize = size;
        }
    }
    
    void setSessionTicketKeys(final SslSessionTicketKey[] ticketKeys) {
        this.sessionTicketCallback.setSessionTicketKeys(ticketKeys);
        BoringSSL.SSLContext_setSessionTicketKeys(this.nativeSslContext.address(), ticketKeys != null && ticketKeys.length != 0);
    }
    
    static {
        LOGGER = InternalLoggerFactory.getInstance(QuicheQuicSslContext.class);
        DEFAULT_NAMED_GROUPS = new String[] { "x25519", "secp256r1", "secp384r1", "secp521r1" };
        String[] namedGroups = QuicheQuicSslContext.DEFAULT_NAMED_GROUPS;
        final Set<String> defaultConvertedNamedGroups = new LinkedHashSet<String>(namedGroups.length);
        for (int i = 0; i < namedGroups.length; ++i) {
            defaultConvertedNamedGroups.add(GroupsConverter.toBoringSSL(namedGroups[i]));
        }
        if (Quic.isAvailable()) {
            final long sslCtx = BoringSSL.SSLContext_new();
            try {
                final Iterator<String> defaultGroupsIter = defaultConvertedNamedGroups.iterator();
                while (defaultGroupsIter.hasNext()) {
                    if (BoringSSL.SSLContext_set1_groups_list(sslCtx, new String[] { defaultGroupsIter.next() }) == 0) {
                        defaultGroupsIter.remove();
                    }
                }
                final String groups = SystemPropertyUtil.get("jdk.tls.namedGroups", null);
                if (groups != null) {
                    final String[] nGroups = groups.split(",");
                    final Set<String> supportedNamedGroups = new LinkedHashSet<String>(nGroups.length);
                    final Set<String> supportedConvertedNamedGroups = new LinkedHashSet<String>(nGroups.length);
                    final Set<String> unsupportedNamedGroups = new LinkedHashSet<String>();
                    for (final String namedGroup : nGroups) {
                        final String converted = GroupsConverter.toBoringSSL(namedGroup);
                        if (BoringSSL.SSLContext_set1_groups_list(sslCtx, new String[] { converted }) == 0) {
                            unsupportedNamedGroups.add(namedGroup);
                        }
                        else {
                            supportedConvertedNamedGroups.add(converted);
                            supportedNamedGroups.add(namedGroup);
                        }
                    }
                    if (supportedNamedGroups.isEmpty()) {
                        namedGroups = defaultConvertedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS);
                        QuicheQuicSslContext.LOGGER.info("All configured namedGroups are not supported: {}. Use default: {}.", Arrays.toString(unsupportedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS)), Arrays.toString(QuicheQuicSslContext.DEFAULT_NAMED_GROUPS));
                    }
                    else {
                        final String[] groupArray = supportedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS);
                        if (unsupportedNamedGroups.isEmpty()) {
                            QuicheQuicSslContext.LOGGER.info("Using configured namedGroups -D 'jdk.tls.namedGroup': {} ", Arrays.toString(groupArray));
                        }
                        else {
                            QuicheQuicSslContext.LOGGER.info("Using supported configured namedGroups: {}. Unsupported namedGroups: {}. ", Arrays.toString(groupArray), Arrays.toString(unsupportedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS)));
                        }
                        namedGroups = supportedConvertedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS);
                    }
                }
                else {
                    namedGroups = defaultConvertedNamedGroups.toArray(EmptyArrays.EMPTY_STRINGS);
                }
            }
            finally {
                BoringSSL.SSLContext_free(sslCtx);
            }
        }
        NAMED_GROUPS = namedGroups;
    }
    
    private static final class QuicheQuicApplicationProtocolNegotiator implements ApplicationProtocolNegotiator
    {
        private final List<String> protocols;
        
        QuicheQuicApplicationProtocolNegotiator(final String... protocols) {
            if (protocols == null) {
                this.protocols = Collections.emptyList();
            }
            else {
                this.protocols = Collections.unmodifiableList((List<? extends String>)Arrays.asList((T[])protocols));
            }
        }
        
        @Override
        public List<String> protocols() {
            return this.protocols;
        }
    }
    
    private static final class QuicheQuicSslSessionContext implements QuicSslSessionContext
    {
        private final QuicheQuicSslContext context;
        
        QuicheQuicSslSessionContext(final QuicheQuicSslContext context) {
            this.context = context;
        }
        
        @Nullable
        @Override
        public SSLSession getSession(final byte[] sessionId) {
            return null;
        }
        
        @Override
        public Enumeration<byte[]> getIds() {
            return new Enumeration<byte[]>() {
                @Override
                public boolean hasMoreElements() {
                    return false;
                }
                
                @Override
                public byte[] nextElement() {
                    throw new NoSuchElementException();
                }
            };
        }
        
        @Override
        public void setSessionTimeout(final int seconds) throws IllegalArgumentException {
            this.context.setSessionTimeout(seconds);
        }
        
        @Override
        public int getSessionTimeout() {
            return (int)this.context.sessionTimeout();
        }
        
        @Override
        public void setSessionCacheSize(final int size) throws IllegalArgumentException {
            this.context.setSessionCacheSize(size);
        }
        
        @Override
        public int getSessionCacheSize() {
            return (int)this.context.sessionCacheSize();
        }
        
        @Override
        public void setTicketKeys(final SslSessionTicketKey... keys) {
            this.context.setSessionTicketKeys(keys);
        }
    }
    
    static final class NativeSslContext extends AbstractReferenceCounted
    {
        private final long ctx;
        
        NativeSslContext(final long ctx) {
            this.ctx = ctx;
        }
        
        long address() {
            return this.ctx;
        }
        
        @Override
        protected void deallocate() {
            BoringSSL.SSLContext_free(this.ctx);
        }
        
        @Override
        public ReferenceCounted touch(final Object hint) {
            return this;
        }
        
        @Override
        public String toString() {
            return "NativeSslContext{ctx=" + this.ctx + '}';
        }
    }
    
    private static final class BoringSSLAsyncPrivateKeyMethodAdapter implements BoringSSLPrivateKeyMethod
    {
        private final QuicheQuicSslEngineMap engineMap;
        private final BoringSSLAsyncPrivateKeyMethod privateKeyMethod;
        
        BoringSSLAsyncPrivateKeyMethodAdapter(final QuicheQuicSslEngineMap engineMap, final BoringSSLAsyncPrivateKeyMethod privateKeyMethod) {
            this.engineMap = engineMap;
            this.privateKeyMethod = privateKeyMethod;
        }
        
        @Override
        public void sign(final long ssl, final int signatureAlgorithm, final byte[] input, final BiConsumer<byte[], Throwable> callback) {
            final QuicheQuicSslEngine engine = this.engineMap.get(ssl);
            if (engine == null) {
                callback.accept(null, null);
            }
            else {
                this.privateKeyMethod.sign(engine, signatureAlgorithm, input).addListener(f -> {
                    final Throwable cause = f.cause();
                    if (cause != null) {
                        callback.accept(null, cause);
                    }
                    else {
                        callback.accept(f.getNow(), null);
                    }
                });
            }
        }
        
        @Override
        public void decrypt(final long ssl, final byte[] input, final BiConsumer<byte[], Throwable> callback) {
            final QuicheQuicSslEngine engine = this.engineMap.get(ssl);
            if (engine == null) {
                callback.accept(null, null);
            }
            else {
                this.privateKeyMethod.decrypt(engine, input).addListener(f -> {
                    final Throwable cause = f.cause();
                    if (cause != null) {
                        callback.accept(null, cause);
                    }
                    else {
                        callback.accept(f.getNow(), null);
                    }
                });
            }
        }
    }
}
