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

package io.netty.handler.ssl;

import io.netty.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.internal.ObjectUtil;
import java.util.Locale;
import io.netty.util.CharsetUtil;
import io.netty.buffer.ByteBuf;
import io.netty.util.concurrent.ScheduledFuture;

public abstract class AbstractSniHandler<T> extends SslClientHelloHandler<T>
{
    protected final long handshakeTimeoutMillis;
    private ScheduledFuture<?> timeoutFuture;
    private String hostname;
    
    private static String extractSniHostname(final ByteBuf in) {
        int offset = in.readerIndex();
        final int endOffset = in.writerIndex();
        offset += 34;
        if (endOffset - offset >= 6) {
            final int sessionIdLength = in.getUnsignedByte(offset);
            offset += sessionIdLength + 1;
            final int cipherSuitesLength = in.getUnsignedShort(offset);
            offset += cipherSuitesLength + 2;
            final int compressionMethodLength = in.getUnsignedByte(offset);
            offset += compressionMethodLength + 1;
            final int extensionsLength = in.getUnsignedShort(offset);
            offset += 2;
            final int extensionsLimit = offset + extensionsLength;
            if (extensionsLimit <= endOffset) {
                while (extensionsLimit - offset >= 4) {
                    final int extensionType = in.getUnsignedShort(offset);
                    offset += 2;
                    final int extensionLength = in.getUnsignedShort(offset);
                    offset += 2;
                    if (extensionsLimit - offset < extensionLength) {
                        break;
                    }
                    if (extensionType == 0) {
                        offset += 2;
                        if (extensionsLimit - offset < 3) {
                            break;
                        }
                        final int serverNameType = in.getUnsignedByte(offset);
                        ++offset;
                        if (serverNameType != 0) {
                            break;
                        }
                        final int serverNameLength = in.getUnsignedShort(offset);
                        offset += 2;
                        if (extensionsLimit - offset < serverNameLength) {
                            break;
                        }
                        final String hostname = in.toString(offset, serverNameLength, CharsetUtil.US_ASCII);
                        return hostname.toLowerCase(Locale.US);
                    }
                    else {
                        offset += extensionLength;
                    }
                }
            }
        }
        return null;
    }
    
    protected AbstractSniHandler(final long handshakeTimeoutMillis) {
        this(0, handshakeTimeoutMillis);
    }
    
    protected AbstractSniHandler(final int maxClientHelloLength, final long handshakeTimeoutMillis) {
        super(maxClientHelloLength);
        this.handshakeTimeoutMillis = ObjectUtil.checkPositiveOrZero(handshakeTimeoutMillis, "handshakeTimeoutMillis");
    }
    
    public AbstractSniHandler() {
        this(0, 0L);
    }
    
    @Override
    public void handlerAdded(final ChannelHandlerContext ctx) throws Exception {
        if (ctx.channel().isActive()) {
            this.checkStartTimeout(ctx);
        }
    }
    
    @Override
    public void channelActive(final ChannelHandlerContext ctx) throws Exception {
        ctx.fireChannelActive();
        this.checkStartTimeout(ctx);
    }
    
    private void checkStartTimeout(final ChannelHandlerContext ctx) {
        if (this.handshakeTimeoutMillis <= 0L || this.timeoutFuture != null) {
            return;
        }
        this.timeoutFuture = ctx.executor().schedule((Runnable)new Runnable() {
            @Override
            public void run() {
                if (ctx.channel().isActive()) {
                    final SslHandshakeTimeoutException exception = new SslHandshakeTimeoutException("handshake timed out after " + AbstractSniHandler.this.handshakeTimeoutMillis + "ms");
                    ctx.fireUserEventTriggered((Object)new SniCompletionEvent(exception));
                    ctx.close();
                }
            }
        }, this.handshakeTimeoutMillis, TimeUnit.MILLISECONDS);
    }
    
    @Override
    protected Future<T> lookup(final ChannelHandlerContext ctx, final ByteBuf clientHello) throws Exception {
        this.hostname = ((clientHello == null) ? null : extractSniHostname(clientHello));
        return this.lookup(ctx, this.hostname);
    }
    
    @Override
    protected void onLookupComplete(final ChannelHandlerContext ctx, final Future<T> future) throws Exception {
        if (this.timeoutFuture != null) {
            this.timeoutFuture.cancel(false);
        }
        try {
            this.onLookupComplete(ctx, this.hostname, future);
        }
        finally {
            fireSniCompletionEvent(ctx, this.hostname, future);
        }
    }
    
    protected abstract Future<T> lookup(final ChannelHandlerContext p0, final String p1) throws Exception;
    
    protected abstract void onLookupComplete(final ChannelHandlerContext p0, final String p1, final Future<T> p2) throws Exception;
    
    private static void fireSniCompletionEvent(final ChannelHandlerContext ctx, final String hostname, final Future<?> future) {
        final Throwable cause = future.cause();
        if (cause == null) {
            ctx.fireUserEventTriggered((Object)new SniCompletionEvent(hostname));
        }
        else {
            ctx.fireUserEventTriggered((Object)new SniCompletionEvent(hostname, cause));
        }
    }
}
