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

package io.netty.handler.codec.quic;

import java.util.Arrays;
import java.security.Principal;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSessionBindingEvent;
import javax.net.ssl.SSLSessionBindingListener;
import java.util.HashMap;
import io.netty.util.internal.ObjectUtil;
import javax.net.ssl.SSLSessionContext;
import io.netty.handler.ssl.util.LazyJavaxX509Certificate;
import io.netty.handler.ssl.util.LazyX509Certificate;
import io.netty.util.internal.EmptyArrays;
import java.util.Map;
import javax.security.cert.X509Certificate;
import io.netty.handler.ssl.ClientAuth;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLEngineResult;
import java.nio.ByteBuffer;
import javax.net.ssl.SSLParameters;
import io.netty.util.NetUtil;
import java.util.function.LongFunction;
import java.util.Collections;
import javax.net.ssl.SNIHostName;
import org.jetbrains.annotations.Nullable;
import java.util.function.Consumer;
import javax.net.ssl.SNIServerName;
import java.util.List;
import java.security.cert.Certificate;

final class QuicheQuicSslEngine extends QuicSslEngine
{
    QuicheQuicSslContext ctx;
    private final String peerHost;
    private final int peerPort;
    private final QuicheQuicSslSession session;
    private volatile Certificate[] localCertificateChain;
    private List<SNIServerName> sniHostNames;
    private boolean handshakeFinished;
    private String applicationProtocol;
    private boolean sessionReused;
    final String tlsHostName;
    volatile QuicheQuicConnection connection;
    volatile Consumer<String> sniSelectedCallback;
    
    QuicheQuicSslEngine(final QuicheQuicSslContext ctx, @Nullable final String peerHost, final int peerPort) {
        this.session = new QuicheQuicSslSession();
        this.ctx = ctx;
        this.peerHost = peerHost;
        this.peerPort = peerPort;
        if (ctx.isClient() && isValidHostNameForSNI(peerHost)) {
            this.tlsHostName = peerHost;
            this.sniHostNames = (List<SNIServerName>)Collections.singletonList(new SNIHostName(this.tlsHostName));
        }
        else {
            this.tlsHostName = null;
        }
    }
    
    long moveTo(final String hostname, final QuicheQuicSslContext ctx) {
        this.ctx.remove(this);
        this.ctx = ctx;
        final long added = ctx.add(this);
        final Consumer<String> sniSelectedCallback = this.sniSelectedCallback;
        if (sniSelectedCallback != null) {
            sniSelectedCallback.accept(hostname);
        }
        return added;
    }
    
    @Nullable
    QuicheQuicConnection createConnection(final LongFunction<Long> connectionCreator) {
        return this.ctx.createConnection(connectionCreator, this);
    }
    
    void setLocalCertificateChain(final Certificate[] localCertificateChain) {
        this.localCertificateChain = localCertificateChain;
    }
    
    static boolean isValidHostNameForSNI(@Nullable final String hostname) {
        return hostname != null && hostname.indexOf(46) > 0 && !hostname.endsWith(".") && !NetUtil.isValidIpV4Address(hostname) && !NetUtil.isValidIpV6Address(hostname);
    }
    
    @Override
    public SSLParameters getSSLParameters() {
        final SSLParameters parameters = super.getSSLParameters();
        parameters.setServerNames(this.sniHostNames);
        return parameters;
    }
    
    @Override
    public synchronized String getApplicationProtocol() {
        return this.applicationProtocol;
    }
    
    @Override
    public synchronized String getHandshakeApplicationProtocol() {
        return this.applicationProtocol;
    }
    
    @Override
    public SSLEngineResult wrap(final ByteBuffer[] srcs, final int offset, final int length, final ByteBuffer dst) {
        throw new UnsupportedOperationException();
    }
    
    @Override
    public SSLEngineResult unwrap(final ByteBuffer src, final ByteBuffer[] dsts, final int offset, final int length) {
        throw new UnsupportedOperationException();
    }
    
    @Nullable
    @Override
    public Runnable getDelegatedTask() {
        return null;
    }
    
    @Override
    public void closeInbound() {
        throw new UnsupportedOperationException();
    }
    
    @Override
    public boolean isInboundDone() {
        return false;
    }
    
    @Override
    public void closeOutbound() {
        throw new UnsupportedOperationException();
    }
    
    @Override
    public boolean isOutboundDone() {
        return false;
    }
    
    @Override
    public String[] getSupportedCipherSuites() {
        return this.ctx.cipherSuites().toArray(new String[0]);
    }
    
    @Override
    public String[] getEnabledCipherSuites() {
        return this.getSupportedCipherSuites();
    }
    
    @Override
    public void setEnabledCipherSuites(final String[] suites) {
        throw new UnsupportedOperationException();
    }
    
    @Override
    public String[] getSupportedProtocols() {
        return new String[] { "TLSv1.3" };
    }
    
    @Override
    public String[] getEnabledProtocols() {
        return this.getSupportedProtocols();
    }
    
    @Override
    public void setEnabledProtocols(final String[] protocols) {
        throw new UnsupportedOperationException();
    }
    
    @Override
    public SSLSession getSession() {
        return this.session;
    }
    
    @Nullable
    @Override
    public SSLSession getHandshakeSession() {
        if (this.handshakeFinished) {
            return null;
        }
        return this.session;
    }
    
    @Override
    public void beginHandshake() {
    }
    
    @Override
    public SSLEngineResult.HandshakeStatus getHandshakeStatus() {
        if (this.handshakeFinished) {
            return SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING;
        }
        return SSLEngineResult.HandshakeStatus.NEED_WRAP;
    }
    
    @Override
    public void setUseClientMode(final boolean clientMode) {
        if (clientMode != this.ctx.isClient()) {
            throw new UnsupportedOperationException();
        }
    }
    
    @Override
    public boolean getUseClientMode() {
        return this.ctx.isClient();
    }
    
    @Override
    public void setNeedClientAuth(final boolean b) {
        throw new UnsupportedOperationException();
    }
    
    @Override
    public boolean getNeedClientAuth() {
        return this.ctx.clientAuth == ClientAuth.REQUIRE;
    }
    
    @Override
    public void setWantClientAuth(final boolean b) {
        throw new UnsupportedOperationException();
    }
    
    @Override
    public boolean getWantClientAuth() {
        return this.ctx.clientAuth == ClientAuth.OPTIONAL;
    }
    
    @Override
    public void setEnableSessionCreation(final boolean flag) {
        throw new UnsupportedOperationException();
    }
    
    @Override
    public boolean getEnableSessionCreation() {
        return false;
    }
    
    synchronized void handshakeFinished(final byte[] id, final String cipher, final String protocol, final byte[] peerCertificate, final byte[][] peerCertificateChain, final long creationTime, final long timeout, final byte[] applicationProtocol, final boolean sessionReused) {
        if (applicationProtocol == null) {
            this.applicationProtocol = null;
        }
        else {
            this.applicationProtocol = new String(applicationProtocol);
        }
        this.session.handshakeFinished(id, cipher, protocol, peerCertificate, peerCertificateChain, creationTime, timeout);
        this.sessionReused = sessionReused;
        this.handshakeFinished = true;
    }
    
    void removeSessionFromCacheIfInvalid() {
        this.session.removeFromCacheIfInvalid();
    }
    
    synchronized boolean isSessionReused() {
        return this.sessionReused;
    }
    
    private final class QuicheQuicSslSession implements SSLSession
    {
        private X509Certificate[] x509PeerCerts;
        private Certificate[] peerCerts;
        private String protocol;
        private String cipher;
        private byte[] id;
        private long creationTime;
        private long timeout;
        private boolean invalid;
        private long lastAccessedTime;
        private Map<String, Object> values;
        
        private QuicheQuicSslSession() {
            this.creationTime = -1L;
            this.timeout = -1L;
            this.lastAccessedTime = -1L;
        }
        
        private boolean isEmpty(final Object[] arr) {
            return arr == null || arr.length == 0;
        }
        
        private boolean isEmpty(final byte[] arr) {
            return arr == null || arr.length == 0;
        }
        
        void handshakeFinished(final byte[] id, final String cipher, final String protocol, final byte[] peerCertificate, final byte[][] peerCertificateChain, final long creationTime, final long timeout) {
            synchronized (QuicheQuicSslEngine.this) {
                this.initPeerCerts(peerCertificateChain, peerCertificate);
                this.id = id;
                this.cipher = cipher;
                this.protocol = protocol;
                this.creationTime = creationTime * 1000L;
                this.timeout = timeout * 1000L;
                this.lastAccessedTime = System.currentTimeMillis();
            }
        }
        
        void removeFromCacheIfInvalid() {
            if (!this.isValid()) {
                this.removeFromCache();
            }
        }
        
        private void removeFromCache() {
            final QuicClientSessionCache cache = QuicheQuicSslEngine.this.ctx.getSessionCache();
            if (cache != null) {
                cache.removeSession(this.getPeerHost(), this.getPeerPort());
            }
        }
        
        private void initPeerCerts(final byte[][] chain, final byte[] clientCert) {
            if (QuicheQuicSslEngine.this.getUseClientMode()) {
                if (this.isEmpty(chain)) {
                    this.peerCerts = EmptyArrays.EMPTY_CERTIFICATES;
                    this.x509PeerCerts = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES;
                }
                else {
                    this.peerCerts = new Certificate[chain.length];
                    this.x509PeerCerts = new X509Certificate[chain.length];
                    this.initCerts(chain, 0);
                }
            }
            else if (this.isEmpty(clientCert)) {
                this.peerCerts = EmptyArrays.EMPTY_CERTIFICATES;
                this.x509PeerCerts = EmptyArrays.EMPTY_JAVAX_X509_CERTIFICATES;
            }
            else if (this.isEmpty(chain)) {
                this.peerCerts = new Certificate[] { new LazyX509Certificate(clientCert) };
                this.x509PeerCerts = new X509Certificate[] { new LazyJavaxX509Certificate(clientCert) };
            }
            else {
                this.peerCerts = new Certificate[chain.length + 1];
                this.x509PeerCerts = new X509Certificate[chain.length + 1];
                this.peerCerts[0] = new LazyX509Certificate(clientCert);
                this.x509PeerCerts[0] = new LazyJavaxX509Certificate(clientCert);
                this.initCerts(chain, 1);
            }
        }
        
        private void initCerts(final byte[][] chain, final int startPos) {
            for (int i = 0; i < chain.length; ++i) {
                final int certPos = startPos + i;
                this.peerCerts[certPos] = new LazyX509Certificate(chain[i]);
                this.x509PeerCerts[certPos] = new LazyJavaxX509Certificate(chain[i]);
            }
        }
        
        @Override
        public byte[] getId() {
            synchronized (this) {
                if (this.id == null) {
                    return EmptyArrays.EMPTY_BYTES;
                }
                return this.id.clone();
            }
        }
        
        @Override
        public SSLSessionContext getSessionContext() {
            return QuicheQuicSslEngine.this.ctx.sessionContext();
        }
        
        @Override
        public long getCreationTime() {
            synchronized (QuicheQuicSslEngine.this) {
                return this.creationTime;
            }
        }
        
        @Override
        public long getLastAccessedTime() {
            return this.lastAccessedTime;
        }
        
        @Override
        public void invalidate() {
            final boolean removeFromCache;
            synchronized (this) {
                removeFromCache = !this.invalid;
                this.invalid = true;
            }
            if (removeFromCache) {
                this.removeFromCache();
            }
        }
        
        @Override
        public boolean isValid() {
            synchronized (QuicheQuicSslEngine.this) {
                return !this.invalid && System.currentTimeMillis() - this.timeout < this.creationTime;
            }
        }
        
        @Override
        public void putValue(final String name, final Object value) {
            ObjectUtil.checkNotNull(name, "name");
            ObjectUtil.checkNotNull(value, "value");
            final Object old;
            synchronized (this) {
                Map<String, Object> values = this.values;
                if (values == null) {
                    final HashMap<String, Object> values2 = new HashMap<String, Object>(2);
                    this.values = values2;
                    values = values2;
                }
                old = values.put(name, value);
            }
            if (value instanceof SSLSessionBindingListener) {
                ((SSLSessionBindingListener)value).valueBound(this.newSSLSessionBindingEvent(name));
            }
            this.notifyUnbound(old, name);
        }
        
        @Nullable
        @Override
        public Object getValue(final String name) {
            ObjectUtil.checkNotNull(name, "name");
            synchronized (this) {
                if (this.values == null) {
                    return null;
                }
                return this.values.get(name);
            }
        }
        
        @Override
        public void removeValue(final String name) {
            ObjectUtil.checkNotNull(name, "name");
            final Object old;
            synchronized (this) {
                final Map<String, Object> values = this.values;
                if (values == null) {
                    return;
                }
                old = values.remove(name);
            }
            this.notifyUnbound(old, name);
        }
        
        @Override
        public String[] getValueNames() {
            synchronized (this) {
                final Map<String, Object> values = this.values;
                if (values == null || values.isEmpty()) {
                    return EmptyArrays.EMPTY_STRINGS;
                }
                return values.keySet().toArray(new String[0]);
            }
        }
        
        private SSLSessionBindingEvent newSSLSessionBindingEvent(final String name) {
            return new SSLSessionBindingEvent(QuicheQuicSslEngine.this.session, name);
        }
        
        private void notifyUnbound(@Nullable final Object value, final String name) {
            if (value instanceof SSLSessionBindingListener) {
                ((SSLSessionBindingListener)value).valueUnbound(this.newSSLSessionBindingEvent(name));
            }
        }
        
        @Override
        public Certificate[] getPeerCertificates() throws SSLPeerUnverifiedException {
            synchronized (QuicheQuicSslEngine.this) {
                if (this.isEmpty(this.peerCerts)) {
                    throw new SSLPeerUnverifiedException("peer not verified");
                }
                return this.peerCerts.clone();
            }
        }
        
        @Override
        public Certificate[] getLocalCertificates() {
            final Certificate[] localCerts = QuicheQuicSslEngine.this.localCertificateChain;
            if (localCerts == null) {
                return null;
            }
            return localCerts.clone();
        }
        
        @Override
        public X509Certificate[] getPeerCertificateChain() throws SSLPeerUnverifiedException {
            synchronized (QuicheQuicSslEngine.this) {
                if (this.isEmpty(this.x509PeerCerts)) {
                    throw new SSLPeerUnverifiedException("peer not verified");
                }
                return this.x509PeerCerts.clone();
            }
        }
        
        @Override
        public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
            final Certificate[] peer = this.getPeerCertificates();
            return ((java.security.cert.X509Certificate)peer[0]).getSubjectX500Principal();
        }
        
        @Nullable
        @Override
        public Principal getLocalPrincipal() {
            final Certificate[] local = QuicheQuicSslEngine.this.localCertificateChain;
            if (local == null || local.length == 0) {
                return null;
            }
            return ((java.security.cert.X509Certificate)local[0]).getIssuerX500Principal();
        }
        
        @Override
        public String getCipherSuite() {
            return this.cipher;
        }
        
        @Override
        public String getProtocol() {
            return this.protocol;
        }
        
        @Nullable
        @Override
        public String getPeerHost() {
            return QuicheQuicSslEngine.this.peerHost;
        }
        
        @Override
        public int getPeerPort() {
            return QuicheQuicSslEngine.this.peerPort;
        }
        
        @Override
        public int getPacketBufferSize() {
            return -1;
        }
        
        @Override
        public int getApplicationBufferSize() {
            return -1;
        }
        
        @Override
        public boolean equals(final Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || this.getClass() != o.getClass()) {
                return false;
            }
            final QuicheQuicSslSession that = (QuicheQuicSslSession)o;
            return Arrays.equals(this.getId(), that.getId());
        }
        
        @Override
        public int hashCode() {
            return Arrays.hashCode(this.getId());
        }
    }
}
