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

package io.netty.handler.codec.quic;

import io.netty.util.AsciiString;
import io.netty.util.internal.SystemPropertyUtil;
import java.util.Iterator;
import org.jetbrains.annotations.Nullable;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

final class QuicClientSessionCache
{
    private static final int DEFAULT_CACHE_SIZE;
    private final AtomicInteger maximumCacheSize;
    private final AtomicInteger sessionTimeout;
    private int sessionCounter;
    private final Map<HostPort, SessionHolder> sessions;
    
    QuicClientSessionCache() {
        this.maximumCacheSize = new AtomicInteger(QuicClientSessionCache.DEFAULT_CACHE_SIZE);
        this.sessionTimeout = new AtomicInteger(300);
        this.sessions = new LinkedHashMap<HostPort, SessionHolder>() {
            private static final long serialVersionUID = -7773696788135734448L;
            
            @Override
            protected boolean removeEldestEntry(final Map.Entry<HostPort, SessionHolder> eldest) {
                final int maxSize = QuicClientSessionCache.this.maximumCacheSize.get();
                return maxSize >= 0 && this.size() > maxSize;
            }
        };
    }
    
    void saveSession(@Nullable final String host, final int port, final long creationTime, final long timeout, final byte[] session, final boolean isSingleUse) {
        final HostPort hostPort = keyFor(host, port);
        if (hostPort != null) {
            synchronized (this.sessions) {
                if (++this.sessionCounter == 255) {
                    this.sessionCounter = 0;
                    this.expungeInvalidSessions();
                }
                this.sessions.put(hostPort, new SessionHolder(creationTime, timeout, session, isSingleUse));
            }
        }
    }
    
    boolean hasSession(@Nullable final String host, final int port) {
        final HostPort hostPort = keyFor(host, port);
        if (hostPort != null) {
            synchronized (this.sessions) {
                return this.sessions.containsKey(hostPort);
            }
        }
        return false;
    }
    
    byte[] getSession(@Nullable final String host, final int port) {
        final HostPort hostPort = keyFor(host, port);
        if (hostPort != null) {
            final SessionHolder sessionHolder;
            synchronized (this.sessions) {
                sessionHolder = this.sessions.get(hostPort);
                if (sessionHolder == null) {
                    return null;
                }
                if (sessionHolder.isSingleUse()) {
                    this.sessions.remove(hostPort);
                }
            }
            if (sessionHolder.isValid()) {
                return sessionHolder.sessionBytes();
            }
        }
        return null;
    }
    
    void removeSession(@Nullable final String host, final int port) {
        final HostPort hostPort = keyFor(host, port);
        if (hostPort != null) {
            synchronized (this.sessions) {
                this.sessions.remove(hostPort);
            }
        }
    }
    
    void setSessionTimeout(final int seconds) {
        final int oldTimeout = this.sessionTimeout.getAndSet(seconds);
        if (oldTimeout > seconds) {
            this.clear();
        }
    }
    
    int getSessionTimeout() {
        return this.sessionTimeout.get();
    }
    
    void setSessionCacheSize(final int size) {
        final long oldSize = this.maximumCacheSize.getAndSet(size);
        if (oldSize > size || size == 0) {
            this.clear();
        }
    }
    
    int getSessionCacheSize() {
        return this.maximumCacheSize.get();
    }
    
    void clear() {
        synchronized (this.sessions) {
            this.sessions.clear();
        }
    }
    
    private void expungeInvalidSessions() {
        assert Thread.holdsLock(this.sessions);
        if (this.sessions.isEmpty()) {
            return;
        }
        final long now = System.currentTimeMillis();
        final Iterator<Map.Entry<HostPort, SessionHolder>> iterator = this.sessions.entrySet().iterator();
        while (iterator.hasNext()) {
            final SessionHolder sessionHolder = (SessionHolder)iterator.next().getValue();
            if (sessionHolder.isValid(now)) {
                break;
            }
            iterator.remove();
        }
    }
    
    @Nullable
    private static HostPort keyFor(@Nullable final String host, final int port) {
        if (host == null && port < 1) {
            return null;
        }
        return new HostPort(host, port);
    }
    
    static {
        final int cacheSize = SystemPropertyUtil.getInt("javax.net.ssl.sessionCacheSize", 20480);
        if (cacheSize >= 0) {
            DEFAULT_CACHE_SIZE = cacheSize;
        }
        else {
            DEFAULT_CACHE_SIZE = 20480;
        }
    }
    
    private static final class SessionHolder
    {
        private final long creationTime;
        private final long timeout;
        private final byte[] sessionBytes;
        private final boolean isSingleUse;
        
        SessionHolder(final long creationTime, final long timeout, final byte[] session, final boolean isSingleUse) {
            this.creationTime = creationTime;
            this.timeout = timeout;
            this.sessionBytes = session;
            this.isSingleUse = isSingleUse;
        }
        
        boolean isValid() {
            return this.isValid(System.currentTimeMillis());
        }
        
        boolean isValid(final long current) {
            return current <= this.creationTime + this.timeout;
        }
        
        boolean isSingleUse() {
            return this.isSingleUse;
        }
        
        byte[] sessionBytes() {
            return this.sessionBytes;
        }
    }
    
    private static final class HostPort
    {
        private final int hash;
        private final String host;
        private final int port;
        
        HostPort(@Nullable final String host, final int port) {
            this.host = host;
            this.port = port;
            this.hash = 31 * AsciiString.hashCode(host) + port;
        }
        
        @Override
        public int hashCode() {
            return this.hash;
        }
        
        @Override
        public boolean equals(final Object obj) {
            if (!(obj instanceof HostPort)) {
                return false;
            }
            final HostPort other = (HostPort)obj;
            return this.port == other.port && this.host.equalsIgnoreCase(other.host);
        }
        
        @Override
        public String toString() {
            return "HostPort{host='" + this.host + '\'' + ", port=" + this.port + '}';
        }
    }
}
